Skip to content

Commit

Permalink
Escrow interface refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
Psirex committed Nov 28, 2024
1 parent 08e622a commit 0f7489e
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 119 deletions.
28 changes: 12 additions & 16 deletions contracts/DualGovernance.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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/IEscrow.sol";
import {ITimelock} from "./interfaces/ITimelock.sol";
import {ITiebreaker} from "./interfaces/ITiebreaker.sol";
import {IDualGovernance} from "./interfaces/IDualGovernance.sol";
Expand Down Expand Up @@ -51,7 +51,7 @@ contract DualGovernance is IDualGovernance {

event CancelAllPendingProposalsSkipped();
event CancelAllPendingProposalsExecuted();
event EscrowMasterCopyDeployed(IEscrow escrowMasterCopy);
event EscrowMasterCopyDeployed(IEscrowBase escrowMasterCopy);
event ResealCommitteeSet(address resealCommittee);

// ---
Expand Down Expand Up @@ -114,10 +114,6 @@ contract DualGovernance is IDualGovernance {
/// @notice The address of the Reseal Manager.
IResealManager public immutable RESEAL_MANAGER;

/// @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
// ---
Expand Down Expand Up @@ -151,17 +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);
emit EscrowMasterCopyDeployed(escrowMasterCopy);

_stateMachine.initialize(dependencies.configProvider, ESCROW_MASTER_COPY);
_stateMachine.initialize(dependencies.configProvider, escrowMasterCopy);
}

// ---
Expand All @@ -183,7 +179,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();
}
Expand All @@ -198,7 +194,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 timelock contract.
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);
Expand All @@ -214,7 +210,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();

Proposers.Proposer memory proposer = _proposers.getProposer(msg.sender);
if (proposer.executor != TIMELOCK.getAdminExecutor()) {
Expand Down Expand Up @@ -283,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.
Expand Down Expand Up @@ -443,7 +439,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);
RESEAL_MANAGER.resume(sealable);
}
Expand All @@ -453,7 +449,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);
}
Expand All @@ -478,7 +474,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 (msg.sender != _resealCommittee) {
revert CallerIsNotResealCommittee(msg.sender);
}
Expand Down
86 changes: 51 additions & 35 deletions contracts/Escrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ 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, ISignallingEscrow, IRageQuitEscrow} from "./interfaces/IEscrow.sol";
import {IStETH} from "./interfaces/IStETH.sol";
import {IWstETH} from "./interfaces/IWstETH.sol";
import {IWithdrawalQueue, WithdrawalRequestStatus} 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";

Expand All @@ -31,22 +31,10 @@ struct LockedAssetsTotals {
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;
}

/// @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;
Expand Down Expand Up @@ -101,7 +89,7 @@ contract Escrow is IEscrow {

/// @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;
IEscrowBase public immutable ESCROW_MASTER_COPY;

/// @dev The address of the Dual Governance contract.
IDualGovernance public immutable DUAL_GOVERNANCE;
Expand Down Expand Up @@ -130,7 +118,7 @@ contract Escrow is IEscrow {
IDualGovernance dualGovernance,
uint256 minWithdrawalsBatchSize
) {
_SELF = address(this);
ESCROW_MASTER_COPY = this;
DUAL_GOVERNANCE = dualGovernance;

ST_ETH = stETH;
Expand All @@ -144,7 +132,7 @@ contract Escrow is IEscrow {
/// @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();
Expand All @@ -155,6 +143,13 @@ contract Escrow is IEscrow {
ST_ETH.approve(address(WITHDRAWAL_QUEUE), type(uint256).max);
}

// ---
// Base Escrow Getters
// ---
function getEscrowState() external view returns (State) {
return _escrowState.state;
}

// ---
// Lock & Unlock stETH
// ---
Expand Down Expand Up @@ -322,12 +317,45 @@ contract Escrow is IEscrow {
/// have sufficient time to claim it.
/// @param rageQuitEthWithdrawalsDelay The waiting period that vetoers must observe after the Rage Quit process
/// is finalized before they can withdraw ETH from the Escrow.
function startRageQuit(Duration rageQuitExtensionPeriodDuration, Duration rageQuitEthWithdrawalsDelay) external {
function startRageQuit(
Duration rageQuitExtensionPeriodDuration,
Duration rageQuitEthWithdrawalsDelay
) external returns (IRageQuitEscrow rageQuitEscrow) {
_checkCallerIsDualGovernance();
_escrowState.startRageQuit(rageQuitExtensionPeriodDuration, rageQuitEthWithdrawalsDelay);
_batchesQueue.open(WITHDRAWAL_QUEUE.getLastRequestId());
rageQuitEscrow = IRageQuitEscrow(this);
}

// ---
// 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
});
}

function getMinAssetsLockDuration() external view returns (Duration minAssetsLockDuration) {
minAssetsLockDuration = _escrowState.minAssetsLockDuration;
}

// TODO: implement
function getLockedUnstETHState(uint256 unstETHId) external view returns (LockedUnstETHState memory) {}

// TODO: implement
function getSignallingEscrowState() external view returns (SignallingEscrowState memory) {}

// ---
// Request Withdrawal Batches
// ---
Expand Down Expand Up @@ -505,9 +533,12 @@ contract Escrow is IEscrow {
}

// ---
// Getters
// Rage Quit Escrow Getters
// ---

// TODO: implement
function getRageQuitEscrowState() external view returns (RageQuitEscrowState memory) {}

/// @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.
Expand Down Expand Up @@ -573,21 +604,6 @@ contract Escrow is IEscrow {
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 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) {
Expand Down
91 changes: 87 additions & 4 deletions contracts/interfaces/IEscrow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,97 @@
pragma solidity 0.8.26;

import {Duration} from "../types/Duration.sol";
import {Timestamp} from "../types/Timestamp.sol";
import {PercentD16} from "../types/PercentD16.sol";

interface IEscrow {
import {ETHValue} from "../types/ETHValue.sol";
import {SharesValue} from "../types/SharesValue.sol";

import {State as EscrowState} from "../libraries/EscrowState.sol";
import {UnstETHRecordStatus} from "../libraries/AssetsAccounting.sol";

interface IEscrowBase {
struct VetoerState {
uint256 stETHLockedShares;
uint256 unstETHLockedShares;
uint256 unstETHIdsCount;
uint256 lastAssetsLockTimestamp;
}

// TODO: add standalone getter
function ESCROW_MASTER_COPY() external view returns (IEscrowBase);

function initialize(Duration minAssetsLockDuration) external;

function startRageQuit(Duration rageQuitExtensionPeriodDuration, Duration rageQuitEthWithdrawalsDelay) external;
function getEscrowState() external view returns (EscrowState);
function getVetoerState(address vetoer) external view returns (VetoerState memory);
}

interface ISignallingEscrow is IEscrowBase {
struct LockedUnstETHState {
UnstETHRecordStatus status;
address lockedBy;
SharesValue shares;
ETHValue claimableAmount;
}

struct SignallingEscrowState {
PercentD16 rageQuitSupport;
//
ETHValue totalStETHClaimedETH;
SharesValue totalStETHLockedShares;
//
ETHValue totalUnstETHFinalizedETH;
SharesValue totalUnstETHUnfinalizedShares;
}

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 returns (IRageQuitEscrow);

function isRageQuitFinalized() external view returns (bool);
function getRageQuitSupport() external view returns (PercentD16 rageQuitSupport);
function setMinAssetsLockDuration(Duration newMinAssetsLockDuration) external;

function getRageQuitSupport() external view returns (PercentD16);
function getMinAssetsLockDuration() external view returns (Duration);
function getLockedUnstETHState(uint256 unstETHId) external view returns (LockedUnstETHState memory);
function getSignallingEscrowState() external view returns (SignallingEscrowState memory);
}

interface IRageQuitEscrow is IEscrowBase {
struct RageQuitEscrowState {
bool isRageQuitFinalized;
bool isWithdrawalsBatchesClosed;
bool isRageQuitExtensionPeriodStarted;
Duration rageQuitExtensionPeriodDuration;
Duration rageQuitWithdrawalsDelay;
uint256 unclaimedUnstETHIdsCount;
Timestamp rageQuitExtensionPeriodStartedAt;
}

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 getRageQuitEscrowState() external view returns (RageQuitEscrowState memory);
function getNextWithdrawalBatch(uint256 limit) external view returns (uint256[] memory unstETHIds);
}
Loading

0 comments on commit 0f7489e

Please sign in to comment.