Skip to content

Commit

Permalink
Merge pull request #77 from lidofinance/feature/reseal-manager-tweaks
Browse files Browse the repository at this point in the history
Reseal manager reseals only one resealable
  • Loading branch information
Psirex authored Aug 7, 2024
2 parents 0fcc95d + 0074a33 commit 578b3b9
Show file tree
Hide file tree
Showing 17 changed files with 757 additions and 73 deletions.
9 changes: 7 additions & 2 deletions contracts/DualGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -266,14 +266,19 @@ contract DualGovernance is IDualGovernance {
// Reseal executor
// ---

function resealSealables(address[] memory sealables) external {
function resealSealable(address sealable) external {
if (msg.sender != _resealCommittee) {
revert NotResealCommittee(msg.sender);
}
if (_stateMachine.getCurrentState() == State.Normal) {
revert ResealIsNotAllowedInNormalState();
}
RESEAL_MANAGER.reseal(sealables);
RESEAL_MANAGER.reseal(sealable);
}

function setResealCommittee(address resealCommittee) external {
_checkSenderIsAdminExecutor();
_resealCommittee = resealCommittee;
}

// ---
Expand Down
37 changes: 28 additions & 9 deletions contracts/ResealManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {ISealable} from "./interfaces/ISealable.sol";
import {ITimelock} from "./interfaces/ITimelock.sol";
import {IResealManager} from "./interfaces/IResealManager.sol";

/// @title ResealManager
/// @dev Allows to extend pause of temporarily paused contracts to permanent pause or resume it.
contract ResealManager is IResealManager {
error SealableWrongPauseState();
error SenderIsNotGovernance();
Expand All @@ -15,31 +17,48 @@ contract ResealManager is IResealManager {
uint256 public constant PAUSE_INFINITELY = type(uint256).max;
ITimelock public immutable EMERGENCY_PROTECTED_TIMELOCK;

/// @notice Initializes the ResealManager contract.
/// @param emergencyProtectedTimelock The address of the EmergencyProtectedTimelock contract.
constructor(ITimelock emergencyProtectedTimelock) {
EMERGENCY_PROTECTED_TIMELOCK = emergencyProtectedTimelock;
EMERGENCY_PROTECTED_TIMELOCK = ITimelock(emergencyProtectedTimelock);
}

function reseal(address[] memory sealables) public {
/// @notice Extends the pause of the specified sealable contract.
/// @dev Works only if conditions are met:
/// - ResealManager has PAUSE_ROLE for target contract;
/// - Contract are paused until timestamp after current timestamp and not for infinite time;
/// - The DAO governance is blocked by DualGovernance;
/// - Function is called by the governance contract.
/// @param sealable The address of the sealable contract.
function reseal(address sealable) public {
_checkSenderIsGovernance();
for (uint256 i = 0; i < sealables.length; ++i) {
uint256 sealableResumeSinceTimestamp = ISealable(sealables[i]).getResumeSinceTimestamp();
if (sealableResumeSinceTimestamp < block.timestamp || sealableResumeSinceTimestamp == PAUSE_INFINITELY) {
revert SealableWrongPauseState();
}
Address.functionCall(sealables[i], abi.encodeWithSelector(ISealable.resume.selector));
Address.functionCall(sealables[i], abi.encodeWithSelector(ISealable.pauseFor.selector, PAUSE_INFINITELY));

uint256 sealableResumeSinceTimestamp = ISealable(sealable).getResumeSinceTimestamp();
if (sealableResumeSinceTimestamp < block.timestamp || sealableResumeSinceTimestamp == PAUSE_INFINITELY) {
revert SealableWrongPauseState();
}
Address.functionCall(sealable, abi.encodeWithSelector(ISealable.resume.selector));
Address.functionCall(sealable, abi.encodeWithSelector(ISealable.pauseFor.selector, PAUSE_INFINITELY));
}

/// @notice Resumes the specified sealable contract if it is scheduled to resume in the future.
/// @dev Works only if conditions are met:
/// - ResealManager has RESUME_ROLE for target contract;
/// - Contract are paused until timestamp after current timestamp;
/// - Function is called by the governance contract.
/// @param sealable The address of the sealable contract.
function resume(address sealable) public {
_checkSenderIsGovernance();

uint256 sealableResumeSinceTimestamp = ISealable(sealable).getResumeSinceTimestamp();
if (sealableResumeSinceTimestamp < block.timestamp) {
revert SealableWrongPauseState();
}
Address.functionCall(sealable, abi.encodeWithSelector(ISealable.resume.selector));
}

/// @notice Ensures that the function can only be called by the governance address.
/// @dev Reverts if the sender is not the governance address.
function _checkSenderIsGovernance() internal view {
address governance = EMERGENCY_PROTECTED_TIMELOCK.getGovernance();
if (msg.sender != governance) {
Expand Down
14 changes: 6 additions & 8 deletions contracts/committees/EmergencyActivationCommittee.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
pragma solidity 0.8.26;

import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {HashConsensus} from "./HashConsensus.sol";

interface IEmergencyProtectedTimelock {
function emergencyActivate() external;
}
import {HashConsensus} from "./HashConsensus.sol";
import {ITimelock} from "../interfaces/ITimelock.sol";

/// @title Emergency Activation Committee Contract
/// @notice This contract allows a committee to approve and execute an emergency activation
Expand All @@ -27,7 +25,7 @@ contract EmergencyActivationCommittee is HashConsensus {

/// @notice Approves the emergency activation by casting a vote
/// @dev Only callable by committee members
function approveEmergencyActivate() public {
function approveActivateEmergencyMode() public {
_checkSenderIsMember();
_vote(EMERGENCY_ACTIVATION_HASH, true);
}
Expand All @@ -36,7 +34,7 @@ contract EmergencyActivationCommittee is HashConsensus {
/// @return support The number of votes in support of the activation
/// @return execuitionQuorum The required number of votes for execution
/// @return isExecuted Whether the activation has been executed
function getEmergencyActivateState()
function getActivateEmergencyModeState()
public
view
returns (uint256 support, uint256 execuitionQuorum, bool isExecuted)
Expand All @@ -46,10 +44,10 @@ contract EmergencyActivationCommittee is HashConsensus {

/// @notice Executes the emergency activation if the quorum is reached
/// @dev Calls the emergencyActivate function on the Emergency Protected Timelock contract
function executeEmergencyActivate() external {
function executeActivateEmergencyMode() external {
_markUsed(EMERGENCY_ACTIVATION_HASH);
Address.functionCall(
EMERGENCY_PROTECTED_TIMELOCK, abi.encodeWithSelector(IEmergencyProtectedTimelock.emergencyActivate.selector)
EMERGENCY_PROTECTED_TIMELOCK, abi.encodeWithSelector(ITimelock.activateEmergencyMode.selector)
);
}
}
13 changes: 3 additions & 10 deletions contracts/committees/EmergencyExecutionCommittee.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@ pragma solidity 0.8.26;
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {HashConsensus} from "./HashConsensus.sol";
import {ProposalsList} from "./ProposalsList.sol";

interface IEmergencyProtectedTimelock {
function emergencyExecute(uint256 proposalId) external;
function emergencyReset() external;
}
import {ITimelock} from "../interfaces/ITimelock.sol";

enum ProposalType {
EmergencyExecute,
Expand Down Expand Up @@ -65,8 +61,7 @@ contract EmergencyExecutionCommittee is HashConsensus, ProposalsList {
(, bytes32 key) = _encodeEmergencyExecute(proposalId);
_markUsed(key);
Address.functionCall(
EMERGENCY_PROTECTED_TIMELOCK,
abi.encodeWithSelector(IEmergencyProtectedTimelock.emergencyExecute.selector, proposalId)
EMERGENCY_PROTECTED_TIMELOCK, abi.encodeWithSelector(ITimelock.emergencyExecute.selector, proposalId)
);
}

Expand Down Expand Up @@ -113,9 +108,7 @@ contract EmergencyExecutionCommittee is HashConsensus, ProposalsList {
function executeEmergencyReset() external {
bytes32 proposalKey = _encodeEmergencyResetProposalKey();
_markUsed(proposalKey);
Address.functionCall(
EMERGENCY_PROTECTED_TIMELOCK, abi.encodeWithSelector(IEmergencyProtectedTimelock.emergencyReset.selector)
);
Address.functionCall(EMERGENCY_PROTECTED_TIMELOCK, abi.encodeWithSelector(ITimelock.emergencyReset.selector));
}

/// @notice Encodes the proposal key for an emergency reset
Expand Down
36 changes: 18 additions & 18 deletions contracts/committees/ResealCommittee.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {HashConsensus} from "./HashConsensus.sol";
import {ProposalsList} from "./ProposalsList.sol";

interface IDualGovernance {
function reseal(address[] memory sealables) external;
function resealSealable(address sealables) external;
}

/// @title Reseal Committee Contract
Expand All @@ -28,52 +28,52 @@ contract ResealCommittee is HashConsensus, ProposalsList {
}

/// @notice Votes on a reseal proposal
/// @dev Allows committee members to vote on resealing a set of addresses
/// @param sealables The addresses to reseal
/// @dev Allows committee members to vote on resealing a sealed address
/// @param sealable The address to reseal
/// @param support Indicates whether the member supports the proposal
function voteReseal(address[] memory sealables, bool support) public {
function voteReseal(address sealable, bool support) public {
_checkSenderIsMember();
(bytes memory proposalData, bytes32 key) = _encodeResealProposal(sealables);
(bytes memory proposalData, bytes32 key) = _encodeResealProposal(sealable);
_vote(key, support);
_pushProposal(key, 0, proposalData);
}

/// @notice Gets the current state of a reseal proposal
/// @dev Retrieves the state of the reseal proposal for a set of addresses
/// @param sealables The addresses for the reseal proposal
/// @dev Retrieves the state of the reseal proposal for a sealed address
/// @param sealable The addresses for the reseal proposal
/// @return support The number of votes in support of the proposal
/// @return execuitionQuorum The required number of votes for execution
/// @return isExecuted Whether the proposal has been executed
function getResealState(address[] memory sealables)
function getResealState(address sealable)
public
view
returns (uint256 support, uint256 execuitionQuorum, bool isExecuted)
{
(, bytes32 key) = _encodeResealProposal(sealables);
(, bytes32 key) = _encodeResealProposal(sealable);
return _getHashState(key);
}

/// @notice Executes an approved reseal proposal
/// @dev Executes the reseal proposal by calling the reseal function on the Dual Governance contract
/// @param sealables The addresses to reseal
function executeReseal(address[] memory sealables) external {
(, bytes32 key) = _encodeResealProposal(sealables);
/// @param sealable The address to reseal
function executeReseal(address sealable) external {
(, bytes32 key) = _encodeResealProposal(sealable);
_markUsed(key);

Address.functionCall(DUAL_GOVERNANCE, abi.encodeWithSelector(IDualGovernance.reseal.selector, sealables));
Address.functionCall(DUAL_GOVERNANCE, abi.encodeWithSelector(IDualGovernance.resealSealable.selector, sealable));

bytes32 resealNonceHash = keccak256(abi.encode(sealables));
bytes32 resealNonceHash = keccak256(abi.encode(sealable));
_resealNonces[resealNonceHash]++;
}

/// @notice Encodes a reseal proposal
/// @dev Internal function to encode the proposal data and generate the proposal key
/// @param sealables The addresses to reseal
/// @param sealable The address to reseal
/// @return data The encoded proposal data
/// @return key The generated proposal key
function _encodeResealProposal(address[] memory sealables) internal view returns (bytes memory data, bytes32 key) {
bytes32 resealNonceHash = keccak256(abi.encode(sealables));
data = abi.encode(sealables, _resealNonces[resealNonceHash]);
function _encodeResealProposal(address sealable) internal view returns (bytes memory data, bytes32 key) {
bytes32 resealNonceHash = keccak256(abi.encode(sealable));
data = abi.encode(sealable, _resealNonces[resealNonceHash]);
key = keccak256(data);
}
}
2 changes: 1 addition & 1 deletion contracts/interfaces/IResealManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ pragma solidity 0.8.26;

interface IResealManager {
function resume(address sealable) external;
function reseal(address[] memory sealables) external;
function reseal(address sealable) external;
}
8 changes: 7 additions & 1 deletion contracts/interfaces/ITimelock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,18 @@ interface ITimelock {
function canSchedule(uint256 proposalId) external view returns (bool);
function canExecute(uint256 proposalId) external view returns (bool);

function getGovernance() external view returns (address);
function getAdminExecutor() external view returns (address);

function getProposal(uint256 proposalId) external view returns (Proposal memory proposal);
function getProposalInfo(uint256 proposalId)
external
view
returns (uint256 id, ProposalStatus status, address executor, Timestamp submittedAt, Timestamp scheduledAt);

function getGovernance() external view returns (address);
function setGovernance(address governance) external;

function activateEmergencyMode() external;
function emergencyExecute(uint256 proposalId) external;
function emergencyReset() external;
}
12 changes: 6 additions & 6 deletions docs/plan-b.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,27 +333,27 @@ Initializes the contract with an owner, committee members, a quorum, and the add
#### Preconditions
- `executionQuorum` MUST be greater than 0.

### Function: `EmergencyActivationCommittee.approveEmergencyActivate`
### Function: `EmergencyActivationCommittee.approveActivateEmergencyMode`
```solidity
function approveEmergencyActivate() public
function approveActivateEmergencyMode() public onlyMember
```
Approves the emergency activation by voting on the `EMERGENCY_ACTIVATION_HASH`.

#### Preconditions
- MUST be called by a committee member.

### Function: `EmergencyActivationCommittee.getEmergencyActivateState`
### Function: `EmergencyActivationCommittee.getActivateEmergencyModeState`
```solidity
function getEmergencyActivateState()
function getActivateEmergencyModeState()
public
view
returns (uint256 support, uint256 executionQuorum, bool isExecuted)
```
Returns the state of the emergency activation proposal, including the support count, quorum, and execution status.

### Function: `EmergencyActivationCommittee.executeEmergencyActivate`
### Function: `EmergencyActivationCommittee.executeActivateEmergencyMode`
```solidity
function executeEmergencyActivate() external
function executeActivateEmergencyMode() external
```
Executes the emergency activation by calling the `emergencyActivate` function on the `EmergencyProtectedTimelock` contract.

Expand Down
20 changes: 10 additions & 10 deletions docs/specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -417,15 +417,15 @@ Initializes the contract with the address of the EmergencyProtectedTimelock cont
### Function ResealManager.reseal

```solidity
function reseal(address[] memory sealables) public
function reseal(address sealable) public
```

Extends the pause of the specified `sealables` contracts. This function can be called by the governance address defined in the `EmergencyProtectedTimelock`.
Extends the pause of the specified `sealable` contract. This function can be called by the governance address defined in the `EmergencyProtectedTimelock`.

#### Preconditions

- The `ResealManager` MUST have `PAUSE_ROLE` and `RESUME_ROLE` for the target contracts.
- The target contracts MUST be paused until a future timestamp and not indefinitely.
- The `ResealManager` MUST have `PAUSE_ROLE` and `RESUME_ROLE` for the target contract.
- The target contract MUST be paused until a future timestamp and not indefinitely.
- The function MUST be called by the governance address defined in `EmergencyProtectedTimelock`.

#### Errors
Expand Down Expand Up @@ -1318,10 +1318,10 @@ executionQuorum MUST be greater than 0.
emergencyProtectedTimelock MUST be a valid address.


### Function: EmergencyActivationCommittee.approveEmergencyActivate
### Function: EmergencyActivationCommittee.approveActivateEmergencyMode

```solidity
function approveEmergencyActivate() public
function approveActivateEmergencyMode() public
```

Approves the emergency activation by voting on the EMERGENCY_ACTIVATION_HASH.
Expand All @@ -1330,21 +1330,21 @@ Approves the emergency activation by voting on the EMERGENCY_ACTIVATION_HASH.

* MUST be called by a member.

### Function: EmergencyActivationCommittee.getEmergencyActivateState
### Function: EmergencyActivationCommittee.getActivateEmergencyModeState

```solidity
function getEmergencyActivateState()
function getActivateEmergencyModeState()
public
view
returns (uint256 support, uint256 executionQuorum, bool isExecuted)
```

Returns the state of the emergency activation proposal including support count, quorum, and execution status.

### Function: EmergencyActivationCommittee.executeEmergencyActivate
### Function: EmergencyActivationCommittee.executeActivateEmergencyMode

```solidity
function executeEmergencyActivate() external
function executeActivateEmergencyMode() external
```

Executes the emergency activation by calling the emergencyActivate function on the EmergencyProtectedTimelock contract.
Expand Down
Loading

0 comments on commit 578b3b9

Please sign in to comment.