diff --git a/.github/workflows/lido-ci.yml b/.github/workflows/lido-ci.yml index 16aa9be5..d43be6df 100644 --- a/.github/workflows/lido-ci.yml +++ b/.github/workflows/lido-ci.yml @@ -6,6 +6,7 @@ branches: - develop - rvdevelop + - tests-on-client-code jobs: test: runs-on: ubuntu-latest @@ -25,7 +26,7 @@ -H "Authorization: Bearer ${{ secrets.RV_COMPUTE_TOKEN }}" \ https://api.github.com/repos/runtimeverification/_kaas_lidofinance_dual-governance/actions/workflows/lido-ci.yml/dispatches \ -d '{ - "ref": "rvdevelop", + "ref": "tests-on-client-code", "inputs": { "branch_name": "'"${{ github.event.pull_request.head.sha || github.sha }}"'", "extra_args": "script", diff --git a/contracts/interfaces/IStETH.sol b/contracts/interfaces/IStETH.sol index 764dc145..d38670c9 100644 --- a/contracts/interfaces/IStETH.sol +++ b/contracts/interfaces/IStETH.sol @@ -9,7 +9,7 @@ interface IStETH is IERC20 { function getPooledEthByShares(uint256 sharesAmount) external view returns (uint256); - function transferShares(address to, uint256 amount) external; + function transferShares(address to, uint256 amount) external returns (uint256); function transferSharesFrom( address _sender, address _recipient, diff --git a/contracts/model/StETHModel.sol b/contracts/model/StETHModel.sol index e85f6fd8..03e287b9 100644 --- a/contracts/model/StETHModel.sol +++ b/contracts/model/StETHModel.sol @@ -1,6 +1,8 @@ pragma solidity 0.8.23; -contract StETHModel { +import "contracts/interfaces/IStETH.sol"; + +contract StETHModel is IStETH { uint256 private totalPooledEther; uint256 private totalShares; mapping(address => uint256) private shares; diff --git a/contracts/model/WithdrawalQueueModel.sol b/contracts/model/WithdrawalQueueModel.sol new file mode 100644 index 00000000..2c484d62 --- /dev/null +++ b/contracts/model/WithdrawalQueueModel.sol @@ -0,0 +1,9 @@ +pragma solidity 0.8.23; + +contract WithdrawalQueueModel { + uint256 _lastFinalizedRequestId; + + function getLastFinalizedRequestId() external view returns (uint256) { + return _lastFinalizedRequestId; + } +} diff --git a/contracts/model/WstETHAdapted.sol b/contracts/model/WstETHAdapted.sol new file mode 100644 index 00000000..e5c59c6f --- /dev/null +++ b/contracts/model/WstETHAdapted.sol @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2021 Lido + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.23; + +import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; +import "contracts/interfaces/IStETH.sol"; + +/** + * @title StETH token wrapper with static balances. + * @dev It's an ERC20 token that represents the account's share of the total + * supply of stETH tokens. WstETH token's balance only changes on transfers, + * unlike StETH that is also changed when oracles report staking rewards and + * penalties. It's a "power user" token for DeFi protocols which don't + * support rebasable tokens. + * + * The contract is also a trustless wrapper that accepts stETH tokens and mints + * wstETH in return. Then the user unwraps, the contract burns user's wstETH + * and sends user locked stETH in return. + * + * The contract provides the staking shortcut: user can send ETH with regular + * transfer and get wstETH in return. The contract will send ETH to Lido submit + * method, staking it and wrapping the received stETH. + * + */ +contract WstETHAdapted is ERC20Permit { + IStETH public stETH; + + /** + * @param _stETH address of the StETH token to wrap + */ + constructor(IStETH _stETH) + public + ERC20Permit("Wrapped liquid staked Ether 2.0") + ERC20("Wrapped liquid staked Ether 2.0", "wstETH") + { + stETH = _stETH; + } + + /** + * @notice Exchanges stETH to wstETH + * @param _stETHAmount amount of stETH to wrap in exchange for wstETH + * @dev Requirements: + * - `_stETHAmount` must be non-zero + * - msg.sender must approve at least `_stETHAmount` stETH to this + * contract. + * - msg.sender must have at least `_stETHAmount` of stETH. + * User should first approve _stETHAmount to the WstETH contract + * @return Amount of wstETH user receives after wrap + */ + function wrap(uint256 _stETHAmount) external returns (uint256) { + require(_stETHAmount > 0, "wstETH: can't wrap zero stETH"); + uint256 wstETHAmount = stETH.getSharesByPooledEth(_stETHAmount); + _mint(msg.sender, wstETHAmount); + stETH.transferFrom(msg.sender, address(this), _stETHAmount); + return wstETHAmount; + } + + /** + * @notice Exchanges wstETH to stETH + * @param _wstETHAmount amount of wstETH to uwrap in exchange for stETH + * @dev Requirements: + * - `_wstETHAmount` must be non-zero + * - msg.sender must have at least `_wstETHAmount` wstETH. + * @return Amount of stETH user receives after unwrap + */ + function unwrap(uint256 _wstETHAmount) external returns (uint256) { + require(_wstETHAmount > 0, "wstETH: zero amount unwrap not allowed"); + uint256 stETHAmount = stETH.getPooledEthByShares(_wstETHAmount); + _burn(msg.sender, _wstETHAmount); + stETH.transfer(msg.sender, stETHAmount); + return stETHAmount; + } + + /** + * @notice Shortcut to stake ETH and auto-wrap returned stETH + */ + /* + receive() external payable { + uint256 shares = stETH.submit{value: msg.value}(address(0)); + _mint(msg.sender, shares); + } + */ + + /** + * @notice Get amount of wstETH for a given amount of stETH + * @param _stETHAmount amount of stETH + * @return Amount of wstETH for a given stETH amount + */ + function getWstETHByStETH(uint256 _stETHAmount) external view returns (uint256) { + return stETH.getSharesByPooledEth(_stETHAmount); + } + + /** + * @notice Get amount of stETH for a given amount of wstETH + * @param _wstETHAmount amount of wstETH + * @return Amount of stETH for a given wstETH amount + */ + function getStETHByWstETH(uint256 _wstETHAmount) external view returns (uint256) { + return stETH.getPooledEthByShares(_wstETHAmount); + } + + /** + * @notice Get amount of stETH for a one wstETH + * @return Amount of stETH for 1 wstETH + */ + function stEthPerToken() external view returns (uint256) { + return stETH.getPooledEthByShares(1 ether); + } + + /** + * @notice Get amount of wstETH for a one stETH + * @return Amount of wstETH for a 1 stETH + */ + function tokensPerStEth() external view returns (uint256) { + return stETH.getSharesByPooledEth(1 ether); + } +} diff --git a/test/kontrol/DualGovernanceSetUp.sol b/test/kontrol/DualGovernanceSetUp.sol index 2ce33f1f..4a3cc3be 100644 --- a/test/kontrol/DualGovernanceSetUp.sol +++ b/test/kontrol/DualGovernanceSetUp.sol @@ -1,26 +1,46 @@ pragma solidity 0.8.23; -import "contracts/model/DualGovernanceModel.sol"; -import "contracts/model/EmergencyProtectedTimelockModel.sol"; -import "contracts/model/EscrowModel.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; + +import "contracts/Configuration.sol"; +import "contracts/DualGovernance.sol"; +import "contracts/EmergencyProtectedTimelock.sol"; +import "contracts/Escrow.sol"; import "contracts/model/StETHModel.sol"; +import "contracts/model/WstETHAdapted.sol"; +import "contracts/model/WithdrawalQueueModel.sol"; import "test/kontrol/StorageSetup.sol"; contract DualGovernanceSetUp is StorageSetup { - DualGovernanceModel dualGovernance; - EmergencyProtectedTimelockModel timelock; + using DualGovernanceState for DualGovernanceState.Store; + + Configuration config; + DualGovernance dualGovernance; + EmergencyProtectedTimelock timelock; StETHModel stEth; - EscrowModel signallingEscrow; - EscrowModel rageQuitEscrow; + WstETHAdapted wstEth; + WithdrawalQueueModel withdrawalQueue; + IEscrow signallingEscrow; + IEscrow rageQuitEscrow; function setUp() public { stEth = new StETHModel(); - uint256 emergencyProtectionTimelock = 0; // Regular deployment mode - dualGovernance = new DualGovernanceModel(address(stEth), emergencyProtectionTimelock); - timelock = dualGovernance.emergencyProtectedTimelock(); - signallingEscrow = dualGovernance.signallingEscrow(); - rageQuitEscrow = new EscrowModel(address(dualGovernance), address(stEth)); + wstEth = new WstETHAdapted(IStETH(stEth)); + withdrawalQueue = new WithdrawalQueueModel(); + + // Placeholder addresses + address adminExecutor = address(uint160(uint256(keccak256("adminExecutor")))); + address emergencyGovernance = address(uint160(uint256(keccak256("emergencyGovernance")))); + address adminProposer = address(uint160(uint256(keccak256("adminProposer")))); + + config = new Configuration(adminExecutor, emergencyGovernance, new address[](0)); + timelock = new EmergencyProtectedTimelock(address(config)); + Escrow escrowMasterCopy = new Escrow(address(stEth), address(wstEth), address(withdrawalQueue), address(config)); + dualGovernance = + new DualGovernance(address(config), address(timelock), address(escrowMasterCopy), adminProposer); + signallingEscrow = IEscrow(_loadAddress(address(dualGovernance), 5)); + rageQuitEscrow = IEscrow(Clones.clone(address(escrowMasterCopy))); // ?STORAGE // ?WORD: totalPooledEther @@ -59,5 +79,7 @@ contract DualGovernanceSetUp is StorageSetup { // ?STORAGE3 kevm.symbolicStorage(address(timelock)); + + kevm.symbolicStorage(address(withdrawalQueue)); } } diff --git a/test/kontrol/EscrowAccounting.t.sol b/test/kontrol/EscrowAccounting.t.sol index 7bb0f5ca..f5fd4b88 100644 --- a/test/kontrol/EscrowAccounting.t.sol +++ b/test/kontrol/EscrowAccounting.t.sol @@ -1,20 +1,36 @@ pragma solidity 0.8.23; -import "contracts/model/DualGovernanceModel.sol"; -import "contracts/model/EmergencyProtectedTimelockModel.sol"; -import "contracts/model/EscrowModel.sol"; +import "contracts/Configuration.sol"; +import "contracts/DualGovernance.sol"; +import "contracts/EmergencyProtectedTimelock.sol"; +import "contracts/Escrow.sol"; import "contracts/model/StETHModel.sol"; +import "contracts/model/WithdrawalQueueModel.sol"; +import "contracts/model/WstETHAdapted.sol"; -import "test/kontrol/StorageSetup.sol"; +import {StorageSetup} from "test/kontrol/StorageSetup.sol"; contract EscrowAccountingTest is StorageSetup { + Configuration config; StETHModel stEth; - EscrowModel escrow; + WstETHAdapted wstEth; + WithdrawalQueueModel withdrawalQueue; + Escrow escrow; function _setUpInitialState() public { stEth = new StETHModel(); - address dualGovernanceAddress = address(uint160(uint256(keccak256("dualGovernance")))); // arbitrary DG address - escrow = new EscrowModel(dualGovernanceAddress, address(stEth)); + wstEth = new WstETHAdapted(IStETH(stEth)); + withdrawalQueue = new WithdrawalQueueModel(); + + // Placeholder addresses + address adminExecutor = address(uint160(uint256(keccak256("adminExecutor")))); + address emergencyGovernance = address(uint160(uint256(keccak256("emergencyGovernance")))); + address dualGovernanceAddress = address(uint160(uint256(keccak256("dualGovernance")))); + + config = new Configuration(adminExecutor, emergencyGovernance, new address[](0)); + + escrow = new Escrow(address(stEth), address(wstEth), address(withdrawalQueue), address(config)); + escrow.initialize(dualGovernanceAddress); // ?STORAGE // ?WORD: totalPooledEther @@ -24,30 +40,23 @@ contract EscrowAccountingTest is StorageSetup { } function _setUpGenericState() public { - stEth = new StETHModel(); - escrow = new EscrowModel(address(0), address(0)); - - // ?STORAGE - // ?WORD: totalPooledEther - // ?WORD0: totalShares - // ?WORD1: shares[escrow] - _stEthStorageSetup(stEth, escrow); + _setUpInitialState(); address dualGovernanceAddress = address(uint160(kevm.freshUInt(20))); // ?WORD2 uint8 currentState = uint8(kevm.freshUInt(1)); // ?WORD3 - vm.assume(currentState < 2); + vm.assume(currentState < 3); // ?STORAGE0 // ?WORD4: totalSharesLocked // ?WORD5: totalClaimedEthAmount // ?WORD6: rageQuitExtensionDelayPeriodEnd - _escrowStorageSetup(escrow, DualGovernanceModel(dualGovernanceAddress), stEth, currentState); + _escrowStorageSetup(escrow, DualGovernance(dualGovernanceAddress), stEth, EscrowState(currentState)); } function testRageQuitSupport() public { _setUpGenericState(); - uint256 totalSharesLocked = escrow.totalSharesLocked(); + uint128 totalSharesLocked = escrow.getLockedAssetsTotals().shares; uint256 totalFundsLocked = stEth.getPooledEthByShares(totalSharesLocked); uint256 expectedRageQuitSupport = totalFundsLocked * 1e18 / stEth.totalSupply(); @@ -55,24 +64,30 @@ contract EscrowAccountingTest is StorageSetup { } function _escrowInvariants(Mode mode) internal view { - _establish(mode, escrow.totalSharesLocked() <= stEth.sharesOf(address(escrow))); - uint256 totalPooledEther = stEth.getPooledEthByShares(escrow.totalSharesLocked()); + LockedAssetsTotals memory totals = escrow.getLockedAssetsTotals(); + _establish(mode, totals.shares <= stEth.sharesOf(address(escrow))); + _establish(mode, totals.sharesFinalized <= totals.shares); + uint256 totalPooledEther = stEth.getPooledEthByShares(totals.shares); _establish(mode, totalPooledEther <= stEth.balanceOf(address(escrow))); - _establish(mode, escrow.totalWithdrawalRequestAmount() <= totalPooledEther); - _establish(mode, escrow.totalClaimedEthAmount() <= escrow.totalWithdrawalRequestAmount()); - _establish(mode, escrow.totalWithdrawnPostRageQuit() <= escrow.totalClaimedEthAmount()); + _establish(mode, totals.amountFinalized == stEth.getPooledEthByShares(totals.sharesFinalized)); + _establish(mode, totals.amountFinalized <= totalPooledEther); + _establish(mode, totals.amountClaimed <= totals.amountFinalized); + EscrowState currentState = _getCurrentState(escrow); + _establish(mode, 0 < uint8(currentState)); + _establish(mode, uint8(currentState) < 3); } function _signallingEscrowInvariants(Mode mode) internal view { - if (escrow.currentState() == EscrowModel.State.SignallingEscrow) { - _establish(mode, escrow.totalWithdrawalRequestAmount() == 0); - _establish(mode, escrow.totalClaimedEthAmount() == 0); - _establish(mode, escrow.totalWithdrawnPostRageQuit() == 0); + if (_getCurrentState(escrow) == EscrowState.SignallingEscrow) { + LockedAssetsTotals memory totals = escrow.getLockedAssetsTotals(); + _establish(mode, totals.sharesFinalized == 0); + _establish(mode, totals.amountFinalized == 0); + _establish(mode, totals.amountClaimed == 0); } } function _escrowUserInvariants(Mode mode, address user) internal view { - _establish(mode, escrow.shares(user) <= escrow.totalSharesLocked()); + _establish(mode, escrow.getVetoerState(user).stETHShares <= escrow.getLockedAssetsTotals().shares); } function testEscrowInvariantsHoldInitially() public { @@ -86,7 +101,7 @@ contract EscrowAccountingTest is StorageSetup { } struct AccountingRecord { - EscrowModel.State escrowState; + EscrowState escrowState; uint256 allowance; uint256 userBalance; uint256 escrowBalance; @@ -99,16 +114,16 @@ contract EscrowAccountingTest is StorageSetup { } function _saveAccountingRecord(address user) internal view returns (AccountingRecord memory ar) { - ar.escrowState = escrow.currentState(); + ar.escrowState = _getCurrentState(escrow); ar.allowance = stEth.allowance(user, address(escrow)); ar.userBalance = stEth.balanceOf(user); ar.escrowBalance = stEth.balanceOf(address(escrow)); ar.userShares = stEth.sharesOf(user); ar.escrowShares = stEth.sharesOf(address(escrow)); - ar.userSharesLocked = escrow.shares(user); - ar.totalSharesLocked = escrow.totalSharesLocked(); + ar.userSharesLocked = escrow.getVetoerState(user).stETHShares; + ar.totalSharesLocked = escrow.getLockedAssetsTotals().shares; ar.totalEth = stEth.getPooledEthByShares(ar.totalSharesLocked); - ar.userLastLockedTime = escrow.lastLockedTimes(user); + ar.userLastLockedTime = _getLastAssetsLockTimestamp(escrow, user); } function _assumeFreshAddress(address account) internal { @@ -140,7 +155,7 @@ contract EscrowAccountingTest is StorageSetup { vm.assume(stEth.balanceOf(sender) < ethUpperBound); AccountingRecord memory pre = _saveAccountingRecord(sender); - vm.assume(pre.escrowState == EscrowModel.State.SignallingEscrow); + vm.assume(pre.escrowState == EscrowState.SignallingEscrow); vm.assume(0 < amount); vm.assume(amount <= pre.userBalance); vm.assume(amount <= pre.allowance); @@ -154,7 +169,7 @@ contract EscrowAccountingTest is StorageSetup { _escrowUserInvariants(Mode.Assume, sender); vm.startPrank(sender); - escrow.lock(amount); + escrow.lockStETH(amount); vm.stopPrank(); _escrowInvariants(Mode.Assert); @@ -162,7 +177,7 @@ contract EscrowAccountingTest is StorageSetup { _escrowUserInvariants(Mode.Assert, sender); AccountingRecord memory post = _saveAccountingRecord(sender); - assert(post.escrowState == EscrowModel.State.SignallingEscrow); + assert(post.escrowState == EscrowState.SignallingEscrow); assert(post.userShares == pre.userShares - amountInShares); assert(post.escrowShares == pre.escrowShares + amountInShares); assert(post.userSharesLocked == pre.userSharesLocked + amountInShares); @@ -186,19 +201,19 @@ contract EscrowAccountingTest is StorageSetup { // Placeholder address to avoid complications with keccak of symbolic addresses address sender = address(uint160(uint256(keccak256("sender")))); vm.assume(stEth.sharesOf(sender) < ethUpperBound); - vm.assume(escrow.lastLockedTimes(sender) < timeUpperBound); + vm.assume(_getLastAssetsLockTimestamp(escrow, sender) < timeUpperBound); AccountingRecord memory pre = _saveAccountingRecord(sender); - vm.assume(pre.escrowState == EscrowModel.State.SignallingEscrow); + vm.assume(pre.escrowState == EscrowState.SignallingEscrow); vm.assume(pre.userSharesLocked <= pre.totalSharesLocked); - vm.assume(block.timestamp >= pre.userLastLockedTime + escrow.SIGNALLING_ESCROW_MIN_LOCK_TIME()); + vm.assume(block.timestamp >= pre.userLastLockedTime + config.SIGNALLING_ESCROW_MIN_LOCK_TIME()); _escrowInvariants(Mode.Assume); _signallingEscrowInvariants(Mode.Assume); _escrowUserInvariants(Mode.Assume, sender); vm.startPrank(sender); - escrow.unlock(); + escrow.unlockStETH(); vm.stopPrank(); _escrowInvariants(Mode.Assert); @@ -206,7 +221,7 @@ contract EscrowAccountingTest is StorageSetup { _escrowUserInvariants(Mode.Assert, sender); AccountingRecord memory post = _saveAccountingRecord(sender); - assert(post.escrowState == EscrowModel.State.SignallingEscrow); + assert(post.escrowState == EscrowState.SignallingEscrow); assert(post.userShares == pre.userShares + pre.userSharesLocked); assert(post.userSharesLocked == 0); assert(post.totalSharesLocked == pre.totalSharesLocked - pre.userSharesLocked); diff --git a/test/kontrol/EscrowOperations.t.sol b/test/kontrol/EscrowOperations.t.sol index df21d84e..ab959a11 100644 --- a/test/kontrol/EscrowOperations.t.sol +++ b/test/kontrol/EscrowOperations.t.sol @@ -12,18 +12,18 @@ contract EscrowOperationsTest is EscrowAccountingTest { // Placeholder address to avoid complications with keccak of symbolic addresses address sender = address(uint160(uint256(keccak256("sender")))); vm.assume(stEth.sharesOf(sender) < ethUpperBound); - vm.assume(escrow.lastLockedTimes(sender) < timeUpperBound); + vm.assume(_getLastAssetsLockTimestamp(escrow, sender) < timeUpperBound); AccountingRecord memory pre = _saveAccountingRecord(sender); - vm.assume(pre.escrowState == EscrowModel.State.SignallingEscrow); + vm.assume(pre.escrowState == EscrowState.SignallingEscrow); vm.assume(pre.userSharesLocked <= pre.totalSharesLocked); - uint256 lockPeriod = pre.userLastLockedTime + escrow.SIGNALLING_ESCROW_MIN_LOCK_TIME(); + uint256 lockPeriod = pre.userLastLockedTime + config.SIGNALLING_ESCROW_MIN_LOCK_TIME(); if (block.timestamp < lockPeriod) { vm.prank(sender); vm.expectRevert("Lock period not expired."); - escrow.unlock(); + escrow.unlockStETH(); } } @@ -51,33 +51,33 @@ contract EscrowOperationsTest is EscrowAccountingTest { _signallingEscrowInvariants(Mode.Assume); _escrowUserInvariants(Mode.Assume, sender); - if (pre.escrowState == EscrowModel.State.RageQuitEscrow) { + if (pre.escrowState == EscrowState.RageQuitEscrow) { vm.prank(sender); vm.expectRevert("Cannot lock in current state."); - escrow.lock(amount); + escrow.lockStETH(amount); vm.prank(sender); vm.expectRevert("Cannot unlock in current state."); - escrow.unlock(); + escrow.unlockStETH(); } else { vm.prank(sender); - escrow.lock(amount); + escrow.lockStETH(amount); AccountingRecord memory afterLock = _saveAccountingRecord(sender); vm.assume(afterLock.userShares < ethUpperBound); vm.assume(afterLock.userLastLockedTime < timeUpperBound); vm.assume(afterLock.userSharesLocked <= afterLock.totalSharesLocked); - vm.assume(block.timestamp >= afterLock.userLastLockedTime + escrow.SIGNALLING_ESCROW_MIN_LOCK_TIME()); + vm.assume(block.timestamp >= afterLock.userLastLockedTime + config.SIGNALLING_ESCROW_MIN_LOCK_TIME()); vm.prank(sender); - escrow.unlock(); + escrow.unlockStETH(); _escrowInvariants(Mode.Assert); _signallingEscrowInvariants(Mode.Assert); _escrowUserInvariants(Mode.Assert, sender); AccountingRecord memory post = _saveAccountingRecord(sender); - assert(post.escrowState == EscrowModel.State.SignallingEscrow); + assert(post.escrowState == EscrowState.SignallingEscrow); assert(post.userShares == pre.userShares); assert(post.escrowShares == pre.escrowShares); assert(post.userSharesLocked == 0); @@ -89,6 +89,8 @@ contract EscrowOperationsTest is EscrowAccountingTest { /** * Test that a user cannot withdraw funds from the escrow until the RageQuitEthClaimTimelock has elapsed after the RageQuitExtensionDelay period. */ + // TODO: Uncomment this test and adapt it to the client code + /* function testCannotWithdrawBeforeEthClaimTimelockElapsed() external { _setUpGenericState(); @@ -98,7 +100,7 @@ contract EscrowOperationsTest is EscrowAccountingTest { vm.assume(stEth.balanceOf(sender) < ethUpperBound); AccountingRecord memory pre = _saveAccountingRecord(sender); - vm.assume(pre.escrowState == EscrowModel.State.RageQuitEscrow); + vm.assume(pre.escrowState == EscrowState.RageQuitEscrow); vm.assume(pre.userSharesLocked > 0); vm.assume(pre.userSharesLocked <= pre.totalSharesLocked); uint256 userEth = stEth.getPooledEthByShares(pre.userSharesLocked); @@ -110,6 +112,7 @@ contract EscrowOperationsTest is EscrowAccountingTest { vm.assume(escrow.lastWithdrawalRequestSubmitted()); vm.assume(escrow.claimedWithdrawalRequests() == escrow.withdrawalRequestCount()); + vm.assume(escrow.getIsWithdrawalsClaimed()); vm.assume(escrow.rageQuitExtensionDelayPeriodEnd() < block.timestamp); // Assumption for simplicity vm.assume(escrow.rageQuitSequenceNumber() < 2); @@ -133,4 +136,5 @@ contract EscrowOperationsTest is EscrowAccountingTest { assert(post.userSharesLocked == 0); } } + */ } diff --git a/test/kontrol/KontrolTest.sol b/test/kontrol/KontrolTest.sol index 07786f09..589a117b 100644 --- a/test/kontrol/KontrolTest.sol +++ b/test/kontrol/KontrolTest.sol @@ -23,6 +23,14 @@ contract KontrolTest is Test, KontrolCheats { } } + function _loadUInt256(address contractAddress, uint256 slot) internal view returns (uint256) { + return uint256(vm.load(contractAddress, bytes32(slot))); + } + + function _loadAddress(address contractAddress, uint256 slot) internal view returns (address) { + return address(uint160(uint256(vm.load(contractAddress, bytes32(slot))))); + } + function _storeBytes32(address contractAddress, uint256 slot, bytes32 value) internal { vm.store(contractAddress, bytes32(slot), value); } diff --git a/test/kontrol/StorageSetup.sol b/test/kontrol/StorageSetup.sol index ef58ba19..3201bc51 100644 --- a/test/kontrol/StorageSetup.sol +++ b/test/kontrol/StorageSetup.sol @@ -1,14 +1,15 @@ pragma solidity 0.8.23; -import "contracts/model/DualGovernanceModel.sol"; -import "contracts/model/EmergencyProtectedTimelockModel.sol"; -import "contracts/model/EscrowModel.sol"; +import "contracts/DualGovernance.sol"; +import "contracts/EmergencyProtectedTimelock.sol"; +import "contracts/Escrow.sol"; import "contracts/model/StETHModel.sol"; +import "contracts/model/WstETHAdapted.sol"; import "test/kontrol/KontrolTest.sol"; contract StorageSetup is KontrolTest { - function _stEthStorageSetup(StETHModel _stEth, EscrowModel _escrow) internal { + function _stEthStorageSetup(StETHModel _stEth, IEscrow _escrow) internal { kevm.symbolicStorage(address(_stEth)); // Slot 0 uint256 totalPooledEther = kevm.freshUInt(32); @@ -26,120 +27,166 @@ contract StorageSetup is KontrolTest { _stEth.setShares(address(_escrow), shares); } + function _wstEthStorageSetup(WstETHAdapted _wstEth, IStETH _stEth) internal { + kevm.symbolicStorage(address(_wstEth)); + } + + function _getEnteredAt(DualGovernance _dualGovernance) internal view returns (uint40) { + return uint40(_loadUInt256(address(_dualGovernance), 4) >> 8); + } + + function _getVetoSignallingActivationTime(DualGovernance _dualGovernance) internal view returns (uint40) { + return uint40(_loadUInt256(address(_dualGovernance), 5) >> 48); + } + + function _getVetoSignallingReactivationTime(DualGovernance _dualGovernance) internal view returns (uint40) { + return uint40(_loadUInt256(address(_dualGovernance), 5)); + } + function _dualGovernanceStorageSetup( - DualGovernanceModel _dualGovernance, - EmergencyProtectedTimelockModel _timelock, + DualGovernance _dualGovernance, + EmergencyProtectedTimelock _timelock, StETHModel _stEth, - EscrowModel _signallingEscrow, - EscrowModel _rageQuitEscrow + IEscrow _signallingEscrow, + IEscrow _rageQuitEscrow ) internal { kevm.symbolicStorage(address(_dualGovernance)); - // Slot 0 - _storeAddress(address(_dualGovernance), 0, address(_timelock)); - // Slot 1 - _storeAddress(address(_dualGovernance), 1, address(_signallingEscrow)); - // Slot 2 - _storeAddress(address(_dualGovernance), 2, address(_rageQuitEscrow)); - // Slot 3 - _storeAddress(address(_dualGovernance), 3, address(_stEth)); - // Slot 6 - uint256 lastStateChangeTime = kevm.freshUInt(32); - vm.assume(lastStateChangeTime <= block.timestamp); - vm.assume(lastStateChangeTime < timeUpperBound); - _storeUInt256(address(_dualGovernance), 6, lastStateChangeTime); - // Slot 7 - uint256 lastSubStateActivationTime = kevm.freshUInt(32); - vm.assume(lastSubStateActivationTime <= block.timestamp); - vm.assume(lastSubStateActivationTime < timeUpperBound); - _storeUInt256(address(_dualGovernance), 7, lastSubStateActivationTime); - // Slot 8 - uint256 lastStateReactivationTime = kevm.freshUInt(32); - vm.assume(lastStateReactivationTime <= block.timestamp); - vm.assume(lastStateReactivationTime < timeUpperBound); - _storeUInt256(address(_dualGovernance), 8, lastStateReactivationTime); - // Slot 9 - uint256 lastVetoSignallingTime = kevm.freshUInt(32); - vm.assume(lastVetoSignallingTime <= block.timestamp); - vm.assume(lastVetoSignallingTime < timeUpperBound); - _storeUInt256(address(_dualGovernance), 9, lastVetoSignallingTime); - // Slot 10 - uint256 rageQuitSequenceNumber = kevm.freshUInt(32); - vm.assume(rageQuitSequenceNumber < type(uint256).max); - _storeUInt256(address(_dualGovernance), 10, rageQuitSequenceNumber); - // Slot 11 + // Slot 4 + 0 = 4 uint256 currentState = kevm.freshUInt(32); vm.assume(currentState <= 4); - _storeUInt256(address(_dualGovernance), 11, currentState); + uint256 enteredAt = kevm.freshUInt(32); + vm.assume(enteredAt <= block.timestamp); + vm.assume(enteredAt < timeUpperBound); + uint256 vetoSignallingActivationTime = kevm.freshUInt(32); + vm.assume(vetoSignallingActivationTime <= block.timestamp); + vm.assume(vetoSignallingActivationTime < timeUpperBound); + bytes memory slot4Abi = abi.encodePacked( + uint8(0), + uint160(address(_signallingEscrow)), + uint40(vetoSignallingActivationTime), + uint40(enteredAt), + uint8(currentState) + ); + bytes32 slot4; + assembly { + slot4 := mload(add(slot4Abi, 0x20)) + } + _storeBytes32(address(_dualGovernance), 4, slot4); + // Slot 4 + 1 = 5 + uint256 vetoSignallingReactivationTime = kevm.freshUInt(32); + vm.assume(vetoSignallingReactivationTime <= block.timestamp); + vm.assume(vetoSignallingReactivationTime < timeUpperBound); + uint256 lastAdoptableStateExitedAt = kevm.freshUInt(32); + vm.assume(lastAdoptableStateExitedAt <= block.timestamp); + vm.assume(lastAdoptableStateExitedAt < timeUpperBound); + uint256 rageQuitRound = kevm.freshUInt(32); + vm.assume(rageQuitRound < type(uint8).max); + bytes memory slot5Abi = abi.encodePacked( + uint8(0), + uint8(rageQuitRound), + uint160(address(_rageQuitEscrow)), + uint40(lastAdoptableStateExitedAt), + uint40(vetoSignallingReactivationTime) + ); + bytes32 slot5; + assembly { + slot5 := mload(add(slot5Abi, 0x20)) + } + _storeBytes32(address(_dualGovernance), 5, slot5); } function _signallingEscrowStorageSetup( - EscrowModel _signallingEscrow, - DualGovernanceModel _dualGovernance, + IEscrow _signallingEscrow, + DualGovernance _dualGovernance, StETHModel _stEth ) internal { - _escrowStorageSetup( - _signallingEscrow, - _dualGovernance, - _stEth, - 0 // SignallingEscrow - ); + _escrowStorageSetup(_signallingEscrow, _dualGovernance, _stEth, EscrowState.SignallingEscrow); - vm.assume(_signallingEscrow.rageQuitExtensionDelayPeriodEnd() == 0); + uint256 rageQuitTimelockStartedAt = _loadUInt256(address(_signallingEscrow), 12); + vm.assume(rageQuitTimelockStartedAt == 0); } function _rageQuitEscrowStorageSetup( - EscrowModel _rageQuitEscrow, - DualGovernanceModel _dualGovernance, + IEscrow _rageQuitEscrow, + DualGovernance _dualGovernance, StETHModel _stEth ) internal { - _escrowStorageSetup( - _rageQuitEscrow, - _dualGovernance, - _stEth, - 1 // RageQuitEscrow - ); + _escrowStorageSetup(_rageQuitEscrow, _dualGovernance, _stEth, EscrowState.RageQuitEscrow); + } + + function _getCurrentState(Escrow _escrow) internal view returns (EscrowState) { + return EscrowState(uint8(uint256(vm.load(address(_escrow), 0)))); + } + + function _getLastAssetsLockTimestamp(Escrow _escrow, address _vetoer) internal view returns (uint40) { + uint256 assetsSlot = 3; + uint256 vetoerAddressPadded = uint256(uint160(_vetoer)); + bytes32 vetoerAssetsSlot = keccak256(abi.encodePacked(vetoerAddressPadded, assetsSlot)); + bytes32 lastAssetsLockTimestampSlot = bytes32(uint256(vetoerAssetsSlot) + 2); + uint256 offset = 128; + return uint40(uint256(vm.load(address(_escrow), lastAssetsLockTimestampSlot)) >> offset); } function _escrowStorageSetup( - EscrowModel _escrow, - DualGovernanceModel _dualGovernance, + IEscrow _escrow, + DualGovernance _dualGovernance, StETHModel _stEth, - uint8 _currentState + EscrowState _currentState ) internal { kevm.symbolicStorage(address(_escrow)); - // Slot 0: dualGovernance - _storeAddress(address(_escrow), 0, address(_dualGovernance)); - // Slot 1: stEth - _storeAddress(address(_escrow), 1, address(_stEth)); - // Slot 3: totalSharesLocked - uint256 totalSharesLocked = kevm.freshUInt(32); - vm.assume(totalSharesLocked < ethUpperBound); - _storeUInt256(address(_escrow), 3, totalSharesLocked); - // Slot 4: totalClaimedEthAmount - uint256 totalClaimedEthAmount = kevm.freshUInt(32); - vm.assume(totalClaimedEthAmount <= totalSharesLocked); - _storeUInt256(address(_escrow), 4, totalClaimedEthAmount); - // Slot 6: withdrawalRequestCount - uint256 withdrawalRequestCount = kevm.freshUInt(32); - vm.assume(withdrawalRequestCount < type(uint256).max); - _storeUInt256(address(_escrow), 6, withdrawalRequestCount); - // Slot 7: lastWithdrawalRequestSubmitted - uint256 lastWithdrawalRequestSubmitted = kevm.freshUInt(32); - vm.assume(lastWithdrawalRequestSubmitted < 2); - _storeUInt256(address(_escrow), 7, lastWithdrawalRequestSubmitted); - // Slot 8: claimedWithdrawalRequests - uint256 claimedWithdrawalRequests = kevm.freshUInt(32); - vm.assume(claimedWithdrawalRequests < type(uint256).max); - _storeUInt256(address(_escrow), 8, claimedWithdrawalRequests); - // Slot 13: rageQuitExtensionDelayPeriodEnd - uint256 rageQuitExtensionDelayPeriodEnd = kevm.freshUInt(32); - _storeUInt256(address(_escrow), 13, rageQuitExtensionDelayPeriodEnd); - // Slot 15: rageQuitEthClaimTimelockStart - uint256 rageQuitEthClaimTimelockStart = kevm.freshUInt(32); - vm.assume(rageQuitEthClaimTimelockStart <= block.timestamp); - vm.assume(rageQuitEthClaimTimelockStart < timeUpperBound); - _storeUInt256(address(_escrow), 15, rageQuitEthClaimTimelockStart); - // Slot 16: currentState - _storeUInt256(address(_escrow), 16, uint256(_currentState)); + // Slot 0 + { + bytes memory slot0Abi = abi.encodePacked(uint88(0), uint160(address(_dualGovernance)), uint8(_currentState)); + bytes32 slot0; + assembly { + slot0 := mload(add(slot0Abi, 0x20)) + } + _storeBytes32(address(_escrow), 0, slot0); + // Slot 1 + 0 + 0 = 1 + uint256 shares = kevm.freshUInt(32); + vm.assume(shares < ethUpperBound); + uint256 sharesFinalized = kevm.freshUInt(32); + vm.assume(sharesFinalized < ethUpperBound); + bytes memory slot1Abi = abi.encodePacked(uint128(sharesFinalized), uint128(shares)); + bytes32 slot1; + assembly { + slot1 := mload(add(slot1Abi, 0x20)) + } + _storeBytes32(address(_escrow), 1, slot1); + } + // Slot 1 + 0 + 1 = 2 + { + uint256 amountFinalized = kevm.freshUInt(32); + vm.assume(amountFinalized < ethUpperBound); + uint256 amountClaimed = kevm.freshUInt(32); + vm.assume(amountClaimed < ethUpperBound); + bytes memory slot2Abi = abi.encodePacked(uint128(amountClaimed), uint128(amountFinalized)); + bytes32 slot2; + assembly { + slot2 := mload(add(slot2Abi, 0x20)) + } + _storeBytes32(address(_escrow), 2, slot2); + } + // Slot 10 + { + uint256 rageQuitExtraTimelock = kevm.freshUInt(32); + vm.assume(rageQuitExtraTimelock <= block.timestamp); + vm.assume(rageQuitExtraTimelock < timeUpperBound); + _storeUInt256(address(_escrow), 10, rageQuitExtraTimelock); + } + // Slot 11 + { + uint256 rageQuitWithdrawalsTimelock = kevm.freshUInt(32); + vm.assume(rageQuitWithdrawalsTimelock <= block.timestamp); + vm.assume(rageQuitWithdrawalsTimelock < timeUpperBound); + _storeUInt256(address(_escrow), 11, rageQuitWithdrawalsTimelock); + } + // Slot 12 + { + uint256 rageQuitTimelockStartedAt = kevm.freshUInt(32); + vm.assume(rageQuitTimelockStartedAt <= block.timestamp); + vm.assume(rageQuitTimelockStartedAt < timeUpperBound); + _storeUInt256(address(_escrow), 12, rageQuitTimelockStartedAt); + } } } diff --git a/test/kontrol/VetoSignalling.t.sol b/test/kontrol/VetoSignalling.t.sol index 59493d28..6ba3b3ef 100644 --- a/test/kontrol/VetoSignalling.t.sol +++ b/test/kontrol/VetoSignalling.t.sol @@ -5,6 +5,8 @@ import "kontrol-cheatcodes/KontrolCheats.sol"; import "@openzeppelin/contracts/utils/math/Math.sol"; +import {State} from "contracts/libraries/DualGovernanceState.sol"; + import "test/kontrol/DualGovernanceSetUp.sol"; contract VetoSignallingTest is DualGovernanceSetUp { @@ -14,14 +16,14 @@ contract VetoSignallingTest is DualGovernanceSetUp { */ function testTransitionNormalToVetoSignalling() external { uint256 rageQuitSupport = signallingEscrow.getRageQuitSupport(); - vm.assume(rageQuitSupport > dualGovernance.FIRST_SEAL_RAGE_QUIT_SUPPORT()); - vm.assume(dualGovernance.currentState() == DualGovernanceModel.State.Normal); + vm.assume(rageQuitSupport > config.FIRST_SEAL_RAGE_QUIT_SUPPORT()); + vm.assume(dualGovernance.currentState() == State.Normal); dualGovernance.activateNextState(); - assert(dualGovernance.currentState() == DualGovernanceModel.State.VetoSignalling); + assert(dualGovernance.currentState() == State.VetoSignalling); } struct StateRecord { - DualGovernanceModel.State state; + State state; uint256 timestamp; uint256 rageQuitSupport; uint256 maxRageQuitSupport; @@ -35,8 +37,7 @@ contract VetoSignallingTest is DualGovernanceSetUp { */ function _vetoSignallingInvariants(Mode mode, StateRecord memory sr) internal view { require( - sr.state != DualGovernanceModel.State.Normal && sr.state != DualGovernanceModel.State.VetoCooldown - && sr.state != DualGovernanceModel.State.RageQuit, + sr.state != State.Normal && sr.state != State.VetoCooldown && sr.state != State.RageQuit, "Invariants only apply to the Veto Signalling states." ); @@ -64,7 +65,25 @@ contract VetoSignallingTest is DualGovernanceSetUp { */ function _vetoSignallingRageQuitInvariant(Mode mode, StateRecord memory sr) internal view { _establish(mode, sr.rageQuitSupport <= sr.maxRageQuitSupport); - _establish(mode, dualGovernance.FIRST_SEAL_RAGE_QUIT_SUPPORT() < sr.maxRageQuitSupport); + _establish(mode, config.FIRST_SEAL_RAGE_QUIT_SUPPORT() < sr.maxRageQuitSupport); + } + + function _calculateDynamicTimelock(Configuration _config, uint256 rageQuitSupport) public view returns (uint256) { + if (rageQuitSupport <= _config.FIRST_SEAL_RAGE_QUIT_SUPPORT()) { + return 0; + } else if (rageQuitSupport <= _config.SECOND_SEAL_RAGE_QUIT_SUPPORT()) { + return _linearInterpolation(_config, rageQuitSupport); + } else { + return _config.DYNAMIC_TIMELOCK_MAX_DURATION(); + } + } + + function _linearInterpolation(Configuration _config, uint256 rageQuitSupport) private view returns (uint256) { + uint256 L_min = _config.DYNAMIC_TIMELOCK_MIN_DURATION(); + uint256 L_max = _config.DYNAMIC_TIMELOCK_MAX_DURATION(); + return L_min + + ((rageQuitSupport - _config.FIRST_SEAL_RAGE_QUIT_SUPPORT()) * (L_max - L_min)) + / (_config.SECOND_SEAL_RAGE_QUIT_SUPPORT() - _config.FIRST_SEAL_RAGE_QUIT_SUPPORT()); } /** @@ -74,18 +93,18 @@ contract VetoSignallingTest is DualGovernanceSetUp { * in the Deactivation sub-state. Otherwise, it is in the parent state. */ function _vetoSignallingDeactivationInvariant(Mode mode, StateRecord memory sr) internal view { - uint256 dynamicTimelock = dualGovernance.calculateDynamicTimelock(sr.rageQuitSupport); + uint256 dynamicTimelock = _calculateDynamicTimelock(config, sr.rageQuitSupport); // Note: creates three branches in symbolic execution if (sr.timestamp <= sr.activationTime + dynamicTimelock) { - _establish(mode, sr.state == DualGovernanceModel.State.VetoSignalling); + _establish(mode, sr.state == State.VetoSignalling); } else if ( sr.timestamp - <= Math.max(sr.reactivationTime, sr.activationTime) + dualGovernance.VETO_SIGNALLING_MIN_ACTIVE_DURATION() + <= Math.max(sr.reactivationTime, sr.activationTime) + config.VETO_SIGNALLING_MIN_ACTIVE_DURATION() ) { - _establish(mode, sr.state == DualGovernanceModel.State.VetoSignalling); + _establish(mode, sr.state == State.VetoSignalling); } else { - _establish(mode, sr.state == DualGovernanceModel.State.VetoSignallingDeactivation); + _establish(mode, sr.state == State.VetoSignallingDeactivation); } } @@ -102,13 +121,13 @@ contract VetoSignallingTest is DualGovernanceSetUp { function _vetoSignallingMaxDelayInvariant(Mode mode, StateRecord memory sr) internal view { // Note: creates two branches in symbolic execution if (_maxDeactivationDelayPassed(sr)) { - _establish(mode, sr.state == DualGovernanceModel.State.VetoSignallingDeactivation); + _establish(mode, sr.state == State.VetoSignallingDeactivation); } } function _maxDeactivationDelayPassed(StateRecord memory sr) internal view returns (bool) { - uint256 maxDeactivationDelay = dualGovernance.calculateDynamicTimelock(sr.maxRageQuitSupport) - + dualGovernance.VETO_SIGNALLING_MIN_ACTIVE_DURATION(); + uint256 maxDeactivationDelay = + _calculateDynamicTimelock(config, sr.maxRageQuitSupport) + config.VETO_SIGNALLING_MIN_ACTIVE_DURATION(); return sr.activationTime + maxDeactivationDelay < sr.timestamp; } @@ -122,8 +141,8 @@ contract VetoSignallingTest is DualGovernanceSetUp { sr.timestamp = lastInteractionTimestamp; sr.rageQuitSupport = previousRageQuitSupport; sr.maxRageQuitSupport = maxRageQuitSupport; - sr.activationTime = dualGovernance.lastStateChangeTime(); - sr.reactivationTime = dualGovernance.lastStateReactivationTime(); + sr.activationTime = _getVetoSignallingActivationTime(dualGovernance); + sr.reactivationTime = _getVetoSignallingReactivationTime(dualGovernance); } function _recordCurrentState(uint256 previousMaxRageQuitSupport) internal view returns (StateRecord memory sr) { @@ -132,8 +151,8 @@ contract VetoSignallingTest is DualGovernanceSetUp { sr.rageQuitSupport = signallingEscrow.getRageQuitSupport(); sr.maxRageQuitSupport = previousMaxRageQuitSupport < sr.rageQuitSupport ? sr.rageQuitSupport : previousMaxRageQuitSupport; - sr.activationTime = dualGovernance.lastStateChangeTime(); - sr.reactivationTime = dualGovernance.lastStateReactivationTime(); + sr.activationTime = _getVetoSignallingActivationTime(dualGovernance); + sr.reactivationTime = _getVetoSignallingReactivationTime(dualGovernance); } /** @@ -159,15 +178,15 @@ contract VetoSignallingTest is DualGovernanceSetUp { function testVetoSignallingInvariantsHoldInitially() external { vm.assume(block.timestamp < timeUpperBound); - vm.assume(dualGovernance.currentState() != DualGovernanceModel.State.VetoSignalling); - vm.assume(dualGovernance.currentState() != DualGovernanceModel.State.VetoSignallingDeactivation); + vm.assume(dualGovernance.currentState() != State.VetoSignalling); + vm.assume(dualGovernance.currentState() != State.VetoSignallingDeactivation); dualGovernance.activateNextState(); StateRecord memory sr = _recordCurrentState(0); // Consider only the case where we have transitioned to Veto Signalling - if (sr.state == DualGovernanceModel.State.VetoSignalling) { + if (sr.state == State.VetoSignalling) { _vetoSignallingInvariants(Mode.Assert, sr); } } @@ -191,19 +210,16 @@ contract VetoSignallingTest is DualGovernanceSetUp { StateRecord memory previous = _recordPreviousState(lastInteractionTimestamp, previousRageQuitSupport, maxRageQuitSupport); - vm.assume(previous.state != DualGovernanceModel.State.Normal); - vm.assume(previous.state != DualGovernanceModel.State.VetoCooldown); - vm.assume(previous.state != DualGovernanceModel.State.RageQuit); + vm.assume(previous.state != State.Normal); + vm.assume(previous.state != State.VetoCooldown); + vm.assume(previous.state != State.RageQuit); _vetoSignallingInvariants(Mode.Assume, previous); dualGovernance.activateNextState(); StateRecord memory current = _recordCurrentState(maxRageQuitSupport); - if ( - current.state != DualGovernanceModel.State.Normal && current.state != DualGovernanceModel.State.VetoCooldown - && current.state != DualGovernanceModel.State.RageQuit - ) { + if (current.state != State.Normal && current.state != State.VetoCooldown && current.state != State.RageQuit) { _vetoSignallingInvariants(Mode.Assert, current); } } @@ -224,32 +240,29 @@ contract VetoSignallingTest is DualGovernanceSetUp { StateRecord memory previous = _recordPreviousState(lastInteractionTimestamp, previousRageQuitSupport, maxRageQuitSupport); - vm.assume(previous.maxRageQuitSupport <= dualGovernance.SECOND_SEAL_RAGE_QUIT_SUPPORT()); + vm.assume(previous.maxRageQuitSupport <= config.SECOND_SEAL_RAGE_QUIT_SUPPORT()); vm.assume(_maxDeactivationDelayPassed(previous)); vm.assume(signallingEscrow.getRageQuitSupport() <= previous.maxRageQuitSupport); - vm.assume( - previous.state == DualGovernanceModel.State.VetoSignalling - || previous.state == DualGovernanceModel.State.VetoSignallingDeactivation - ); + vm.assume(previous.state == State.VetoSignalling || previous.state == State.VetoSignallingDeactivation); _vetoSignallingInvariants(Mode.Assume, previous); - assert(previous.state == DualGovernanceModel.State.VetoSignallingDeactivation); + assert(previous.state == State.VetoSignallingDeactivation); dualGovernance.activateNextState(); StateRecord memory current = _recordCurrentState(maxRageQuitSupport); - uint256 deactivationStartTime = dualGovernance.lastSubStateActivationTime(); - uint256 deactivationEndTime = deactivationStartTime + dualGovernance.VETO_SIGNALLING_DEACTIVATION_MAX_DURATION(); + uint256 deactivationStartTime = _getEnteredAt(dualGovernance); + uint256 deactivationEndTime = deactivationStartTime + config.VETO_SIGNALLING_DEACTIVATION_MAX_DURATION(); // The protocol is either in the Deactivation sub-state, or, if the // maximum deactivation duration has passed, in the Veto Cooldown state if (deactivationEndTime < block.timestamp) { - assert(current.state == DualGovernanceModel.State.VetoCooldown); + assert(current.state == State.VetoCooldown); } else { - assert(current.state == DualGovernanceModel.State.VetoSignallingDeactivation); + assert(current.state == State.VetoSignallingDeactivation); } } } diff --git a/test/kontrol/scripts/run-kontrol.sh b/test/kontrol/scripts/run-kontrol.sh index 3df99eef..f048d04b 100755 --- a/test/kontrol/scripts/run-kontrol.sh +++ b/test/kontrol/scripts/run-kontrol.sh @@ -109,7 +109,7 @@ if [ "$SCRIPT_TESTS" == true ]; then "EscrowAccountingTest.testUnlockStEth" "EscrowOperationsTest.testCannotUnlockBeforeMinLockTime" "EscrowOperationsTest.testCannotLockUnlockInRageQuitEscrowState" - "EscrowOperationsTest.testCannotWithdrawBeforeEthClaimTimelockElapsed" + #"EscrowOperationsTest.testCannotWithdrawBeforeEthClaimTimelockElapsed" ) elif [ "$CUSTOM_TESTS" != 0 ]; then test_list=( "${@:${CUSTOM_TESTS}}" )