diff --git a/contracts/DualGovernance.sol b/contracts/DualGovernance.sol index 98f84462..a5e537b1 100644 --- a/contracts/DualGovernance.sol +++ b/contracts/DualGovernance.sol @@ -7,7 +7,7 @@ import {Timestamp} from "./types/Timestamp.sol"; import {IStETH} from "./interfaces/IStETH.sol"; import {IWstETH} from "./interfaces/IWstETH.sol"; import {IWithdrawalQueue} from "./interfaces/IWithdrawalQueue.sol"; -import {IEscrow} from "./interfaces/IEscrow.sol"; +import {IEscrowBase} from "./interfaces/IEscrowBase.sol"; import {ITimelock} from "./interfaces/ITimelock.sol"; import {ITiebreaker} from "./interfaces/ITiebreaker.sol"; import {IDualGovernance} from "./interfaces/IDualGovernance.sol"; @@ -52,8 +52,8 @@ contract DualGovernance is IDualGovernance { event CancelAllPendingProposalsSkipped(); event CancelAllPendingProposalsExecuted(); - event EscrowMasterCopyDeployed(IEscrow escrowMasterCopy); event ProposalsCancellerSet(address proposalsCanceller); + event EscrowMasterCopyDeployed(IEscrowBase escrowMasterCopy); // --- // Sanity Check Parameters & Immutables @@ -112,10 +112,6 @@ contract DualGovernance is IDualGovernance { /// @notice The address of the Timelock contract. ITimelock public immutable TIMELOCK; - /// @notice The address of the Escrow contract used as the implementation for the Signalling and Rage Quit - /// instances of the Escrows managed by the DualGovernance contract. - IEscrow public immutable ESCROW_MASTER_COPY; - // --- // Aspects // --- @@ -151,16 +147,17 @@ contract DualGovernance is IDualGovernance { MAX_TIEBREAKER_ACTIVATION_TIMEOUT = sanityCheckParams.maxTiebreakerActivationTimeout; MAX_SEALABLE_WITHDRAWAL_BLOCKERS_COUNT = sanityCheckParams.maxSealableWithdrawalBlockersCount; - ESCROW_MASTER_COPY = new Escrow({ + IEscrowBase escrowMasterCopy = new Escrow({ dualGovernance: this, stETH: dependencies.stETH, wstETH: dependencies.wstETH, withdrawalQueue: dependencies.withdrawalQueue, minWithdrawalsBatchSize: sanityCheckParams.minWithdrawalsBatchSize }); - emit EscrowMasterCopyDeployed(ESCROW_MASTER_COPY); - _stateMachine.initialize(dependencies.configProvider, ESCROW_MASTER_COPY); + emit EscrowMasterCopyDeployed(escrowMasterCopy); + + _stateMachine.initialize(dependencies.configProvider, escrowMasterCopy); _resealer.setResealManager(address(dependencies.resealManager)); } @@ -183,7 +180,7 @@ contract DualGovernance is IDualGovernance { ExternalCall[] calldata calls, string calldata metadata ) external returns (uint256 proposalId) { - _stateMachine.activateNextState(ESCROW_MASTER_COPY); + _stateMachine.activateNextState(); if (!_stateMachine.canSubmitProposal({useEffectiveState: false})) { revert ProposalSubmissionBlocked(); } @@ -198,7 +195,7 @@ contract DualGovernance is IDualGovernance { /// @param proposalId The unique identifier of the proposal to be scheduled. This ID is obtained when the proposal /// is initially submitted to the Dual Governance system. function scheduleProposal(uint256 proposalId) external { - _stateMachine.activateNextState(ESCROW_MASTER_COPY); + _stateMachine.activateNextState(); Timestamp proposalSubmittedAt = TIMELOCK.getProposalDetails(proposalId).submittedAt; if (!_stateMachine.canScheduleProposal({useEffectiveState: false, proposalSubmittedAt: proposalSubmittedAt})) { revert ProposalSchedulingBlocked(proposalId); @@ -214,7 +211,7 @@ contract DualGovernance is IDualGovernance { /// @return isProposalsCancelled A boolean indicating whether the proposals were successfully canceled (`true`) /// or the cancellation was skipped due to an inappropriate state (`false`). function cancelAllPendingProposals() external returns (bool) { - _stateMachine.activateNextState(ESCROW_MASTER_COPY); + _stateMachine.activateNextState(); if (msg.sender != _proposalsCanceller) { revert CallerIsNotProposalsCanceller(msg.sender); @@ -282,7 +279,7 @@ contract DualGovernance is IDualGovernance { /// @dev This function should be called when the `persisted` and `effective` states of the system are not equal. /// If the states are already synchronized, the function will complete without making any changes to the system state. function activateNextState() external { - _stateMachine.activateNextState(ESCROW_MASTER_COPY); + _stateMachine.activateNextState(); } /// @notice Updates the address of the configuration provider for the Dual Governance system. @@ -472,7 +469,7 @@ contract DualGovernance is IDualGovernance { /// @param sealable The address of the sealable contract to be resumed. function tiebreakerResumeSealable(address sealable) external { _tiebreaker.checkCallerIsTiebreakerCommittee(); - _stateMachine.activateNextState(ESCROW_MASTER_COPY); + _stateMachine.activateNextState(); _tiebreaker.checkTie(_stateMachine.getPersistedState(), _stateMachine.normalOrVetoCooldownExitedAt); _resealer.resealManager.resume(sealable); } @@ -482,7 +479,7 @@ contract DualGovernance is IDualGovernance { /// @param proposalId The unique identifier of the proposal to be scheduled. function tiebreakerScheduleProposal(uint256 proposalId) external { _tiebreaker.checkCallerIsTiebreakerCommittee(); - _stateMachine.activateNextState(ESCROW_MASTER_COPY); + _stateMachine.activateNextState(); _tiebreaker.checkTie(_stateMachine.getPersistedState(), _stateMachine.normalOrVetoCooldownExitedAt); TIMELOCK.schedule(proposalId); } @@ -507,7 +504,7 @@ contract DualGovernance is IDualGovernance { /// the ResealManager contract. /// @param sealable The address of the sealable contract to be resealed. function resealSealable(address sealable) external { - _stateMachine.activateNextState(ESCROW_MASTER_COPY); + _stateMachine.activateNextState(); if (_stateMachine.getPersistedState() == State.Normal) { revert ResealIsNotAllowedInNormalState(); } diff --git a/contracts/Escrow.sol b/contracts/Escrow.sol index 87c61c97..1a24919e 100644 --- a/contracts/Escrow.sol +++ b/contracts/Escrow.sol @@ -4,25 +4,27 @@ pragma solidity 0.8.26; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {Duration} from "./types/Duration.sol"; -import {Timestamp} from "./types/Timestamp.sol"; import {ETHValue, ETHValues} from "./types/ETHValue.sol"; import {SharesValue, SharesValues} from "./types/SharesValue.sol"; import {PercentD16, PercentsD16} from "./types/PercentD16.sol"; -import {IEscrow} from "./interfaces/IEscrow.sol"; +import {IEscrowBase} from "./interfaces/IEscrowBase.sol"; +import {ISignallingEscrow} from "./interfaces/ISignallingEscrow.sol"; +import {IRageQuitEscrow} from "./interfaces/IRageQuitEscrow.sol"; + import {IStETH} from "./interfaces/IStETH.sol"; import {IWstETH} from "./interfaces/IWstETH.sol"; import {IWithdrawalQueue} from "./interfaces/IWithdrawalQueue.sol"; import {IDualGovernance} from "./interfaces/IDualGovernance.sol"; -import {EscrowState} from "./libraries/EscrowState.sol"; +import {EscrowState, State} from "./libraries/EscrowState.sol"; import {WithdrawalsBatchesQueue} from "./libraries/WithdrawalsBatchesQueue.sol"; import {HolderAssets, StETHAccounting, UnstETHAccounting, AssetsAccounting} from "./libraries/AssetsAccounting.sol"; /// @notice This contract is used to accumulate stETH, wstETH, unstETH, and withdrawn ETH from vetoers during the /// veto signalling and rage quit processes. /// @dev This contract is intended to be used behind a minimal proxy deployed by the DualGovernance contract. -contract Escrow is IEscrow { +contract Escrow is ISignallingEscrow, IRageQuitEscrow { using EscrowState for EscrowState.Context; using AssetsAccounting for AssetsAccounting.Context; using WithdrawalsBatchesQueue for WithdrawalsBatchesQueue.Context; @@ -30,6 +32,7 @@ contract Escrow is IEscrow { // --- // Errors // --- + error EmptyUnstETHIds(); error UnclaimedBatches(); error UnexpectedUnstETHId(); @@ -75,9 +78,11 @@ contract Escrow is IEscrow { // Implementation Immutables // --- - /// @dev Reference to the address of the implementation contract, used to distinguish whether the call - /// is made to the proxy or directly to the implementation. - address private immutable _SELF; + /// @notice The address of the implementation used for Signalling and Rage Quit escrows deployed + /// by the DualGovernance contract. + /// @dev This address is also used to distinguish whether the call is made to the proxy or directly + /// to the implementation. + IEscrowBase public immutable ESCROW_MASTER_COPY; /// @dev The address of the Dual Governance contract. IDualGovernance public immutable DUAL_GOVERNANCE; @@ -106,7 +111,7 @@ contract Escrow is IEscrow { IDualGovernance dualGovernance, uint256 minWithdrawalsBatchSize ) { - _SELF = address(this); + ESCROW_MASTER_COPY = this; DUAL_GOVERNANCE = dualGovernance; ST_ETH = stETH; @@ -116,11 +121,15 @@ contract Escrow is IEscrow { MIN_WITHDRAWALS_BATCH_SIZE = minWithdrawalsBatchSize; } + // --- + // Escrow Base + // --- + /// @notice Initializes the proxy instance with the specified minimum assets lock duration. /// @param minAssetsLockDuration The minimum duration that must pass from the last stETH, wstETH, or unstETH lock /// by the vetoer before they are allowed to unlock assets from the Escrow. function initialize(Duration minAssetsLockDuration) external { - if (address(this) == _SELF) { + if (this == ESCROW_MASTER_COPY) { revert NonProxyCallsForbidden(); } _checkCallerIsDualGovernance(); @@ -131,8 +140,14 @@ contract Escrow is IEscrow { ST_ETH.approve(address(WITHDRAWAL_QUEUE), type(uint256).max); } + /// @notice Retrieves the current state of the Escrow. + /// @return State The current state of the Escrow. + function getEscrowState() external view returns (State) { + return _escrowState.state; + } + // --- - // Lock & Unlock stETH + // Signalling Escrow: Lock & Unlock stETH // --- /// @notice Locks the vetoer's specified `amount` of stETH in the Veto Signalling Escrow, thereby increasing @@ -165,7 +180,7 @@ contract Escrow is IEscrow { } // --- - // Lock & Unlock wstETH + // Signalling Escrow: Lock & Unlock wstETH // --- /// @notice Locks the vetoer's specified `amount` of wstETH in the Veto Signalling Escrow, thereby increasing @@ -199,7 +214,7 @@ contract Escrow is IEscrow { } // --- - // Lock & Unlock unstETH + // Signalling Escrow: Lock & Unlock unstETH // --- /// @notice Locks the specified unstETH NFTs, identified by their ids, in the Veto Signalling Escrow, thereby increasing @@ -263,7 +278,7 @@ contract Escrow is IEscrow { } // --- - // Start Rage Quit + // Signalling Escrow: Start Rage Quit // --- /// @notice Irreversibly converts the Signalling Escrow into the Rage Quit Escrow, allowing vetoers who have locked @@ -281,7 +296,107 @@ contract Escrow is IEscrow { } // --- - // Request Withdrawal Batches + // Signalling Escrow: Management + // --- + + /// @notice Sets the minimum duration that must elapse after the last stETH, wstETH, or unstETH lock + /// by a vetoer before they are permitted to unlock their assets from the Escrow. + /// @param newMinAssetsLockDuration The new minimum lock duration to be set. + function setMinAssetsLockDuration(Duration newMinAssetsLockDuration) external { + _checkCallerIsDualGovernance(); + _escrowState.setMinAssetsLockDuration(newMinAssetsLockDuration); + } + + // --- + // Signalling Escrow: Getters + // --- + + /// @notice Returns the current Rage Quit support value as a percentage. + /// @return rageQuitSupport The current Rage Quit support as a `PercentD16` value. + function getRageQuitSupport() external view returns (PercentD16) { + StETHAccounting memory stETHTotals = _accounting.stETHTotals; + UnstETHAccounting memory unstETHTotals = _accounting.unstETHTotals; + + uint256 finalizedETH = unstETHTotals.finalizedETH.toUint256(); + uint256 unfinalizedShares = (stETHTotals.lockedShares + unstETHTotals.unfinalizedShares).toUint256(); + + return PercentsD16.fromFraction({ + numerator: ST_ETH.getPooledEthByShares(unfinalizedShares) + finalizedETH, + denominator: ST_ETH.totalSupply() + finalizedETH + }); + } + + /// @notice Returns the minimum duration that must elapse after the last stETH, wstETH, or unstETH lock + /// by a vetoer before they are permitted to unlock their assets from the Escrow. + function getMinAssetsLockDuration() external view returns (Duration minAssetsLockDuration) { + minAssetsLockDuration = _escrowState.minAssetsLockDuration; + } + + /// @notice Returns the state of locked assets for a specific vetoer. + /// @param vetoer The address of the vetoer whose locked asset state is being queried. + /// @return details A struct containing information about the vetoer's locked assets, including: + /// - `unstETHIdsCount`: The total number of unstETH NFTs locked by the vetoer. + /// - `stETHLockedShares`: The total number of stETH shares locked by the vetoer. + /// - `unstETHLockedShares`: The total number of unstETH shares locked by the vetoer. + /// - `lastAssetsLockTimestamp`: The timestamp of the last assets lock by the vetoer. + function getVetoerDetails(address vetoer) external view returns (VetoerDetails memory details) { + HolderAssets storage assets = _accounting.assets[vetoer]; + + details.unstETHIdsCount = assets.unstETHIds.length; + details.stETHLockedShares = assets.stETHLockedShares; + details.unstETHLockedShares = assets.unstETHLockedShares; + details.lastAssetsLockTimestamp = assets.lastAssetsLockTimestamp; + } + + // @notice Retrieves the unstETH NFT ids of the specified vetoer. + /// @param vetoer The address of the vetoer whose unstETH NFTs are being queried. + /// @return unstETHIds An array of unstETH NFT ids locked by the vetoer. + function getVetoerUnstETHIds(address vetoer) external view returns (uint256[] memory unstETHIds) { + unstETHIds = _accounting.assets[vetoer].unstETHIds; + } + + /// @notice Returns the total amounts of locked and claimed assets in the Escrow. + /// @return details A struct containing the total amounts of locked and claimed assets, including: + /// - `totalStETHClaimedETH`: The total amount of ETH claimed from locked stETH. + /// - `totalStETHLockedShares`: The total number of stETH shares currently locked in the Escrow. + /// - `totalUnstETHUnfinalizedShares`: The total number of shares from unstETH NFTs that have not yet been finalized. + /// - `totalUnstETHFinalizedETH`: The total amount of ETH from finalized unstETH NFTs. + function getSignallingEscrowDetails() external view returns (SignallingEscrowDetails memory details) { + StETHAccounting memory stETHTotals = _accounting.stETHTotals; + details.totalStETHClaimedETH = stETHTotals.claimedETH; + details.totalStETHLockedShares = stETHTotals.lockedShares; + + UnstETHAccounting memory unstETHTotals = _accounting.unstETHTotals; + details.totalUnstETHUnfinalizedShares = unstETHTotals.unfinalizedShares; + details.totalUnstETHFinalizedETH = unstETHTotals.finalizedETH; + } + + /// @notice Retrieves details of locked unstETH records for the given ids. + /// @param unstETHIds The array of ids for the unstETH records to retrieve. + /// @return unstETHDetails An array of `LockedUnstETHDetails` containing the details for each provided unstETH id. + /// + /// The details include: + /// - `id`: The id of the locked unstETH NFT. + /// - `status`: The current status of the unstETH record. + /// - `lockedBy`: The address that locked the unstETH record. + /// - `shares`: The number of shares associated with the locked unstETH. + /// - `claimableAmount`: The amount of claimable ETH contained in the unstETH. This value is 0 + /// until the unstETH is finalized or claimed. + function getLockedUnstETHDetails(uint256[] calldata unstETHIds) + external + view + returns (LockedUnstETHDetails[] memory unstETHDetails) + { + uint256 unstETHIdsCount = unstETHIds.length; + unstETHDetails = new LockedUnstETHDetails[](unstETHIdsCount); + + for (uint256 i = 0; i < unstETHIdsCount; ++i) { + unstETHDetails[i] = _accounting.getLockedUnstETHDetails(unstETHIds[i]); + } + } + + // --- + // Rage Quit Escrow: Request Withdrawal Batches // --- /// @notice Creates unstETH NFTs from the stETH held in the Rage Quit Escrow via the WithdrawalQueue contract. @@ -325,7 +440,7 @@ contract Escrow is IEscrow { } // --- - // Claim Requested Withdrawal Batches + // Rage Quit Escrow: Claim Requested Withdrawal Batches // --- /// @notice Allows the claim of finalized withdrawal NFTs generated via the `Escrow.requestNextWithdrawalsBatch()` method. @@ -361,7 +476,7 @@ contract Escrow is IEscrow { } // --- - // Start Rage Quit Extension Delay + // Rage Quit Escrow: Start Rage Quit Extension Delay // --- /// @notice Initiates the Rage Quit Extension Period once all withdrawal batches have been claimed. @@ -394,7 +509,7 @@ contract Escrow is IEscrow { } // --- - // Claim Locked unstETH NFTs + // Rage Quit Escrow: Claim Locked unstETH NFTs // --- /// @notice Allows users to claim finalized unstETH NFTs locked in the Rage Quit Escrow contract. @@ -418,25 +533,7 @@ contract Escrow is IEscrow { } // --- - // Escrow Management - // --- - - /// @notice Returns the minimum duration that must elapse after the last stETH, wstETH, or unstETH lock - /// by a vetoer before they are permitted to unlock their assets from the Escrow. - function getMinAssetsLockDuration() external view returns (Duration) { - return _escrowState.minAssetsLockDuration; - } - - /// @notice Sets the minimum duration that must elapse after the last stETH, wstETH, or unstETH lock - /// by a vetoer before they are permitted to unlock their assets from the Escrow. - /// @param newMinAssetsLockDuration The new minimum lock duration to be set. - function setMinAssetsLockDuration(Duration newMinAssetsLockDuration) external { - _checkCallerIsDualGovernance(); - _escrowState.setMinAssetsLockDuration(newMinAssetsLockDuration); - } - - // --- - // Withdraw Logic + // Rage Quit Escrow: Withdraw Logic // --- /// @notice Allows the caller (i.e., `msg.sender`) to withdraw all stETH and wstETH they have previously locked @@ -463,55 +560,9 @@ contract Escrow is IEscrow { } // --- - // Getters + // Rage Quit Escrow: Getters // --- - /// @notice Returns the total amounts of locked and claimed assets in the Escrow. - /// @return totals A struct containing the total amounts of locked and claimed assets, including: - /// - `stETHClaimedETH`: The total amount of ETH claimed from locked stETH. - /// - `stETHLockedShares`: The total number of stETH shares currently locked in the Escrow. - /// - `unstETHUnfinalizedShares`: The total number of shares from unstETH NFTs that have not yet been finalized. - /// - `unstETHFinalizedETH`: The total amount of ETH from finalized unstETH NFTs. - function getLockedAssetsTotals() external view returns (LockedAssetsTotals memory totals) { - StETHAccounting memory stETHTotals = _accounting.stETHTotals; - totals.stETHClaimedETH = stETHTotals.claimedETH.toUint256(); - totals.stETHLockedShares = stETHTotals.lockedShares.toUint256(); - - UnstETHAccounting memory unstETHTotals = _accounting.unstETHTotals; - totals.unstETHUnfinalizedShares = unstETHTotals.unfinalizedShares.toUint256(); - totals.unstETHFinalizedETH = unstETHTotals.finalizedETH.toUint256(); - } - - /// @notice Returns the state of locked assets for a specific vetoer. - /// @param vetoer The address of the vetoer whose locked asset state is being queried. - /// @return state A struct containing information about the vetoer's locked assets, including: - /// - `stETHLockedShares`: The total number of stETH shares locked by the vetoer. - /// - `unstETHLockedShares`: The total number of unstETH shares locked by the vetoer. - /// - `unstETHIdsCount`: The total number of unstETH NFTs locked by the vetoer. - /// - `lastAssetsLockTimestamp`: The timestamp of the last assets lock by the vetoer. - function getVetoerState(address vetoer) external view returns (VetoerState memory state) { - HolderAssets storage assets = _accounting.assets[vetoer]; - - state.unstETHIdsCount = assets.unstETHIds.length; - state.stETHLockedShares = assets.stETHLockedShares.toUint256(); - state.unstETHLockedShares = assets.unstETHLockedShares.toUint256(); - state.lastAssetsLockTimestamp = assets.lastAssetsLockTimestamp.toSeconds(); - } - - // @notice Retrieves the unstETH NFT ids of the specified vetoer. - /// @param vetoer The address of the vetoer whose unstETH NFTs are being queried. - /// @return unstETHIds An array of unstETH NFT ids locked by the vetoer. - function getVetoerUnstETHIds(address vetoer) external view returns (uint256[] memory unstETHIds) { - unstETHIds = _accounting.assets[vetoer].unstETHIds; - } - - /// @notice Returns the total count of unstETH NFTs that have not been claimed yet. - /// @return unclaimedUnstETHIdsCount The total number of unclaimed unstETH NFTs. - function getUnclaimedUnstETHIdsCount() external view returns (uint256) { - _escrowState.checkRageQuitEscrow(); - return _batchesQueue.getTotalUnclaimedUnstETHIdsCount(); - } - /// @notice Retrieves the unstETH NFT ids of the next batch available for claiming. /// @param limit The maximum number of unstETH NFTs to return in the batch. /// @return unstETHIds An array of unstETH NFT ids available for the next withdrawal batch. @@ -528,38 +579,33 @@ contract Escrow is IEscrow { return _batchesQueue.isClosed(); } - /// @notice Returns whether the Rage Quit Extension Period has started. - /// @return isRageQuitExtensionPeriodStarted A boolean value indicating whether the Rage Quit Extension Period - /// has started (`true`) or not (`false`). - function isRageQuitExtensionPeriodStarted() external view returns (bool) { - return _escrowState.isRageQuitExtensionPeriodStarted(); - } - - /// @notice Returns the timestamp when the Rage Quit Extension Period started. - /// @return rageQuitExtensionPeriodStartedAt The timestamp when the Rage Quit Extension Period began. - function getRageQuitExtensionPeriodStartedAt() external view returns (Timestamp) { - return _escrowState.rageQuitExtensionPeriodStartedAt; - } - - /// @notice Returns the current Rage Quit support value as a percentage. - /// @return rageQuitSupport The current Rage Quit support as a `PercentD16` value. - function getRageQuitSupport() external view returns (PercentD16) { - StETHAccounting memory stETHTotals = _accounting.stETHTotals; - UnstETHAccounting memory unstETHTotals = _accounting.unstETHTotals; - - uint256 finalizedETH = unstETHTotals.finalizedETH.toUint256(); - uint256 unfinalizedShares = (stETHTotals.lockedShares + unstETHTotals.unfinalizedShares).toUint256(); - - return PercentsD16.fromFraction({ - numerator: ST_ETH.getPooledEthByShares(unfinalizedShares) + finalizedETH, - denominator: ST_ETH.totalSupply() + finalizedETH - }); + /// @notice Returns the total count of unstETH NFTs that have not been claimed yet. + /// @return unclaimedUnstETHIdsCount The total number of unclaimed unstETH NFTs. + function getUnclaimedUnstETHIdsCount() external view returns (uint256) { + _escrowState.checkRageQuitEscrow(); + return _batchesQueue.getTotalUnclaimedUnstETHIdsCount(); } /// @notice Returns whether the Rage Quit process has been finalized. /// @return A boolean value indicating whether the Rage Quit process has been finalized (`true`) or not (`false`). function isRageQuitFinalized() external view returns (bool) { - return _escrowState.isRageQuitEscrow() && _escrowState.isRageQuitExtensionPeriodPassed(); + _escrowState.checkRageQuitEscrow(); + return _escrowState.isRageQuitExtensionPeriodPassed(); + } + + /// @notice Retrieves details about the current state of the rage quit escrow. + /// @return details A `RageQuitEscrowDetails` struct containing the following fields: + /// - `isRageQuitExtensionPeriodStarted`: Indicates whether the rage quit extension period has started. + /// - `rageQuitEthWithdrawalsDelay`: The delay period for ETH withdrawals during rage quit. + /// - `rageQuitExtensionPeriodDuration`: The duration of the rage quit extension period. + /// - `rageQuitExtensionPeriodStartedAt`: The timestamp when the rage quit extension period started. + function getRageQuitEscrowDetails() external view returns (RageQuitEscrowDetails memory details) { + _escrowState.checkRageQuitEscrow(); + + details.rageQuitEthWithdrawalsDelay = _escrowState.rageQuitEthWithdrawalsDelay; + details.rageQuitExtensionPeriodDuration = _escrowState.rageQuitExtensionPeriodDuration; + details.rageQuitExtensionPeriodStartedAt = _escrowState.rageQuitExtensionPeriodStartedAt; + details.isRageQuitExtensionPeriodStarted = _escrowState.isRageQuitExtensionPeriodStarted(); } // --- diff --git a/contracts/interfaces/IDualGovernance.sol b/contracts/interfaces/IDualGovernance.sol index f2e060cd..721b2eba 100644 --- a/contracts/interfaces/IDualGovernance.sol +++ b/contracts/interfaces/IDualGovernance.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.26; import {IDualGovernanceConfigProvider} from "./IDualGovernanceConfigProvider.sol"; import {IGovernance} from "./IGovernance.sol"; -import {IEscrow} from "./IEscrow.sol"; import {IResealManager} from "./IResealManager.sol"; import {ITiebreaker} from "./ITiebreaker.sol"; import {Timestamp} from "../types/Timestamp.sol"; @@ -26,7 +25,6 @@ interface IDualGovernance is IGovernance, ITiebreaker { function MIN_TIEBREAKER_ACTIVATION_TIMEOUT() external view returns (Duration); function MAX_TIEBREAKER_ACTIVATION_TIMEOUT() external view returns (Duration); function MAX_SEALABLE_WITHDRAWAL_BLOCKERS_COUNT() external view returns (uint256); - function ESCROW_MASTER_COPY() external view returns (IEscrow); function canSubmitProposal() external view returns (bool); function canCancelAllPendingProposals() external view returns (bool); diff --git a/contracts/interfaces/IEscrow.sol b/contracts/interfaces/IEscrow.sol deleted file mode 100644 index a51a5030..00000000 --- a/contracts/interfaces/IEscrow.sol +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.26; - -import {Duration} from "../types/Duration.sol"; -import {PercentD16} from "../types/PercentD16.sol"; -import {Timestamp} from "../types/Timestamp.sol"; - -interface IEscrow { - /// @notice Summary of the total locked assets in the Escrow. - /// @param stETHLockedShares The total number of stETH shares currently locked in the Escrow. - /// @param stETHClaimedETH The total amount of ETH claimed from the stETH shares locked in the Escrow. - /// @param unstETHUnfinalizedShares The total number of shares from unstETH NFTs that have not yet been marked as finalized. - /// @param unstETHFinalizedETH The total amount of ETH claimable from unstETH NFTs that have been marked as finalized. - struct LockedAssetsTotals { - uint256 stETHLockedShares; - uint256 stETHClaimedETH; - uint256 unstETHUnfinalizedShares; - uint256 unstETHFinalizedETH; - } - - /// @notice Summary of the assets locked in the Escrow by a specific vetoer. - /// @param stETHLockedShares The total number of stETH shares currently locked in the Escrow by the vetoer. - /// @param unstETHLockedShares The total number of unstETH shares currently locked in the Escrow by the vetoer. - /// @param unstETHIdsCount The total number of unstETH NFTs locked in the Escrow by the vetoer. - /// @param lastAssetsLockTimestamp The timestamp of the last time the vetoer locked stETH, wstETH, or unstETH in the Escrow. - struct VetoerState { - uint256 stETHLockedShares; - uint256 unstETHLockedShares; - uint256 unstETHIdsCount; - uint256 lastAssetsLockTimestamp; - } - - function initialize(Duration minAssetsLockDuration) external; - - function lockStETH(uint256 amount) external returns (uint256 lockedStETHShares); - function unlockStETH() external returns (uint256 unlockedStETHShares); - function lockWstETH(uint256 amount) external returns (uint256 lockedStETHShares); - function unlockWstETH() external returns (uint256 unlockedStETHShares); - function lockUnstETH(uint256[] memory unstETHIds) external; - function unlockUnstETH(uint256[] memory unstETHIds) external; - function markUnstETHFinalized(uint256[] memory unstETHIds, uint256[] calldata hints) external; - - function startRageQuit(Duration rageQuitExtensionPeriodDuration, Duration rageQuitEthWithdrawalsDelay) external; - - function requestNextWithdrawalsBatch(uint256 batchSize) external; - - function claimNextWithdrawalsBatch(uint256 fromUnstETHId, uint256[] calldata hints) external; - function claimNextWithdrawalsBatch(uint256 maxUnstETHIdsCount) external; - - function startRageQuitExtensionPeriod() external; - function claimUnstETH(uint256[] calldata unstETHIds, uint256[] calldata hints) external; - - function withdrawETH() external; - function withdrawETH(uint256[] calldata unstETHIds) external; - - function getLockedAssetsTotals() external view returns (LockedAssetsTotals memory totals); - function getVetoerState(address vetoer) external view returns (VetoerState memory state); - function getUnclaimedUnstETHIdsCount() external view returns (uint256); - function getNextWithdrawalBatch(uint256 limit) external view returns (uint256[] memory unstETHIds); - function isWithdrawalsBatchesClosed() external view returns (bool); - function isRageQuitExtensionPeriodStarted() external view returns (bool); - function getRageQuitExtensionPeriodStartedAt() external view returns (Timestamp); - - function isRageQuitFinalized() external view returns (bool); - function getRageQuitSupport() external view returns (PercentD16 rageQuitSupport); - function getMinAssetsLockDuration() external view returns (Duration minAssetsLockDuration); - function setMinAssetsLockDuration(Duration newMinAssetsLockDuration) external; -} diff --git a/contracts/interfaces/IEscrowBase.sol b/contracts/interfaces/IEscrowBase.sol new file mode 100644 index 00000000..c1e8f22e --- /dev/null +++ b/contracts/interfaces/IEscrowBase.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {Duration} from "../types/Duration.sol"; + +import {State} from "../libraries/EscrowState.sol"; + +interface IEscrowBase { + function ESCROW_MASTER_COPY() external view returns (IEscrowBase); + + function initialize(Duration minAssetsLockDuration) external; + + function getEscrowState() external view returns (State); +} diff --git a/contracts/interfaces/IRageQuitEscrow.sol b/contracts/interfaces/IRageQuitEscrow.sol new file mode 100644 index 00000000..6649db93 --- /dev/null +++ b/contracts/interfaces/IRageQuitEscrow.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {Duration} from "../types/Duration.sol"; +import {Timestamp} from "../types/Timestamp.sol"; + +import {IEscrowBase} from "./IEscrowBase.sol"; + +interface IRageQuitEscrow is IEscrowBase { + struct RageQuitEscrowDetails { + Duration rageQuitEthWithdrawalsDelay; + Duration rageQuitExtensionPeriodDuration; + Timestamp rageQuitExtensionPeriodStartedAt; + bool isRageQuitExtensionPeriodStarted; + } + + function requestNextWithdrawalsBatch(uint256 batchSize) external; + + function claimNextWithdrawalsBatch(uint256 fromUnstETHId, uint256[] calldata hints) external; + function claimNextWithdrawalsBatch(uint256 maxUnstETHIdsCount) external; + function claimUnstETH(uint256[] calldata unstETHIds, uint256[] calldata hints) external; + + function startRageQuitExtensionPeriod() external; + + function withdrawETH() external; + function withdrawETH(uint256[] calldata unstETHIds) external; + + function isRageQuitFinalized() external view returns (bool); + function getNextWithdrawalBatch(uint256 limit) external view returns (uint256[] memory unstETHIds); + function isWithdrawalsBatchesClosed() external view returns (bool); + function getUnclaimedUnstETHIdsCount() external view returns (uint256); + function getRageQuitEscrowDetails() external view returns (RageQuitEscrowDetails memory); +} diff --git a/contracts/interfaces/ISignallingEscrow.sol b/contracts/interfaces/ISignallingEscrow.sol new file mode 100644 index 00000000..de91a3a6 --- /dev/null +++ b/contracts/interfaces/ISignallingEscrow.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {ETHValue} from "../types/ETHValue.sol"; +import {Duration} from "../types/Duration.sol"; +import {Timestamp} from "../types/Timestamp.sol"; +import {PercentD16} from "../types/PercentD16.sol"; +import {SharesValue} from "../types/SharesValue.sol"; +import {UnstETHRecordStatus} from "../libraries/AssetsAccounting.sol"; + +import {IEscrowBase} from "./IEscrowBase.sol"; +import {IRageQuitEscrow} from "./IRageQuitEscrow.sol"; + +interface ISignallingEscrow is IEscrowBase { + struct VetoerDetails { + uint256 unstETHIdsCount; + SharesValue stETHLockedShares; + SharesValue unstETHLockedShares; + Timestamp lastAssetsLockTimestamp; + } + + struct LockedUnstETHDetails { + uint256 id; + UnstETHRecordStatus status; + address lockedBy; + SharesValue shares; + ETHValue claimableAmount; + } + + struct SignallingEscrowDetails { + SharesValue totalStETHLockedShares; + ETHValue totalStETHClaimedETH; + SharesValue totalUnstETHUnfinalizedShares; + ETHValue totalUnstETHFinalizedETH; + } + + function lockStETH(uint256 amount) external returns (uint256 lockedStETHShares); + function unlockStETH() external returns (uint256 unlockedStETHShares); + + function lockWstETH(uint256 amount) external returns (uint256 lockedStETHShares); + function unlockWstETH() external returns (uint256 wstETHUnlocked); + + function lockUnstETH(uint256[] memory unstETHIds) external; + function unlockUnstETH(uint256[] memory unstETHIds) external; + + function markUnstETHFinalized(uint256[] memory unstETHIds, uint256[] calldata hints) external; + + function startRageQuit(Duration rageQuitExtensionPeriodDuration, Duration rageQuitEthWithdrawalsDelay) external; + + function setMinAssetsLockDuration(Duration newMinAssetsLockDuration) external; + + function getRageQuitSupport() external view returns (PercentD16); + function getMinAssetsLockDuration() external view returns (Duration); + + function getVetoerDetails(address vetoer) external view returns (VetoerDetails memory); + function getVetoerUnstETHIds(address vetoer) external view returns (uint256[] memory); + function getSignallingEscrowDetails() external view returns (SignallingEscrowDetails memory); + + function getLockedUnstETHDetails(uint256[] calldata unstETHIds) + external + view + returns (LockedUnstETHDetails[] memory); +} diff --git a/contracts/libraries/AssetsAccounting.sol b/contracts/libraries/AssetsAccounting.sol index 84c1ba53..214430a4 100644 --- a/contracts/libraries/AssetsAccounting.sol +++ b/contracts/libraries/AssetsAccounting.sol @@ -8,6 +8,7 @@ import {SharesValue, SharesValues} from "../types/SharesValue.sol"; import {IndexOneBased, IndicesOneBased} from "../types/IndexOneBased.sol"; import {IWithdrawalQueue} from "../interfaces/IWithdrawalQueue.sol"; +import {ISignallingEscrow} from "../interfaces/ISignallingEscrow.sol"; /// @notice Tracks the stETH and unstETH tokens associated with users. /// @param stETHLockedShares Total number of stETH shares held by the user. @@ -341,6 +342,30 @@ library AssetsAccounting { emit UnstETHWithdrawn(unstETHIds, amountWithdrawn); } + // --- + // Getters + // --- + + /// @notice Retrieves details of locked unstETH record for the given id. + /// @param unstETHId The id for the locked unstETH record to retrieve. + /// @return unstETHDetails A `LockedUnstETHDetails` struct containing the details for provided unstETH id. + function getLockedUnstETHDetails( + Context storage self, + uint256 unstETHId + ) internal view returns (ISignallingEscrow.LockedUnstETHDetails memory unstETHDetails) { + UnstETHRecord memory unstETHRecord = self.unstETHRecords[unstETHId]; + + if (unstETHRecord.status == UnstETHRecordStatus.NotLocked) { + revert InvalidUnstETHStatus(unstETHId, UnstETHRecordStatus.NotLocked); + } + + unstETHDetails.id = unstETHId; + unstETHDetails.status = unstETHRecord.status; + unstETHDetails.lockedBy = unstETHRecord.lockedBy; + unstETHDetails.shares = unstETHRecord.shares; + unstETHDetails.claimableAmount = unstETHRecord.claimableAmount; + } + // --- // Checks // --- diff --git a/contracts/libraries/DualGovernanceStateMachine.sol b/contracts/libraries/DualGovernanceStateMachine.sol index 85aba270..83a6d73b 100644 --- a/contracts/libraries/DualGovernanceStateMachine.sol +++ b/contracts/libraries/DualGovernanceStateMachine.sol @@ -7,7 +7,9 @@ import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; import {Duration} from "../types/Duration.sol"; import {Timestamp, Timestamps} from "../types/Timestamp.sol"; -import {IEscrow} from "../interfaces/IEscrow.sol"; +import {IEscrowBase} from "../interfaces/IEscrowBase.sol"; +import {ISignallingEscrow} from "../interfaces/ISignallingEscrow.sol"; +import {IRageQuitEscrow} from "../interfaces/IRageQuitEscrow.sol"; import {IDualGovernance} from "../interfaces/IDualGovernance.sol"; import {IDualGovernanceConfigProvider} from "../interfaces/IDualGovernanceConfigProvider.sol"; @@ -66,7 +68,7 @@ library DualGovernanceStateMachine { /// @dev slot 0: [48..87] Timestamp vetoSignallingActivatedAt; /// @dev slot 0: [88..247] - IEscrow signallingEscrow; + ISignallingEscrow signallingEscrow; /// @dev slot 0: [248..255] uint8 rageQuitRound; /// @dev slot 1: [0..39] @@ -74,7 +76,7 @@ library DualGovernanceStateMachine { /// @dev slot 1: [40..79] Timestamp normalOrVetoCooldownExitedAt; /// @dev slot 1: [80..239] - IEscrow rageQuitEscrow; + IRageQuitEscrow rageQuitEscrow; /// @dev slot 2: [0..159] IDualGovernanceConfigProvider configProvider; } @@ -90,7 +92,7 @@ library DualGovernanceStateMachine { // Events // --- - event NewSignallingEscrowDeployed(IEscrow indexed escrow); + event NewSignallingEscrowDeployed(ISignallingEscrow indexed escrow); event DualGovernanceStateChanged(State indexed from, State indexed to, Context state); event ConfigProviderSet(IDualGovernanceConfigProvider newConfigProvider); @@ -114,7 +116,7 @@ library DualGovernanceStateMachine { function initialize( Context storage self, IDualGovernanceConfigProvider configProvider, - IEscrow escrowMasterCopy + IEscrowBase escrowMasterCopy ) internal { if (self.state != State.Unset) { revert AlreadyInitialized(); @@ -137,9 +139,7 @@ library DualGovernanceStateMachine { /// `escrowMasterCopy` as the implementation for the minimal proxy, while the previous Signalling Escrow /// instance is converted into the RageQuit escrow. /// @param self The context of the Dual Governance State Machine. - /// @param escrowMasterCopy The address of the master copy used as the implementation for the minimal proxy - /// to deploy a new instance of the Signalling Escrow. - function activateNextState(Context storage self, IEscrow escrowMasterCopy) internal { + function activateNextState(Context storage self) internal { DualGovernanceConfig.Context memory config = getDualGovernanceConfig(self); (State currentState, State newState) = self.getStateTransition(config); @@ -165,7 +165,7 @@ library DualGovernanceStateMachine { self.vetoSignallingActivatedAt = newStateEnteredAt; } } else if (newState == State.RageQuit) { - IEscrow signallingEscrow = self.signallingEscrow; + ISignallingEscrow signallingEscrow = self.signallingEscrow; uint256 currentRageQuitRound = self.rageQuitRound; @@ -177,8 +177,8 @@ library DualGovernanceStateMachine { signallingEscrow.startRageQuit( config.rageQuitExtensionPeriodDuration, config.calcRageQuitWithdrawalsDelay(newRageQuitRound) ); - self.rageQuitEscrow = signallingEscrow; - _deployNewSignallingEscrow(self, escrowMasterCopy, config.minAssetsLockDuration); + self.rageQuitEscrow = IRageQuitEscrow(address(signallingEscrow)); + _deployNewSignallingEscrow(self, signallingEscrow.ESCROW_MASTER_COPY(), config.minAssetsLockDuration); } emit DualGovernanceStateChanged(currentState, newState, self); @@ -190,7 +190,7 @@ library DualGovernanceStateMachine { function setConfigProvider(Context storage self, IDualGovernanceConfigProvider newConfigProvider) internal { _setConfigProvider(self, newConfigProvider); - IEscrow signallingEscrow = self.signallingEscrow; + ISignallingEscrow signallingEscrow = self.signallingEscrow; Duration newMinAssetsLockDuration = newConfigProvider.getDualGovernanceConfig().minAssetsLockDuration; /// @dev minAssetsLockDuration is stored as a storage variable in the Signalling Escrow instance. @@ -320,10 +320,10 @@ library DualGovernanceStateMachine { function _deployNewSignallingEscrow( Context storage self, - IEscrow escrowMasterCopy, + IEscrowBase escrowMasterCopy, Duration minAssetsLockDuration ) private { - IEscrow newSignallingEscrow = IEscrow(Clones.clone(address(escrowMasterCopy))); + ISignallingEscrow newSignallingEscrow = ISignallingEscrow(Clones.clone(address(escrowMasterCopy))); newSignallingEscrow.initialize(minAssetsLockDuration); self.signallingEscrow = newSignallingEscrow; emit NewSignallingEscrowDeployed(newSignallingEscrow); diff --git a/contracts/libraries/Proposers.sol b/contracts/libraries/Proposers.sol index 058d7b6f..a8fb4b17 100644 --- a/contracts/libraries/Proposers.sol +++ b/contracts/libraries/Proposers.sol @@ -154,8 +154,10 @@ library Proposers { /// @param self The context of the Proposers library. /// @return proposers An array of `Proposer` structs representing all registered proposers. function getAllProposers(Context storage self) internal view returns (Proposer[] memory proposers) { - proposers = new Proposer[](self.proposers.length); - for (uint256 i = 0; i < proposers.length; ++i) { + uint256 proposersCount = self.proposers.length; + proposers = new Proposer[](proposersCount); + + for (uint256 i = 0; i < proposersCount; ++i) { proposers[i] = getProposer(self, self.proposers[i]); } } diff --git a/scripts/deploy/DeployVerification.sol b/scripts/deploy/DeployVerification.sol index 36ba1446..dfbc4d7f 100644 --- a/scripts/deploy/DeployVerification.sol +++ b/scripts/deploy/DeployVerification.sol @@ -6,6 +6,7 @@ import {Durations} from "contracts/types/Duration.sol"; import {Executor} from "contracts/Executor.sol"; import {IEmergencyProtectedTimelock} from "contracts/interfaces/IEmergencyProtectedTimelock.sol"; import {ITiebreaker} from "contracts/interfaces/ITiebreaker.sol"; +import {IEscrowBase} from "contracts/interfaces/IEscrowBase.sol"; import {TiebreakerCoreCommittee} from "contracts/committees/TiebreakerCoreCommittee.sol"; import {TiebreakerSubCommittee} from "contracts/committees/TiebreakerSubCommittee.sol"; import {TimelockedGovernance} from "contracts/TimelockedGovernance.sol"; @@ -178,7 +179,7 @@ library DeployVerification { "Incorrect parameter MAX_SEALABLE_WITHDRAWAL_BLOCKERS_COUNT" ); - Escrow escrowTemplate = Escrow(payable(address(dg.ESCROW_MASTER_COPY()))); + Escrow escrowTemplate = Escrow(payable(address(IEscrowBase(dg.getVetoSignallingEscrow()).ESCROW_MASTER_COPY()))); require(escrowTemplate.DUAL_GOVERNANCE() == dg, "Escrow has incorrect DualGovernance address"); require(escrowTemplate.ST_ETH() == lidoAddresses.stETH, "Escrow has incorrect StETH address"); require(escrowTemplate.WST_ETH() == lidoAddresses.wstETH, "Escrow has incorrect WstETH address"); diff --git a/test/mocks/EscrowMock.sol b/test/mocks/EscrowMock.sol deleted file mode 100644 index d87c5965..00000000 --- a/test/mocks/EscrowMock.sol +++ /dev/null @@ -1,133 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.26; - -import {Duration} from "contracts/types/Duration.sol"; -import {PercentD16} from "contracts/types/PercentD16.sol"; -import {Timestamp} from "contracts/types/Timestamp.sol"; - -import {IEscrow} from "contracts/interfaces/IEscrow.sol"; - -/* solhint-disable custom-errors */ -contract EscrowMock is IEscrow { - event __RageQuitStarted(Duration rageQuitExtraTimelock, Duration rageQuitWithdrawalsTimelock); - - Duration public __minAssetsLockDuration; - PercentD16 public __rageQuitSupport; - bool public __isRageQuitFinalized; - - function __setRageQuitSupport(PercentD16 newRageQuitSupport) external { - __rageQuitSupport = newRageQuitSupport; - } - - function __setIsRageQuitFinalized(bool newIsRageQuitFinalized) external { - __isRageQuitFinalized = newIsRageQuitFinalized; - } - - function initialize(Duration minAssetsLockDuration) external { - __minAssetsLockDuration = minAssetsLockDuration; - } - - function lockStETH(uint256 /* amount */ ) external returns (uint256 /* lockedStETHShares */ ) { - revert("Not implemented"); - } - - function unlockStETH() external returns (uint256 /* unlockedStETHShares */ ) { - revert("Not implemented"); - } - - function lockWstETH(uint256 /* amount */ ) external returns (uint256 /* lockedStETHShares */ ) { - revert("Not implemented"); - } - - function unlockWstETH() external returns (uint256 /* unlockedStETHShares */ ) { - revert("Not implemented"); - } - - function lockUnstETH(uint256[] memory /* unstETHIds */ ) external { - revert("Not implemented"); - } - - function unlockUnstETH(uint256[] memory /* unstETHIds */ ) external { - revert("Not implemented"); - } - - function markUnstETHFinalized(uint256[] memory, /* unstETHIds */ uint256[] calldata /* hints */ ) external { - revert("Not implemented"); - } - - function startRageQuit(Duration rageQuitExtensionPeriodDuration, Duration rageQuitEthWithdrawalsDelay) external { - emit __RageQuitStarted(rageQuitExtensionPeriodDuration, rageQuitEthWithdrawalsDelay); - } - - function requestNextWithdrawalsBatch(uint256 /* batchSize */ ) external { - revert("Not implemented"); - } - - function claimNextWithdrawalsBatch(uint256, /* fromUnstETHId */ uint256[] calldata /* hints */ ) external { - revert("Not implemented"); - } - - function claimNextWithdrawalsBatch(uint256 /* maxUnstETHIdsCount */ ) external { - revert("Not implemented"); - } - - function startRageQuitExtensionPeriod() external { - revert("Not implemented"); - } - - function claimUnstETH(uint256[] calldata, /* unstETHIds */ uint256[] calldata /* hints */ ) external { - revert("Not implemented"); - } - - function withdrawETH() external { - revert("Not implemented"); - } - - function withdrawETH(uint256[] calldata /* unstETHIds */ ) external { - revert("Not implemented"); - } - - function getLockedAssetsTotals() external view returns (LockedAssetsTotals memory /* totals */ ) { - revert("Not implemented"); - } - - function getVetoerState(address /* vetoer */ ) external view returns (VetoerState memory /* state */ ) { - revert("Not implemented"); - } - - function getUnclaimedUnstETHIdsCount() external view returns (uint256) { - revert("Not implemented"); - } - - function getNextWithdrawalBatch(uint256 /* limit */ ) external view returns (uint256[] memory /* unstETHIds */ ) { - revert("Not implemented"); - } - - function isWithdrawalsBatchesClosed() external view returns (bool) { - revert("Not implemented"); - } - - function isRageQuitExtensionPeriodStarted() external view returns (bool) { - revert("Not implemented"); - } - - function getRageQuitExtensionPeriodStartedAt() external view returns (Timestamp) { - revert("Not implemented"); - } - - function isRageQuitFinalized() external view returns (bool) { - return __isRageQuitFinalized; - } - - function getRageQuitSupport() external view returns (PercentD16 rageQuitSupport) { - return __rageQuitSupport; - } - - function setMinAssetsLockDuration(Duration newMinAssetsLockDuration) external { - __minAssetsLockDuration = newMinAssetsLockDuration; - } - - function getMinAssetsLockDuration() external view returns (Duration minAssetsLockDuration) { - return __minAssetsLockDuration; - } -} diff --git a/test/scenario/escrow.t.sol b/test/scenario/escrow.t.sol index 31f9c754..26604958 100644 --- a/test/scenario/escrow.t.sol +++ b/test/scenario/escrow.t.sol @@ -7,13 +7,14 @@ import {Duration, Durations} from "contracts/types/Duration.sol"; import {PercentsD16} from "contracts/types/PercentD16.sol"; import {IWithdrawalQueue} from "contracts/interfaces/IWithdrawalQueue.sol"; +import {ISignallingEscrow} from "contracts/interfaces/ISignallingEscrow.sol"; import {EscrowState, State} from "contracts/libraries/EscrowState.sol"; - -import {IEscrow} from "contracts/interfaces/IEscrow.sol"; -import {Escrow, WithdrawalsBatchesQueue} from "contracts/Escrow.sol"; +import {WithdrawalsBatchesQueue} from "contracts/libraries/WithdrawalsBatchesQueue.sol"; import {AssetsAccounting, UnstETHRecordStatus} from "contracts/libraries/AssetsAccounting.sol"; +import {Escrow} from "contracts/Escrow.sol"; + import {ScenarioTestBlueprint, LidoUtils, console} from "../utils/scenario-test-blueprint.sol"; contract EscrowHappyPath is ScenarioTestBlueprint { @@ -220,22 +221,22 @@ contract EscrowHappyPath is ScenarioTestBlueprint { _lockUnstETH(_VETOER_1, unstETHIds); - IEscrow.VetoerState memory vetoerState = escrow.getVetoerState(_VETOER_1); - assertEq(vetoerState.unstETHIdsCount, 2); + Escrow.VetoerDetails memory vetoerDetails = escrow.getVetoerDetails(_VETOER_1); + assertEq(vetoerDetails.unstETHIdsCount, 2); - IEscrow.LockedAssetsTotals memory totals = escrow.getLockedAssetsTotals(); - assertEq(totals.unstETHFinalizedETH, 0); - assertEq(totals.unstETHUnfinalizedShares, totalSharesLocked); + ISignallingEscrow.SignallingEscrowDetails memory escrowDetails = escrow.getSignallingEscrowDetails(); + assertEq(escrowDetails.totalUnstETHFinalizedETH.toUint256(), 0); + assertEq(escrowDetails.totalUnstETHUnfinalizedShares.toUint256(), totalSharesLocked); _finalizeWithdrawalQueue(unstETHIds[0]); uint256[] memory hints = _lido.withdrawalQueue.findCheckpointHints(unstETHIds, 1, _lido.withdrawalQueue.getLastCheckpointIndex()); escrow.markUnstETHFinalized(unstETHIds, hints); - totals = escrow.getLockedAssetsTotals(); - assertEq(totals.unstETHUnfinalizedShares, statuses[0].amountOfShares); + escrowDetails = escrow.getSignallingEscrowDetails(); + assertEq(escrowDetails.totalUnstETHUnfinalizedShares.toUint256(), statuses[0].amountOfShares); uint256 ethAmountFinalized = _lido.withdrawalQueue.getClaimableEther(unstETHIds, hints)[0]; - assertApproxEqAbs(totals.unstETHFinalizedETH, ethAmountFinalized, 1); + assertApproxEqAbs(escrowDetails.totalUnstETHFinalizedETH.toUint256(), ethAmountFinalized, 1); } function test_get_rage_quit_support() public { @@ -257,8 +258,8 @@ contract EscrowHappyPath is ScenarioTestBlueprint { uint256 totalSupply = _lido.stETH.totalSupply(); // epsilon is 2 here, because the wstETH unwrap may produce 1 wei error and stETH transfer 1 wei - assertApproxEqAbs(escrow.getVetoerState(_VETOER_1).stETHLockedShares, 2 * sharesToLock, 2); - assertEq(escrow.getVetoerState(_VETOER_1).unstETHIdsCount, 2); + assertApproxEqAbs(escrow.getVetoerDetails(_VETOER_1).stETHLockedShares.toUint256(), 2 * sharesToLock, 2); + assertEq(escrow.getVetoerDetails(_VETOER_1).unstETHIdsCount, 2); assertEq(escrow.getRageQuitSupport(), PercentsD16.fromFraction({numerator: 4 ether, denominator: totalSupply})); @@ -267,10 +268,12 @@ contract EscrowHappyPath is ScenarioTestBlueprint { _lido.withdrawalQueue.findCheckpointHints(unstETHIds, 1, _lido.withdrawalQueue.getLastCheckpointIndex()); escrow.markUnstETHFinalized(unstETHIds, hints); - assertEq(escrow.getLockedAssetsTotals().unstETHUnfinalizedShares, sharesToLock); + assertEq(escrow.getSignallingEscrowDetails().totalUnstETHUnfinalizedShares.toUint256(), sharesToLock); uint256 ethAmountFinalized = _lido.withdrawalQueue.getClaimableEther(unstETHIds, hints)[0]; - assertApproxEqAbs(escrow.getLockedAssetsTotals().unstETHFinalizedETH, ethAmountFinalized, 1); + assertApproxEqAbs( + escrow.getSignallingEscrowDetails().totalUnstETHFinalizedETH.toUint256(), ethAmountFinalized, 1 + ); assertEq( escrow.getRageQuitSupport(), diff --git a/test/unit/DualGovernance.t.sol b/test/unit/DualGovernance.t.sol index 4d792f27..fc9ce1b5 100644 --- a/test/unit/DualGovernance.t.sol +++ b/test/unit/DualGovernance.t.sol @@ -26,7 +26,7 @@ import {IWstETH} from "contracts/interfaces/IWstETH.sol"; import {IWithdrawalQueue} from "contracts/interfaces/IWithdrawalQueue.sol"; import {ITimelock} from "contracts/interfaces/ITimelock.sol"; import {ITiebreaker} from "contracts/interfaces/ITiebreaker.sol"; -import {IEscrow} from "contracts/interfaces/IEscrow.sol"; +import {IEscrowBase} from "contracts/interfaces/IEscrowBase.sol"; import {UnitTest} from "test/utils/unit-test.sol"; import {StETHMock} from "test/mocks/StETHMock.sol"; @@ -152,7 +152,7 @@ contract DualGovernanceUnitTests is UnitTest { address predictedEscrowCopyAddress = computeAddress(predictedDualGovernanceAddress, 1); vm.expectEmit(); - emit DualGovernance.EscrowMasterCopyDeployed(IEscrow(predictedEscrowCopyAddress)); + emit DualGovernance.EscrowMasterCopyDeployed(IEscrowBase(predictedEscrowCopyAddress)); vm.expectEmit(); emit Resealer.ResealManagerSet(address(_RESEAL_MANAGER_STUB)); @@ -181,7 +181,10 @@ contract DualGovernanceUnitTests is UnitTest { assertEq(dualGovernanceLocal.MIN_TIEBREAKER_ACTIVATION_TIMEOUT(), minTiebreakerActivationTimeout); assertEq(dualGovernanceLocal.MAX_TIEBREAKER_ACTIVATION_TIMEOUT(), maxTiebreakerActivationTimeout); assertEq(dualGovernanceLocal.MAX_SEALABLE_WITHDRAWAL_BLOCKERS_COUNT(), maxSealableWithdrawalBlockersCount); - assertEq(address(dualGovernanceLocal.ESCROW_MASTER_COPY()), predictedEscrowCopyAddress); + assertEq( + address(IEscrowBase(dualGovernanceLocal.getVetoSignallingEscrow()).ESCROW_MASTER_COPY()), + predictedEscrowCopyAddress + ); } // --- diff --git a/test/unit/Escrow.t.sol b/test/unit/Escrow.t.sol index f22cea67..1572bc39 100644 --- a/test/unit/Escrow.t.sol +++ b/test/unit/Escrow.t.sol @@ -16,7 +16,9 @@ import {EscrowState as EscrowStateLib, State as EscrowState} from "contracts/lib import {WithdrawalsBatchesQueue} from "contracts/libraries/WithdrawalsBatchesQueue.sol"; import {AssetsAccounting, UnstETHRecordStatus} from "contracts/libraries/AssetsAccounting.sol"; -import {IEscrow} from "contracts/interfaces/IEscrow.sol"; +import {ISignallingEscrow} from "contracts/interfaces/ISignallingEscrow.sol"; +import {IRageQuitEscrow} from "contracts/interfaces/IRageQuitEscrow.sol"; + import {IStETH} from "contracts/interfaces/IStETH.sol"; import {IWstETH} from "contracts/interfaces/IWstETH.sol"; import {IDualGovernance} from "contracts/interfaces/IDualGovernance.sol"; @@ -28,6 +30,8 @@ import {WithdrawalQueueMock} from "test/mocks/WithdrawalQueueMock.sol"; import {UnitTest} from "test/utils/unit-test.sol"; import {Random} from "test/utils/random.sol"; +interface IEscrow is ISignallingEscrow, IRageQuitEscrow {} + contract EscrowUnitTests is UnitTest { Random.Context private _random; address private _dualGovernance = makeAddr("dualGovernance"); @@ -129,13 +133,25 @@ contract EscrowUnitTests is UnitTest { function testFuzz_initialize_RevertOn_CalledNotFromDualGovernance(address stranger) external { vm.assume(stranger != _dualGovernance); - IEscrow instance = _createEscrowProxy(100); + IEscrow instance = IEscrow(address(_createEscrowProxy(100))); vm.prank(stranger); vm.expectRevert(abi.encodeWithSelector(Escrow.CallerIsNotDualGovernance.selector, stranger)); instance.initialize(Durations.ZERO); } + // --- + // getEscrowState() + // --- + + function test_getEscrowState_HappyPath() external { + assertTrue(_masterCopy.getEscrowState() == EscrowState.NotInitialized); + assertTrue(_escrow.getEscrowState() == EscrowState.SignallingEscrow); + + _transitToRageQuit(); + assertTrue(_escrow.getEscrowState() == EscrowState.RageQuitEscrow); + } + // --- // lockStETH() // --- @@ -164,19 +180,19 @@ contract EscrowUnitTests is UnitTest { assertEq(vetoerBalanceAfter, vetoerBalanceBefore - amount); assertEq(escrowBalanceAfter, escrowBalanceBefore + amount); - IEscrow.LockedAssetsTotals memory escrowLockedAssets = _escrow.getLockedAssetsTotals(); + IEscrow.SignallingEscrowDetails memory signallingEscrowDetails = _escrow.getSignallingEscrowDetails(); - assertEq(escrowLockedAssets.stETHLockedShares, lockedStETHShares); - assertEq(escrowLockedAssets.stETHClaimedETH, 0); - assertEq(escrowLockedAssets.unstETHUnfinalizedShares, 0); - assertEq(escrowLockedAssets.unstETHFinalizedETH, 0); + assertEq(signallingEscrowDetails.totalStETHLockedShares.toUint256(), lockedStETHShares); + assertEq(signallingEscrowDetails.totalStETHClaimedETH.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHUnfinalizedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHFinalizedETH.toUint256(), 0); - IEscrow.VetoerState memory state = _escrow.getVetoerState(_vetoer); + IEscrow.VetoerDetails memory state = _escrow.getVetoerDetails(_vetoer); assertEq(state.unstETHIdsCount, 0); - assertEq(state.stETHLockedShares, lockedStETHShares); - assertEq(state.unstETHLockedShares, 0); - assertEq(state.lastAssetsLockTimestamp, Timestamps.now().toSeconds()); + assertEq(state.stETHLockedShares.toUint256(), lockedStETHShares); + assertEq(state.unstETHLockedShares.toUint256(), 0); + assertEq(state.lastAssetsLockTimestamp, Timestamps.now()); } function test_lockStETH_RevertOn_UnexpectedEscrowState() external { @@ -216,18 +232,18 @@ contract EscrowUnitTests is UnitTest { assertEq(vetoerBalanceAfter, vetoerBalanceBefore + amount); assertEq(escrowBalanceAfter, escrowBalanceBefore - amount); - IEscrow.LockedAssetsTotals memory escrowLockedAssets = _escrow.getLockedAssetsTotals(); + IEscrow.SignallingEscrowDetails memory signallingEscrowDetails = _escrow.getSignallingEscrowDetails(); - assertEq(escrowLockedAssets.stETHLockedShares, 0); - assertEq(escrowLockedAssets.stETHClaimedETH, 0); - assertEq(escrowLockedAssets.unstETHUnfinalizedShares, 0); - assertEq(escrowLockedAssets.unstETHFinalizedETH, 0); + assertEq(signallingEscrowDetails.totalStETHLockedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalStETHClaimedETH.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHUnfinalizedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHFinalizedETH.toUint256(), 0); - IEscrow.VetoerState memory state = _escrow.getVetoerState(_vetoer); + IEscrow.VetoerDetails memory state = _escrow.getVetoerDetails(_vetoer); assertEq(state.unstETHIdsCount, 0); - assertEq(state.stETHLockedShares, 0); - assertEq(state.unstETHLockedShares, 0); + assertEq(state.stETHLockedShares.toUint256(), 0); + assertEq(state.unstETHLockedShares.toUint256(), 0); } function test_unlockStETH_RevertOn_UnexpectedEscrowState() external { @@ -283,19 +299,19 @@ contract EscrowUnitTests is UnitTest { assertEq(vetoerWStBalanceAfter, vetoerWStBalanceBefore - lockedStETHShares); assertEq(escrowBalanceAfter, escrowBalanceBefore + lockedStETHShares); - IEscrow.LockedAssetsTotals memory escrowLockedAssets = _escrow.getLockedAssetsTotals(); + IEscrow.SignallingEscrowDetails memory signallingEscrowDetails = _escrow.getSignallingEscrowDetails(); - assertEq(escrowLockedAssets.stETHLockedShares, lockedStETHShares); - assertEq(escrowLockedAssets.stETHClaimedETH, 0); - assertEq(escrowLockedAssets.unstETHUnfinalizedShares, 0); - assertEq(escrowLockedAssets.unstETHFinalizedETH, 0); + assertEq(signallingEscrowDetails.totalStETHLockedShares.toUint256(), lockedStETHShares); + assertEq(signallingEscrowDetails.totalStETHClaimedETH.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHUnfinalizedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHFinalizedETH.toUint256(), 0); - IEscrow.VetoerState memory state = _escrow.getVetoerState(_vetoer); + IEscrow.VetoerDetails memory state = _escrow.getVetoerDetails(_vetoer); assertEq(state.unstETHIdsCount, 0); - assertEq(state.stETHLockedShares, lockedStETHShares); - assertEq(state.unstETHLockedShares, 0); - assertEq(state.lastAssetsLockTimestamp, Timestamps.now().toSeconds()); + assertEq(state.stETHLockedShares.toUint256(), lockedStETHShares); + assertEq(state.unstETHLockedShares.toUint256(), 0); + assertEq(state.lastAssetsLockTimestamp, Timestamps.now()); } function test_lockWstETH_RevertOn_UnexpectedEscrowState() external { @@ -336,18 +352,18 @@ contract EscrowUnitTests is UnitTest { assertEq(vetoerWStBalanceAfter, vetoerWStBalanceBefore + unlockedStETHShares); assertEq(escrowBalanceAfter, escrowBalanceBefore - unlockedStETHShares); - IEscrow.LockedAssetsTotals memory escrowLockedAssets = _escrow.getLockedAssetsTotals(); + IEscrow.SignallingEscrowDetails memory signallingEscrowDetails = _escrow.getSignallingEscrowDetails(); - assertEq(escrowLockedAssets.stETHLockedShares, 0); - assertEq(escrowLockedAssets.stETHClaimedETH, 0); - assertEq(escrowLockedAssets.unstETHUnfinalizedShares, 0); - assertEq(escrowLockedAssets.unstETHFinalizedETH, 0); + assertEq(signallingEscrowDetails.totalStETHLockedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalStETHClaimedETH.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHUnfinalizedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHFinalizedETH.toUint256(), 0); - IEscrow.VetoerState memory state = _escrow.getVetoerState(_vetoer); + IEscrow.VetoerDetails memory state = _escrow.getVetoerDetails(_vetoer); assertEq(state.unstETHIdsCount, 0); - assertEq(state.stETHLockedShares, 0); - assertEq(state.unstETHLockedShares, 0); + assertEq(state.stETHLockedShares.toUint256(), 0); + assertEq(state.unstETHLockedShares.toUint256(), 0); } function test_unlockWstETH_RevertOn_UnexpectedEscrowState() external { @@ -413,18 +429,21 @@ contract EscrowUnitTests is UnitTest { vm.prank(_vetoer); _escrow.lockUnstETH(unstethIds); - IEscrow.LockedAssetsTotals memory escrowLockedAssets = _escrow.getLockedAssetsTotals(); + IEscrow.SignallingEscrowDetails memory signallingEscrowDetails = _escrow.getSignallingEscrowDetails(); - assertEq(escrowLockedAssets.stETHLockedShares, 0); - assertEq(escrowLockedAssets.stETHClaimedETH, 0); - assertEq(escrowLockedAssets.unstETHUnfinalizedShares, statuses[0].amountOfShares + statuses[1].amountOfShares); - assertEq(escrowLockedAssets.unstETHFinalizedETH, 0); + assertEq(signallingEscrowDetails.totalStETHLockedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalStETHClaimedETH.toUint256(), 0); + assertEq( + signallingEscrowDetails.totalUnstETHUnfinalizedShares.toUint256(), + statuses[0].amountOfShares + statuses[1].amountOfShares + ); + assertEq(signallingEscrowDetails.totalUnstETHFinalizedETH.toUint256(), 0); - IEscrow.VetoerState memory state = _escrow.getVetoerState(_vetoer); + IEscrow.VetoerDetails memory state = _escrow.getVetoerDetails(_vetoer); assertEq(state.unstETHIdsCount, 2); - assertEq(state.stETHLockedShares, 0); - assertEq(state.unstETHLockedShares, statuses[0].amountOfShares + statuses[1].amountOfShares); + assertEq(state.stETHLockedShares.toUint256(), 0); + assertEq(state.unstETHLockedShares.toUint256(), statuses[0].amountOfShares + statuses[1].amountOfShares); } function test_lockUnstETH_RevertOn_EmptyUnstETHIds() external { @@ -470,18 +489,18 @@ contract EscrowUnitTests is UnitTest { vm.prank(_vetoer); _escrow.unlockUnstETH(unstethIds); - IEscrow.LockedAssetsTotals memory escrowLockedAssets = _escrow.getLockedAssetsTotals(); + IEscrow.SignallingEscrowDetails memory signallingEscrowDetails = _escrow.getSignallingEscrowDetails(); - assertEq(escrowLockedAssets.stETHLockedShares, 0); - assertEq(escrowLockedAssets.stETHClaimedETH, 0); - assertEq(escrowLockedAssets.unstETHUnfinalizedShares, 0); - assertEq(escrowLockedAssets.unstETHFinalizedETH, 0); + assertEq(signallingEscrowDetails.totalStETHLockedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalStETHClaimedETH.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHUnfinalizedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHFinalizedETH.toUint256(), 0); - IEscrow.VetoerState memory state = _escrow.getVetoerState(_vetoer); + IEscrow.VetoerDetails memory state = _escrow.getVetoerDetails(_vetoer); assertEq(state.unstETHIdsCount, 0); - assertEq(state.stETHLockedShares, 0); - assertEq(state.unstETHLockedShares, 0); + assertEq(state.stETHLockedShares.toUint256(), 0); + assertEq(state.unstETHLockedShares.toUint256(), 0); } function test_unlockUnstETH_EmptyUnstETHIds() external { @@ -651,12 +670,12 @@ contract EscrowUnitTests is UnitTest { // --- function test_claimNextWithdrawalsBatch_2_HappyPath() external { - IEscrow.LockedAssetsTotals memory escrowLockedAssets = _escrow.getLockedAssetsTotals(); + IEscrow.SignallingEscrowDetails memory signallingEscrowDetails = _escrow.getSignallingEscrowDetails(); - assertEq(escrowLockedAssets.stETHLockedShares, 0); - assertEq(escrowLockedAssets.stETHClaimedETH, 0); - assertEq(escrowLockedAssets.unstETHUnfinalizedShares, 0); - assertEq(escrowLockedAssets.unstETHFinalizedETH, 0); + assertEq(signallingEscrowDetails.totalStETHLockedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalStETHClaimedETH.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHUnfinalizedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHFinalizedETH.toUint256(), 0); uint256[] memory unstEthIds = _getUnstEthIdsFromWQ(); @@ -678,18 +697,18 @@ contract EscrowUnitTests is UnitTest { emit AssetsAccounting.ETHClaimed(ETHValues.from(stethAmount)); _escrow.claimNextWithdrawalsBatch(unstEthIds[0], new uint256[](unstEthIds.length)); - escrowLockedAssets = _escrow.getLockedAssetsTotals(); + signallingEscrowDetails = _escrow.getSignallingEscrowDetails(); - assertEq(escrowLockedAssets.stETHLockedShares, stethAmount); - assertEq(escrowLockedAssets.stETHClaimedETH, stethAmount); - assertEq(escrowLockedAssets.unstETHUnfinalizedShares, 0); - assertEq(escrowLockedAssets.unstETHFinalizedETH, 0); + assertEq(signallingEscrowDetails.totalStETHLockedShares.toUint256(), stethAmount); + assertEq(signallingEscrowDetails.totalStETHClaimedETH.toUint256(), stethAmount); + assertEq(signallingEscrowDetails.totalUnstETHUnfinalizedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHFinalizedETH.toUint256(), 0); - IEscrow.VetoerState memory state = _escrow.getVetoerState(_vetoer); + IEscrow.VetoerDetails memory state = _escrow.getVetoerDetails(_vetoer); assertEq(state.unstETHIdsCount, 0); - assertEq(state.stETHLockedShares, stethAmount); - assertEq(state.unstETHLockedShares, 0); + assertEq(state.stETHLockedShares.toUint256(), stethAmount); + assertEq(state.unstETHLockedShares.toUint256(), 0); } function test_claimNextWithdrawalsBatch_2_RevertOn_UnexpectedState() external { @@ -753,12 +772,12 @@ contract EscrowUnitTests is UnitTest { // --- function test_claimNextWithdrawalsBatch_1_HappyPath() external { - IEscrow.LockedAssetsTotals memory escrowLockedAssets = _escrow.getLockedAssetsTotals(); + IEscrow.SignallingEscrowDetails memory signallingEscrowDetails = _escrow.getSignallingEscrowDetails(); - assertEq(escrowLockedAssets.stETHLockedShares, 0); - assertEq(escrowLockedAssets.stETHClaimedETH, 0); - assertEq(escrowLockedAssets.unstETHUnfinalizedShares, 0); - assertEq(escrowLockedAssets.unstETHFinalizedETH, 0); + assertEq(signallingEscrowDetails.totalStETHLockedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalStETHClaimedETH.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHUnfinalizedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHFinalizedETH.toUint256(), 0); uint256[] memory unstEthIds = _getUnstEthIdsFromWQ(); @@ -769,26 +788,26 @@ contract EscrowUnitTests is UnitTest { _ensureUnstEthAddedToWithdrawalsBatchesQueue(unstEthIds, stethAmount); - IEscrow.VetoerState memory vetoerState = _escrow.getVetoerState(_vetoer); + IEscrow.VetoerDetails memory vetoerState = _escrow.getVetoerDetails(_vetoer); assertEq(vetoerState.unstETHIdsCount, 0); - assertEq(vetoerState.stETHLockedShares, stethAmount); - assertEq(vetoerState.unstETHLockedShares, 0); + assertEq(vetoerState.stETHLockedShares.toUint256(), stethAmount); + assertEq(vetoerState.unstETHLockedShares.toUint256(), 0); _claimStEthViaWQ(unstEthIds, stethAmount); - escrowLockedAssets = _escrow.getLockedAssetsTotals(); + signallingEscrowDetails = _escrow.getSignallingEscrowDetails(); - assertEq(escrowLockedAssets.stETHLockedShares, stethAmount); - assertEq(escrowLockedAssets.stETHClaimedETH, stethAmount); - assertEq(escrowLockedAssets.unstETHUnfinalizedShares, 0); - assertEq(escrowLockedAssets.unstETHFinalizedETH, 0); + assertEq(signallingEscrowDetails.totalStETHLockedShares.toUint256(), stethAmount); + assertEq(signallingEscrowDetails.totalStETHClaimedETH.toUint256(), stethAmount); + assertEq(signallingEscrowDetails.totalUnstETHUnfinalizedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHFinalizedETH.toUint256(), 0); - vetoerState = _escrow.getVetoerState(_vetoer); + vetoerState = _escrow.getVetoerDetails(_vetoer); assertEq(vetoerState.unstETHIdsCount, 0); - assertEq(vetoerState.stETHLockedShares, stethAmount); - assertEq(vetoerState.unstETHLockedShares, 0); + assertEq(vetoerState.stETHLockedShares.toUint256(), stethAmount); + assertEq(vetoerState.unstETHLockedShares.toUint256(), 0); } function test_claimNextWithdrawalsBatch_1_RevertOn_UnexpectedState() external { @@ -892,18 +911,18 @@ contract EscrowUnitTests is UnitTest { _claimUnstEthFromEscrow(unstEthAmounts, unstEthIds, hints); - IEscrow.LockedAssetsTotals memory escrowLockedAssets = _escrow.getLockedAssetsTotals(); + IEscrow.SignallingEscrowDetails memory signallingEscrowDetails = _escrow.getSignallingEscrowDetails(); - assertEq(escrowLockedAssets.stETHLockedShares, 0); - assertEq(escrowLockedAssets.stETHClaimedETH, 0); - assertEq(escrowLockedAssets.unstETHUnfinalizedShares, 0); - assertEq(escrowLockedAssets.unstETHFinalizedETH, unstEthAmounts[0]); + assertEq(signallingEscrowDetails.totalStETHLockedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalStETHClaimedETH.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHUnfinalizedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHFinalizedETH.toUint256(), unstEthAmounts[0]); - IEscrow.VetoerState memory state = _escrow.getVetoerState(_vetoer); + IEscrow.VetoerDetails memory state = _escrow.getVetoerDetails(_vetoer); assertEq(state.unstETHIdsCount, 1); - assertEq(state.stETHLockedShares, 0); - assertEq(state.unstETHLockedShares, unstEthAmounts[0]); + assertEq(state.stETHLockedShares.toUint256(), 0); + assertEq(state.unstETHLockedShares.toUint256(), unstEthAmounts[0]); } function test_claimUnstETH_RevertOn_UnexpectedEscrowState() external { @@ -999,7 +1018,7 @@ contract EscrowUnitTests is UnitTest { _ensureUnstEthAddedToWithdrawalsBatchesQueue(unstEthIds, stethAmount); _claimStEthViaWQ(unstEthIds, stethAmount); _ensureRageQuitExtensionPeriodStartedNow(); - assertTrue(_escrow.isRageQuitExtensionPeriodStarted()); + assertTrue(_escrow.getRageQuitEscrowDetails().isRageQuitExtensionPeriodStarted); _wait(Durations.from(1)); @@ -1011,18 +1030,18 @@ contract EscrowUnitTests is UnitTest { assertEq(_vetoer.balance, balanceBefore + stethAmount); - IEscrow.LockedAssetsTotals memory escrowLockedAssets = _escrow.getLockedAssetsTotals(); + IEscrow.SignallingEscrowDetails memory signallingEscrowDetails = _escrow.getSignallingEscrowDetails(); - assertEq(escrowLockedAssets.stETHLockedShares, stethAmount); - assertEq(escrowLockedAssets.stETHClaimedETH, stethAmount); - assertEq(escrowLockedAssets.unstETHUnfinalizedShares, 0); - assertEq(escrowLockedAssets.unstETHFinalizedETH, 0); + assertEq(signallingEscrowDetails.totalStETHLockedShares.toUint256(), stethAmount); + assertEq(signallingEscrowDetails.totalStETHClaimedETH.toUint256(), stethAmount); + assertEq(signallingEscrowDetails.totalUnstETHUnfinalizedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHFinalizedETH.toUint256(), 0); - IEscrow.VetoerState memory state = _escrow.getVetoerState(_vetoer); + IEscrow.VetoerDetails memory state = _escrow.getVetoerDetails(_vetoer); assertEq(state.unstETHIdsCount, 0); - assertEq(state.stETHLockedShares, 0); - assertEq(state.unstETHLockedShares, 0); + assertEq(state.stETHLockedShares.toUint256(), 0); + assertEq(state.unstETHLockedShares.toUint256(), 0); } function test_withdrawETH_RevertOn_UnexpectedEscrowState() external { @@ -1049,7 +1068,7 @@ contract EscrowUnitTests is UnitTest { _ensureUnstEthAddedToWithdrawalsBatchesQueue(unstEthIds, stethAmount); _claimStEthViaWQ(unstEthIds, stethAmount); _ensureRageQuitExtensionPeriodStartedNow(); - assertTrue(_escrow.isRageQuitExtensionPeriodStarted()); + assertTrue(_escrow.getRageQuitEscrowDetails().isRageQuitExtensionPeriodStarted); vm.startPrank(_vetoer); vm.expectRevert(EscrowStateLib.EthWithdrawalsDelayNotPassed.selector); @@ -1084,7 +1103,7 @@ contract EscrowUnitTests is UnitTest { _ensureUnstEthAddedToWithdrawalsBatchesQueue(unstEthIds, stethAmount); _claimStEthViaWQ(unstEthIds, stethAmount); _ensureRageQuitExtensionPeriodStartedNow(); - assertTrue(_escrow.isRageQuitExtensionPeriodStarted()); + assertTrue(_escrow.getRageQuitEscrowDetails().isRageQuitExtensionPeriodStarted); _wait(_minLockAssetDuration); @@ -1116,7 +1135,7 @@ contract EscrowUnitTests is UnitTest { _ensureWithdrawalsBatchesQueueClosed(); _ensureRageQuitExtensionPeriodStartedNow(); - assertTrue(_escrow.isRageQuitExtensionPeriodStarted()); + assertTrue(_escrow.getRageQuitEscrowDetails().isRageQuitExtensionPeriodStarted); _wait(Durations.from(1)); @@ -1128,18 +1147,18 @@ contract EscrowUnitTests is UnitTest { assertEq(_vetoer.balance, balanceBefore + sum); - IEscrow.LockedAssetsTotals memory escrowLockedAssets = _escrow.getLockedAssetsTotals(); + IEscrow.SignallingEscrowDetails memory signallingEscrowDetails = _escrow.getSignallingEscrowDetails(); - assertEq(escrowLockedAssets.stETHLockedShares, 0); - assertEq(escrowLockedAssets.stETHClaimedETH, 0); - assertEq(escrowLockedAssets.unstETHUnfinalizedShares, 0); - assertEq(escrowLockedAssets.unstETHFinalizedETH, unstEthAmounts[0] + unstEthAmounts[1]); + assertEq(signallingEscrowDetails.totalStETHLockedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalStETHClaimedETH.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHUnfinalizedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHFinalizedETH.toUint256(), unstEthAmounts[0] + unstEthAmounts[1]); - IEscrow.VetoerState memory state = _escrow.getVetoerState(_vetoer); + IEscrow.VetoerDetails memory state = _escrow.getVetoerDetails(_vetoer); assertEq(state.unstETHIdsCount, 2); - assertEq(state.stETHLockedShares, 0); - assertEq(state.unstETHLockedShares, unstEthAmounts[0] + unstEthAmounts[1]); + assertEq(state.stETHLockedShares.toUint256(), 0); + assertEq(state.unstETHLockedShares.toUint256(), unstEthAmounts[0] + unstEthAmounts[1]); } function test_withdrawETH_2_RevertOn_EmptyUnstETHIds() external { @@ -1175,7 +1194,7 @@ contract EscrowUnitTests is UnitTest { _ensureWithdrawalsBatchesQueueClosed(); _ensureRageQuitExtensionPeriodStartedNow(); - assertTrue(_escrow.isRageQuitExtensionPeriodStarted()); + assertTrue(_escrow.getRageQuitEscrowDetails().isRageQuitExtensionPeriodStarted); _wait(Durations.from(1)); @@ -1201,7 +1220,7 @@ contract EscrowUnitTests is UnitTest { _ensureWithdrawalsBatchesQueueClosed(); _ensureRageQuitExtensionPeriodStartedNow(); - assertTrue(_escrow.isRageQuitExtensionPeriodStarted()); + assertTrue(_escrow.getRageQuitEscrowDetails().isRageQuitExtensionPeriodStarted); _wait(Durations.from(1)); @@ -1222,12 +1241,12 @@ contract EscrowUnitTests is UnitTest { // --- function test_getLockedAssetsTotals() external view { - IEscrow.LockedAssetsTotals memory escrowLockedAssets = _escrow.getLockedAssetsTotals(); + IEscrow.SignallingEscrowDetails memory signallingEscrowDetails = _escrow.getSignallingEscrowDetails(); - assertEq(escrowLockedAssets.stETHLockedShares, 0); - assertEq(escrowLockedAssets.stETHClaimedETH, 0); - assertEq(escrowLockedAssets.unstETHUnfinalizedShares, 0); - assertEq(escrowLockedAssets.unstETHFinalizedETH, 0); + assertEq(signallingEscrowDetails.totalStETHLockedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalStETHClaimedETH.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHUnfinalizedShares.toUint256(), 0); + assertEq(signallingEscrowDetails.totalUnstETHFinalizedETH.toUint256(), 0); } // --- @@ -1237,12 +1256,12 @@ contract EscrowUnitTests is UnitTest { function test_getVetoerState() external { _vetoerLockedStEth(stethAmount); - IEscrow.VetoerState memory state = _escrow.getVetoerState(_vetoer); + IEscrow.VetoerDetails memory state = _escrow.getVetoerDetails(_vetoer); assertEq(state.unstETHIdsCount, 0); - assertEq(state.stETHLockedShares, _stETH.getSharesByPooledEth(stethAmount)); - assertEq(state.unstETHLockedShares, 0); - assertEq(state.lastAssetsLockTimestamp, Timestamps.now().toSeconds()); + assertEq(state.stETHLockedShares.toUint256(), _stETH.getSharesByPooledEth(stethAmount)); + assertEq(state.unstETHLockedShares.toUint256(), 0); + assertEq(state.lastAssetsLockTimestamp, Timestamps.now()); } // --- @@ -1284,22 +1303,47 @@ contract EscrowUnitTests is UnitTest { } // --- - // getUnclaimedUnstETHIdsCount() + // getLockedUnstETHDetails() // --- - function test_getUnclaimedUnstETHIdsCount() external { - _transitToRageQuit(); - assertEq(_escrow.getUnclaimedUnstETHIdsCount(), 0); - } + function test_getLockedUnstETHDetails_HappyPath() external { + uint256[] memory unstEthAmounts = new uint256[](2); + unstEthAmounts[0] = 1 ether; + unstEthAmounts[1] = 10 ether; - function test_getUnclaimedUnstETHIdsCount_RevertOn_UnexpectedState_Signaling() external { - vm.expectRevert(abi.encodeWithSelector(EscrowStateLib.UnexpectedState.selector, EscrowState.RageQuitEscrow)); - _escrow.getUnclaimedUnstETHIdsCount(); + assertEq(_escrow.getVetoerUnstETHIds(_vetoer).length, 0); + + uint256[] memory unstEthIds = _vetoerLockedUnstEth(unstEthAmounts); + + IEscrow.LockedUnstETHDetails[] memory unstETHDetails = _escrow.getLockedUnstETHDetails(unstEthIds); + + assertEq(unstETHDetails.length, unstEthIds.length); + + assertEq(unstETHDetails[0].id, unstEthIds[0]); + assertEq(unstETHDetails[0].lockedBy, _vetoer); + assertTrue(unstETHDetails[0].status == UnstETHRecordStatus.Locked); + assertEq(unstETHDetails[0].shares.toUint256(), unstEthAmounts[0]); + assertEq(unstETHDetails[0].claimableAmount.toUint256(), 0); + + assertEq(unstETHDetails[1].id, unstEthIds[1]); + assertEq(unstETHDetails[1].lockedBy, _vetoer); + assertTrue(unstETHDetails[1].status == UnstETHRecordStatus.Locked); + assertEq(unstETHDetails[1].shares.toUint256(), unstEthAmounts[1]); + assertEq(unstETHDetails[1].claimableAmount.toUint256(), 0); } - function test_getUnclaimedUnstETHIdsCount_RevertOn_UnexpectedState_NotInitialized() external { - vm.expectRevert(abi.encodeWithSelector(EscrowStateLib.UnexpectedState.selector, EscrowState.RageQuitEscrow)); - _masterCopy.getUnclaimedUnstETHIdsCount(); + function test_getLockedUnstETHDetails_RevertOn_unstETHNotLocked() external { + uint256 notLockedUnstETHId = 42; + + uint256[] memory notLockedUnstETHIds = new uint256[](1); + notLockedUnstETHIds[0] = notLockedUnstETHId; + + vm.expectRevert( + abi.encodeWithSelector( + AssetsAccounting.InvalidUnstETHStatus.selector, notLockedUnstETHId, UnstETHRecordStatus.NotLocked + ) + ); + _escrow.getLockedUnstETHDetails(notLockedUnstETHIds); } // --- @@ -1385,25 +1429,36 @@ contract EscrowUnitTests is UnitTest { // --- function test_isRageQuitExtensionPeriodStarted() external { - assertFalse(_escrow.isRageQuitExtensionPeriodStarted()); - _transitToRageQuit(); + assertFalse(_escrow.getRageQuitEscrowDetails().isRageQuitExtensionPeriodStarted); + _ensureWithdrawalsBatchesQueueClosed(); _ensureRageQuitExtensionPeriodStartedNow(); - assertTrue(_escrow.isRageQuitExtensionPeriodStarted()); + assertTrue(_escrow.getRageQuitEscrowDetails().isRageQuitExtensionPeriodStarted); - assertEq(_escrow.getRageQuitExtensionPeriodStartedAt(), Timestamps.now()); + assertEq(_escrow.getRageQuitEscrowDetails().rageQuitExtensionPeriodStartedAt, Timestamps.now()); } // --- // getRageQuitExtensionPeriodStartedAt() // --- - function test_getRageQuitExtensionPeriodStartedAt() external view { - Timestamp res = _escrow.getRageQuitExtensionPeriodStartedAt(); + function test_getRageQuitExtensionPeriodStartedAt_RevertOn_NotInitializedState() external { + vm.expectRevert(abi.encodeWithSelector(EscrowStateLib.UnexpectedState.selector, EscrowState.RageQuitEscrow)); + _masterCopy.getRageQuitEscrowDetails(); + } + + function test_getRageQuitExtensionPeriodStartedAt_RevertOn_SignallingState() external { + vm.expectRevert(abi.encodeWithSelector(EscrowStateLib.UnexpectedState.selector, EscrowState.RageQuitEscrow)); + _escrow.getRageQuitEscrowDetails().rageQuitExtensionPeriodStartedAt; + } + + function test_getRageQuitExtensionPeriodStartedAt() external { + _transitToRageQuit(); + Timestamp res = _escrow.getRageQuitEscrowDetails().rageQuitExtensionPeriodStartedAt; assertEq(res.toSeconds(), Timestamps.ZERO.toSeconds()); } @@ -1463,6 +1518,20 @@ contract EscrowUnitTests is UnitTest { assertTrue(_escrow.isRageQuitFinalized()); } + // --- + // getRageQuitEscrowDetails() + // --- + + function test_getRageQuitEscrowDetails_RevertOn_UnexpectedState_Signaling() external { + vm.expectRevert(abi.encodeWithSelector(EscrowStateLib.UnexpectedState.selector, EscrowState.RageQuitEscrow)); + _escrow.getRageQuitEscrowDetails(); + } + + function test_getRageQuitEscrowDetails_RevertOn_UnexpectedState_NotInitialized() external { + vm.expectRevert(abi.encodeWithSelector(EscrowStateLib.UnexpectedState.selector, EscrowState.RageQuitEscrow)); + _masterCopy.getRageQuitEscrowDetails(); + } + // --- // receive() // --- diff --git a/test/unit/libraries/AssetsAccounting.t.sol b/test/unit/libraries/AssetsAccounting.t.sol index e1c9b3df..8e2e6734 100644 --- a/test/unit/libraries/AssetsAccounting.t.sol +++ b/test/unit/libraries/AssetsAccounting.t.sol @@ -11,6 +11,7 @@ import {IndicesOneBased} from "contracts/types/IndexOneBased.sol"; import {Durations} from "contracts/types/Duration.sol"; import {Timestamps} from "contracts/types/Timestamp.sol"; import {IWithdrawalQueue} from "contracts/interfaces/IWithdrawalQueue.sol"; +import {ISignallingEscrow} from "contracts/interfaces/ISignallingEscrow.sol"; import {AssetsAccounting, UnstETHRecordStatus} from "contracts/libraries/AssetsAccounting.sol"; import {UnitTest, Duration} from "test/utils/unit-test.sol"; @@ -1419,6 +1420,62 @@ contract AssetsAccountingUnitTests is UnitTest { AssetsAccounting.accountUnstETHWithdraw(_accountingContext, holder, unstETHIds); } + // --- + // getLockedUnstETHDetails + // --- + + function test_getLockedUnstETHDetails_HappyPath() external { + uint256 unstETHIdsCount = 4; + uint256[] memory unstETHIds = new uint256[](unstETHIdsCount); + for (uint256 i = 0; i < unstETHIdsCount; ++i) { + unstETHIds[i] = genRandomUnstEthId(i); + address holder = address(uint160(uint256(keccak256(abi.encode(i))))); + + _accountingContext.unstETHRecords[unstETHIds[i]].lockedBy = holder; + _accountingContext.unstETHRecords[unstETHIds[i]].status = UnstETHRecordStatus(i + 1); + _accountingContext.unstETHRecords[unstETHIds[i]].shares = SharesValues.from((i + 1) * 1 ether); + _accountingContext.unstETHRecords[unstETHIds[i]].claimableAmount = ETHValues.from((i + 1) * 10 ether); + + _accountingContext.assets[holder].unstETHIds.push(unstETHIds[i]); + } + + for (uint256 i = 0; i < unstETHIdsCount; ++i) { + ISignallingEscrow.LockedUnstETHDetails memory unstETHDetails = + AssetsAccounting.getLockedUnstETHDetails(_accountingContext, unstETHIds[i]); + + assertEq(unstETHDetails.id, unstETHIds[i]); + assertEq(unstETHDetails.status, UnstETHRecordStatus(i + 1)); + assertEq(unstETHDetails.lockedBy, address(uint160(uint256(keccak256(abi.encode(i)))))); + assertEq(unstETHDetails.shares, SharesValues.from((i + 1) * 1 ether)); + assertEq(unstETHDetails.claimableAmount, ETHValues.from((i + 1) * 10 ether)); + } + } + + function test_getLockedUnstETHDetails_RevertOn_UnstETHNotLocked() external { + uint256 unstETHIdsCount = 4; + uint256[] memory unstETHIds = new uint256[](unstETHIdsCount); + for (uint256 i = 0; i < unstETHIdsCount; ++i) { + unstETHIds[i] = genRandomUnstEthId(i); + address holder = address(uint160(uint256(keccak256(abi.encode(i))))); + + _accountingContext.unstETHRecords[unstETHIds[i]].lockedBy = holder; + _accountingContext.unstETHRecords[unstETHIds[i]].status = UnstETHRecordStatus(i + 1); + _accountingContext.unstETHRecords[unstETHIds[i]].shares = SharesValues.from(i * 1 ether); + _accountingContext.unstETHRecords[unstETHIds[i]].claimableAmount = ETHValues.from(i * 10 ether); + + _accountingContext.assets[holder].unstETHIds.push(unstETHIds[i]); + } + + uint256 notLockedUnstETHId = genRandomUnstEthId(5); + + vm.expectRevert( + abi.encodeWithSelector( + AssetsAccounting.InvalidUnstETHStatus.selector, notLockedUnstETHId, UnstETHRecordStatus.NotLocked + ) + ); + AssetsAccounting.getLockedUnstETHDetails(_accountingContext, notLockedUnstETHId); + } + // --- // checkMinAssetsLockDurationPassed // --- diff --git a/test/unit/libraries/DualGovernanceStateMachine.t.sol b/test/unit/libraries/DualGovernanceStateMachine.t.sol index b6c5cea3..7ae20adc 100644 --- a/test/unit/libraries/DualGovernanceStateMachine.t.sol +++ b/test/unit/libraries/DualGovernanceStateMachine.t.sol @@ -3,7 +3,9 @@ pragma solidity 0.8.26; import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; -import {IEscrow} from "contracts/interfaces/IEscrow.sol"; +import {IEscrowBase} from "contracts/interfaces/IEscrowBase.sol"; +import {ISignallingEscrow} from "contracts/interfaces/ISignallingEscrow.sol"; +import {IRageQuitEscrow} from "contracts/interfaces/IRageQuitEscrow.sol"; import {Durations} from "contracts/types/Duration.sol"; import {Timestamp, Timestamps} from "contracts/types/Timestamp.sol"; @@ -46,9 +48,10 @@ contract DualGovernanceStateMachineUnitTests is UnitTest { DualGovernanceStateMachine.Context private _stateMachine; function setUp() external { - _stateMachine.initialize(_CONFIG_PROVIDER, IEscrow(_ESCROW_MASTER_COPY_MOCK)); + _stateMachine.initialize(_CONFIG_PROVIDER, IEscrowBase(_ESCROW_MASTER_COPY_MOCK)); _mockRageQuitFinalized(false); _mockRageQuitSupport(PercentsD16.from(0)); + _mockEscrowMasterCopy(); } function test_initialize_RevertOn_ReInitialization() external { @@ -448,20 +451,32 @@ contract DualGovernanceStateMachineUnitTests is UnitTest { // Test helper methods // --- + function _mockEscrowMasterCopy() internal { + vm.mockCall( + _ESCROW_MASTER_COPY_MOCK, + abi.encodeWithSelector(IEscrowBase.ESCROW_MASTER_COPY.selector), + abi.encode(_ESCROW_MASTER_COPY_MOCK) + ); + } + function _mockRageQuitSupport(PercentD16 rageQuitSupport) internal { vm.mockCall( - _ESCROW_MASTER_COPY_MOCK, abi.encodeCall(IEscrow.getRageQuitSupport, ()), abi.encode(rageQuitSupport) + _ESCROW_MASTER_COPY_MOCK, + abi.encodeCall(ISignallingEscrow.getRageQuitSupport, ()), + abi.encode(rageQuitSupport) ); } function _mockRageQuitFinalized(bool isRageQuitFinalized) internal { vm.mockCall( - _ESCROW_MASTER_COPY_MOCK, abi.encodeCall(IEscrow.isRageQuitFinalized, ()), abi.encode(isRageQuitFinalized) + _ESCROW_MASTER_COPY_MOCK, + abi.encodeCall(IRageQuitEscrow.isRageQuitFinalized, ()), + abi.encode(isRageQuitFinalized) ); } function _activateNextState() internal { - _stateMachine.activateNextState(IEscrow(_ESCROW_MASTER_COPY_MOCK)); + _stateMachine.activateNextState(); } function _assertState(State persisted, State effective) internal { @@ -509,6 +524,6 @@ contract DualGovernanceStateMachineUnitTests is UnitTest { } function external__initialize() external { - _stateMachine.initialize(_CONFIG_PROVIDER, IEscrow(_ESCROW_MASTER_COPY_MOCK)); + _stateMachine.initialize(_CONFIG_PROVIDER, IEscrowBase(_ESCROW_MASTER_COPY_MOCK)); } } diff --git a/test/unit/libraries/DualGovernanceStateTransitions.t.sol b/test/unit/libraries/DualGovernanceStateTransitions.t.sol index f9798105..9c3abf47 100644 --- a/test/unit/libraries/DualGovernanceStateTransitions.t.sol +++ b/test/unit/libraries/DualGovernanceStateTransitions.t.sol @@ -5,7 +5,9 @@ import {Duration, Durations} from "contracts/types/Duration.sol"; import {Timestamp, Timestamps} from "contracts/types/Timestamp.sol"; import {PercentD16, PercentsD16} from "contracts/types/PercentD16.sol"; -import {IEscrow} from "contracts/interfaces/IEscrow.sol"; +import {IEscrowBase} from "contracts/interfaces/IEscrowBase.sol"; +import {ISignallingEscrow} from "contracts/interfaces/ISignallingEscrow.sol"; +import {IRageQuitEscrow} from "contracts/interfaces/IRageQuitEscrow.sol"; import { State, @@ -50,7 +52,7 @@ contract DualGovernanceStateTransitionsUnitTestSuite is UnitTest { rageQuitEthWithdrawalsDelayGrowth: Durations.from(15 days) }) ); - DualGovernanceStateMachine.initialize(_stateMachine, _configProvider, IEscrow(_escrowMasterCopyMock)); + DualGovernanceStateMachine.initialize(_stateMachine, _configProvider, IEscrowBase(_escrowMasterCopyMock)); _setMockRageQuitSupportInBP(0); } @@ -353,13 +355,13 @@ contract DualGovernanceStateTransitionsUnitTestSuite is UnitTest { function _setupRageQuitState() internal { _stateMachine.state = State.RageQuit; _stateMachine.enteredAt = Timestamps.now(); - _stateMachine.rageQuitEscrow = IEscrow(_escrowMasterCopyMock); + _stateMachine.rageQuitEscrow = IRageQuitEscrow(_escrowMasterCopyMock); } function _setMockRageQuitSupportInBP(uint256 bpValue) internal { vm.mockCall( address(_stateMachine.signallingEscrow), - abi.encodeWithSelector(IEscrow.getRageQuitSupport.selector), + abi.encodeWithSelector(ISignallingEscrow.getRageQuitSupport.selector), abi.encode(PercentsD16.fromBasisPoints(bpValue)) ); } @@ -367,7 +369,7 @@ contract DualGovernanceStateTransitionsUnitTestSuite is UnitTest { function _setMockIsRageQuitFinalized(bool isRageQuitFinalized) internal { vm.mockCall( address(_stateMachine.rageQuitEscrow), - abi.encodeWithSelector(IEscrow.isRageQuitFinalized.selector), + abi.encodeWithSelector(IRageQuitEscrow.isRageQuitFinalized.selector), abi.encode(isRageQuitFinalized) ); } diff --git a/test/utils/scenario-test-blueprint.sol b/test/utils/scenario-test-blueprint.sol index 864cf88c..f6385c4c 100644 --- a/test/utils/scenario-test-blueprint.sol +++ b/test/utils/scenario-test-blueprint.sol @@ -12,7 +12,7 @@ import {PercentD16} from "contracts/types/PercentD16.sol"; import {Duration, Durations} from "contracts/types/Duration.sol"; import {Timestamp, Timestamps} from "contracts/types/Timestamp.sol"; -import {IEscrow} from "contracts/interfaces/IEscrow.sol"; +import {ISignallingEscrow} from "contracts/interfaces/ISignallingEscrow.sol"; import {Escrow} from "contracts/Escrow.sol"; // --- @@ -199,7 +199,7 @@ contract ScenarioTestBlueprint is TestingAssertEqExtender, SetupDeployment { function _unlockWstETH(address vetoer) internal { Escrow escrow = _getVetoSignallingEscrow(); uint256 wstETHBalanceBefore = _lido.wstETH.balanceOf(vetoer); - IEscrow.VetoerState memory vetoerStateBefore = escrow.getVetoerState(vetoer); + ISignallingEscrow.VetoerDetails memory vetoerDetailsBefore = escrow.getVetoerDetails(vetoer); vm.startPrank(vetoer); uint256 wstETHUnlocked = escrow.unlockWstETH(); @@ -207,14 +207,17 @@ contract ScenarioTestBlueprint is TestingAssertEqExtender, SetupDeployment { // 1 wei rounding issue may arise because of the wrapping stETH into wstETH before // sending funds to the user - assertApproxEqAbs(wstETHUnlocked, vetoerStateBefore.stETHLockedShares, 1); - assertApproxEqAbs(_lido.wstETH.balanceOf(vetoer), wstETHBalanceBefore + vetoerStateBefore.stETHLockedShares, 1); + assertApproxEqAbs(wstETHUnlocked, vetoerDetailsBefore.stETHLockedShares.toUint256(), 1); + assertApproxEqAbs( + _lido.wstETH.balanceOf(vetoer), wstETHBalanceBefore + vetoerDetailsBefore.stETHLockedShares.toUint256(), 1 + ); } function _lockUnstETH(address vetoer, uint256[] memory unstETHIds) internal { Escrow escrow = _getVetoSignallingEscrow(); - IEscrow.VetoerState memory vetoerStateBefore = escrow.getVetoerState(vetoer); - IEscrow.LockedAssetsTotals memory lockedAssetsTotalsBefore = escrow.getLockedAssetsTotals(); + ISignallingEscrow.VetoerDetails memory vetoerDetailsBefore = escrow.getVetoerDetails(vetoer); + ISignallingEscrow.SignallingEscrowDetails memory signallingEscrowDetailsBefore = + escrow.getSignallingEscrowDetails(); uint256 unstETHTotalSharesLocked = 0; IWithdrawalQueue.WithdrawalRequestStatus[] memory statuses = @@ -233,20 +236,22 @@ contract ScenarioTestBlueprint is TestingAssertEqExtender, SetupDeployment { assertEq(_lido.withdrawalQueue.ownerOf(unstETHIds[i]), address(escrow)); } - IEscrow.VetoerState memory vetoerStateAfter = escrow.getVetoerState(vetoer); - assertEq(vetoerStateAfter.unstETHIdsCount, vetoerStateBefore.unstETHIdsCount + unstETHIds.length); + ISignallingEscrow.VetoerDetails memory vetoerDetailsAfter = escrow.getVetoerDetails(vetoer); + assertEq(vetoerDetailsAfter.unstETHIdsCount, vetoerDetailsBefore.unstETHIdsCount + unstETHIds.length); - IEscrow.LockedAssetsTotals memory lockedAssetsTotalsAfter = escrow.getLockedAssetsTotals(); + ISignallingEscrow.SignallingEscrowDetails memory signallingEscrowDetailsAfter = + escrow.getSignallingEscrowDetails(); assertEq( - lockedAssetsTotalsAfter.unstETHUnfinalizedShares, - lockedAssetsTotalsBefore.unstETHUnfinalizedShares + unstETHTotalSharesLocked + signallingEscrowDetailsAfter.totalUnstETHUnfinalizedShares.toUint256(), + signallingEscrowDetailsBefore.totalUnstETHUnfinalizedShares.toUint256() + unstETHTotalSharesLocked ); } function _unlockUnstETH(address vetoer, uint256[] memory unstETHIds) internal { Escrow escrow = _getVetoSignallingEscrow(); - IEscrow.VetoerState memory vetoerStateBefore = escrow.getVetoerState(vetoer); - IEscrow.LockedAssetsTotals memory lockedAssetsTotalsBefore = escrow.getLockedAssetsTotals(); + ISignallingEscrow.VetoerDetails memory vetoerDetailsBefore = escrow.getVetoerDetails(vetoer); + ISignallingEscrow.SignallingEscrowDetails memory signallingEscrowDetailsBefore = + escrow.getSignallingEscrowDetails(); uint256 unstETHTotalSharesUnlocked = 0; IWithdrawalQueue.WithdrawalRequestStatus[] memory statuses = @@ -263,14 +268,15 @@ contract ScenarioTestBlueprint is TestingAssertEqExtender, SetupDeployment { assertEq(_lido.withdrawalQueue.ownerOf(unstETHIds[i]), vetoer); } - IEscrow.VetoerState memory vetoerStateAfter = escrow.getVetoerState(vetoer); - assertEq(vetoerStateAfter.unstETHIdsCount, vetoerStateBefore.unstETHIdsCount - unstETHIds.length); + ISignallingEscrow.VetoerDetails memory vetoerDetailsAfter = escrow.getVetoerDetails(vetoer); + assertEq(vetoerDetailsAfter.unstETHIdsCount, vetoerDetailsBefore.unstETHIdsCount - unstETHIds.length); // TODO: implement correct assert. It must consider was unstETH finalized or not - IEscrow.LockedAssetsTotals memory lockedAssetsTotalsAfter = escrow.getLockedAssetsTotals(); + ISignallingEscrow.SignallingEscrowDetails memory signallingEscrowDetailsAfter = + escrow.getSignallingEscrowDetails(); assertEq( - lockedAssetsTotalsAfter.unstETHUnfinalizedShares, - lockedAssetsTotalsBefore.unstETHUnfinalizedShares - unstETHTotalSharesUnlocked + signallingEscrowDetailsAfter.totalUnstETHUnfinalizedShares.toUint256(), + signallingEscrowDetailsBefore.totalUnstETHUnfinalizedShares.toUint256() - unstETHTotalSharesUnlocked ); }