From f259f47639079c1d67fbc73f3f43e1cc8dedcbeb Mon Sep 17 00:00:00 2001 From: Bogdan Kovtun Date: Fri, 15 Nov 2024 03:11:21 +0400 Subject: [PATCH] Reset RageQuitRound in the VetoCooldown state --- .../libraries/DualGovernanceStateMachine.sol | 2 +- docs/mechanism.md | 7 +- .../DualGovernanceStateMachine.t.sol | 676 +++++++++--------- test/utils/testing-assert-eq-extender.sol | 4 + 4 files changed, 333 insertions(+), 356 deletions(-) diff --git a/contracts/libraries/DualGovernanceStateMachine.sol b/contracts/libraries/DualGovernanceStateMachine.sol index cfb174a7..670283d4 100644 --- a/contracts/libraries/DualGovernanceStateMachine.sol +++ b/contracts/libraries/DualGovernanceStateMachine.sol @@ -154,7 +154,7 @@ library DualGovernanceStateMachine { self.normalOrVetoCooldownExitedAt = Timestamps.now(); } - if (newState == State.Normal && self.rageQuitRound != 0) { + if (newState == State.VetoCooldown && self.rageQuitRound != 0) { self.rageQuitRound = 0; } else if (newState == State.VetoSignalling) { if (currentState == State.VetoSignallingDeactivation) { diff --git a/docs/mechanism.md b/docs/mechanism.md index 07b9f427..91234dd1 100644 --- a/docs/mechanism.md +++ b/docs/mechanism.md @@ -12,7 +12,7 @@ Additionally, there is a Gate Seal emergency committee that allows pausing certa The Dual governance mechanism (DG) is an iteration on the protocol governance that gives stakers a say by allowing them to block DAO decisions and providing a negotiation device between stakers and the DAO. -Another way of looking at dual governance is that it implements: +Another way of looking at dual governance is that it implements: 1) a dynamic user-extensible timelock on DAO decisions 2) a rage quit mechanism for stakers taking into account the specifics of how Ethereum withdrawals work. @@ -307,7 +307,7 @@ W(i) = \min \left\{ W_{min} + i * W_{growth} \,,\, W_{max} \right\} where $W_{min}$ is `RageQuitEthWithdrawalsMinDelay`, $W_{max}$ is `RageQuitEthWithdrawalsMaxDelay`, $W_{growth}$ is `rageQuitEthWithdrawalsDelayGrowth`. -The rage quit sequence number is calculated as follows: each time the Normal state is entered, the sequence number is set to 0; each time the Rage Quit state is entered, the number is incremented by 1. +The rage quit sequence number is calculated as follows: each time the VetoCooldown state is entered, the sequence number is set to 0; each time the Rage Quit state is entered, the number is incremented by 1. ```env # Proposed values, to be modeled and refined @@ -399,6 +399,9 @@ Dual governance should not cover: ## Changelog +### 2024-11-15 +The rage quit sequence number is now reset in the `VetoCooldown` state instead of the `Normal` state. This adjustment ensures that the ETH withdrawal timelock does not increase unnecessarily in cases where, after a Rage Quit, Dual Governance cycles through `VetoSignalling` → `VetoSignallingDeactivation` → `VetoCooldown` without entering the `Normal` state, as the DAO remains operational and can continue submitting and executing proposals in this scenario. + ### 2024-09-12 - Explicitly described the `VetoSignallingDeactivation` -> `RageQuit` state transition. - Renamed `RageQuitExtensionDelay` to `RageQuitExtensionPeriodDuration`. diff --git a/test/unit/libraries/DualGovernanceStateMachine.t.sol b/test/unit/libraries/DualGovernanceStateMachine.t.sol index 829d5b87..3377885f 100644 --- a/test/unit/libraries/DualGovernanceStateMachine.t.sol +++ b/test/unit/libraries/DualGovernanceStateMachine.t.sol @@ -3,6 +3,8 @@ pragma solidity 0.8.26; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {IEscrow} from "contracts/interfaces/IEscrow.sol"; + import {Durations} from "contracts/types/Duration.sol"; import {Timestamp, Timestamps} from "contracts/types/Timestamp.sol"; import {PercentD16, PercentsD16} from "contracts/types/PercentD16.sol"; @@ -14,12 +16,12 @@ import { } from "contracts/ImmutableDualGovernanceConfigProvider.sol"; import {UnitTest} from "test/utils/unit-test.sol"; -import {IEscrow, EscrowMock} from "test/mocks/EscrowMock.sol"; contract DualGovernanceStateMachineUnitTests is UnitTest { using DualGovernanceStateMachine for DualGovernanceStateMachine.Context; - IEscrow private immutable _ESCROW_MASTER_COPY = new EscrowMock(); + address private immutable _ESCROW_MASTER_COPY_MOCK = makeAddr("ESCROW_MASTER_COPY_MOCK"); + ImmutableDualGovernanceConfigProvider internal immutable _CONFIG_PROVIDER = new ImmutableDualGovernanceConfigProvider( DualGovernanceConfig.Context({ firstSealRageQuitSupport: PercentsD16.fromBasisPoints(3_00), // 3% @@ -44,7 +46,9 @@ contract DualGovernanceStateMachineUnitTests is UnitTest { DualGovernanceStateMachine.Context private _stateMachine; function setUp() external { - _stateMachine.initialize(_CONFIG_PROVIDER, _ESCROW_MASTER_COPY); + _stateMachine.initialize(_CONFIG_PROVIDER, IEscrow(_ESCROW_MASTER_COPY_MOCK)); + _mockRageQuitFinalized(false); + _mockRageQuitSupport(PercentsD16.from(0)); } function test_initialize_RevertOn_ReInitialization() external { @@ -56,42 +60,126 @@ contract DualGovernanceStateMachineUnitTests is UnitTest { // activateNextState() // --- + function test_activateNextState_SideEffects_RageQuitRoundResetInVetoCooldownAfterRageQuit() external { + // Transition state machine into the VetoSignalling state + _mockRageQuitSupport(_CONFIG_PROVIDER.SECOND_SEAL_RAGE_QUIT_SUPPORT() + PercentsD16.fromBasisPoints(1)); + _activateNextState(); + _wait(_CONFIG_PROVIDER.VETO_SIGNALLING_MAX_DURATION().plusSeconds(1)); + + // Simulate the Rage Quit process has completed and in the SignallingEscrow the first seal is not crossed + _mockRageQuitFinalized(true); + _activateNextState(); + _mockRageQuitSupport(PercentsD16.fromBasisPoints(0)); + + // Rage Quit Round should reset after system entered the VetoCooldown + + _assertState({persisted: State.RageQuit, effective: State.VetoCooldown}); + assertEq(_stateMachine.rageQuitRound, 1); + + _activateNextState(); + + _assertState({persisted: State.VetoCooldown, effective: State.VetoCooldown}); + assertEq(_stateMachine.rageQuitRound, 0); + } + + function test_activateNextState_SideEffects_RageQuitRoundResetInVetoCooldownAfterVetoSignallingDeactivation() + external + { + // Transition state machine into the RageQuit state + _mockRageQuitSupport(_CONFIG_PROVIDER.SECOND_SEAL_RAGE_QUIT_SUPPORT() + PercentsD16.fromBasisPoints(1)); + _activateNextState(); + _wait(_CONFIG_PROVIDER.VETO_SIGNALLING_MAX_DURATION().plusSeconds(1)); + + // Simulate the Rage Quit process has completed and in the SignallingEscrow the first seal is crossed + _mockRageQuitFinalized(true); + _activateNextState(); + + _assertState({persisted: State.RageQuit, effective: State.VetoSignalling}); + assertEq(_stateMachine.rageQuitRound, 1); + + _activateNextState(); + + _assertState({persisted: State.VetoSignalling, effective: State.VetoSignalling}); + assertEq(_stateMachine.rageQuitRound, 1); + + _wait(_CONFIG_PROVIDER.VETO_SIGNALLING_MAX_DURATION().dividedBy(2)); + + _assertState({persisted: State.VetoSignalling, effective: State.VetoSignalling}); + assertEq(_stateMachine.rageQuitRound, 1); + + // Simulate the Rage Quit support decreased + _mockRageQuitSupport(PercentsD16.from(3_00)); + + _assertState({persisted: State.VetoSignalling, effective: State.VetoSignallingDeactivation}); + assertEq(_stateMachine.rageQuitRound, 1); + + _activateNextState(); + + _assertState({persisted: State.VetoSignallingDeactivation, effective: State.VetoSignallingDeactivation}); + assertEq(_stateMachine.rageQuitRound, 1); + + _wait(_CONFIG_PROVIDER.VETO_SIGNALLING_DEACTIVATION_MAX_DURATION().plusSeconds(1)); + + _assertState({persisted: State.VetoSignallingDeactivation, effective: State.VetoCooldown}); + assertEq(_stateMachine.rageQuitRound, 1); + + _activateNextState(); + + _assertState({persisted: State.VetoCooldown, effective: State.VetoCooldown}); + assertEq(_stateMachine.rageQuitRound, 0); + } + function test_activateNextState_HappyPath_MaxRageQuitsRound() external { - assertEq(_stateMachine.state, State.Normal); + _assertState({persisted: State.Normal, effective: State.Normal}); - for (uint256 i = 0; i < 2 * DualGovernanceStateMachine.MAX_RAGE_QUIT_ROUND; ++i) { - address signallingEscrow = address(_stateMachine.signallingEscrow); - EscrowMock(signallingEscrow).__setRageQuitSupport( - _CONFIG_PROVIDER.SECOND_SEAL_RAGE_QUIT_SUPPORT() + PercentsD16.fromBasisPoints(1_00) - ); - assertTrue( - _stateMachine.signallingEscrow.getRageQuitSupport() > _CONFIG_PROVIDER.SECOND_SEAL_RAGE_QUIT_SUPPORT() - ); - assertEq(_stateMachine.rageQuitRound, Math.min(i, DualGovernanceStateMachine.MAX_RAGE_QUIT_ROUND)); + // For the simplicity, simulate that Signalling Escrow always has rage quit support greater than the second seal + _mockRageQuitSupport(_CONFIG_PROVIDER.SECOND_SEAL_RAGE_QUIT_SUPPORT() + PercentsD16.fromBasisPoints(1_00)); + // And that the Rage Quit finalized + _mockRageQuitFinalized(true); - // wait here the full duration of the veto cooldown to make sure it's over from the previous iteration - _wait(_CONFIG_PROVIDER.VETO_COOLDOWN_DURATION().plusSeconds(1)); + assertTrue( + _stateMachine.signallingEscrow.getRageQuitSupport() > _CONFIG_PROVIDER.SECOND_SEAL_RAGE_QUIT_SUPPORT() + ); + _assertState({persisted: State.Normal, effective: State.VetoSignalling}); + _activateNextState(); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); - assertEq(_stateMachine.state, State.VetoSignalling); + // Simulate sequential Rage Quits + for (uint256 i = 0; i < 2 * DualGovernanceStateMachine.MAX_RAGE_QUIT_ROUND; ++i) { + _assertState({persisted: State.VetoSignalling, effective: State.VetoSignalling}); + assertEq(_stateMachine.rageQuitRound, Math.min(i, DualGovernanceStateMachine.MAX_RAGE_QUIT_ROUND)); _wait(_CONFIG_PROVIDER.VETO_SIGNALLING_MAX_DURATION().plusSeconds(1)); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.state, State.RageQuit); + // Effective state is VetoSignalling, as the rage quit is considered finalized + _assertState({persisted: State.RageQuit, effective: State.VetoSignalling}); assertEq(_stateMachine.rageQuitRound, Math.min(i + 1, DualGovernanceStateMachine.MAX_RAGE_QUIT_ROUND)); - EscrowMock(signallingEscrow).__setIsRageQuitFinalized(true); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); - assertEq(_stateMachine.state, State.VetoCooldown); + _activateNextState(); } + _wait(_CONFIG_PROVIDER.VETO_SIGNALLING_MAX_DURATION().dividedBy(2)); + // after the sequential rage quits chain is broken, the rage quit resets to 0 - _wait(_CONFIG_PROVIDER.VETO_COOLDOWN_DURATION().plusSeconds(1)); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _mockRageQuitSupport(PercentsD16.from(0)); + + _assertState({persisted: State.VetoSignalling, effective: State.VetoSignallingDeactivation}); + assertEq(_stateMachine.rageQuitRound, DualGovernanceStateMachine.MAX_RAGE_QUIT_ROUND); + + _activateNextState(); + + _assertState({persisted: State.VetoSignallingDeactivation, effective: State.VetoSignallingDeactivation}); + assertEq(_stateMachine.rageQuitRound, DualGovernanceStateMachine.MAX_RAGE_QUIT_ROUND); + _wait(_CONFIG_PROVIDER.VETO_SIGNALLING_DEACTIVATION_MAX_DURATION().plusSeconds(1)); + + _assertState({persisted: State.VetoSignallingDeactivation, effective: State.VetoCooldown}); + assertEq(_stateMachine.rageQuitRound, DualGovernanceStateMachine.MAX_RAGE_QUIT_ROUND); + + _activateNextState(); + + _assertState({persisted: State.VetoCooldown, effective: State.VetoCooldown}); assertEq(_stateMachine.rageQuitRound, 0); - assertEq(_stateMachine.state, State.Normal); } // --- @@ -99,102 +187,71 @@ contract DualGovernanceStateMachineUnitTests is UnitTest { // --- function test_canSubmitProposal_HappyPath() external { - address signallingEscrow = address(_stateMachine.signallingEscrow); - - assertEq(_stateMachine.getPersistedState(), State.Normal); - assertEq(_stateMachine.getEffectiveState(), State.Normal); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: true})); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: false})); + _assertState({persisted: State.Normal, effective: State.Normal}); + _assertCanSubmitProposal({persisted: true, effective: true}); // simulate the first threshold of veto signalling was reached - EscrowMock(signallingEscrow).__setRageQuitSupport( - _CONFIG_PROVIDER.FIRST_SEAL_RAGE_QUIT_SUPPORT() + PercentD16.wrap(1) - ); + _mockRageQuitSupport(_CONFIG_PROVIDER.FIRST_SEAL_RAGE_QUIT_SUPPORT() + PercentsD16.from(1)); - assertEq(_stateMachine.getPersistedState(), State.Normal); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignalling); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: false})); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: true})); + _assertState({persisted: State.Normal, effective: State.VetoSignalling}); + _assertCanSubmitProposal({persisted: true, effective: true}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.VetoSignalling); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignalling); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: false})); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: false})); + _assertState({persisted: State.VetoSignalling, effective: State.VetoSignalling}); + _assertCanSubmitProposal({persisted: true, effective: true}); - _wait(_CONFIG_PROVIDER.VETO_SIGNALLING_MIN_DURATION().plusSeconds(1 minutes)); + _wait(_CONFIG_PROVIDER.VETO_SIGNALLING_MIN_DURATION().plusSeconds(1)); - assertEq(_stateMachine.getPersistedState(), State.VetoSignalling); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignallingDeactivation); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: false})); - assertFalse(_stateMachine.canSubmitProposal({useEffectiveState: true})); + _assertState({persisted: State.VetoSignalling, effective: State.VetoSignallingDeactivation}); + _assertCanSubmitProposal({persisted: true, effective: false}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.VetoSignallingDeactivation); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignallingDeactivation); - assertFalse(_stateMachine.canSubmitProposal({useEffectiveState: false})); - assertFalse(_stateMachine.canSubmitProposal({useEffectiveState: true})); + _assertState({persisted: State.VetoSignallingDeactivation, effective: State.VetoSignallingDeactivation}); + _assertCanSubmitProposal({persisted: false, effective: false}); // simulate the second threshold of veto signalling was reached - EscrowMock(signallingEscrow).__setRageQuitSupport( - _CONFIG_PROVIDER.SECOND_SEAL_RAGE_QUIT_SUPPORT() + PercentsD16.fromBasisPoints(1_00) - ); + _mockRageQuitSupport(_CONFIG_PROVIDER.SECOND_SEAL_RAGE_QUIT_SUPPORT() + PercentsD16.from(1)); - assertEq(_stateMachine.getPersistedState(), State.VetoSignallingDeactivation); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignalling); - assertFalse(_stateMachine.canSubmitProposal({useEffectiveState: false})); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: true})); + _assertState({persisted: State.VetoSignallingDeactivation, effective: State.VetoSignalling}); + _assertCanSubmitProposal({persisted: false, effective: true}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.VetoSignalling); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignalling); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: false})); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: true})); + _assertState({persisted: State.VetoSignalling, effective: State.VetoSignalling}); + _assertCanSubmitProposal({persisted: true, effective: true}); _wait(_CONFIG_PROVIDER.VETO_SIGNALLING_MAX_DURATION().plusSeconds(1)); - assertEq(_stateMachine.getPersistedState(), State.VetoSignalling); - assertEq(_stateMachine.getEffectiveState(), State.RageQuit); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: false})); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: true})); + _assertState({persisted: State.VetoSignalling, effective: State.RageQuit}); + _assertCanSubmitProposal({persisted: true, effective: true}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.RageQuit); - assertEq(_stateMachine.getEffectiveState(), State.RageQuit); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: false})); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: true})); + _assertState({persisted: State.RageQuit, effective: State.RageQuit}); + _assertCanSubmitProposal({persisted: true, effective: true}); - EscrowMock(address(_stateMachine.rageQuitEscrow)).__setIsRageQuitFinalized(true); + _mockRageQuitFinalized(true); + _mockRageQuitSupport(PercentsD16.from(0)); - assertEq(_stateMachine.getPersistedState(), State.RageQuit); - assertEq(_stateMachine.getEffectiveState(), State.VetoCooldown); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: false})); - assertFalse(_stateMachine.canSubmitProposal({useEffectiveState: true})); + _assertState({persisted: State.RageQuit, effective: State.VetoCooldown}); + _assertCanSubmitProposal({persisted: true, effective: false}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.VetoCooldown); - assertEq(_stateMachine.getEffectiveState(), State.VetoCooldown); - assertFalse(_stateMachine.canSubmitProposal({useEffectiveState: false})); - assertFalse(_stateMachine.canSubmitProposal({useEffectiveState: true})); + _assertState({persisted: State.VetoCooldown, effective: State.VetoCooldown}); + _assertCanSubmitProposal({persisted: false, effective: false}); _wait(_CONFIG_PROVIDER.VETO_COOLDOWN_DURATION().plusSeconds(1)); - assertEq(_stateMachine.getPersistedState(), State.VetoCooldown); - assertEq(_stateMachine.getEffectiveState(), State.Normal); - assertFalse(_stateMachine.canSubmitProposal({useEffectiveState: false})); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: true})); + _assertState({persisted: State.VetoCooldown, effective: State.Normal}); + _assertCanSubmitProposal({persisted: false, effective: true}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.Normal); - assertEq(_stateMachine.getEffectiveState(), State.Normal); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: true})); - assertTrue(_stateMachine.canSubmitProposal({useEffectiveState: false})); + _assertState({persisted: State.Normal, effective: State.Normal}); + _assertCanSubmitProposal({persisted: true, effective: true}); } // --- @@ -202,232 +259,117 @@ contract DualGovernanceStateMachineUnitTests is UnitTest { // --- function test_canScheduleProposal_HappyPath() external { - address signallingEscrow = address(_stateMachine.signallingEscrow); Timestamp proposalSubmittedAt = Timestamps.now(); - assertEq(_stateMachine.getPersistedState(), State.Normal); - assertEq(_stateMachine.getEffectiveState(), State.Normal); - assertTrue( - _stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: proposalSubmittedAt}) - ); - assertTrue( - _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: proposalSubmittedAt}) - ); + _assertState({persisted: State.Normal, effective: State.Normal}); + _assertCanScheduleProposal({proposalSubmittedAt: proposalSubmittedAt, persisted: true, effective: true}); // simulate the first threshold of veto signalling was reached - EscrowMock(signallingEscrow).__setRageQuitSupport( - _CONFIG_PROVIDER.FIRST_SEAL_RAGE_QUIT_SUPPORT() + PercentD16.wrap(1) - ); + _mockRageQuitSupport(_CONFIG_PROVIDER.FIRST_SEAL_RAGE_QUIT_SUPPORT() + PercentsD16.from(1)); - assertEq(_stateMachine.getPersistedState(), State.Normal); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignalling); - assertTrue( - _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: proposalSubmittedAt}) - ); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: proposalSubmittedAt}) - ); + _assertState({persisted: State.Normal, effective: State.VetoSignalling}); + _assertCanScheduleProposal({proposalSubmittedAt: proposalSubmittedAt, persisted: true, effective: false}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.VetoSignalling); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignalling); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: proposalSubmittedAt}) - ); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: proposalSubmittedAt}) - ); + _assertState({persisted: State.VetoSignalling, effective: State.VetoSignalling}); + _assertCanScheduleProposal({proposalSubmittedAt: proposalSubmittedAt, persisted: false, effective: false}); - _wait(_CONFIG_PROVIDER.VETO_SIGNALLING_MIN_DURATION().plusSeconds(1 minutes)); + _wait(_CONFIG_PROVIDER.VETO_SIGNALLING_MIN_DURATION().plusSeconds(1)); - assertEq(_stateMachine.getPersistedState(), State.VetoSignalling); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignallingDeactivation); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: proposalSubmittedAt}) - ); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: proposalSubmittedAt}) - ); + _assertState({persisted: State.VetoSignalling, effective: State.VetoSignallingDeactivation}); + _assertCanScheduleProposal({proposalSubmittedAt: proposalSubmittedAt, persisted: false, effective: false}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.VetoSignallingDeactivation); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignallingDeactivation); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: proposalSubmittedAt}) - ); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: proposalSubmittedAt}) - ); + _assertState({persisted: State.VetoSignallingDeactivation, effective: State.VetoSignallingDeactivation}); + _assertCanScheduleProposal({proposalSubmittedAt: proposalSubmittedAt, persisted: false, effective: false}); // simulate the second threshold of veto signalling was reached - EscrowMock(signallingEscrow).__setRageQuitSupport( - _CONFIG_PROVIDER.SECOND_SEAL_RAGE_QUIT_SUPPORT() + PercentsD16.fromBasisPoints(1_00) - ); + _mockRageQuitSupport(_CONFIG_PROVIDER.SECOND_SEAL_RAGE_QUIT_SUPPORT() + PercentsD16.from(1)); - assertEq(_stateMachine.getPersistedState(), State.VetoSignallingDeactivation); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignalling); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: proposalSubmittedAt}) - ); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: proposalSubmittedAt}) - ); + _assertState({persisted: State.VetoSignallingDeactivation, effective: State.VetoSignalling}); + _assertCanScheduleProposal({proposalSubmittedAt: proposalSubmittedAt, persisted: false, effective: false}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.VetoSignalling); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignalling); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: proposalSubmittedAt}) - ); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: proposalSubmittedAt}) - ); + _assertState({persisted: State.VetoSignalling, effective: State.VetoSignalling}); + _assertCanScheduleProposal({proposalSubmittedAt: proposalSubmittedAt, persisted: false, effective: false}); _wait(_CONFIG_PROVIDER.VETO_SIGNALLING_MAX_DURATION().plusSeconds(1)); - assertEq(_stateMachine.getPersistedState(), State.VetoSignalling); - assertEq(_stateMachine.getEffectiveState(), State.RageQuit); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: proposalSubmittedAt}) - ); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: proposalSubmittedAt}) - ); - - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _assertState({persisted: State.VetoSignalling, effective: State.RageQuit}); + _assertCanScheduleProposal({proposalSubmittedAt: proposalSubmittedAt, persisted: false, effective: false}); - assertEq(_stateMachine.getPersistedState(), State.RageQuit); - assertEq(_stateMachine.getEffectiveState(), State.RageQuit); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: proposalSubmittedAt}) - ); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: proposalSubmittedAt}) - ); + _activateNextState(); - EscrowMock(address(_stateMachine.rageQuitEscrow)).__setIsRageQuitFinalized(true); - - assertEq(_stateMachine.getPersistedState(), State.RageQuit); - assertEq(_stateMachine.getEffectiveState(), State.VetoCooldown); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: proposalSubmittedAt}) - ); - assertTrue( - _stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: proposalSubmittedAt}) - ); - // for proposals submitted at the same block the VetoSignalling started scheduling is allowed - assertTrue( - _stateMachine.canScheduleProposal({ - useEffectiveState: true, - proposalSubmittedAt: _stateMachine.vetoSignallingActivatedAt - }) - ); - // for proposals submitted after the VetoSignalling started scheduling is forbidden - assertFalse( - _stateMachine.canScheduleProposal({ - useEffectiveState: true, - proposalSubmittedAt: Durations.from(1 seconds).addTo(_stateMachine.vetoSignallingActivatedAt) - }) - ); - assertFalse(_stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: Timestamps.now()})); + _assertState({persisted: State.RageQuit, effective: State.RageQuit}); + _assertCanScheduleProposal({proposalSubmittedAt: proposalSubmittedAt, persisted: false, effective: false}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _mockRageQuitFinalized(true); + _mockRageQuitSupport(PercentsD16.from(0)); - assertEq(_stateMachine.getPersistedState(), State.VetoCooldown); - assertEq(_stateMachine.getEffectiveState(), State.VetoCooldown); + _assertState({persisted: State.RageQuit, effective: State.VetoCooldown}); + _assertCanScheduleProposal({proposalSubmittedAt: proposalSubmittedAt, persisted: false, effective: true}); - // persisted - assertTrue( - _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: proposalSubmittedAt}) - ); - assertTrue( - _stateMachine.canScheduleProposal({ - useEffectiveState: false, - proposalSubmittedAt: _stateMachine.vetoSignallingActivatedAt - }) - ); - // for proposals submitted after the VetoSignalling started scheduling is forbidden - assertFalse( - _stateMachine.canScheduleProposal({ - useEffectiveState: false, - proposalSubmittedAt: Durations.from(1 seconds).addTo(_stateMachine.vetoSignallingActivatedAt) - }) - ); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: Timestamps.now()}) - ); - - // effective - assertTrue( - _stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: proposalSubmittedAt}) - ); // for proposals submitted at the same block the VetoSignalling started scheduling is allowed - assertTrue( - _stateMachine.canScheduleProposal({ - useEffectiveState: true, - proposalSubmittedAt: _stateMachine.vetoSignallingActivatedAt - }) - ); + _assertCanScheduleProposal({ + proposalSubmittedAt: _stateMachine.vetoSignallingActivatedAt, + persisted: false, + effective: true + }); // for proposals submitted after the VetoSignalling started scheduling is forbidden - assertFalse( - _stateMachine.canScheduleProposal({ - useEffectiveState: true, - proposalSubmittedAt: Durations.from(1 seconds).addTo(_stateMachine.vetoSignallingActivatedAt) - }) - ); - assertFalse(_stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: Timestamps.now()})); + _assertCanScheduleProposal({ + proposalSubmittedAt: Durations.from(1 seconds).addTo(_stateMachine.vetoSignallingActivatedAt), + persisted: false, + effective: false + }); + _assertCanScheduleProposal({proposalSubmittedAt: Timestamps.now(), persisted: false, effective: false}); + + _activateNextState(); + + _assertState({persisted: State.VetoCooldown, effective: State.VetoCooldown}); + + _assertCanScheduleProposal({proposalSubmittedAt: proposalSubmittedAt, persisted: true, effective: true}); + _assertCanScheduleProposal({ + proposalSubmittedAt: _stateMachine.vetoSignallingActivatedAt, + persisted: true, + effective: true + }); + // for proposals submitted after the VetoSignalling started scheduling is forbidden + _assertCanScheduleProposal({ + proposalSubmittedAt: Durations.from(1 seconds).addTo(_stateMachine.vetoSignallingActivatedAt), + persisted: false, + effective: false + }); + _assertCanScheduleProposal({proposalSubmittedAt: Timestamps.now(), persisted: false, effective: false}); _wait(_CONFIG_PROVIDER.VETO_COOLDOWN_DURATION().plusSeconds(1)); - assertEq(_stateMachine.getPersistedState(), State.VetoCooldown); - assertEq(_stateMachine.getEffectiveState(), State.Normal); + _assertState({persisted: State.VetoCooldown, effective: State.Normal}); - // persisted - assertTrue( - _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: proposalSubmittedAt}) - ); - assertTrue( - _stateMachine.canScheduleProposal({ - useEffectiveState: false, - proposalSubmittedAt: _stateMachine.vetoSignallingActivatedAt - }) - ); - // for proposals submitted after the VetoSignalling started scheduling is forbidden - assertFalse( - _stateMachine.canScheduleProposal({ - useEffectiveState: false, - proposalSubmittedAt: Durations.from(1 seconds).addTo(_stateMachine.vetoSignallingActivatedAt) - }) - ); - assertFalse( - _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: Timestamps.now()}) - ); + _assertCanScheduleProposal({proposalSubmittedAt: proposalSubmittedAt, persisted: true, effective: true}); + _assertCanScheduleProposal({ + proposalSubmittedAt: _stateMachine.vetoSignallingActivatedAt, + persisted: true, + effective: true + }); - // effective - assertTrue( - _stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: proposalSubmittedAt}) - ); - assertTrue(_stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: Timestamps.now()})); + // for proposals submitted after the VetoSignalling started scheduling is forbidden + _assertCanScheduleProposal({ + proposalSubmittedAt: Durations.from(1 seconds).addTo(_stateMachine.vetoSignallingActivatedAt), + persisted: false, + effective: true + }); + _assertCanScheduleProposal({proposalSubmittedAt: Timestamps.now(), persisted: false, effective: true}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.Normal); - assertEq(_stateMachine.getEffectiveState(), State.Normal); + _assertState({persisted: State.Normal, effective: State.Normal}); // persisted - assertTrue( - _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: proposalSubmittedAt}) - ); - assertTrue(_stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: Timestamps.now()})); - - // effective - assertTrue( - _stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: proposalSubmittedAt}) - ); - assertTrue(_stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: Timestamps.now()})); + _assertCanScheduleProposal({proposalSubmittedAt: proposalSubmittedAt, persisted: true, effective: true}); + _assertCanScheduleProposal({proposalSubmittedAt: Timestamps.now(), persisted: true, effective: true}); } // --- @@ -435,110 +377,138 @@ contract DualGovernanceStateMachineUnitTests is UnitTest { // --- function test_canCancelAllPendingProposals_HappyPath() external { - address signallingEscrow = address(_stateMachine.signallingEscrow); - Timestamp proposalSubmittedAt = Timestamps.now(); - - assertEq(_stateMachine.getPersistedState(), State.Normal); - assertEq(_stateMachine.getEffectiveState(), State.Normal); - assertFalse(_stateMachine.canCancelAllPendingProposals({useEffectiveState: true})); - assertFalse(_stateMachine.canCancelAllPendingProposals({useEffectiveState: false})); + _assertState({persisted: State.Normal, effective: State.Normal}); + _assertCanCancelAllPendingProposals({persisted: false, effective: false}); // simulate the first threshold of veto signalling was reached - EscrowMock(signallingEscrow).__setRageQuitSupport( - _CONFIG_PROVIDER.FIRST_SEAL_RAGE_QUIT_SUPPORT() + PercentD16.wrap(1) - ); + _mockRageQuitSupport(_CONFIG_PROVIDER.FIRST_SEAL_RAGE_QUIT_SUPPORT() + PercentsD16.from(1)); - assertEq(_stateMachine.getPersistedState(), State.Normal); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignalling); - assertFalse(_stateMachine.canCancelAllPendingProposals({useEffectiveState: false})); - assertTrue(_stateMachine.canCancelAllPendingProposals({useEffectiveState: true})); + _assertState({persisted: State.Normal, effective: State.VetoSignalling}); + _assertCanCancelAllPendingProposals({persisted: false, effective: true}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.VetoSignalling); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignalling); - assertTrue(_stateMachine.canCancelAllPendingProposals({useEffectiveState: false})); - assertTrue(_stateMachine.canCancelAllPendingProposals({useEffectiveState: true})); + _assertState({persisted: State.VetoSignalling, effective: State.VetoSignalling}); + _assertCanCancelAllPendingProposals({persisted: true, effective: true}); _wait(_CONFIG_PROVIDER.VETO_SIGNALLING_MIN_DURATION().plusSeconds(1 minutes)); - assertEq(_stateMachine.getPersistedState(), State.VetoSignalling); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignallingDeactivation); - assertTrue(_stateMachine.canCancelAllPendingProposals({useEffectiveState: false})); - assertTrue(_stateMachine.canCancelAllPendingProposals({useEffectiveState: true})); + _assertState({persisted: State.VetoSignalling, effective: State.VetoSignallingDeactivation}); + _assertCanCancelAllPendingProposals({persisted: true, effective: true}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.VetoSignallingDeactivation); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignallingDeactivation); - assertTrue(_stateMachine.canCancelAllPendingProposals({useEffectiveState: false})); - assertTrue(_stateMachine.canCancelAllPendingProposals({useEffectiveState: true})); + _assertState({persisted: State.VetoSignallingDeactivation, effective: State.VetoSignallingDeactivation}); + _assertCanCancelAllPendingProposals({persisted: true, effective: true}); // simulate the second threshold of veto signalling was reached - EscrowMock(signallingEscrow).__setRageQuitSupport( - _CONFIG_PROVIDER.SECOND_SEAL_RAGE_QUIT_SUPPORT() + PercentsD16.fromBasisPoints(1_00) - ); + _mockRageQuitSupport(_CONFIG_PROVIDER.SECOND_SEAL_RAGE_QUIT_SUPPORT() + PercentsD16.from(1)); - assertEq(_stateMachine.getPersistedState(), State.VetoSignallingDeactivation); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignalling); - assertTrue(_stateMachine.canCancelAllPendingProposals({useEffectiveState: false})); - assertTrue(_stateMachine.canCancelAllPendingProposals({useEffectiveState: true})); + _assertState({persisted: State.VetoSignallingDeactivation, effective: State.VetoSignalling}); + _assertCanCancelAllPendingProposals({persisted: true, effective: true}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.VetoSignalling); - assertEq(_stateMachine.getEffectiveState(), State.VetoSignalling); - assertTrue(_stateMachine.canCancelAllPendingProposals({useEffectiveState: false})); - assertTrue(_stateMachine.canCancelAllPendingProposals({useEffectiveState: true})); + _assertState({persisted: State.VetoSignalling, effective: State.VetoSignalling}); + _assertCanCancelAllPendingProposals({persisted: true, effective: true}); _wait(_CONFIG_PROVIDER.VETO_SIGNALLING_MAX_DURATION().plusSeconds(1)); - assertEq(_stateMachine.getPersistedState(), State.VetoSignalling); - assertEq(_stateMachine.getEffectiveState(), State.RageQuit); - assertTrue(_stateMachine.canCancelAllPendingProposals({useEffectiveState: false})); - assertFalse(_stateMachine.canCancelAllPendingProposals({useEffectiveState: true})); + _assertState({persisted: State.VetoSignalling, effective: State.RageQuit}); + _assertCanCancelAllPendingProposals({persisted: true, effective: false}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.RageQuit); - assertEq(_stateMachine.getEffectiveState(), State.RageQuit); - assertFalse(_stateMachine.canCancelAllPendingProposals({useEffectiveState: false})); - assertFalse(_stateMachine.canCancelAllPendingProposals({useEffectiveState: true})); + _assertState({persisted: State.RageQuit, effective: State.RageQuit}); + _assertCanCancelAllPendingProposals({persisted: false, effective: false}); - EscrowMock(address(_stateMachine.rageQuitEscrow)).__setIsRageQuitFinalized(true); + _mockRageQuitFinalized(true); + _mockRageQuitSupport(PercentsD16.from(0)); - assertEq(_stateMachine.getPersistedState(), State.RageQuit); - assertEq(_stateMachine.getEffectiveState(), State.VetoCooldown); - assertFalse(_stateMachine.canCancelAllPendingProposals({useEffectiveState: false})); - assertFalse(_stateMachine.canCancelAllPendingProposals({useEffectiveState: true})); + _assertState({persisted: State.RageQuit, effective: State.VetoCooldown}); + _assertCanCancelAllPendingProposals({persisted: false, effective: false}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.VetoCooldown); - assertEq(_stateMachine.getEffectiveState(), State.VetoCooldown); - assertFalse(_stateMachine.canCancelAllPendingProposals({useEffectiveState: false})); - assertFalse(_stateMachine.canCancelAllPendingProposals({useEffectiveState: true})); + _assertState({persisted: State.VetoCooldown, effective: State.VetoCooldown}); + _assertCanCancelAllPendingProposals({persisted: false, effective: false}); _wait(_CONFIG_PROVIDER.VETO_COOLDOWN_DURATION().plusSeconds(1)); - assertEq(_stateMachine.getPersistedState(), State.VetoCooldown); - assertEq(_stateMachine.getEffectiveState(), State.Normal); - assertFalse(_stateMachine.canCancelAllPendingProposals({useEffectiveState: false})); - assertFalse(_stateMachine.canCancelAllPendingProposals({useEffectiveState: true})); + _assertState({persisted: State.VetoCooldown, effective: State.Normal}); + _assertCanCancelAllPendingProposals({persisted: false, effective: false}); - _stateMachine.activateNextState(_ESCROW_MASTER_COPY); + _activateNextState(); - assertEq(_stateMachine.getPersistedState(), State.Normal); - assertEq(_stateMachine.getEffectiveState(), State.Normal); - assertFalse(_stateMachine.canCancelAllPendingProposals({useEffectiveState: false})); - assertFalse(_stateMachine.canCancelAllPendingProposals({useEffectiveState: true})); + _assertState({persisted: State.Normal, effective: State.Normal}); + _assertCanCancelAllPendingProposals({persisted: false, effective: false}); } // --- // Test helper methods // --- + function _mockRageQuitSupport(PercentD16 rageQuitSupport) internal { + vm.mockCall( + _ESCROW_MASTER_COPY_MOCK, abi.encodeCall(IEscrow.getRageQuitSupport, ()), abi.encode(rageQuitSupport) + ); + } + + function _mockRageQuitFinalized(bool isRageQuitFinalized) internal { + vm.mockCall( + _ESCROW_MASTER_COPY_MOCK, abi.encodeCall(IEscrow.isRageQuitFinalized, ()), abi.encode(isRageQuitFinalized) + ); + } + + function _activateNextState() internal { + _stateMachine.activateNextState(IEscrow(_ESCROW_MASTER_COPY_MOCK)); + } + + function _assertState(State persisted, State effective) internal { + assertEq(_stateMachine.getPersistedState(), persisted, "Unexpected Persisted State"); + assertEq(_stateMachine.getEffectiveState(), effective, "Unexpected Effective State"); + } + + function _assertCanCancelAllPendingProposals(bool persisted, bool effective) internal { + assertEq( + _stateMachine.canCancelAllPendingProposals({useEffectiveState: false}), + persisted, + "Unexpected persisted canCancelAllPendingProposals() value" + ); + assertEq( + _stateMachine.canCancelAllPendingProposals({useEffectiveState: true}), + effective, + "Unexpected effective canCancelAllPendingProposals() value" + ); + } + + function _assertCanScheduleProposal(Timestamp proposalSubmittedAt, bool persisted, bool effective) internal { + assertEq( + _stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: proposalSubmittedAt}), + persisted, + "Unexpected persisted canScheduleProposal() value" + ); + assertEq( + _stateMachine.canScheduleProposal({useEffectiveState: true, proposalSubmittedAt: proposalSubmittedAt}), + effective, + "Unexpected persisted canScheduleProposal() value" + ); + } + + function _assertCanSubmitProposal(bool persisted, bool effective) internal { + assertEq( + _stateMachine.canSubmitProposal({useEffectiveState: false}), + persisted, + "Unexpected persisted canSubmitProposal() value" + ); + assertEq( + _stateMachine.canSubmitProposal({useEffectiveState: true}), + effective, + "Unexpected effective canSubmitProposal() value" + ); + } + function external__initialize() external { - _stateMachine.initialize(_CONFIG_PROVIDER, _ESCROW_MASTER_COPY); + _stateMachine.initialize(_CONFIG_PROVIDER, IEscrow(_ESCROW_MASTER_COPY_MOCK)); } } diff --git a/test/utils/testing-assert-eq-extender.sol b/test/utils/testing-assert-eq-extender.sol index fcac37da..d4dc06cd 100644 --- a/test/utils/testing-assert-eq-extender.sol +++ b/test/utils/testing-assert-eq-extender.sol @@ -41,6 +41,10 @@ contract TestingAssertEqExtender is Test { assertEq(uint256(a), uint256(b)); } + function assertEq(DualGovernanceState a, DualGovernanceState b, string memory message) internal { + assertEq(uint256(a), uint256(b), message); + } + function assertEq(Balances memory b1, Balances memory b2, uint256 sharesEpsilon) internal { assertEq(b1.wstETHShares, b2.wstETHShares); assertEq(b1.wstETHAmount, b2.wstETHAmount);