Skip to content

Commit

Permalink
Merge pull request #139 from lidofinance/feature/split-dg-and-reseal-…
Browse files Browse the repository at this point in the history
…functionality

Split dg and reseal functionality
  • Loading branch information
Psirex authored Oct 27, 2024
2 parents 534ab6b + 4a89ce8 commit 2d7b26d
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 87 deletions.
52 changes: 17 additions & 35 deletions contracts/DualGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {IDualGovernanceConfigProvider} from "./interfaces/IDualGovernanceConfigP
import {Proposers} from "./libraries/Proposers.sol";
import {Tiebreaker} from "./libraries/Tiebreaker.sol";
import {ExternalCall} from "./libraries/ExternalCalls.sol";
import {Resealer} from "./libraries/Resealer.sol";
import {State, DualGovernanceStateMachine} from "./libraries/DualGovernanceStateMachine.sol";

import {Escrow} from "./Escrow.sol";
Expand All @@ -31,6 +32,7 @@ contract DualGovernance is IDualGovernance {
using Proposers for Proposers.Context;
using Tiebreaker for Tiebreaker.Context;
using DualGovernanceStateMachine for DualGovernanceStateMachine.Context;
using Resealer for Resealer.Context;

// ---
// Errors
Expand All @@ -43,8 +45,6 @@ contract DualGovernance is IDualGovernance {
error ProposalSubmissionBlocked();
error ProposalSchedulingBlocked(uint256 proposalId);
error ResealIsNotAllowedInNormalState();
error InvalidResealManager(address resealManager);
error InvalidResealCommittee(address resealCommittee);

// ---
// Events
Expand All @@ -53,8 +53,6 @@ contract DualGovernance is IDualGovernance {
event CancelAllPendingProposalsSkipped();
event CancelAllPendingProposalsExecuted();
event EscrowMasterCopyDeployed(IEscrow escrowMasterCopy);
event ResealCommitteeSet(address resealCommittee);
event ResealManagerSet(address resealManager);

// ---
// Sanity Check Parameters & Immutables
Expand Down Expand Up @@ -130,16 +128,8 @@ contract DualGovernance is IDualGovernance {
/// @dev The state machine implementation controlling the state of the Dual Governance.
DualGovernanceStateMachine.Context internal _stateMachine;

// ---
// Standalone State Variables
// ---

/// @dev The address of the Reseal Committee which is allowed to "reseal" sealables paused for a limited
/// period of time when the Dual Governance proposal adoption is blocked.
address internal _resealCommittee;

/// @dev The address of the Reseal Manager.
IResealManager internal _resealManager;
/// @dev The functionality for sealing/resuming critical components of Lido protocol.
Resealer.Context internal _resealer;

// ---
// Constructor
Expand All @@ -162,7 +152,7 @@ contract DualGovernance is IDualGovernance {
emit EscrowMasterCopyDeployed(ESCROW_MASTER_COPY);

_stateMachine.initialize(dependencies.configProvider, ESCROW_MASTER_COPY);
_setResealManager(address(dependencies.resealManager));
_resealer.setResealManager(address(dependencies.resealManager));
}

// ---
Expand Down Expand Up @@ -446,7 +436,7 @@ contract DualGovernance is IDualGovernance {
_tiebreaker.checkCallerIsTiebreakerCommittee();
_stateMachine.activateNextState(ESCROW_MASTER_COPY);
_tiebreaker.checkTie(_stateMachine.getPersistedState(), _stateMachine.normalOrVetoCooldownExitedAt);
_resealManager.resume(sealable);
_resealer.resealManager.resume(sealable);
}

/// @notice Allows the tiebreaker committee to schedule for execution a submitted proposal when
Expand Down Expand Up @@ -480,51 +470,43 @@ contract DualGovernance is IDualGovernance {
/// @param sealable The address of the sealable contract to be resealed.
function resealSealable(address sealable) external {
_stateMachine.activateNextState(ESCROW_MASTER_COPY);
if (msg.sender != _resealCommittee) {
revert CallerIsNotResealCommittee(msg.sender);
}
if (_stateMachine.getPersistedState() == State.Normal) {
revert ResealIsNotAllowedInNormalState();
}
_resealManager.reseal(sealable);
_resealer.checkCallerIsResealCommittee();
_resealer.resealManager.reseal(sealable);
}

/// @notice Sets the address of the reseal committee.
/// @param resealCommittee The address of the new reseal committee.
function setResealCommittee(address resealCommittee) external {
_checkCallerIsAdminExecutor();
if (resealCommittee == _resealCommittee) {
revert InvalidResealCommittee(resealCommittee);
}
_resealCommittee = resealCommittee;
emit ResealCommitteeSet(resealCommittee);
_resealer.setResealCommittee(resealCommittee);
}

/// @notice Sets the address of the Reseal Manager.
/// @param resealManager The address of the new Reseal Manager.
function setResealManager(address resealManager) external {
_checkCallerIsAdminExecutor();
_setResealManager(resealManager);
_resealer.setResealManager(resealManager);
}

/// @notice Gets the address of the Reseal Manager.
/// @return resealManager The address of the Reseal Manager.
function getResealManager() external view returns (IResealManager) {
return _resealManager;
return _resealer.resealManager;
}

/// @notice Gets the address of the reseal committee.
/// @return resealCommittee The address of the reseal committee.
function getResealCommittee() external view returns (address) {
return _resealer.resealCommittee;
}

// ---
// Internal methods
// ---

function _setResealManager(address resealManager) internal {
if (resealManager == address(_resealManager) || resealManager == address(0)) {
revert InvalidResealManager(resealManager);
}
_resealManager = IResealManager(resealManager);
emit ResealManagerSet(resealManager);
}

function _checkCallerIsAdminExecutor() internal view {
if (TIMELOCK.getAdminExecutor() != msg.sender) {
revert CallerIsNotAdminExecutor(msg.sender);
Expand Down
64 changes: 64 additions & 0 deletions contracts/libraries/Resealer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;

import {IResealManager} from "../interfaces/IResealManager.sol";

/// @title Resealer Library
/// @dev Library for managing sealing operations for critical components of Lido protocol.
library Resealer {
// ---
// Errors
// ---
error InvalidResealManager(address resealManager);
error InvalidResealCommittee(address resealCommittee);
error CallerIsNotResealCommittee(address caller);

// ---
// Events
// ---
event ResealCommitteeSet(address resealCommittee);
event ResealManagerSet(address resealManager);

// ---
// Data Types
// ---

/// @dev Struct to hold the context of the reseal operations.
/// @param resealManager The address of the Reseal Manager.
/// @param resealCommittee The address of the Reseal Committee which is allowed to "reseal" sealables paused for a limited
/// period of time when the Dual Governance proposal adoption is blocked.
struct Context {
IResealManager resealManager;
address resealCommittee;
}

/// @dev Sets a new Reseal Manager contract address.
/// @param self The context struct containing the current state.
/// @param newResealManager The address of the new Reseal Manager.
function setResealManager(Context storage self, address newResealManager) internal {
if (newResealManager == address(self.resealManager) || newResealManager == address(0)) {
revert InvalidResealManager(newResealManager);
}
self.resealManager = IResealManager(newResealManager);
emit ResealManagerSet(newResealManager);
}

/// @dev Sets a new reseal committee address.
/// @param self The context struct containing the current state.
/// @param newResealCommittee The address of the new reseal committee.
function setResealCommittee(Context storage self, address newResealCommittee) internal {
if (newResealCommittee == self.resealCommittee) {
revert InvalidResealCommittee(newResealCommittee);
}
self.resealCommittee = newResealCommittee;
emit ResealCommitteeSet(newResealCommittee);
}

/// @dev Checks if the caller is the reseal committee.
/// @param self The context struct containing the current state.
function checkCallerIsResealCommittee(Context storage self) internal view {
if (msg.sender != self.resealCommittee) {
revert CallerIsNotResealCommittee(msg.sender);
}
}
}
78 changes: 26 additions & 52 deletions test/unit/DualGovernance.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {Escrow} from "contracts/Escrow.sol";
import {Executor} from "contracts/Executor.sol";
import {DualGovernance, State, DualGovernanceStateMachine} from "contracts/DualGovernance.sol";
import {Tiebreaker} from "contracts/libraries/Tiebreaker.sol";
import {Resealer} from "contracts/libraries/Resealer.sol";
import {Status as ProposalStatus} from "contracts/libraries/ExecutableProposals.sol";
import {Proposers} from "contracts/libraries/Proposers.sol";
import {IResealManager} from "contracts/interfaces/IResealManager.sol";
Expand Down Expand Up @@ -39,6 +40,7 @@ contract DualGovernanceUnitTests is UnitTest {
Executor private _executor = new Executor(address(this));

address private vetoer = makeAddr("vetoer");
address private resealCommittee = makeAddr("resealCommittee");

StETHMock private immutable _STETH_MOCK = new StETHMock();
IWithdrawalQueue private immutable _WITHDRAWAL_QUEUE_MOCK = new WithdrawalQueueMock();
Expand Down Expand Up @@ -95,6 +97,12 @@ contract DualGovernanceUnitTests is UnitTest {
abi.encodeWithSelector(DualGovernance.registerProposer.selector, address(this), address(_executor))
);

_executor.execute(
address(_dualGovernance),
0,
abi.encodeWithSelector(DualGovernance.setResealCommittee.selector, resealCommittee)
);

_escrow = Escrow(payable(_dualGovernance.getVetoSignallingEscrow()));
_STETH_MOCK.mint(vetoer, 10 ether);
vm.prank(vetoer);
Expand All @@ -115,7 +123,7 @@ contract DualGovernanceUnitTests is UnitTest {
vm.expectEmit();
emit DualGovernance.EscrowMasterCopyDeployed(IEscrow(predictedEscrowCopyAddress));
vm.expectEmit();
emit DualGovernance.ResealManagerSet(address(_RESEAL_MANAGER_STUB));
emit Resealer.ResealManagerSet(address(_RESEAL_MANAGER_STUB));

Duration minTiebreakerActivationTimeout = Durations.from(30 days);
Duration maxTiebreakerActivationTimeout = Durations.from(180 days);
Expand Down Expand Up @@ -2022,15 +2030,8 @@ contract DualGovernanceUnitTests is UnitTest {
// ---

function test_resealSealable_HappyPath() external {
address resealCommittee = makeAddr("RESEAL_COMMITTEE");
address sealable = address(new SealableMock());

_executor.execute(
address(_dualGovernance),
0,
abi.encodeWithSelector(DualGovernance.setResealCommittee.selector, resealCommittee)
);

vm.startPrank(vetoer);
_escrow.lockStETH(5 ether);
vm.stopPrank();
Expand Down Expand Up @@ -2069,15 +2070,8 @@ contract DualGovernanceUnitTests is UnitTest {
}

function test_resealSealable_RevertOn_NormalState() external {
address resealCommittee = makeAddr("RESEAL_COMMITTEE");
address sealable = address(new SealableMock());

_executor.execute(
address(_dualGovernance),
0,
abi.encodeWithSelector(DualGovernance.setResealCommittee.selector, resealCommittee)
);

assertEq(_dualGovernance.getPersistedState(), State.Normal);

vm.prank(resealCommittee);
Expand All @@ -2090,14 +2084,13 @@ contract DualGovernanceUnitTests is UnitTest {
// ---

function testFuzz_setResealCommittee_HappyPath(address newResealCommittee) external {
vm.expectEmit();
emit DualGovernance.ResealCommitteeSet(newResealCommittee);

vm.assume(newResealCommittee != resealCommittee);
_executor.execute(
address(_dualGovernance),
0,
abi.encodeWithSelector(DualGovernance.setResealCommittee.selector, newResealCommittee)
);
assertEq(newResealCommittee, address(_dualGovernance.getResealCommittee()));
}

function testFuzz_setResealCommittee_RevertOn_NotAdminExecutor(address stranger) external {
Expand All @@ -2108,54 +2101,20 @@ contract DualGovernanceUnitTests is UnitTest {
_dualGovernance.setResealCommittee(makeAddr("NEW_RESEAL_COMMITTEE"));
}

function testFuzz_setResealCommittee_RevertOn_InvalidResealCommittee(address newResealCommittee) external {
_executor.execute(
address(_dualGovernance),
0,
abi.encodeWithSelector(DualGovernance.setResealCommittee.selector, newResealCommittee)
);

vm.expectRevert(abi.encodeWithSelector(DualGovernance.InvalidResealCommittee.selector, newResealCommittee));
_executor.execute(
address(_dualGovernance),
0,
abi.encodeWithSelector(DualGovernance.setResealCommittee.selector, newResealCommittee)
);
}

// ---
// setResealManager()
// ---

function testFuzz_setResealManager_HappyPath(address newResealManager) external {
vm.assume(newResealManager != address(0) && newResealManager != address(_RESEAL_MANAGER_STUB));

vm.expectEmit();
emit DualGovernance.ResealManagerSet(newResealManager);

_executor.execute(
address(_dualGovernance),
0,
abi.encodeWithSelector(DualGovernance.setResealManager.selector, newResealManager)
);
}

function test_setResealManager_RevertOn_InvalidAddress() external {
vm.expectRevert(
abi.encodeWithSelector(DualGovernance.InvalidResealManager.selector, address(_RESEAL_MANAGER_STUB))
);
_executor.execute(
address(_dualGovernance),
0,
abi.encodeWithSelector(DualGovernance.setResealManager.selector, address(_RESEAL_MANAGER_STUB))
);

vm.expectRevert(abi.encodeWithSelector(DualGovernance.InvalidResealManager.selector, address(0)));
_executor.execute(
address(_dualGovernance), 0, abi.encodeWithSelector(DualGovernance.setResealManager.selector, address(0))
);
}

function test_setResealManger_RevertOn_CallerIsNotAdminExecutor(address stranger) external {
vm.assume(stranger != address(_executor));

Expand All @@ -2170,6 +2129,7 @@ contract DualGovernanceUnitTests is UnitTest {

function testFuzz_getResealManager_HappyPath(address newResealManager) external {
vm.assume(newResealManager != address(_RESEAL_MANAGER_STUB));
vm.assume(newResealManager != address(0));

_executor.execute(
address(_dualGovernance),
Expand All @@ -2180,6 +2140,20 @@ contract DualGovernanceUnitTests is UnitTest {
assertEq(newResealManager, address(_dualGovernance.getResealManager()));
}

// ---
// getResealCommittee()
// ---

function testFuzz_getResealCommittee_HappyPath(address newResealCommittee) external {
vm.assume(newResealCommittee != resealCommittee);
_executor.execute(
address(_dualGovernance),
0,
abi.encodeWithSelector(DualGovernance.setResealCommittee.selector, newResealCommittee)
);
assertEq(newResealCommittee, address(_dualGovernance.getResealCommittee()));
}

// ---
// Helper methods
// ---
Expand Down
1 change: 1 addition & 0 deletions test/unit/EmergencyProtectedTimelock.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -977,6 +977,7 @@ contract EmergencyProtectedTimelockUnitTests is UnitTest {
}

function testFuzz_getAdminExecutor(address executor) external {
vm.assume(executor != address(0));
EmergencyProtectedTimelock timelock = new EmergencyProtectedTimelock(
EmergencyProtectedTimelock.SanityCheckParams({
maxAfterSubmitDelay: Durations.from(45 days),
Expand Down
Loading

0 comments on commit 2d7b26d

Please sign in to comment.