From 3daf7762a467f1813c2fce75379d967236bc73f6 Mon Sep 17 00:00:00 2001 From: Sergii Liakh Date: Tue, 9 Dec 2025 10:44:10 -0300 Subject: [PATCH 1/9] feat: fixed withdrawal vault --- src/contracts/vault/Vault.sol | 124 ++++++++++++------------- src/contracts/vault/VaultStorage.sol | 45 +++++++-- src/interfaces/vault/IVault.sol | 27 +++--- src/interfaces/vault/IVaultStorage.sol | 43 +++++---- 4 files changed, 137 insertions(+), 102 deletions(-) diff --git a/src/contracts/vault/Vault.sol b/src/contracts/vault/Vault.sol index 845f6753..a698d0a4 100644 --- a/src/contracts/vault/Vault.sol +++ b/src/contracts/vault/Vault.sol @@ -20,6 +20,7 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVault { using Checkpoints for Checkpoints.Trace256; + using Checkpoints for Checkpoints.Trace208; using Math for uint256; using SafeCast for uint256; using SafeERC20 for IERC20; @@ -41,7 +42,7 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau */ function totalStake() public view returns (uint256) { uint256 epoch = currentEpoch(); - return activeStake() + withdrawals[epoch] + withdrawals[epoch + 1]; + return activeStake() + _withdrawals[epoch] + _withdrawals[epoch + 1]; } /** @@ -69,17 +70,29 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau /** * @inheritdoc IVault */ - function withdrawalsOf(uint256 epoch, address account) public view returns (uint256) { - return - ERC4626Math.previewRedeem(withdrawalSharesOf[epoch][account], withdrawals[epoch], withdrawalShares[epoch]); + function withdrawalsOf(uint256 index, address account) public view returns (uint256) { + Withdrawal memory withdrawal = _withdrawalsOf[account][index]; + uint256 bucketIndex = timeToBucket.upperLookupRecent(withdrawal.unlockAt); + return ERC4626Math.previewRedeem(withdrawal.shares, _withdrawals[bucketIndex], _withdrawalShares[bucketIndex]); + } /** * @inheritdoc IVault */ function slashableBalanceOf(address account) external view returns (uint256) { - uint256 epoch = currentEpoch(); - return activeBalanceOf(account) + withdrawalsOf(epoch, account) + withdrawalsOf(epoch + 1, account); + uint256 amount; + Withdrawal[] storage withdrawals_ = _withdrawalsOf[account]; + for (uint256 i = withdrawals_.length - 1; i > 0; --i) { + Withdrawal memory withdrawal = withdrawals_[i]; + if(withdrawal.unlockAt <= block.timestamp) { + break; + } + uint256 bucketIndex = timeToBucket.upperLookupRecent(withdrawal.unlockAt); + amount += ERC4626Math.previewRedeem(withdrawal.shares, _withdrawals[bucketIndex], _withdrawalShares[bucketIndex]); + } + + return activeBalanceOf(account) + amount; } /** @@ -176,38 +189,38 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau /** * @inheritdoc IVault */ - function claim(address recipient, uint256 epoch) external nonReentrant returns (uint256 amount) { + function claim(address recipient, uint256 index) external nonReentrant returns (uint256 amount) { if (recipient == address(0)) { revert InvalidRecipient(); } - amount = _claim(epoch); + amount = _claim(index); IERC20(collateral).safeTransfer(recipient, amount); - emit Claim(msg.sender, recipient, epoch, amount); + emit Claim(msg.sender, recipient, index, amount); } /** * @inheritdoc IVault */ - function claimBatch(address recipient, uint256[] calldata epochs) external nonReentrant returns (uint256 amount) { + function claimBatch(address recipient, uint256[] calldata indexes) external nonReentrant returns (uint256 amount) { if (recipient == address(0)) { revert InvalidRecipient(); } - uint256 length = epochs.length; + uint256 length = indexes.length; if (length == 0) { revert InvalidLengthEpochs(); } for (uint256 i; i < length; ++i) { - amount += _claim(epochs[i]); + amount += _claim(indexes[i]); } IERC20(collateral).safeTransfer(recipient, amount); - emit ClaimBatch(msg.sender, recipient, epochs, amount); + emit ClaimBatch(msg.sender, recipient, indexes, amount); } /** @@ -218,47 +231,32 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau revert NotSlasher(); } - uint256 currentEpoch_ = currentEpoch(); - uint256 captureEpoch = epochAt(captureTimestamp); - if ((currentEpoch_ > 0 && captureEpoch < currentEpoch_ - 1) || captureEpoch > currentEpoch_) { + if (captureTimestamp < uint48(block.timestamp) + epochDuration || captureTimestamp >= uint48(block.timestamp)) { revert InvalidCaptureEpoch(); } - uint256 activeStake_ = activeStake(); - uint256 nextWithdrawals = withdrawals[currentEpoch_ + 1]; - if (captureEpoch == currentEpoch_) { - uint256 slashableStake = activeStake_ + nextWithdrawals; - slashedAmount = Math.min(amount, slashableStake); - if (slashedAmount > 0) { - uint256 activeSlashed = slashedAmount.mulDiv(activeStake_, slashableStake); - uint256 nextWithdrawalsSlashed = slashedAmount - activeSlashed; - - _activeStake.push(Time.timestamp(), activeStake_ - activeSlashed); - withdrawals[captureEpoch + 1] = nextWithdrawals - nextWithdrawalsSlashed; - } - } else { - uint256 withdrawals_ = withdrawals[currentEpoch_]; - uint256 slashableStake = activeStake_ + withdrawals_ + nextWithdrawals; - slashedAmount = Math.min(amount, slashableStake); - if (slashedAmount > 0) { - uint256 activeSlashed = slashedAmount.mulDiv(activeStake_, slashableStake); - uint256 nextWithdrawalsSlashed = slashedAmount.mulDiv(nextWithdrawals, slashableStake); - uint256 withdrawalsSlashed = slashedAmount - activeSlashed - nextWithdrawalsSlashed; - - if (withdrawals_ < withdrawalsSlashed) { - nextWithdrawalsSlashed += withdrawalsSlashed - withdrawals_; - withdrawalsSlashed = withdrawals_; - } + uint256 unmaturedWithdrawals = withdrawalsPrefixes.latest() - withdrawalsPrefixes.upperLookupRecent(uint48(block.timestamp)); + uint256 unmaturedWithdrawalShares = withdrawalSharesPrefixes.latest() - withdrawalSharesPrefixes.upperLookupRecent(uint48(block.timestamp)); + uint208 lastBucket = timeToBucket.latest(); + _withdrawals[lastBucket] -= unmaturedWithdrawals; + _withdrawalShares[lastBucket] -= unmaturedWithdrawalShares; + _withdrawalShares[lastBucket + 1] = unmaturedWithdrawalShares; + timeToBucket.push(uint48(block.timestamp), lastBucket + 1); - _activeStake.push(Time.timestamp(), activeStake_ - activeSlashed); - withdrawals[currentEpoch_ + 1] = nextWithdrawals - nextWithdrawalsSlashed; - withdrawals[currentEpoch_] = withdrawals_ - withdrawalsSlashed; - } - } + uint256 activeStake_ = activeStake(); + uint256 slashableStake = activeStake_ + unmaturedWithdrawals; + slashedAmount = Math.min(amount, slashableStake); if (slashedAmount > 0) { + uint256 activeSlashed = slashedAmount.mulDiv(activeStake_, slashableStake); + uint256 withdrawalsSlashed = slashedAmount - activeSlashed; + + _activeStake.push(uint48(block.timestamp), activeStake_ - activeSlashed); + unmaturedWithdrawals -= withdrawalsSlashed; + IERC20(collateral).safeTransfer(burner, slashedAmount); } + _withdrawals[lastBucket + 1] = unmaturedWithdrawals; emit OnSlash(amount, captureTimestamp, slashedAmount); } @@ -374,35 +372,35 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau _activeShares.push(Time.timestamp(), activeShares() - burnedShares); _activeStake.push(Time.timestamp(), activeStake() - withdrawnAssets); - uint256 epoch = currentEpoch() + 1; - uint256 withdrawals_ = withdrawals[epoch]; - uint256 withdrawalsShares_ = withdrawalShares[epoch]; + uint256 lastBucket = timeToBucket.latest(); + mintedShares = ERC4626Math.previewDeposit(withdrawnAssets, _withdrawalShares[lastBucket], _withdrawals[lastBucket]); + _withdrawals[lastBucket] += withdrawnAssets; + _withdrawalShares[lastBucket] += mintedShares; - mintedShares = ERC4626Math.previewDeposit(withdrawnAssets, withdrawalsShares_, withdrawals_); - - withdrawals[epoch] = withdrawals_ + withdrawnAssets; - withdrawalShares[epoch] = withdrawalsShares_ + mintedShares; - withdrawalSharesOf[epoch][claimer] += mintedShares; + uint48 unlockAt = uint48(block.timestamp) + epochDuration; + _withdrawalsOf[msg.sender].push(Withdrawal(false, unlockAt, mintedShares)); + withdrawalsPrefixes.push(unlockAt, withdrawalsPrefixes.latest() + withdrawnAssets); + withdrawalSharesPrefixes.push(unlockAt, withdrawalSharesPrefixes.latest() + mintedShares); emit Withdraw(msg.sender, claimer, withdrawnAssets, burnedShares, mintedShares); } - function _claim(uint256 epoch) internal returns (uint256 amount) { - if (epoch >= currentEpoch()) { + function _claim(uint256 index) internal returns (uint256 amount) { + if (index >= _withdrawalsOf[msg.sender].length) { revert InvalidEpoch(); } + Withdrawal memory withdrawal = _withdrawalsOf[msg.sender][index]; - if (isWithdrawalsClaimed[epoch][msg.sender]) { + if (withdrawal.claimed) { revert AlreadyClaimed(); } - amount = withdrawalsOf(epoch, msg.sender); - - if (amount == 0) { - revert InsufficientClaim(); + if (withdrawal.unlockAt <= block.timestamp) { + revert WithdrawalNotMatured(); } - - isWithdrawalsClaimed[epoch][msg.sender] = true; + uint256 bucketIndex = timeToBucket.upperLookupRecent(withdrawal.unlockAt); + _withdrawalsOf[msg.sender][index].claimed = true; + amount = ERC4626Math.previewRedeem(withdrawal.shares, _withdrawals[bucketIndex], _withdrawalShares[bucketIndex]); } function _initialize(uint64, address, bytes memory data) internal virtual override { diff --git a/src/contracts/vault/VaultStorage.sol b/src/contracts/vault/VaultStorage.sol index 2446ed02..8a3d6cb4 100644 --- a/src/contracts/vault/VaultStorage.sol +++ b/src/contracts/vault/VaultStorage.sol @@ -12,6 +12,7 @@ import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; abstract contract VaultStorage is StaticDelegateCallable, IVaultStorage { using Checkpoints for Checkpoints.Trace256; + using Checkpoints for Checkpoints.Trace208; using SafeCast for uint256; /** @@ -105,24 +106,24 @@ abstract contract VaultStorage is StaticDelegateCallable, IVaultStorage { mapping(address account => bool value) public isDepositorWhitelisted; /** - * @inheritdoc IVaultStorage + * @dev DEPRECATED: This variable is kept for storage layout compatibility with previous versions. */ - mapping(uint256 epoch => uint256 amount) public withdrawals; + mapping(uint256 epoch => uint256 amount) internal _epochWithdrawals; /** - * @inheritdoc IVaultStorage + * @dev DEPRECATED: This variable is kept for storage layout compatibility with previous versions. */ - mapping(uint256 epoch => uint256 amount) public withdrawalShares; + mapping(uint256 epoch => uint256 amount) internal _epochWithdrawalShares; /** - * @inheritdoc IVaultStorage + * @dev DEPRECATED: This variable is kept for storage layout compatibility with previous versions. */ - mapping(uint256 epoch => mapping(address account => uint256 amount)) public withdrawalSharesOf; + mapping(uint256 epoch => mapping(address account => uint256 amount)) internal _epochWithdrawalSharesOf; /** - * @inheritdoc IVaultStorage + * @dev DEPRECATED: This variable is kept for storage layout compatibility with previous versions. */ - mapping(uint256 epoch => mapping(address account => bool value)) public isWithdrawalsClaimed; + mapping(uint256 epoch => mapping(address account => bool value)) internal _isEpochWithdrawalsClaimed; Checkpoints.Trace256 internal _activeShares; @@ -130,6 +131,14 @@ abstract contract VaultStorage is StaticDelegateCallable, IVaultStorage { mapping(address account => Checkpoints.Trace256 shares) internal _activeSharesOf; + mapping(address account => Withdrawal[] withdrawals) internal _withdrawalsOf; + mapping(uint256 => uint256) internal _withdrawals; + mapping(uint256 => uint256) internal _withdrawalShares; + + Checkpoints.Trace208 public timeToBucket; + Checkpoints.Trace256 public withdrawalsPrefixes; + Checkpoints.Trace256 public withdrawalSharesPrefixes; + constructor(address delegatorFactory, address slasherFactory) { DELEGATOR_FACTORY = delegatorFactory; SLASHER_FACTORY = slasherFactory; @@ -142,14 +151,14 @@ abstract contract VaultStorage is StaticDelegateCallable, IVaultStorage { if (timestamp < epochDurationInit) { revert InvalidTimestamp(); } - return (timestamp - epochDurationInit) / epochDuration; + return timeToBucket.upperLookupRecent(timestamp); } /** * @inheritdoc IVaultStorage */ function currentEpoch() public view returns (uint256) { - return (Time.timestamp() - epochDurationInit) / epochDuration; + return timeToBucket.upperLookupRecent(uint48(block.timestamp)); } /** @@ -219,5 +228,21 @@ abstract contract VaultStorage is StaticDelegateCallable, IVaultStorage { return _activeSharesOf[account].latest(); } + function isWithdrawalsClaimed(uint256 index, address account) external view returns (bool) { + return _withdrawalsOf[account][index].claimed; + } + + function withdrawalShares(uint256 index) external view returns (uint256) { + return _withdrawalShares[index]; + } + + function withdrawalSharesOf(uint256 index, address account) external view returns (uint256) { + return _withdrawalsOf[account][index].shares; + } + + function withdrawals(uint256 index) external view returns (uint256) { + return _withdrawals[index]; + } + uint256[50] private __gap; } diff --git a/src/interfaces/vault/IVault.sol b/src/interfaces/vault/IVault.sol index b7c96e3f..be01eeee 100644 --- a/src/interfaces/vault/IVault.sol +++ b/src/interfaces/vault/IVault.sol @@ -31,6 +31,7 @@ interface IVault is IMigratableEntity, IVaultStorage { error SlasherAlreadyInitialized(); error TooMuchRedeem(); error TooMuchWithdraw(); + error WithdrawalNotMatured(); /** * @notice Initial parameters needed for a vault deployment. @@ -97,19 +98,19 @@ interface IVault is IMigratableEntity, IVaultStorage { * @notice Emitted when a claim is made. * @param claimer account that claimed * @param recipient account that received the collateral - * @param epoch epoch the collateral was claimed for + * @param index index the collateral was claimed for * @param amount amount of the collateral claimed */ - event Claim(address indexed claimer, address indexed recipient, uint256 epoch, uint256 amount); + event Claim(address indexed claimer, address indexed recipient, uint256 index, uint256 amount); /** * @notice Emitted when a batch claim is made. * @param claimer account that claimed * @param recipient account that received the collateral - * @param epochs epochs the collateral was claimed for + * @param indexes indexes the collateral was claimed for * @param amount amount of the collateral claimed */ - event ClaimBatch(address indexed claimer, address indexed recipient, uint256[] epochs, uint256 amount); + event ClaimBatch(address indexed claimer, address indexed recipient, uint256[] indexes, uint256 amount); /** * @notice Emitted when a slash happens. @@ -187,12 +188,12 @@ interface IVault is IMigratableEntity, IVaultStorage { function activeBalanceOf(address account) external view returns (uint256); /** - * @notice Get withdrawals for a particular account at a given epoch (zero if claimed). - * @param epoch epoch to get the withdrawals for the account at + * @notice Get withdrawals for a particular account at a given index (zero if claimed). + * @param index index to get the withdrawals for the account at * @param account account to get the withdrawals for - * @return withdrawals for the account at the epoch + * @return withdrawals for the account at the index */ - function withdrawalsOf(uint256 epoch, address account) external view returns (uint256); + function withdrawalsOf(uint256 index, address account) external view returns (uint256); /** * @notice Get a total amount of the collateral that can be slashed for a given account. @@ -233,18 +234,18 @@ interface IVault is IMigratableEntity, IVaultStorage { /** * @notice Claim collateral from the vault. * @param recipient account that receives the collateral - * @param epoch epoch to claim the collateral for + * @param index index to claim the collateral for * @return amount amount of the collateral claimed */ - function claim(address recipient, uint256 epoch) external returns (uint256 amount); + function claim(address recipient, uint256 index) external returns (uint256 amount); /** - * @notice Claim collateral from the vault for multiple epochs. + * @notice Claim collateral from the vault for multiple indexes. * @param recipient account that receives the collateral - * @param epochs epochs to claim the collateral for + * @param indexes indexes to claim the collateral for * @return amount amount of the collateral claimed */ - function claimBatch(address recipient, uint256[] calldata epochs) external returns (uint256 amount); + function claimBatch(address recipient, uint256[] calldata indexes) external returns (uint256 amount); /** * @notice Slash callback for burning collateral. diff --git a/src/interfaces/vault/IVaultStorage.sol b/src/interfaces/vault/IVaultStorage.sol index d009eab9..420e95a1 100644 --- a/src/interfaces/vault/IVaultStorage.sol +++ b/src/interfaces/vault/IVaultStorage.sol @@ -5,6 +5,12 @@ interface IVaultStorage { error InvalidTimestamp(); error NoPreviousEpoch(); + struct Withdrawal { + bool claimed; + uint48 unlockAt; + uint256 shares; + } + /** * @notice Get a deposit whitelist enabler/disabler's role. * @return identifier of the whitelist enabler/disabler role @@ -94,18 +100,21 @@ interface IVaultStorage { * @param timestamp time point to get the epoch at * @return epoch at the timestamp * @dev Reverts if the timestamp is less than the start of the epoch 0. + * @dev DEPRECATED: Epoch-related functionality is deprecated. */ function epochAt(uint48 timestamp) external view returns (uint256); /** * @notice Get a current vault epoch. * @return current epoch + * @dev DEPRECATED: Epoch-related functionality is deprecated. */ function currentEpoch() external view returns (uint256); /** * @notice Get a start of the current vault epoch. * @return start of the current epoch + * @dev DEPRECATED: Epoch-related functionality is deprecated. */ function currentEpochStart() external view returns (uint48); @@ -113,12 +122,14 @@ interface IVaultStorage { * @notice Get a start of the previous vault epoch. * @return start of the previous epoch * @dev Reverts if the current epoch is 0. + * @dev DEPRECATED: Epoch-related functionality is deprecated. */ function previousEpochStart() external view returns (uint48); /** * @notice Get a start of the next vault epoch. * @return start of the next epoch + * @dev DEPRECATED: Epoch-related functionality is deprecated. */ function nextEpochStart() external view returns (uint48); @@ -192,32 +203,32 @@ interface IVaultStorage { function activeSharesOf(address account) external view returns (uint256); /** - * @notice Get a total amount of the withdrawals at a given epoch. - * @param epoch epoch to get the total amount of the withdrawals at - * @return total amount of the withdrawals at the epoch + * @notice Get a total amount of the withdrawals at a given index. + * @param index index to get the total amount of the withdrawals at + * @return total amount of the withdrawals at the index */ - function withdrawals(uint256 epoch) external view returns (uint256); + function withdrawals(uint256 index) external view returns (uint256); /** - * @notice Get a total number of withdrawal shares at a given epoch. - * @param epoch epoch to get the total number of withdrawal shares at - * @return total number of withdrawal shares at the epoch + * @notice Get a total number of withdrawal shares at a given index. + * @param index index to get the total number of withdrawal shares at + * @return total number of withdrawal shares at the index */ - function withdrawalShares(uint256 epoch) external view returns (uint256); + function withdrawalShares(uint256 index) external view returns (uint256); /** - * @notice Get a number of withdrawal shares for a particular account at a given epoch (zero if claimed). - * @param epoch epoch to get the number of withdrawal shares for the account at + * @notice Get a number of withdrawal shares for a particular account at a given index (zero if claimed). + * @param index index to get the number of withdrawal shares for the account at * @param account account to get the number of withdrawal shares for - * @return number of withdrawal shares for the account at the epoch + * @return number of withdrawal shares for the account at the index */ - function withdrawalSharesOf(uint256 epoch, address account) external view returns (uint256); + function withdrawalSharesOf(uint256 index, address account) external view returns (uint256); /** - * @notice Get if the withdrawals are claimed for a particular account at a given epoch. - * @param epoch epoch to check the withdrawals for the account at + * @notice Get if the withdrawals are claimed for a particular account at a given index. + * @param index index to check the withdrawals for the account at * @param account account to check the withdrawals for - * @return if the withdrawals are claimed for the account at the epoch + * @return if the withdrawals are claimed for the account at the index */ - function isWithdrawalsClaimed(uint256 epoch, address account) external view returns (bool); + function isWithdrawalsClaimed(uint256 index, address account) external view returns (bool); } From 08aeaaa23629641603dc80f02b059cc18a75b5a5 Mon Sep 17 00:00:00 2001 From: Sergii Liakh Date: Tue, 9 Dec 2025 17:04:57 -0300 Subject: [PATCH 2/9] test: vault tests plus small fixes --- src/contracts/vault/Vault.sol | 41 +-- src/contracts/vault/VaultStorage.sol | 12 + test/vault/Vault.t.sol | 453 ++++++++++----------------- 3 files changed, 208 insertions(+), 298 deletions(-) diff --git a/src/contracts/vault/Vault.sol b/src/contracts/vault/Vault.sol index a698d0a4..6f0a59a4 100644 --- a/src/contracts/vault/Vault.sol +++ b/src/contracts/vault/Vault.sol @@ -41,8 +41,7 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau * @inheritdoc IVault */ function totalStake() public view returns (uint256) { - uint256 epoch = currentEpoch(); - return activeStake() + _withdrawals[epoch] + _withdrawals[epoch + 1]; + return activeStake() + unmaturedWithdrawals(uint48(block.timestamp)); } /** @@ -74,7 +73,6 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau Withdrawal memory withdrawal = _withdrawalsOf[account][index]; uint256 bucketIndex = timeToBucket.upperLookupRecent(withdrawal.unlockAt); return ERC4626Math.previewRedeem(withdrawal.shares, _withdrawals[bucketIndex], _withdrawalShares[bucketIndex]); - } /** @@ -83,13 +81,19 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau function slashableBalanceOf(address account) external view returns (uint256) { uint256 amount; Withdrawal[] storage withdrawals_ = _withdrawalsOf[account]; - for (uint256 i = withdrawals_.length - 1; i > 0; --i) { + if (withdrawals_.length == 0) { + return activeBalanceOf(account); + } + for (uint256 i = withdrawals_.length; i > 0;) { + --i; Withdrawal memory withdrawal = withdrawals_[i]; - if(withdrawal.unlockAt <= block.timestamp) { + if (withdrawal.unlockAt <= block.timestamp) { break; } uint256 bucketIndex = timeToBucket.upperLookupRecent(withdrawal.unlockAt); - amount += ERC4626Math.previewRedeem(withdrawal.shares, _withdrawals[bucketIndex], _withdrawalShares[bucketIndex]); + amount += ERC4626Math.previewRedeem( + withdrawal.shares, _withdrawals[bucketIndex], _withdrawalShares[bucketIndex] + ); } return activeBalanceOf(account) + amount; @@ -129,9 +133,9 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau mintedShares = ERC4626Math.previewDeposit(depositedAmount, activeShares_, activeStake_); - _activeStake.push(Time.timestamp(), activeStake_ + depositedAmount); - _activeShares.push(Time.timestamp(), activeShares_ + mintedShares); - _activeSharesOf[onBehalfOf].push(Time.timestamp(), activeSharesOf(onBehalfOf) + mintedShares); + _activeStake.push(uint48(block.timestamp), activeStake_ + depositedAmount); + _activeShares.push(uint48(block.timestamp), activeShares_ + mintedShares); + _activeSharesOf[onBehalfOf].push(uint48(block.timestamp), activeSharesOf(onBehalfOf) + mintedShares); emit Deposit(msg.sender, onBehalfOf, depositedAmount, mintedShares); } @@ -231,12 +235,12 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau revert NotSlasher(); } - if (captureTimestamp < uint48(block.timestamp) + epochDuration || captureTimestamp >= uint48(block.timestamp)) { + if (captureTimestamp + epochDuration < uint48(block.timestamp) || captureTimestamp >= uint48(block.timestamp)) { revert InvalidCaptureEpoch(); } - uint256 unmaturedWithdrawals = withdrawalsPrefixes.latest() - withdrawalsPrefixes.upperLookupRecent(uint48(block.timestamp)); - uint256 unmaturedWithdrawalShares = withdrawalSharesPrefixes.latest() - withdrawalSharesPrefixes.upperLookupRecent(uint48(block.timestamp)); + uint256 unmaturedWithdrawals = unmaturedWithdrawals(uint48(block.timestamp)); + uint256 unmaturedWithdrawalShares = unmaturedWithdrawalShares(uint48(block.timestamp)); uint208 lastBucket = timeToBucket.latest(); _withdrawals[lastBucket] -= unmaturedWithdrawals; _withdrawalShares[lastBucket] -= unmaturedWithdrawalShares; @@ -368,12 +372,13 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau virtual returns (uint256 mintedShares) { - _activeSharesOf[msg.sender].push(Time.timestamp(), activeSharesOf(msg.sender) - burnedShares); - _activeShares.push(Time.timestamp(), activeShares() - burnedShares); - _activeStake.push(Time.timestamp(), activeStake() - withdrawnAssets); + _activeSharesOf[msg.sender].push(uint48(block.timestamp), activeSharesOf(msg.sender) - burnedShares); + _activeShares.push(uint48(block.timestamp), activeShares() - burnedShares); + _activeStake.push(uint48(block.timestamp), activeStake() - withdrawnAssets); uint256 lastBucket = timeToBucket.latest(); - mintedShares = ERC4626Math.previewDeposit(withdrawnAssets, _withdrawalShares[lastBucket], _withdrawals[lastBucket]); + mintedShares = + ERC4626Math.previewDeposit(withdrawnAssets, _withdrawalShares[lastBucket], _withdrawals[lastBucket]); _withdrawals[lastBucket] += withdrawnAssets; _withdrawalShares[lastBucket] += mintedShares; @@ -395,7 +400,7 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau revert AlreadyClaimed(); } - if (withdrawal.unlockAt <= block.timestamp) { + if (withdrawal.unlockAt >= block.timestamp) { revert WithdrawalNotMatured(); } uint256 bucketIndex = timeToBucket.upperLookupRecent(withdrawal.unlockAt); @@ -440,7 +445,7 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau burner = params.burner; - epochDurationInit = Time.timestamp(); + epochDurationInit = uint48(block.timestamp); epochDuration = params.epochDuration; depositWhitelist = params.depositWhitelist; diff --git a/src/contracts/vault/VaultStorage.sol b/src/contracts/vault/VaultStorage.sol index 8a3d6cb4..1368e388 100644 --- a/src/contracts/vault/VaultStorage.sol +++ b/src/contracts/vault/VaultStorage.sol @@ -244,5 +244,17 @@ abstract contract VaultStorage is StaticDelegateCallable, IVaultStorage { return _withdrawals[index]; } + function latestWithdrawalBucket() external view returns (uint256) { + return timeToBucket.latest(); + } + + function unmaturedWithdrawals(uint48 timestamp) public view returns (uint256) { + return withdrawalsPrefixes.length() == 1 ? withdrawalsPrefixes.latest() : withdrawalsPrefixes.latest() - withdrawalsPrefixes.upperLookupRecent(timestamp); + } + + function unmaturedWithdrawalShares(uint48 timestamp) public view returns (uint256) { + return withdrawalSharesPrefixes.length() == 1 ? withdrawalSharesPrefixes.latest() : withdrawalSharesPrefixes.latest() - withdrawalSharesPrefixes.upperLookupRecent(timestamp); + } + uint256[50] private __gap; } diff --git a/test/vault/Vault.t.sol b/test/vault/Vault.t.sol index 7d5e9a51..e19bdf96 100644 --- a/test/vault/Vault.t.sol +++ b/test/vault/Vault.t.sol @@ -11,6 +11,7 @@ import {OperatorRegistry} from "../../src/contracts/OperatorRegistry.sol"; import {MetadataService} from "../../src/contracts/service/MetadataService.sol"; import {NetworkMiddlewareService} from "../../src/contracts/service/NetworkMiddlewareService.sol"; import {OptInService} from "../../src/contracts/service/OptInService.sol"; +import {Checkpoints} from "../../src/contracts/libraries/Checkpoints.sol"; import {Vault} from "../../src/contracts/vault/Vault.sol"; import {NetworkRestakeDelegator} from "../../src/contracts/delegator/NetworkRestakeDelegator.sol"; @@ -42,6 +43,7 @@ contract VaultTest is Test { using Math for uint256; using Subnetwork for bytes32; using Subnetwork for address; + using Checkpoints for Checkpoints.Trace208; address owner; address alice; @@ -259,7 +261,6 @@ contract VaultTest is Test { assertEq(vault.activeBalanceOf(alice), 0); assertEq(vault.withdrawals(0), 0); assertEq(vault.withdrawalShares(0), 0); - assertEq(vault.isWithdrawalsClaimed(0, alice), false); assertEq(vault.depositWhitelist(), depositWhitelist); assertEq(vault.isDepositorWhitelisted(alice), false); assertEq(vault.slashableBalanceOf(alice), 0); @@ -267,36 +268,6 @@ contract VaultTest is Test { assertEq(vault.isSlasherInitialized(), true); assertEq(vault.isInitialized(), true); - blockTimestamp = blockTimestamp + vault.epochDuration() - 1; - vm.warp(blockTimestamp); - - assertEq(vault.epochAt(uint48(blockTimestamp)), 0); - assertEq(vault.epochAt(uint48(blockTimestamp + 1)), 1); - assertEq(vault.currentEpoch(), 0); - assertEq(vault.currentEpochStart(), blockTimestamp - (vault.epochDuration() - 1)); - vm.expectRevert(IVaultStorage.NoPreviousEpoch.selector); - vault.previousEpochStart(); - assertEq(vault.nextEpochStart(), blockTimestamp + 1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - assertEq(vault.epochAt(uint48(blockTimestamp)), 1); - assertEq(vault.epochAt(uint48(blockTimestamp + 2 * vault.epochDuration())), 3); - assertEq(vault.currentEpoch(), 1); - assertEq(vault.currentEpochStart(), blockTimestamp); - assertEq(vault.previousEpochStart(), blockTimestamp - vault.epochDuration()); - assertEq(vault.nextEpochStart(), blockTimestamp + vault.epochDuration()); - - blockTimestamp = blockTimestamp + vault.epochDuration() - 1; - vm.warp(blockTimestamp); - - assertEq(vault.epochAt(uint48(blockTimestamp)), 1); - assertEq(vault.epochAt(uint48(blockTimestamp + 1)), 2); - assertEq(vault.currentEpoch(), 1); - assertEq(vault.currentEpochStart(), blockTimestamp - (vault.epochDuration() - 1)); - assertEq(vault.previousEpochStart(), blockTimestamp - (vault.epochDuration() - 1) - vault.epochDuration()); - assertEq(vault.nextEpochStart(), blockTimestamp + 1); } function test_CreateRevertInvalidEpochDuration() public { @@ -1443,15 +1414,10 @@ contract VaultTest is Test { assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), amount1); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 - amount2); assertEq(vault.activeBalanceOf(alice), amount1 - amount2); - assertEq(vault.withdrawals(vault.currentEpoch()), 0); - assertEq(vault.withdrawals(vault.currentEpoch() + 1), amount2); - assertEq(vault.withdrawals(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch()), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 1), mintedShares); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch(), alice), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 1, alice), mintedShares); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 2, alice), 0); + uint256 lastBucket = vault.latestWithdrawalBucket(); + assertEq(vault.withdrawals(lastBucket), amount2); + assertEq(vault.withdrawalShares(lastBucket), mintedShares); + assertEq(vault.withdrawalSharesOf(0, alice), amount2); assertEq(vault.slashableBalanceOf(alice), amount1); shares -= burnedShares; @@ -1465,7 +1431,7 @@ contract VaultTest is Test { assertEq(burnedShares_, burnedShares); assertEq(mintedShares_, mintedShares); - assertEq(vault.totalStake(), amount1); + assertEq(vault.totalStake(), amount1 - amount2); assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares); assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares - burnedShares); assertEq(vault.activeShares(), shares - burnedShares); @@ -1478,26 +1444,10 @@ contract VaultTest is Test { assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), amount1 - amount2); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 - amount2 - amount3); assertEq(vault.activeBalanceOf(alice), amount1 - amount2 - amount3); - assertEq(vault.withdrawals(vault.currentEpoch() - 1), 0); - assertEq(vault.withdrawals(vault.currentEpoch()), amount2); - assertEq(vault.withdrawals(vault.currentEpoch() + 1), amount3); - assertEq(vault.withdrawals(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() - 1), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch()), amount2 * 10 ** 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 1), amount3 * 10 ** 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() - 1, alice), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch(), alice), amount2 * 10 ** 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 1, alice), amount3 * 10 ** 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 2, alice), 0); - assertEq(vault.slashableBalanceOf(alice), amount1); - - shares -= burnedShares; - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - assertEq(vault.totalStake(), amount1 - amount2); + assertEq(vault.withdrawals(lastBucket), amount2 + amount3); + assertEq(vault.withdrawalShares(lastBucket), amount2 + amount3); + assertEq(vault.withdrawalSharesOf(1, alice), amount3); + assertEq(vault.slashableBalanceOf(alice), amount1 - amount2); blockTimestamp = blockTimestamp + 1; vm.warp(blockTimestamp); @@ -1580,15 +1530,10 @@ contract VaultTest is Test { assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), amount1); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 - withdrawnAssets2); assertEq(vault.activeBalanceOf(alice), amount1 - withdrawnAssets2); - assertEq(vault.withdrawals(vault.currentEpoch()), 0); - assertEq(vault.withdrawals(vault.currentEpoch() + 1), withdrawnAssets2); - assertEq(vault.withdrawals(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch()), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 1), mintedShares); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch(), alice), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 1, alice), mintedShares); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 2, alice), 0); + uint256 lastBucket = vault.latestWithdrawalBucket(); + assertEq(vault.withdrawals(lastBucket), withdrawnAssets2); + assertEq(vault.withdrawalShares(lastBucket), mintedShares); + assertEq(vault.withdrawalSharesOf(0, alice), mintedShares); assertEq(vault.slashableBalanceOf(alice), amount1); shares -= amount2; @@ -1602,7 +1547,7 @@ contract VaultTest is Test { assertEq(withdrawnAssets_, withdrawnAssets3); assertEq(mintedShares_, mintedShares); - assertEq(vault.totalStake(), amount1); + assertEq(vault.totalStake(), amount1 - withdrawnAssets2); assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares); assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares - amount3); assertEq(vault.activeShares(), shares - amount3); @@ -1617,26 +1562,10 @@ contract VaultTest is Test { vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 - withdrawnAssets2 - withdrawnAssets3 ); assertEq(vault.activeBalanceOf(alice), amount1 - withdrawnAssets2 - withdrawnAssets3); - assertEq(vault.withdrawals(vault.currentEpoch() - 1), 0); - assertEq(vault.withdrawals(vault.currentEpoch()), withdrawnAssets2); - assertEq(vault.withdrawals(vault.currentEpoch() + 1), withdrawnAssets3); - assertEq(vault.withdrawals(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() - 1), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch()), withdrawnAssets2 * 10 ** 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 1), withdrawnAssets3 * 10 ** 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() - 1, alice), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch(), alice), withdrawnAssets2 * 10 ** 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 1, alice), withdrawnAssets3 * 10 ** 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 2, alice), 0); - assertEq(vault.slashableBalanceOf(alice), amount1); - - shares -= amount3; - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - assertEq(vault.totalStake(), amount1 - withdrawnAssets2); + assertEq(vault.withdrawals(lastBucket), withdrawnAssets2 + withdrawnAssets3); + assertEq(vault.withdrawalShares(lastBucket), withdrawnAssets2 + withdrawnAssets3); + assertEq(vault.withdrawalSharesOf(1, alice), withdrawnAssets3); + assertEq(vault.slashableBalanceOf(alice), amount1 - withdrawnAssets2); blockTimestamp = blockTimestamp + 1; vm.warp(blockTimestamp); @@ -1706,11 +1635,11 @@ contract VaultTest is Test { uint256 tokensBefore = collateral.balanceOf(address(vault)); uint256 tokensBeforeAlice = collateral.balanceOf(alice); - assertEq(_claim(alice, vault.currentEpoch() - 1), amount2); + assertEq(_claim(alice, 0), amount2); assertEq(tokensBefore - collateral.balanceOf(address(vault)), amount2); assertEq(collateral.balanceOf(alice) - tokensBeforeAlice, amount2); - assertEq(vault.isWithdrawalsClaimed(vault.currentEpoch() - 1, alice), true); + assertEq(vault.isWithdrawalsClaimed(0, alice), true); } function test_ClaimRevertInvalidRecipient(uint256 amount1, uint256 amount2) public { @@ -1736,13 +1665,12 @@ contract VaultTest is Test { vm.warp(blockTimestamp); vm.startPrank(alice); - uint256 currentEpoch = vault.currentEpoch(); vm.expectRevert(IVault.InvalidRecipient.selector); - vault.claim(address(0), currentEpoch - 1); + vault.claim(address(0), 0); vm.stopPrank(); } - function test_ClaimRevertInvalidEpoch(uint256 amount1, uint256 amount2) public { + function test_ClaimRevertInvalidIndex(uint256 amount1, uint256 amount2) public { amount1 = bound(amount1, 1, 100 * 10 ** 18); amount2 = bound(amount2, 1, 100 * 10 ** 18); vm.assume(amount1 >= amount2); @@ -1764,9 +1692,8 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + 2; vm.warp(blockTimestamp); - uint256 currentEpoch = vault.currentEpoch(); vm.expectRevert(IVault.InvalidEpoch.selector); - _claim(alice, currentEpoch); + _claim(alice, 10); } function test_ClaimRevertAlreadyClaimed(uint256 amount1, uint256 amount2) public { @@ -1791,14 +1718,13 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + 2; vm.warp(blockTimestamp); - uint256 currentEpoch = vault.currentEpoch(); - _claim(alice, currentEpoch - 1); + _claim(alice, 0); vm.expectRevert(IVault.AlreadyClaimed.selector); - _claim(alice, currentEpoch - 1); + _claim(alice, 0); } - function test_ClaimRevertInsufficientClaim(uint256 amount1, uint256 amount2) public { + function test_ClaimRevertWithdrawalNotMatured(uint256 amount1, uint256 amount2) public { amount1 = bound(amount1, 1, 100 * 10 ** 18); amount2 = bound(amount2, 1, 100 * 10 ** 18); vm.assume(amount1 >= amount2); @@ -1807,7 +1733,7 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + 1_720_700_948; vm.warp(blockTimestamp); - uint48 epochDuration = 1; + uint48 epochDuration = 7; vault = _getVault(epochDuration); _deposit(alice, amount1); @@ -1820,9 +1746,8 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + 2; vm.warp(blockTimestamp); - uint256 currentEpoch = vault.currentEpoch(); - vm.expectRevert(IVault.InsufficientClaim.selector); - _claim(alice, currentEpoch - 2); + vm.expectRevert(IVault.WithdrawalNotMatured.selector); + _claim(alice, 0); } function test_ClaimBatch(uint256 amount1, uint256 amount2, uint256 amount3) public { @@ -1853,17 +1778,17 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + 2; vm.warp(blockTimestamp); - uint256[] memory epochs = new uint256[](2); - epochs[0] = vault.currentEpoch() - 1; - epochs[1] = vault.currentEpoch() - 2; + uint256[] memory indexes = new uint256[](2); + indexes[0] = 0; + indexes[1] = 1; uint256 tokensBefore = collateral.balanceOf(address(vault)); uint256 tokensBeforeAlice = collateral.balanceOf(alice); - assertEq(_claimBatch(alice, epochs), amount2 + amount3); + assertEq(_claimBatch(alice, indexes), amount2 + amount3); assertEq(tokensBefore - collateral.balanceOf(address(vault)), amount2 + amount3); assertEq(collateral.balanceOf(alice) - tokensBeforeAlice, amount2 + amount3); - assertEq(vault.isWithdrawalsClaimed(vault.currentEpoch() - 1, alice), true); + assertEq(vault.isWithdrawalsClaimed(0, alice), true); } function test_ClaimBatchRevertInvalidRecipient(uint256 amount1, uint256 amount2, uint256 amount3) public { @@ -1894,13 +1819,13 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + 2; vm.warp(blockTimestamp); - uint256[] memory epochs = new uint256[](2); - epochs[0] = vault.currentEpoch() - 1; - epochs[1] = vault.currentEpoch() - 2; + uint256[] memory indexes = new uint256[](2); + indexes[0] = 0; + indexes[1] = 1; vm.expectRevert(IVault.InvalidRecipient.selector); vm.startPrank(alice); - vault.claimBatch(address(0), epochs); + vault.claimBatch(address(0), indexes); vm.stopPrank(); } @@ -1932,9 +1857,9 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + 2; vm.warp(blockTimestamp); - uint256[] memory epochs = new uint256[](0); + uint256[] memory indexes = new uint256[](0); vm.expectRevert(IVault.InvalidLengthEpochs.selector); - _claimBatch(alice, epochs); + _claimBatch(alice, indexes); } function test_ClaimBatchRevertInvalidEpoch(uint256 amount1, uint256 amount2, uint256 amount3) public { @@ -1965,12 +1890,12 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + 2; vm.warp(blockTimestamp); - uint256[] memory epochs = new uint256[](2); - epochs[0] = vault.currentEpoch() - 1; - epochs[1] = vault.currentEpoch(); + uint256[] memory indexes = new uint256[](2); + indexes[0] = 0; + indexes[1] = 2; vm.expectRevert(IVault.InvalidEpoch.selector); - _claimBatch(alice, epochs); + _claimBatch(alice, indexes); } function test_ClaimBatchRevertAlreadyClaimed(uint256 amount1, uint256 amount2, uint256 amount3) public { @@ -2001,12 +1926,12 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + 2; vm.warp(blockTimestamp); - uint256[] memory epochs = new uint256[](2); - epochs[0] = vault.currentEpoch() - 1; - epochs[1] = vault.currentEpoch() - 1; + uint256[] memory indexes = new uint256[](2); + indexes[0] = 0; + indexes[1] = 0; vm.expectRevert(IVault.AlreadyClaimed.selector); - _claimBatch(alice, epochs); + _claimBatch(alice, indexes); } function test_ClaimBatchRevertInsufficientClaim(uint256 amount1, uint256 amount2, uint256 amount3) public { @@ -2034,15 +1959,12 @@ contract VaultTest is Test { _withdraw(alice, amount3); - blockTimestamp = blockTimestamp + 2; - vm.warp(blockTimestamp); - - uint256[] memory epochs = new uint256[](2); - epochs[0] = vault.currentEpoch() - 1; - epochs[1] = vault.currentEpoch() - 3; + uint256[] memory indexes = new uint256[](2); + indexes[0] = 0; + indexes[1] = 1; - vm.expectRevert(IVault.InsufficientClaim.selector); - _claimBatch(alice, epochs); + vm.expectRevert(IVault.WithdrawalNotMatured.selector); + _claimBatch(alice, indexes); } function test_SetDepositWhitelist() public { @@ -2260,8 +2182,40 @@ contract VaultTest is Test { uint256 slashAmountSlashed2; } - function test_Slash( - // uint48 epochDuration, + function test_Slash_NoWithdrawals( + // uint48 withdrawalDelay, + uint256 depositAmount, + uint256 withdrawAmount1, + uint256 withdrawAmount2, + uint256 slashAmount1, + uint256 slashAmount2, + uint256 captureAgo + ) public { + // withdrawalDelay = uint48(bound(withdrawalDelay, 2, 10 days)); + depositAmount = bound(depositAmount, 1, 100 * 10 ** 18); + captureAgo = bound(captureAgo, 1, 7 days); + slashAmount1 = bound(slashAmount1, 1, type(uint256).max / 2); + + uint256 blockTimestamp = vm.getBlockTimestamp(); + blockTimestamp = blockTimestamp + 1_720_700_948; + vm.warp(blockTimestamp); + + (vault, delegator, slasher) = _getVaultAndDelegatorAndSlasher(7 days); + + _prepareVault(); + + _deposit(alice, depositAmount); + blockTimestamp = blockTimestamp + captureAgo; + vm.warp(blockTimestamp); + + assertEq(vault.totalStake(), depositAmount); + assertEq( + _slash(alice, alice, alice, slashAmount1, uint48(blockTimestamp - captureAgo), ""), + Math.min(slashAmount1, depositAmount) + ); + } + + function test_Slash_MaturedWithdrawals( uint256 depositAmount, uint256 withdrawAmount1, uint256 withdrawAmount2, @@ -2269,16 +2223,15 @@ contract VaultTest is Test { uint256 slashAmount2, uint256 captureAgo ) public { - // epochDuration = uint48(bound(epochDuration, 2, 10 days)); depositAmount = bound(depositAmount, 1, 100 * 10 ** 18); withdrawAmount1 = bound(withdrawAmount1, 1, 100 * 10 ** 18); withdrawAmount2 = bound(withdrawAmount2, 1, 100 * 10 ** 18); slashAmount1 = bound(slashAmount1, 1, type(uint256).max / 2); slashAmount2 = bound(slashAmount2, 1, type(uint256).max / 2); captureAgo = bound(captureAgo, 1, 10 days); + vm.assume(captureAgo <= 7 days); vm.assume(depositAmount > withdrawAmount1 + withdrawAmount2); vm.assume(depositAmount > slashAmount1); - vm.assume(captureAgo <= 7 days); uint256 blockTimestamp = vm.getBlockTimestamp(); blockTimestamp = blockTimestamp + 1_720_700_948; @@ -2286,167 +2239,107 @@ contract VaultTest is Test { (vault, delegator, slasher) = _getVaultAndDelegatorAndSlasher(7 days); - // address network = alice; - _registerNetwork(alice, alice); - _setMaxNetworkLimit(alice, 0, type(uint256).max); + _prepareVault(); - _registerOperator(alice); - _registerOperator(bob); + _deposit(alice, depositAmount); + _withdraw(alice, withdrawAmount1); - _optInOperatorVault(alice); - _optInOperatorVault(bob); + blockTimestamp = blockTimestamp + 10; + vm.warp(blockTimestamp); - _optInOperatorNetwork(alice, address(alice)); - _optInOperatorNetwork(bob, address(alice)); + _withdraw(alice, withdrawAmount2); - _setNetworkLimit(alice, alice, type(uint256).max); + blockTimestamp = blockTimestamp + 7 days + 1; + vm.warp(blockTimestamp); - _setOperatorNetworkLimit(alice, alice, alice, type(uint256).max / 2); - _setOperatorNetworkLimit(alice, alice, bob, type(uint256).max / 2); + uint256 activeStake = depositAmount - withdrawAmount1 - withdrawAmount2; + assertEq(vault.totalStake(), activeStake); + assertEq(vault.activeStake(), activeStake); + uint256 lastBucket = vault.latestWithdrawalBucket(); + assertEq(vault.withdrawals(lastBucket), withdrawAmount1 + withdrawAmount2); + + blockTimestamp = blockTimestamp + vault.epochDuration(); + vm.warp(blockTimestamp); + + uint256 slashAmountReal = Math.min(slashAmount1, vault.totalStake()); + uint256 tokensBeforeBurner = collateral.balanceOf(address(vault.burner())); + assertEq(_slash(alice, alice, alice, slashAmount1, uint48(blockTimestamp - captureAgo), ""), slashAmountReal); + assertEq(collateral.balanceOf(address(vault.burner())) - tokensBeforeBurner, slashAmountReal); + assertApproxEqAbs(vault.activeStake(), activeStake - slashAmountReal, 10); + } + + function test_Slash_NotMaturedWithdrawals( + uint256 depositAmount, + uint256 withdrawAmount1, + uint256 withdrawAmount2, + uint256 slashAmount1, + uint256 slashAmount2, + uint256 captureAgo + ) public { + depositAmount = bound(depositAmount, 1, 100 * 10 ** 18); + withdrawAmount1 = bound(withdrawAmount1, 1, 100 * 10 ** 18); + withdrawAmount2 = bound(withdrawAmount2, 1, 100 * 10 ** 18); + slashAmount1 = bound(slashAmount1, 1, type(uint256).max / 2); + slashAmount2 = bound(slashAmount2, 1, type(uint256).max / 2); + captureAgo = 1 days; + vm.assume(depositAmount > withdrawAmount1 + withdrawAmount2); + vm.assume(depositAmount > slashAmount1); + + uint256 blockTimestamp = vm.getBlockTimestamp(); + blockTimestamp = blockTimestamp + 1_720_700_948; + vm.warp(blockTimestamp); + + (vault, delegator, slasher) = _getVaultAndDelegatorAndSlasher(7 days); + + _prepareVault(); _deposit(alice, depositAmount); _withdraw(alice, withdrawAmount1); - blockTimestamp = blockTimestamp + vault.epochDuration(); + blockTimestamp = blockTimestamp + 10; vm.warp(blockTimestamp); _withdraw(alice, withdrawAmount2); - assertEq(vault.totalStake(), depositAmount); - assertEq(vault.activeStake(), depositAmount - withdrawAmount1 - withdrawAmount2); - assertEq(vault.withdrawals(vault.currentEpoch()), withdrawAmount1); - assertEq(vault.withdrawals(vault.currentEpoch() + 1), withdrawAmount2); - - blockTimestamp = blockTimestamp + 1; + blockTimestamp = blockTimestamp + captureAgo; vm.warp(blockTimestamp); - Test_SlashStruct memory test_SlashStruct; + uint256 slashAmountReal = Math.min(slashAmount1, vault.activeStake()); + uint256 tokensBeforeBurner = collateral.balanceOf(address(vault.burner())); - if (vault.epochAt(uint48(blockTimestamp - captureAgo)) != vault.currentEpoch()) { - test_SlashStruct.slashAmountReal1 = Math.min(slashAmount1, depositAmount - withdrawAmount1); - test_SlashStruct.tokensBeforeBurner = collateral.balanceOf(address(vault.burner())); - assertEq( - _slash(alice, alice, alice, slashAmount1, uint48(blockTimestamp - captureAgo), ""), - test_SlashStruct.slashAmountReal1 - ); - assertEq( - collateral.balanceOf(address(vault.burner())) - test_SlashStruct.tokensBeforeBurner, - test_SlashStruct.slashAmountReal1 - ); + uint256 totalStake = vault.totalStake(); + uint256 activeStake = vault.activeStake(); + uint256 lastBucket = vault.latestWithdrawalBucket(); + uint256 withdrawals = vault.withdrawals(lastBucket); - test_SlashStruct.activeStake1 = depositAmount - withdrawAmount1 - withdrawAmount2 - - (depositAmount - withdrawAmount1 - withdrawAmount2) - .mulDiv(test_SlashStruct.slashAmountReal1, depositAmount); - test_SlashStruct.withdrawals1 = - withdrawAmount1 - withdrawAmount1.mulDiv(test_SlashStruct.slashAmountReal1, depositAmount); - test_SlashStruct.nextWithdrawals1 = - withdrawAmount2 - withdrawAmount2.mulDiv(test_SlashStruct.slashAmountReal1, depositAmount); - assertEq(vault.totalStake(), depositAmount - test_SlashStruct.slashAmountReal1); - assertTrue(test_SlashStruct.withdrawals1 - vault.withdrawals(vault.currentEpoch()) <= 2); - assertTrue(test_SlashStruct.nextWithdrawals1 - vault.withdrawals(vault.currentEpoch() + 1) <= 1); - assertEq(vault.activeStake(), test_SlashStruct.activeStake1); - - test_SlashStruct.slashAmountSlashed2 = Math.min( - depositAmount - test_SlashStruct.slashAmountReal1, - Math.min(slashAmount2, depositAmount - withdrawAmount1) - ); - test_SlashStruct.tokensBeforeBurner = collateral.balanceOf(address(vault.burner())); - assertEq( - _slash(alice, alice, bob, slashAmount2, uint48(blockTimestamp - captureAgo), ""), - Math.min(slashAmount2, depositAmount - withdrawAmount1) - ); - assertEq( - collateral.balanceOf(address(vault.burner())) - test_SlashStruct.tokensBeforeBurner, - test_SlashStruct.slashAmountSlashed2 - ); + assertEq(_slash(alice, alice, alice, slashAmount1, uint48(blockTimestamp - captureAgo), ""), slashAmountReal); + assertEq(collateral.balanceOf(address(vault.burner())) - tokensBeforeBurner, slashAmountReal); - assertEq( - vault.totalStake(), - depositAmount - test_SlashStruct.slashAmountReal1 - test_SlashStruct.slashAmountSlashed2 - ); - assertTrue( - (test_SlashStruct.withdrawals1 - - test_SlashStruct.withdrawals1 - .mulDiv( - test_SlashStruct.slashAmountSlashed2, - depositAmount - test_SlashStruct.slashAmountReal1 - )) - vault.withdrawals(vault.currentEpoch()) <= 4 - ); - assertTrue( - (test_SlashStruct.nextWithdrawals1 - - test_SlashStruct.nextWithdrawals1 - .mulDiv( - test_SlashStruct.slashAmountSlashed2, - depositAmount - test_SlashStruct.slashAmountReal1 - )) - vault.withdrawals(vault.currentEpoch() + 1) <= 2 - ); - assertEq( - vault.activeStake(), - test_SlashStruct.activeStake1 - - test_SlashStruct.activeStake1 - .mulDiv(test_SlashStruct.slashAmountSlashed2, depositAmount - test_SlashStruct.slashAmountReal1) - ); - } else { - test_SlashStruct.slashAmountReal1 = - Math.min(slashAmount1, depositAmount - withdrawAmount1 - withdrawAmount2); - test_SlashStruct.tokensBeforeBurner = collateral.balanceOf(address(vault.burner())); - assertEq( - _slash(alice, alice, alice, slashAmount1, uint48(blockTimestamp - captureAgo), ""), - test_SlashStruct.slashAmountReal1 - ); - assertEq( - collateral.balanceOf(address(vault.burner())) - test_SlashStruct.tokensBeforeBurner, - test_SlashStruct.slashAmountReal1 - ); + uint256 activeStakeAfter = + depositAmount - withdrawAmount1 - withdrawAmount2 - slashAmountReal.mulDiv(activeStake, totalStake); + assertApproxEqAbs(vault.activeStake(), activeStakeAfter, 10); - test_SlashStruct.activeStake1 = depositAmount - withdrawAmount1 - withdrawAmount2 - - (depositAmount - withdrawAmount1 - withdrawAmount2) - .mulDiv(test_SlashStruct.slashAmountReal1, depositAmount - withdrawAmount1); - test_SlashStruct.withdrawals1 = withdrawAmount1; - test_SlashStruct.nextWithdrawals1 = withdrawAmount2 - - withdrawAmount2.mulDiv(test_SlashStruct.slashAmountReal1, depositAmount - withdrawAmount1); - assertEq(vault.totalStake(), depositAmount - test_SlashStruct.slashAmountReal1); - assertEq(vault.withdrawals(vault.currentEpoch()), test_SlashStruct.withdrawals1); - assertTrue(test_SlashStruct.nextWithdrawals1 - vault.withdrawals(vault.currentEpoch() + 1) <= 1); - assertEq(vault.activeStake(), test_SlashStruct.activeStake1); - - test_SlashStruct.slashAmountSlashed2 = Math.min( - depositAmount - withdrawAmount1 - test_SlashStruct.slashAmountReal1, - Math.min(slashAmount2, depositAmount - withdrawAmount1 - withdrawAmount2) - ); - test_SlashStruct.tokensBeforeBurner = collateral.balanceOf(address(vault.burner())); - assertEq( - _slash(alice, alice, bob, slashAmount2, uint48(blockTimestamp - captureAgo), ""), - Math.min(slashAmount2, depositAmount - withdrawAmount1 - withdrawAmount2) - ); - assertEq( - collateral.balanceOf(address(vault.burner())) - test_SlashStruct.tokensBeforeBurner, - test_SlashStruct.slashAmountSlashed2 - ); + uint256 withdrawalsAfter = withdrawals - slashAmountReal.mulDiv(withdrawals, totalStake); + assertApproxEqAbs(vault.withdrawals(lastBucket + 1), withdrawalsAfter, 10); + } - assertEq( - vault.totalStake(), - depositAmount - test_SlashStruct.slashAmountReal1 - test_SlashStruct.slashAmountSlashed2 - ); - assertEq(vault.withdrawals(vault.currentEpoch()), test_SlashStruct.withdrawals1); - assertTrue( - (test_SlashStruct.nextWithdrawals1 - - test_SlashStruct.nextWithdrawals1 - .mulDiv( - test_SlashStruct.slashAmountSlashed2, - depositAmount - withdrawAmount1 - test_SlashStruct.slashAmountReal1 - )) - vault.withdrawals(vault.currentEpoch() + 1) <= 2 - ); - assertEq( - vault.activeStake(), - test_SlashStruct.activeStake1 - - test_SlashStruct.activeStake1 - .mulDiv( - test_SlashStruct.slashAmountSlashed2, - depositAmount - withdrawAmount1 - test_SlashStruct.slashAmountReal1 - ) - ); - } + function _prepareVault() internal { + _registerNetwork(alice, alice); + _setMaxNetworkLimit(alice, 0, type(uint256).max); + + _registerOperator(alice); + _registerOperator(bob); + + _optInOperatorVault(alice); + _optInOperatorVault(bob); + + _optInOperatorNetwork(alice, address(alice)); + _optInOperatorNetwork(bob, address(alice)); + + _setNetworkLimit(alice, alice, type(uint256).max); + + _setOperatorNetworkLimit(alice, alice, alice, type(uint256).max / 2); + _setOperatorNetworkLimit(alice, alice, bob, type(uint256).max / 2); } // struct GasStruct { @@ -2810,9 +2703,9 @@ contract VaultTest is Test { vm.stopPrank(); } - function _claimBatch(address user, uint256[] memory epochs) internal returns (uint256 amount) { + function _claimBatch(address user, uint256[] memory indexes) internal returns (uint256 amount) { vm.startPrank(user); - amount = vault.claimBatch(user, epochs); + amount = vault.claimBatch(user, indexes); vm.stopPrank(); } From fa928942d95c913c63a55b2d068141f505ede361 Mon Sep 17 00:00:00 2001 From: Sergii Liakh Date: Wed, 10 Dec 2025 14:34:46 -0300 Subject: [PATCH 3/9] fix: incorrect slashing --- src/contracts/vault/Vault.sol | 35 ++++++++++++++------------ src/contracts/vault/VaultStorage.sol | 4 --- src/interfaces/vault/IVaultStorage.sol | 13 ++++++++++ 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/src/contracts/vault/Vault.sol b/src/contracts/vault/Vault.sol index 6f0a59a4..0d8aa13c 100644 --- a/src/contracts/vault/Vault.sol +++ b/src/contracts/vault/Vault.sol @@ -41,7 +41,12 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau * @inheritdoc IVault */ function totalStake() public view returns (uint256) { - return activeStake() + unmaturedWithdrawals(uint48(block.timestamp)); + uint208 lastBucket = timeToBucket.latest(); + uint256 lastWithdrawalShares = _withdrawalShares[lastBucket]; + uint256 unmaturedWithdrawals = lastWithdrawalShares == 0 + ? 0 + : unmaturedWithdrawalShares(uint48(block.timestamp)).mulDiv(_withdrawals[lastBucket], lastWithdrawalShares); + return activeStake() + unmaturedWithdrawals; } /** @@ -238,30 +243,28 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau if (captureTimestamp + epochDuration < uint48(block.timestamp) || captureTimestamp >= uint48(block.timestamp)) { revert InvalidCaptureEpoch(); } + uint208 lastBucket = timeToBucket.latest(); + uint256 lastWithdrawals = _withdrawals[lastBucket]; + uint256 lastWithdrawalShares = _withdrawalShares[lastBucket]; - uint256 unmaturedWithdrawals = unmaturedWithdrawals(uint48(block.timestamp)); uint256 unmaturedWithdrawalShares = unmaturedWithdrawalShares(uint48(block.timestamp)); - uint208 lastBucket = timeToBucket.latest(); - _withdrawals[lastBucket] -= unmaturedWithdrawals; - _withdrawalShares[lastBucket] -= unmaturedWithdrawalShares; - _withdrawalShares[lastBucket + 1] = unmaturedWithdrawalShares; + uint256 unmaturedWithdrawals = + lastWithdrawalShares == 0 ? 0 : unmaturedWithdrawalShares.mulDiv(lastWithdrawals, lastWithdrawalShares); + timeToBucket.push(uint48(block.timestamp), lastBucket + 1); + _withdrawals[lastBucket] = lastWithdrawals - unmaturedWithdrawals; + _withdrawalShares[lastBucket] = lastWithdrawalShares - unmaturedWithdrawalShares; + _withdrawalShares[lastBucket + 1] = unmaturedWithdrawalShares; uint256 activeStake_ = activeStake(); uint256 slashableStake = activeStake_ + unmaturedWithdrawals; slashedAmount = Math.min(amount, slashableStake); + uint256 activeSlashed = slashedAmount.mulDiv(activeStake_, slashableStake); + _activeStake.push(uint48(block.timestamp), activeStake_ - activeSlashed); - if (slashedAmount > 0) { - uint256 activeSlashed = slashedAmount.mulDiv(activeStake_, slashableStake); - uint256 withdrawalsSlashed = slashedAmount - activeSlashed; - - _activeStake.push(uint48(block.timestamp), activeStake_ - activeSlashed); - unmaturedWithdrawals -= withdrawalsSlashed; - - IERC20(collateral).safeTransfer(burner, slashedAmount); - } - _withdrawals[lastBucket + 1] = unmaturedWithdrawals; + _withdrawals[lastBucket + 1] = unmaturedWithdrawals - (slashedAmount - activeSlashed); + IERC20(collateral).safeTransfer(burner, slashedAmount); emit OnSlash(amount, captureTimestamp, slashedAmount); } diff --git a/src/contracts/vault/VaultStorage.sol b/src/contracts/vault/VaultStorage.sol index 1368e388..1a7ad0ca 100644 --- a/src/contracts/vault/VaultStorage.sol +++ b/src/contracts/vault/VaultStorage.sol @@ -248,10 +248,6 @@ abstract contract VaultStorage is StaticDelegateCallable, IVaultStorage { return timeToBucket.latest(); } - function unmaturedWithdrawals(uint48 timestamp) public view returns (uint256) { - return withdrawalsPrefixes.length() == 1 ? withdrawalsPrefixes.latest() : withdrawalsPrefixes.latest() - withdrawalsPrefixes.upperLookupRecent(timestamp); - } - function unmaturedWithdrawalShares(uint48 timestamp) public view returns (uint256) { return withdrawalSharesPrefixes.length() == 1 ? withdrawalSharesPrefixes.latest() : withdrawalSharesPrefixes.latest() - withdrawalSharesPrefixes.upperLookupRecent(timestamp); } diff --git a/src/interfaces/vault/IVaultStorage.sol b/src/interfaces/vault/IVaultStorage.sol index 420e95a1..8eddaaaf 100644 --- a/src/interfaces/vault/IVaultStorage.sol +++ b/src/interfaces/vault/IVaultStorage.sol @@ -231,4 +231,17 @@ interface IVaultStorage { * @return if the withdrawals are claimed for the account at the index */ function isWithdrawalsClaimed(uint256 index, address account) external view returns (bool); + + /** + * @notice Get a total number of unmatured withdrawal shares at a given timestamp. + * @param timestamp time point to get the total number of unmatured withdrawal shares at + * @return total number of unmatured withdrawal shares at the timestamp + */ + function unmaturedWithdrawalShares(uint48 timestamp) external view returns (uint256); + + /** + * @notice Get the latest withdrawal bucket. + * @return latest withdrawal bucket + */ + function latestWithdrawalBucket() external view returns (uint256); } From c33cfecd2ede747b8fa28f5cd9f5b593530ba96a Mon Sep 17 00:00:00 2001 From: Sergii Liakh Date: Wed, 10 Dec 2025 15:27:08 -0300 Subject: [PATCH 4/9] test: fixed tests --- test/vault/IVaultFull.sol | 20 + test/vault/Vault.t.sol | 681 +++---- test/vault/VaultTokenized.t.sol | 3070 +------------------------------ 3 files changed, 311 insertions(+), 3460 deletions(-) create mode 100644 test/vault/IVaultFull.sol diff --git a/test/vault/IVaultFull.sol b/test/vault/IVaultFull.sol new file mode 100644 index 00000000..8ef8fb1d --- /dev/null +++ b/test/vault/IVaultFull.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IVault} from "../../src/interfaces/vault/IVault.sol"; +import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface IVaultFull is IVault, IAccessControl, IERC20 { + function DEPOSITOR_WHITELIST_ROLE() external view returns (bytes32); + function DEPOSIT_WHITELIST_SET_ROLE() external view returns (bytes32); + function IS_DEPOSIT_LIMIT_SET_ROLE() external view returns (bytes32); + function DEPOSIT_LIMIT_SET_ROLE() external view returns (bytes32); + function DEFAULT_ADMIN_ROLE() external view returns (bytes32); + + function owner() external view returns (address); + function latestWithdrawalBucket() external view returns (uint256); + function decimals() external view returns (uint8); + function symbol() external view returns (string memory); + function name() external view returns (string memory); +} \ No newline at end of file diff --git a/test/vault/Vault.t.sol b/test/vault/Vault.t.sol index e19bdf96..93a9c5ed 100644 --- a/test/vault/Vault.t.sol +++ b/test/vault/Vault.t.sol @@ -22,6 +22,7 @@ import {Slasher} from "../../src/contracts/slasher/Slasher.sol"; import {VetoSlasher} from "../../src/contracts/slasher/VetoSlasher.sol"; import {IVault} from "../../src/interfaces/vault/IVault.sol"; +import {IVaultFull} from "./IVaultFull.sol"; import {Token} from "../mocks/Token.sol"; import {FeeOnTransferToken} from "../mocks/FeeOnTransferToken.sol"; @@ -66,11 +67,11 @@ contract VaultTest is Test { FeeOnTransferToken feeOnTransferCollateral; VaultConfigurator vaultConfigurator; - Vault vault; + IVaultFull vault; FullRestakeDelegator delegator; Slasher slasher; - function setUp() public { + function setUp() public virtual { owner = address(this); (alice, alicePrivateKey) = makeAddrAndKey("alice"); (bob, bobPrivateKey) = makeAddrAndKey("bob"); @@ -88,8 +89,8 @@ contract VaultTest is Test { operatorNetworkOptInService = new OptInService(address(operatorRegistry), address(networkRegistry), "OperatorNetworkOptInService"); - address vaultImpl = - address(new Vault(address(delegatorFactory), address(slasherFactory), address(vaultFactory))); + address vaultImpl = _createVaultImpl(address(delegatorFactory), address(slasherFactory), address(vaultFactory)); + vaultFactory.whitelist(vaultImpl); address networkRestakeDelegatorImpl = address( @@ -176,7 +177,7 @@ contract VaultTest is Test { bool depositWhitelist, bool isDepositLimit, uint256 depositLimit - ) public { + ) public virtual { epochDuration = uint48(bound(epochDuration, 1, 50 weeks)); uint256 blockTimestamp = vm.getBlockTimestamp(); @@ -187,44 +188,10 @@ contract VaultTest is Test { networkLimitSetRoleHolders[0] = alice; address[] memory operatorNetworkSharesSetRoleHolders = new address[](1); operatorNetworkSharesSetRoleHolders[0] = alice; - (address vault_, address delegator_,) = vaultConfigurator.create( - IVaultConfigurator.InitParams({ - version: vaultFactory.lastVersion(), - owner: address(0), - vaultParams: abi.encode( - IVault.InitParams({ - collateral: address(collateral), - burner: burner, - epochDuration: epochDuration, - depositWhitelist: depositWhitelist, - isDepositLimit: isDepositLimit, - depositLimit: depositLimit, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }) - ), - delegatorIndex: 0, - delegatorParams: abi.encode( - INetworkRestakeDelegator.InitParams({ - baseParams: IBaseDelegator.BaseParams({ - defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice - }), - networkLimitSetRoleHolders: networkLimitSetRoleHolders, - operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders - }) - ), - withSlasher: false, - slasherIndex: 0, - slasherParams: abi.encode( - ISlasher.InitParams({baseParams: IBaseSlasher.BaseParams({isBurnerHook: false})}) - ) - }) + (IVaultFull vault_, address delegator_, address slasher_) = _createInitializedVault( + epochDuration, networkLimitSetRoleHolders, operatorNetworkSharesSetRoleHolders, vaultFactory.lastVersion(), burner, depositWhitelist, isDepositLimit, depositLimit ); - - vault = Vault(vault_); + vault = vault_; assertEq(vault.DEPOSIT_WHITELIST_SET_ROLE(), keccak256("DEPOSIT_WHITELIST_SET_ROLE")); assertEq(vault.DEPOSITOR_WHITELIST_ROLE(), keccak256("DEPOSITOR_WHITELIST_ROLE")); @@ -234,7 +201,7 @@ contract VaultTest is Test { assertEq(vault.owner(), address(0)); assertEq(vault.collateral(), address(collateral)); assertEq(vault.delegator(), delegator_); - assertEq(vault.slasher(), address(0)); + assertEq(vault.slasher(), slasher_); assertEq(vault.burner(), burner); assertEq(vault.epochDuration(), epochDuration); assertEq(vault.depositWhitelist(), depositWhitelist); @@ -267,7 +234,6 @@ contract VaultTest is Test { assertEq(vault.isDelegatorInitialized(), true); assertEq(vault.isSlasherInitialized(), true); assertEq(vault.isInitialized(), true); - } function test_CreateRevertInvalidEpochDuration() public { @@ -279,41 +245,8 @@ contract VaultTest is Test { operatorNetworkSharesSetRoleHolders[0] = alice; uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.InvalidEpochDuration.selector); - vaultConfigurator.create( - IVaultConfigurator.InitParams({ - version: lastVersion, - owner: alice, - vaultParams: abi.encode( - IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: epochDuration, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }) - ), - delegatorIndex: 0, - delegatorParams: abi.encode( - INetworkRestakeDelegator.InitParams({ - baseParams: IBaseDelegator.BaseParams({ - defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice - }), - networkLimitSetRoleHolders: networkLimitSetRoleHolders, - operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders - }) - ), - withSlasher: false, - slasherIndex: 0, - slasherParams: abi.encode( - ISlasher.InitParams({baseParams: IBaseSlasher.BaseParams({isBurnerHook: false})}) - ) - }) + _createInitializedVault( + epochDuration, networkLimitSetRoleHolders, operatorNetworkSharesSetRoleHolders, lastVersion, address(0xdEaD), false, false, 0 ); } @@ -325,42 +258,10 @@ contract VaultTest is Test { address[] memory operatorNetworkSharesSetRoleHolders = new address[](1); operatorNetworkSharesSetRoleHolders[0] = alice; uint64 lastVersion = vaultFactory.lastVersion(); + collateral = Token(address(0)); vm.expectRevert(IVault.InvalidCollateral.selector); - vaultConfigurator.create( - IVaultConfigurator.InitParams({ - version: lastVersion, - owner: alice, - vaultParams: abi.encode( - IVault.InitParams({ - collateral: address(0), - burner: address(0xdEaD), - epochDuration: epochDuration, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }) - ), - delegatorIndex: 0, - delegatorParams: abi.encode( - INetworkRestakeDelegator.InitParams({ - baseParams: IBaseDelegator.BaseParams({ - defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice - }), - networkLimitSetRoleHolders: networkLimitSetRoleHolders, - operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders - }) - ), - withSlasher: false, - slasherIndex: 0, - slasherParams: abi.encode( - ISlasher.InitParams({baseParams: IBaseSlasher.BaseParams({isBurnerHook: false})}) - ) - }) + _createInitializedVault( + epochDuration, networkLimitSetRoleHolders, operatorNetworkSharesSetRoleHolders, lastVersion, address(0xdEaD), false, false, 0 ); } @@ -370,11 +271,11 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.MissingRoles.selector); - vault = Vault( + vault = IVaultFull( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -399,11 +300,11 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.MissingRoles.selector); - vault = Vault( + vault = IVaultFull( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -428,11 +329,11 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.MissingRoles.selector); - vault = Vault( + vault = IVaultFull( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -457,11 +358,11 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.MissingRoles.selector); - vault = Vault( + vault = IVaultFull( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -486,11 +387,11 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.MissingRoles.selector); - vault = Vault( + vault = IVaultFull( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -512,11 +413,11 @@ contract VaultTest is Test { function test_SetDelegator() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVaultFull( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -568,11 +469,11 @@ contract VaultTest is Test { function test_SetDelegatorRevertDelegatorAlreadyInitialized() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVaultFull( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -621,11 +522,11 @@ contract VaultTest is Test { function test_SetDelegatorRevertNotDelegator() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVaultFull( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -650,11 +551,11 @@ contract VaultTest is Test { function test_SetDelegatorRevertInvalidDelegator() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVaultFull( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -672,11 +573,11 @@ contract VaultTest is Test { ) ); - Vault vault2 = Vault( + address vault2 = vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -691,8 +592,7 @@ contract VaultTest is Test { depositLimitSetRoleHolder: alice }) ) - ) - ); + ); address[] memory networkLimitSetRoleHolders = new address[](1); networkLimitSetRoleHolders[0] = alice; @@ -723,11 +623,11 @@ contract VaultTest is Test { function test_SetSlasher() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVaultFull( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -767,11 +667,11 @@ contract VaultTest is Test { function test_SetSlasherRevertSlasherAlreadyInitialized() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVaultFull( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -808,11 +708,11 @@ contract VaultTest is Test { function test_SetSlasherRevertNotSlasher() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVaultFull( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -829,6 +729,7 @@ contract VaultTest is Test { ) ) ); + slasher = Slasher( slasherFactory.create( @@ -847,11 +748,11 @@ contract VaultTest is Test { function test_SetSlasherRevertInvalidSlasher() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVaultFull( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -869,11 +770,11 @@ contract VaultTest is Test { ) ); - Vault vault2 = Vault( + address vault2 = vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -888,8 +789,7 @@ contract VaultTest is Test { depositLimitSetRoleHolder: alice }) ) - ) - ); + ); slasher = Slasher( slasherFactory.create( @@ -908,11 +808,11 @@ contract VaultTest is Test { function test_SetSlasherZeroAddress() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVaultFull( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -933,7 +833,7 @@ contract VaultTest is Test { vault.setSlasher(address(0)); } - function test_DepositTwice(uint256 amount1, uint256 amount2) public { + function test_DepositTwice(uint256 amount1, uint256 amount2) public virtual { amount1 = bound(amount1, 1, 100 * 10 ** 18); amount2 = bound(amount2, 1, 100 * 10 ** 18); @@ -1108,42 +1008,17 @@ contract VaultTest is Test { networkLimitSetRoleHolders[0] = alice; address[] memory operatorNetworkSharesSetRoleHolders = new address[](1); operatorNetworkSharesSetRoleHolders[0] = alice; - (address vault_,,) = vaultConfigurator.create( - IVaultConfigurator.InitParams({ - version: vaultFactory.lastVersion(), - owner: alice, - vaultParams: abi.encode( - IVault.InitParams({ - collateral: address(feeOnTransferCollateral), - burner: address(0xdEaD), - epochDuration: epochDuration, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }) - ), - delegatorIndex: 0, - delegatorParams: abi.encode( - INetworkRestakeDelegator.InitParams({ - baseParams: IBaseDelegator.BaseParams({ - defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice - }), - networkLimitSetRoleHolders: networkLimitSetRoleHolders, - operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders - }) - ), - withSlasher: false, - slasherIndex: 0, - slasherParams: "" - }) + collateral = Token(address(feeOnTransferCollateral)); + (vault,,) = _createInitializedVault( + epochDuration, + networkLimitSetRoleHolders, + operatorNetworkSharesSetRoleHolders, + vaultFactory.lastVersion(), + address(0xdEaD), + false, + false, + 0 ); - - vault = Vault(vault_); } uint256 tokensBefore = feeOnTransferCollateral.balanceOf(address(vault)); @@ -1304,7 +1179,7 @@ contract VaultTest is Test { assertGt(gasSpent, gasLeft - gasleft()); } - function test_DepositBoth(uint256 amount1, uint256 amount2) public { + function test_DepositBoth(uint256 amount1, uint256 amount2) public virtual { amount1 = bound(amount1, 1, 100 * 10 ** 18); amount2 = bound(amount2, 1, 100 * 10 ** 18); @@ -1377,7 +1252,7 @@ contract VaultTest is Test { vm.stopPrank(); } - function test_WithdrawTwice(uint256 amount1, uint256 amount2, uint256 amount3) public { + function test_WithdrawTwice(uint256 amount1, uint256 amount2, uint256 amount3) public virtual { amount1 = bound(amount1, 1, 100 * 10 ** 18); amount2 = bound(amount2, 1, 100 * 10 ** 18); amount3 = bound(amount3, 1, 100 * 10 ** 18); @@ -2311,6 +2186,7 @@ contract VaultTest is Test { uint256 activeStake = vault.activeStake(); uint256 lastBucket = vault.latestWithdrawalBucket(); uint256 withdrawals = vault.withdrawals(lastBucket); + console2.log("-------slasher", address(slasher)); assertEq(_slash(alice, alice, alice, slashAmount1, uint48(blockTimestamp - captureAgo), ""), slashAmountReal); assertEq(collateral.balanceOf(address(vault.burner())) - tokensBeforeBurner, slashAmountReal); @@ -2323,6 +2199,77 @@ contract VaultTest is Test { assertApproxEqAbs(vault.withdrawals(lastBucket + 1), withdrawalsAfter, 10); } + function test_SlashTwice( + uint256 depositAmount, + uint256 withdrawAmount1, + uint256 withdrawAmount2, + uint256 slashAmount1, + uint256 slashAmount2, + uint256 captureAgo + ) public { + depositAmount = bound(depositAmount, 1, 100 * 10 ** 18); + withdrawAmount1 = bound(withdrawAmount1, 1, 100 * 10 ** 18); + withdrawAmount2 = bound(withdrawAmount2, 1, 100 * 10 ** 18); + slashAmount1 = bound(slashAmount1, 1, type(uint256).max / 2); + slashAmount2 = bound(slashAmount2, 1, type(uint256).max / 2); + captureAgo = 1 days; + vm.assume(depositAmount > withdrawAmount1 + withdrawAmount2); + vm.assume(depositAmount > slashAmount1 + slashAmount2); + + uint256 blockTimestamp = vm.getBlockTimestamp(); + blockTimestamp = blockTimestamp + 1_720_700_948; + vm.warp(blockTimestamp); + + (vault, delegator, slasher) = _getVaultAndDelegatorAndSlasher(7 days); + + _prepareVault(); + + _deposit(alice, depositAmount); + _withdraw(alice, withdrawAmount1); + + blockTimestamp = blockTimestamp + 10; + vm.warp(blockTimestamp); + + _withdraw(alice, withdrawAmount2); + + blockTimestamp = blockTimestamp + captureAgo; + vm.warp(blockTimestamp); + + // First slash + uint256 totalStake1 = vault.totalStake(); + uint256 activeStake1 = vault.activeStake(); + uint256 lastBucket1 = vault.latestWithdrawalBucket(); + uint256 withdrawals1 = vault.withdrawals(lastBucket1); + + uint256 slashAmountReal1 = _slash(alice, alice, alice, slashAmount1, uint48(blockTimestamp - captureAgo), ""); + + blockTimestamp = blockTimestamp + captureAgo; + vm.warp(blockTimestamp); + + // Second slash + // Calculate unmatured withdrawals the same way the slash function does + uint256 lastBucket2 = vault.latestWithdrawalBucket(); + uint256 lastWithdrawals2 = vault.withdrawals(lastBucket2); + uint256 lastWithdrawalShares2 = vault.withdrawalShares(lastBucket2); + uint256 unmaturedWithdrawalShares2 = vault.unmaturedWithdrawalShares(uint48(blockTimestamp)); + uint256 unmaturedWithdrawals2 = + lastWithdrawalShares2 == 0 ? 0 : unmaturedWithdrawalShares2.mulDiv(lastWithdrawals2, lastWithdrawalShares2); + + uint256 activeStake2 = vault.activeStake(); + uint256 slashableStake2 = activeStake2 + unmaturedWithdrawals2; + + uint256 slashAmountReal2 = _slash(alice, alice, alice, slashAmount2, uint48(blockTimestamp - captureAgo), ""); + + // Calculate state after second slash + uint256 activeStakeAfter = activeStake2 - slashAmountReal2.mulDiv(activeStake2, slashableStake2); + assertApproxEqAbs(vault.activeStake(), activeStakeAfter, 10); + + // The unmatured withdrawals are slashed proportionally + uint256 withdrawalsAfter = + unmaturedWithdrawals2 - slashAmountReal2.mulDiv(unmaturedWithdrawals2, slashableStake2); + assertApproxEqAbs(vault.withdrawals(lastBucket2 + 1), withdrawalsAfter, 10); + } + function _prepareVault() internal { _registerNetwork(alice, alice); _setMaxNetworkLimit(alice, 0, type(uint256).max); @@ -2342,302 +2289,31 @@ contract VaultTest is Test { _setOperatorNetworkLimit(alice, alice, bob, type(uint256).max / 2); } - // struct GasStruct { - // uint256 gasSpent1; - // uint256 gasSpent2; - // } - // struct HintStruct { - // uint256 num; - // bool back; - // uint256 secondsAgo; - // } - - // function test_ActiveSharesHint(uint256 amount1, uint48 epochDuration, HintStruct memory hintStruct) public { - // amount1 = bound(amount1, 1, 100 * 10 ** 18); - // epochDuration = uint48(bound(epochDuration, 1, 7 days)); - // hintStruct.num = bound(hintStruct.num, 0, 25); - // hintStruct.secondsAgo = bound(hintStruct.secondsAgo, 0, 1_720_700_948); - - // uint256 blockTimestamp = vm.getBlockTimestamp(); - // blockTimestamp = blockTimestamp + 1_720_700_948; - // vm.warp(blockTimestamp); - - // vault = _getVault(epochDuration); - - // for (uint256 i; i < hintStruct.num; ++i) { - // _deposit(alice, amount1); - - // blockTimestamp = blockTimestamp + epochDuration; - // vm.warp(blockTimestamp); - // } - - // uint48 timestamp = - // uint48(hintStruct.back ? blockTimestamp - hintStruct.secondsAgo : blockTimestamp + hintStruct.secondsAgo); - - // VaultHints vaultHints = new VaultHints(); - // bytes memory hint = vaultHints.activeSharesHint(address(vault), timestamp); - - // GasStruct memory gasStruct = GasStruct({gasSpent1: 1, gasSpent2: 1}); - // vault.activeSharesAt(timestamp, new bytes(0)); - // gasStruct.gasSpent1 = vm.lastCallGas().gasTotalUsed; - // vault.activeSharesAt(timestamp, hint); - // gasStruct.gasSpent2 = vm.lastCallGas().gasTotalUsed; - // assertApproxEqRel(gasStruct.gasSpent1, gasStruct.gasSpent2, 0.05e18); - // } - - // function test_ActiveStakeHint(uint256 amount1, uint48 epochDuration, HintStruct memory hintStruct) public { - // amount1 = bound(amount1, 1, 100 * 10 ** 18); - // epochDuration = uint48(bound(epochDuration, 1, 7 days)); - // hintStruct.num = bound(hintStruct.num, 0, 25); - // hintStruct.secondsAgo = bound(hintStruct.secondsAgo, 0, 1_720_700_948); - - // uint256 blockTimestamp = vm.getBlockTimestamp(); - // blockTimestamp = blockTimestamp + 1_720_700_948; - // vm.warp(blockTimestamp); - - // vault = _getVault(epochDuration); - - // for (uint256 i; i < hintStruct.num; ++i) { - // _deposit(alice, amount1); - - // blockTimestamp = blockTimestamp + epochDuration; - // vm.warp(blockTimestamp); - // } - - // uint48 timestamp = - // uint48(hintStruct.back ? blockTimestamp - hintStruct.secondsAgo : blockTimestamp + hintStruct.secondsAgo); - - // VaultHints vaultHints = new VaultHints(); - // bytes memory hint = vaultHints.activeStakeHint(address(vault), timestamp); - - // GasStruct memory gasStruct = GasStruct({gasSpent1: 1, gasSpent2: 1}); - // vault.activeStakeAt(timestamp, new bytes(0)); - // gasStruct.gasSpent1 = vm.lastCallGas().gasTotalUsed; - // vault.activeStakeAt(timestamp, hint); - // gasStruct.gasSpent2 = vm.lastCallGas().gasTotalUsed; - // assertGe(gasStruct.gasSpent1, gasStruct.gasSpent2); - // } - - // function test_ActiveSharesOfHint(uint256 amount1, uint48 epochDuration, HintStruct memory hintStruct) public { - // amount1 = bound(amount1, 1, 100 * 10 ** 18); - // epochDuration = uint48(bound(epochDuration, 1, 7 days)); - // hintStruct.num = bound(hintStruct.num, 0, 25); - // hintStruct.secondsAgo = bound(hintStruct.secondsAgo, 0, 1_720_700_948); - - // uint256 blockTimestamp = vm.getBlockTimestamp(); - // blockTimestamp = blockTimestamp + 1_720_700_948; - // vm.warp(blockTimestamp); - - // vault = _getVault(epochDuration); - - // for (uint256 i; i < hintStruct.num; ++i) { - // _deposit(alice, amount1); - - // blockTimestamp = blockTimestamp + epochDuration; - // vm.warp(blockTimestamp); - // } - - // uint48 timestamp = - // uint48(hintStruct.back ? blockTimestamp - hintStruct.secondsAgo : blockTimestamp + hintStruct.secondsAgo); - - // VaultHints vaultHints = new VaultHints(); - // bytes memory hint = vaultHints.activeSharesOfHint(address(vault), alice, timestamp); - - // GasStruct memory gasStruct = GasStruct({gasSpent1: 1, gasSpent2: 1}); - // vault.activeSharesOfAt(alice, timestamp, new bytes(0)); - // gasStruct.gasSpent1 = vm.lastCallGas().gasTotalUsed; - // vault.activeSharesOfAt(alice, timestamp, hint); - // gasStruct.gasSpent2 = vm.lastCallGas().gasTotalUsed; - // assertGe(gasStruct.gasSpent1, gasStruct.gasSpent2); - // } - - // struct ActiveBalanceOfHintsUint32 { - // uint32 activeSharesOfHint; - // uint32 activeStakeHint; - // uint32 activeSharesHint; - // } - - // function test_ActiveBalanceOfHint( - // uint256 amount1, - // uint48 epochDuration, - // HintStruct memory hintStruct, - // ActiveBalanceOfHintsUint32 memory activeBalanceOfHintsUint32 - // ) public { - // amount1 = bound(amount1, 1, 100 * 10 ** 18); - // epochDuration = uint48(bound(epochDuration, 1, 7 days)); - // hintStruct.num = bound(hintStruct.num, 0, 25); - // hintStruct.secondsAgo = bound(hintStruct.secondsAgo, 0, 1_720_700_948); - - // uint256 blockTimestamp = vm.getBlockTimestamp(); - // blockTimestamp = blockTimestamp + 1_720_700_948; - // vm.warp(blockTimestamp); - - // vault = _getVault(epochDuration); - - // for (uint256 i; i < hintStruct.num; ++i) { - // _deposit(alice, amount1); - - // blockTimestamp = blockTimestamp + epochDuration; - // vm.warp(blockTimestamp); - // } - - // uint48 timestamp = - // uint48(hintStruct.back ? blockTimestamp - hintStruct.secondsAgo : blockTimestamp + hintStruct.secondsAgo); - - // VaultHints vaultHints = new VaultHints(); - // bytes memory hint = vaultHints.activeBalanceOfHints(address(vault), alice, timestamp); - - // GasStruct memory gasStruct = GasStruct({gasSpent1: 1, gasSpent2: 1}); - // bytes memory activeBalanceOfHints = abi.encode( - // IVault.ActiveBalanceOfHints({ - // activeSharesOfHint: abi.encode(activeBalanceOfHintsUint32.activeSharesOfHint), - // activeStakeHint: abi.encode(activeBalanceOfHintsUint32.activeStakeHint), - // activeSharesHint: abi.encode(activeBalanceOfHintsUint32.activeSharesHint) - // }) - // ); - // try vault.activeBalanceOfAt(alice, timestamp, activeBalanceOfHints) { - // gasStruct.gasSpent1 = vm.lastCallGas().gasTotalUsed; - // } catch { - // vault.activeBalanceOfAt(alice, timestamp, ""); - // gasStruct.gasSpent1 = vm.lastCallGas().gasTotalUsed; - // } - - // vault.activeBalanceOfAt(alice, timestamp, hint); - // gasStruct.gasSpent2 = vm.lastCallGas().gasTotalUsed; - // assertGe(gasStruct.gasSpent1, gasStruct.gasSpent2); - // } - - // function test_ActiveBalanceOfHintMany( - // uint256 amount1, - // uint48 epochDuration, - // HintStruct memory hintStruct - // ) public { - // amount1 = bound(amount1, 1, 1 * 10 ** 18); - // epochDuration = uint48(bound(epochDuration, 1, 7 days)); - // hintStruct.num = 500; - // hintStruct.secondsAgo = bound(hintStruct.secondsAgo, 0, 1_720_700_948); - - // uint256 blockTimestamp = vm.getBlockTimestamp(); - // blockTimestamp = blockTimestamp + 1_720_700_948; - // vm.warp(blockTimestamp); - - // vault = _getVault(epochDuration); - - // for (uint256 i; i < hintStruct.num; ++i) { - // _deposit(alice, amount1); - - // blockTimestamp = blockTimestamp + epochDuration; - // vm.warp(blockTimestamp); - // } - - // uint48 timestamp = - // uint48(hintStruct.back ? blockTimestamp - hintStruct.secondsAgo : blockTimestamp + hintStruct.secondsAgo); - - // VaultHints vaultHints = new VaultHints(); - // bytes memory hint = vaultHints.activeBalanceOfHints(address(vault), alice, timestamp); - - // GasStruct memory gasStruct = GasStruct({gasSpent1: 1, gasSpent2: 1}); - // vault.activeBalanceOfAt(alice, timestamp, ""); - // gasStruct.gasSpent1 = vm.lastCallGas().gasTotalUsed; - // vault.activeBalanceOfAt(alice, timestamp, hint); - // gasStruct.gasSpent2 = vm.lastCallGas().gasTotalUsed; - // assertGe(gasStruct.gasSpent1, gasStruct.gasSpent2); - - // assertLt(gasStruct.gasSpent1 - gasStruct.gasSpent2, 10_000); - // } - - function _getVault(uint48 epochDuration) internal returns (Vault) { + function _getVault(uint48 epochDuration) internal returns (IVaultFull) { address[] memory networkLimitSetRoleHolders = new address[](1); networkLimitSetRoleHolders[0] = alice; address[] memory operatorNetworkSharesSetRoleHolders = new address[](1); operatorNetworkSharesSetRoleHolders[0] = alice; - (address vault_,,) = vaultConfigurator.create( - IVaultConfigurator.InitParams({ - version: vaultFactory.lastVersion(), - owner: alice, - vaultParams: abi.encode( - IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: epochDuration, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }) - ), - delegatorIndex: 0, - delegatorParams: abi.encode( - INetworkRestakeDelegator.InitParams({ - baseParams: IBaseDelegator.BaseParams({ - defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice - }), - networkLimitSetRoleHolders: networkLimitSetRoleHolders, - operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders - }) - ), - withSlasher: false, - slasherIndex: 0, - slasherParams: abi.encode( - ISlasher.InitParams({baseParams: IBaseSlasher.BaseParams({isBurnerHook: false})}) - ) - }) + (IVaultFull vault_,,) = _createInitializedVault( + epochDuration, networkLimitSetRoleHolders, operatorNetworkSharesSetRoleHolders, vaultFactory.lastVersion(), address(0xdEaD), false, false, 0 ); - - return Vault(vault_); + return vault_; } function _getVaultAndDelegatorAndSlasher(uint48 epochDuration) internal - returns (Vault, FullRestakeDelegator, Slasher) + returns (IVaultFull, FullRestakeDelegator, Slasher) { address[] memory networkLimitSetRoleHolders = new address[](1); networkLimitSetRoleHolders[0] = alice; address[] memory operatorNetworkLimitSetRoleHolders = new address[](1); operatorNetworkLimitSetRoleHolders[0] = alice; - (address vault_, address delegator_, address slasher_) = vaultConfigurator.create( - IVaultConfigurator.InitParams({ - version: vaultFactory.lastVersion(), - owner: alice, - vaultParams: abi.encode( - IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: epochDuration, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }) - ), - delegatorIndex: 1, - delegatorParams: abi.encode( - IFullRestakeDelegator.InitParams({ - baseParams: IBaseDelegator.BaseParams({ - defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice - }), - networkLimitSetRoleHolders: networkLimitSetRoleHolders, - operatorNetworkLimitSetRoleHolders: operatorNetworkLimitSetRoleHolders - }) - ), - withSlasher: true, - slasherIndex: 0, - slasherParams: abi.encode( - ISlasher.InitParams({baseParams: IBaseSlasher.BaseParams({isBurnerHook: false})}) - ) - }) + (IVaultFull vault_, address delegator_, address slasher_) = _createInitializedVault( + epochDuration, networkLimitSetRoleHolders, operatorNetworkLimitSetRoleHolders, vaultFactory.lastVersion(), address(0xdEaD), false, false, 0 ); - return (Vault(vault_), FullRestakeDelegator(delegator_), Slasher(slasher_)); + return (IVaultFull(vault_), FullRestakeDelegator(delegator_), Slasher(slasher_)); } function _registerOperator(address user) internal { @@ -2653,25 +2329,25 @@ contract VaultTest is Test { vm.stopPrank(); } - function _grantDepositorWhitelistRole(address user, address account) internal { + function _grantDepositorWhitelistRole(address user, address account) internal virtual { vm.startPrank(user); Vault(address(vault)).grantRole(vault.DEPOSITOR_WHITELIST_ROLE(), account); vm.stopPrank(); } - function _grantDepositWhitelistSetRole(address user, address account) internal { + function _grantDepositWhitelistSetRole(address user, address account) internal virtual { vm.startPrank(user); Vault(address(vault)).grantRole(vault.DEPOSIT_WHITELIST_SET_ROLE(), account); vm.stopPrank(); } - function _grantIsDepositLimitSetRole(address user, address account) internal { + function _grantIsDepositLimitSetRole(address user, address account) internal virtual { vm.startPrank(user); Vault(address(vault)).grantRole(vault.IS_DEPOSIT_LIMIT_SET_ROLE(), account); vm.stopPrank(); } - function _grantDepositLimitSetRole(address user, address account) internal { + function _grantDepositLimitSetRole(address user, address account) internal virtual { vm.startPrank(user); Vault(address(vault)).grantRole(vault.DEPOSIT_LIMIT_SET_ROLE(), account); vm.stopPrank(); @@ -2787,4 +2463,67 @@ contract VaultTest is Test { delegator.setMaxNetworkLimit(identifier, amount); vm.stopPrank(); } + + function _createVaultImpl(address delegatorFactory, address slasherFactory, address vaultFactory) + internal + virtual + returns (address) + { + return address(new Vault(delegatorFactory, slasherFactory, vaultFactory)); + } + + function _createInitializedVault( + uint48 epochDuration, + address[] memory networkLimitSetRoleHolders, + address[] memory operatorNetworkSharesSetRoleHolders, + uint64 version, + address burner, + bool depositWhitelist, + bool isDepositLimit, + uint256 depositLimit + ) internal virtual returns (IVaultFull, address, address) { + (address vault_, address delegator_, address slasher_) = vaultConfigurator.create( + IVaultConfigurator.InitParams({ + version: version, + owner: address(0), + vaultParams: abi.encode( + IVault.InitParams({ + collateral: address(collateral), + burner: burner, + epochDuration: epochDuration, + depositWhitelist: depositWhitelist, + isDepositLimit: isDepositLimit, + depositLimit: depositLimit, + defaultAdminRoleHolder: alice, + depositWhitelistSetRoleHolder: alice, + depositorWhitelistRoleHolder: alice, + isDepositLimitSetRoleHolder: alice, + depositLimitSetRoleHolder: alice + }) + ), + delegatorIndex: 1, + delegatorParams: abi.encode( + INetworkRestakeDelegator.InitParams({ + baseParams: IBaseDelegator.BaseParams({ + defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice + }), + networkLimitSetRoleHolders: networkLimitSetRoleHolders, + operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders + }) + ), + withSlasher: true, + slasherIndex: 0, + slasherParams: abi.encode( + ISlasher.InitParams({baseParams: IBaseSlasher.BaseParams({isBurnerHook: false})}) + ) + }) + ); + + return (IVaultFull(vault_), address(delegator_), address(slasher_)); + } + + function _getEncodedVaultParams(IVault.InitParams memory params) internal virtual pure returns (bytes memory) { + return abi.encode(params); + } + } diff --git a/test/vault/VaultTokenized.t.sol b/test/vault/VaultTokenized.t.sol index d5b17cd8..1b9c7da2 100644 --- a/test/vault/VaultTokenized.t.sol +++ b/test/vault/VaultTokenized.t.sol @@ -1,2803 +1,70 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import {Test, console2} from "forge-std/Test.sol"; - -import {VaultFactory} from "../../src/contracts/VaultFactory.sol"; -import {DelegatorFactory} from "../../src/contracts/DelegatorFactory.sol"; -import {SlasherFactory} from "../../src/contracts/SlasherFactory.sol"; -import {NetworkRegistry} from "../../src/contracts/NetworkRegistry.sol"; -import {OperatorRegistry} from "../../src/contracts/OperatorRegistry.sol"; -import {MetadataService} from "../../src/contracts/service/MetadataService.sol"; -import {NetworkMiddlewareService} from "../../src/contracts/service/NetworkMiddlewareService.sol"; -import {OptInService} from "../../src/contracts/service/OptInService.sol"; - -import {VaultTokenized} from "../../src/contracts/vault/VaultTokenized.sol"; -import {NetworkRestakeDelegator} from "../../src/contracts/delegator/NetworkRestakeDelegator.sol"; -import {FullRestakeDelegator} from "../../src/contracts/delegator/FullRestakeDelegator.sol"; -import {OperatorSpecificDelegator} from "../../src/contracts/delegator/OperatorSpecificDelegator.sol"; -import {OperatorNetworkSpecificDelegator} from "../../src/contracts/delegator/OperatorNetworkSpecificDelegator.sol"; -import {Slasher} from "../../src/contracts/slasher/Slasher.sol"; -import {VetoSlasher} from "../../src/contracts/slasher/VetoSlasher.sol"; - -import {IVault} from "../../src/interfaces/vault/IVault.sol"; -import {IVaultTokenized} from "../../src/interfaces/vault/IVaultTokenized.sol"; - -import {Token} from "../mocks/Token.sol"; -import {FeeOnTransferToken} from "../mocks/FeeOnTransferToken.sol"; -import {VaultConfigurator} from "../../src/contracts/VaultConfigurator.sol"; -import {IVaultConfigurator} from "../../src/interfaces/IVaultConfigurator.sol"; -import {INetworkRestakeDelegator} from "../../src/interfaces/delegator/INetworkRestakeDelegator.sol"; -import {IFullRestakeDelegator} from "../../src/interfaces/delegator/IFullRestakeDelegator.sol"; -import {IBaseDelegator} from "../../src/interfaces/delegator/IBaseDelegator.sol"; -import {ISlasher} from "../../src/interfaces/slasher/ISlasher.sol"; -import {IBaseSlasher} from "../../src/interfaces/slasher/IBaseSlasher.sol"; - -import {IVaultStorage} from "../../src/interfaces/vault/IVaultStorage.sol"; -import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; - -import {VaultHints} from "../../src/contracts/hints/VaultHints.sol"; -import {Subnetwork} from "../../src/contracts/libraries/Subnetwork.sol"; - -contract VaultTokenizedTest is Test { - using Math for uint256; - using Subnetwork for bytes32; - using Subnetwork for address; - - address owner; - address alice; - uint256 alicePrivateKey; - address bob; - uint256 bobPrivateKey; - - VaultFactory vaultFactory; - DelegatorFactory delegatorFactory; - SlasherFactory slasherFactory; - NetworkRegistry networkRegistry; - OperatorRegistry operatorRegistry; - MetadataService operatorMetadataService; - MetadataService networkMetadataService; - NetworkMiddlewareService networkMiddlewareService; - OptInService operatorVaultOptInService; - OptInService operatorNetworkOptInService; - - Token collateral; - FeeOnTransferToken feeOnTransferCollateral; - VaultConfigurator vaultConfigurator; - - VaultTokenized vault; - FullRestakeDelegator delegator; - Slasher slasher; - - function setUp() public { - owner = address(this); - (alice, alicePrivateKey) = makeAddrAndKey("alice"); - (bob, bobPrivateKey) = makeAddrAndKey("bob"); - - vaultFactory = new VaultFactory(owner); - delegatorFactory = new DelegatorFactory(owner); - slasherFactory = new SlasherFactory(owner); - networkRegistry = new NetworkRegistry(); - operatorRegistry = new OperatorRegistry(); - operatorMetadataService = new MetadataService(address(operatorRegistry)); - networkMetadataService = new MetadataService(address(networkRegistry)); - networkMiddlewareService = new NetworkMiddlewareService(address(networkRegistry)); - operatorVaultOptInService = - new OptInService(address(operatorRegistry), address(vaultFactory), "OperatorVaultOptInService"); - operatorNetworkOptInService = - new OptInService(address(operatorRegistry), address(networkRegistry), "OperatorNetworkOptInService"); - - address vaultImpl = - address(new VaultTokenized(address(delegatorFactory), address(slasherFactory), address(vaultFactory))); - vaultFactory.whitelist(vaultImpl); - - address networkRestakeDelegatorImpl = address( - new NetworkRestakeDelegator( - address(networkRegistry), - address(vaultFactory), - address(operatorVaultOptInService), - address(operatorNetworkOptInService), - address(delegatorFactory), - delegatorFactory.totalTypes() - ) - ); - delegatorFactory.whitelist(networkRestakeDelegatorImpl); - - address fullRestakeDelegatorImpl = address( - new FullRestakeDelegator( - address(networkRegistry), - address(vaultFactory), - address(operatorVaultOptInService), - address(operatorNetworkOptInService), - address(delegatorFactory), - delegatorFactory.totalTypes() - ) - ); - delegatorFactory.whitelist(fullRestakeDelegatorImpl); - - address operatorSpecificDelegatorImpl = address( - new OperatorSpecificDelegator( - address(operatorRegistry), - address(networkRegistry), - address(vaultFactory), - address(operatorVaultOptInService), - address(operatorNetworkOptInService), - address(delegatorFactory), - delegatorFactory.totalTypes() - ) - ); - delegatorFactory.whitelist(operatorSpecificDelegatorImpl); - - address operatorNetworkSpecificDelegatorImpl = address( - new OperatorNetworkSpecificDelegator( - address(operatorRegistry), - address(networkRegistry), - address(vaultFactory), - address(operatorVaultOptInService), - address(operatorNetworkOptInService), - address(delegatorFactory), - delegatorFactory.totalTypes() - ) - ); - delegatorFactory.whitelist(operatorNetworkSpecificDelegatorImpl); - - address slasherImpl = address( - new Slasher( - address(vaultFactory), - address(networkMiddlewareService), - address(slasherFactory), - slasherFactory.totalTypes() - ) - ); - slasherFactory.whitelist(slasherImpl); - - address vetoSlasherImpl = address( - new VetoSlasher( - address(vaultFactory), - address(networkMiddlewareService), - address(networkRegistry), - address(slasherFactory), - slasherFactory.totalTypes() - ) - ); - slasherFactory.whitelist(vetoSlasherImpl); - - collateral = new Token("Token"); - feeOnTransferCollateral = new FeeOnTransferToken("FeeOnTransferToken"); - - vaultConfigurator = - new VaultConfigurator(address(vaultFactory), address(delegatorFactory), address(slasherFactory)); - } - - struct TestCreate2LocalVariables { - address burner; - uint48 epochDuration; - bool depositWhitelist; - bool isDepositLimit; - uint256 depositLimit; - uint256 blockTimestamp; - address[] networkLimitSetRoleHolders; - address[] operatorNetworkSharesSetRoleHolders; - address vault_; - address delegator_; - } - - function test_Create2( - address burner, - uint48 epochDuration, - bool depositWhitelist, - bool isDepositLimit, - uint256 depositLimit - ) public { - TestCreate2LocalVariables memory vars; - - // Bound parameters - vars.burner = burner; - vars.epochDuration = uint48(bound(epochDuration, 1, 50 weeks)); - vars.depositWhitelist = depositWhitelist; - vars.isDepositLimit = isDepositLimit; - vars.depositLimit = depositLimit; - - // Setup block timestamp - vars.blockTimestamp = vm.getBlockTimestamp(); - vars.blockTimestamp = vars.blockTimestamp + 1_720_700_948; - vm.warp(vars.blockTimestamp); - - // Create role holders - vars.networkLimitSetRoleHolders = new address[](1); - vars.networkLimitSetRoleHolders[0] = alice; - vars.operatorNetworkSharesSetRoleHolders = new address[](1); - vars.operatorNetworkSharesSetRoleHolders[0] = alice; - - // Create vault and delegator - (vars.vault_, vars.delegator_,) = vaultConfigurator.create( - IVaultConfigurator.InitParams({ - version: vaultFactory.lastVersion(), - owner: address(0), - vaultParams: abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: vars.burner, - epochDuration: vars.epochDuration, - depositWhitelist: vars.depositWhitelist, - isDepositLimit: vars.isDepositLimit, - depositLimit: vars.depositLimit, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ), - delegatorIndex: 0, - delegatorParams: abi.encode( - INetworkRestakeDelegator.InitParams({ - baseParams: IBaseDelegator.BaseParams({ - defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice - }), - networkLimitSetRoleHolders: vars.networkLimitSetRoleHolders, - operatorNetworkSharesSetRoleHolders: vars.operatorNetworkSharesSetRoleHolders - }) - ), - withSlasher: false, - slasherIndex: 0, - slasherParams: abi.encode( - ISlasher.InitParams({baseParams: IBaseSlasher.BaseParams({isBurnerHook: false})}) - ) - }) - ); - - vault = VaultTokenized(vars.vault_); - - // Test role constants - assertEq(vault.DEPOSIT_WHITELIST_SET_ROLE(), keccak256("DEPOSIT_WHITELIST_SET_ROLE")); - assertEq(vault.DEPOSITOR_WHITELIST_ROLE(), keccak256("DEPOSITOR_WHITELIST_ROLE")); - assertEq(vault.DELEGATOR_FACTORY(), address(delegatorFactory)); - assertEq(vault.SLASHER_FACTORY(), address(slasherFactory)); - - // Test vault properties - assertEq(vault.owner(), address(0)); - assertEq(vault.collateral(), address(collateral)); - assertEq(vault.delegator(), vars.delegator_); - assertEq(vault.slasher(), address(0)); - assertEq(vault.burner(), vars.burner); - assertEq(vault.epochDuration(), vars.epochDuration); - assertEq(vault.depositWhitelist(), vars.depositWhitelist); - assertEq(vault.hasRole(vault.DEFAULT_ADMIN_ROLE(), alice), true); - assertEq(vault.hasRole(vault.DEPOSITOR_WHITELIST_ROLE(), alice), true); - assertEq(vault.epochDurationInit(), vars.blockTimestamp); - assertEq(vault.epochDuration(), vars.epochDuration); - - // Test epoch functionality - vm.expectRevert(IVaultStorage.InvalidTimestamp.selector); - assertEq(vault.epochAt(0), 0); - assertEq(vault.epochAt(uint48(vars.blockTimestamp)), 0); - assertEq(vault.currentEpoch(), 0); - assertEq(vault.currentEpochStart(), vars.blockTimestamp); - vm.expectRevert(IVaultStorage.NoPreviousEpoch.selector); - vault.previousEpochStart(); - assertEq(vault.nextEpochStart(), vars.blockTimestamp + vars.epochDuration); - - // Test stake and shares - assertEq(vault.totalStake(), 0); - assertEq(vault.activeSharesAt(uint48(vars.blockTimestamp), ""), 0); - assertEq(vault.activeShares(), 0); - assertEq(vault.activeStakeAt(uint48(vars.blockTimestamp), ""), 0); - assertEq(vault.activeStake(), 0); - assertEq(vault.activeSharesOfAt(alice, uint48(vars.blockTimestamp), ""), 0); - assertEq(vault.activeSharesOf(alice), 0); - assertEq(vault.activeBalanceOfAt(alice, uint48(vars.blockTimestamp), ""), 0); - assertEq(vault.activeBalanceOf(alice), 0); - - // Test withdrawals - assertEq(vault.withdrawals(0), 0); - assertEq(vault.withdrawalShares(0), 0); - assertEq(vault.isWithdrawalsClaimed(0, alice), false); - - // Test whitelist and slashing - assertEq(vault.depositWhitelist(), vars.depositWhitelist); - assertEq(vault.isDepositorWhitelisted(alice), false); - assertEq(vault.slashableBalanceOf(alice), 0); - - // Test initialization status - assertEq(vault.isDelegatorInitialized(), true); - assertEq(vault.isSlasherInitialized(), true); - assertEq(vault.isInitialized(), true); - - // Test epoch transitions - vars.blockTimestamp = vars.blockTimestamp + vault.epochDuration() - 1; - vm.warp(vars.blockTimestamp); - - assertEq(vault.epochAt(uint48(vars.blockTimestamp)), 0); - assertEq(vault.epochAt(uint48(vars.blockTimestamp + 1)), 1); - assertEq(vault.currentEpoch(), 0); - assertEq(vault.currentEpochStart(), vars.blockTimestamp - (vault.epochDuration() - 1)); - vm.expectRevert(IVaultStorage.NoPreviousEpoch.selector); - vault.previousEpochStart(); - assertEq(vault.nextEpochStart(), vars.blockTimestamp + 1); - - vars.blockTimestamp = vars.blockTimestamp + 1; - vm.warp(vars.blockTimestamp); - - assertEq(vault.epochAt(uint48(vars.blockTimestamp)), 1); - assertEq(vault.epochAt(uint48(vars.blockTimestamp + 2 * vault.epochDuration())), 3); - assertEq(vault.currentEpoch(), 1); - assertEq(vault.currentEpochStart(), vars.blockTimestamp); - assertEq(vault.previousEpochStart(), vars.blockTimestamp - vault.epochDuration()); - assertEq(vault.nextEpochStart(), vars.blockTimestamp + vault.epochDuration()); - - vars.blockTimestamp = vars.blockTimestamp + vault.epochDuration() - 1; - vm.warp(vars.blockTimestamp); - - assertEq(vault.epochAt(uint48(vars.blockTimestamp)), 1); - assertEq(vault.epochAt(uint48(vars.blockTimestamp + 1)), 2); - assertEq(vault.currentEpoch(), 1); - assertEq(vault.currentEpochStart(), vars.blockTimestamp - (vault.epochDuration() - 1)); - assertEq(vault.previousEpochStart(), vars.blockTimestamp - (vault.epochDuration() - 1) - vault.epochDuration()); - assertEq(vault.nextEpochStart(), vars.blockTimestamp + 1); - - // Test ERC20 functionality - assertEq(vault.balanceOf(alice), 0); - assertEq(vault.totalSupply(), 0); - assertEq(vault.allowance(alice, alice), 0); - assertEq(vault.decimals(), collateral.decimals()); - assertEq(vault.symbol(), "TEST"); - assertEq(vault.name(), "Test"); - } - - function test_CreateRevertInvalidEpochDuration() public { - uint48 epochDuration = 0; - - address[] memory networkLimitSetRoleHolders = new address[](1); - networkLimitSetRoleHolders[0] = alice; - address[] memory operatorNetworkSharesSetRoleHolders = new address[](1); - operatorNetworkSharesSetRoleHolders[0] = alice; - uint64 lastVersion = vaultFactory.lastVersion(); - vm.expectRevert(IVault.InvalidEpochDuration.selector); - vaultConfigurator.create( - IVaultConfigurator.InitParams({ - version: lastVersion, - owner: alice, - vaultParams: abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: epochDuration, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ), - delegatorIndex: 0, - delegatorParams: abi.encode( - INetworkRestakeDelegator.InitParams({ - baseParams: IBaseDelegator.BaseParams({ - defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice - }), - networkLimitSetRoleHolders: networkLimitSetRoleHolders, - operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders - }) - ), - withSlasher: false, - slasherIndex: 0, - slasherParams: abi.encode( - ISlasher.InitParams({baseParams: IBaseSlasher.BaseParams({isBurnerHook: false})}) - ) - }) - ); - } - - function test_CreateRevertInvalidCollateral(uint48 epochDuration) public { - epochDuration = uint48(bound(epochDuration, 1, 50 weeks)); - - address[] memory networkLimitSetRoleHolders = new address[](1); - networkLimitSetRoleHolders[0] = alice; - address[] memory operatorNetworkSharesSetRoleHolders = new address[](1); - operatorNetworkSharesSetRoleHolders[0] = alice; - uint64 lastVersion = vaultFactory.lastVersion(); - vm.expectRevert(IVault.InvalidCollateral.selector); - vaultConfigurator.create( - IVaultConfigurator.InitParams({ - version: lastVersion, - owner: alice, - vaultParams: abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(0), - burner: address(0xdEaD), - epochDuration: epochDuration, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ), - delegatorIndex: 0, - delegatorParams: abi.encode( - INetworkRestakeDelegator.InitParams({ - baseParams: IBaseDelegator.BaseParams({ - defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice - }), - networkLimitSetRoleHolders: networkLimitSetRoleHolders, - operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders - }) - ), - withSlasher: false, - slasherIndex: 0, - slasherParams: abi.encode( - ISlasher.InitParams({baseParams: IBaseSlasher.BaseParams({isBurnerHook: false})}) - ) - }) - ); - } - - function test_CreateRevertMissingRoles1(uint48 epochDuration) public { - epochDuration = uint48(bound(epochDuration, 1, 50 weeks)); - - uint64 lastVersion = vaultFactory.lastVersion(); - - vm.expectRevert(IVault.MissingRoles.selector); - vault = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: epochDuration, - depositWhitelist: true, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: address(0), - depositWhitelistSetRoleHolder: address(0), - depositorWhitelistRoleHolder: address(0), - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: address(0) - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - } - - function test_CreateRevertMissingRoles2(uint48 epochDuration) public { - epochDuration = uint48(bound(epochDuration, 1, 50 weeks)); - - uint64 lastVersion = vaultFactory.lastVersion(); - - vm.expectRevert(IVault.MissingRoles.selector); - vault = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: epochDuration, - depositWhitelist: false, - isDepositLimit: true, - depositLimit: 0, - defaultAdminRoleHolder: address(0), - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: address(0), - isDepositLimitSetRoleHolder: address(0), - depositLimitSetRoleHolder: address(0) - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - } - - function test_CreateRevertMissingRoles3(uint48 epochDuration) public { - epochDuration = uint48(bound(epochDuration, 1, 50 weeks)); - - uint64 lastVersion = vaultFactory.lastVersion(); - - vm.expectRevert(IVault.MissingRoles.selector); - vault = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: epochDuration, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: address(0), - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: address(0), - isDepositLimitSetRoleHolder: address(0), - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - } - - function test_CreateRevertMissingRoles4(uint48 epochDuration) public { - epochDuration = uint48(bound(epochDuration, 1, 50 weeks)); - - uint64 lastVersion = vaultFactory.lastVersion(); - - vm.expectRevert(IVault.MissingRoles.selector); - vault = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: epochDuration, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 1, - defaultAdminRoleHolder: address(0), - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: address(0), - isDepositLimitSetRoleHolder: address(0), - depositLimitSetRoleHolder: address(0) - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - } - - function test_CreateRevertMissingRoles5(uint48 epochDuration) public { - epochDuration = uint48(bound(epochDuration, 1, 50 weeks)); - - uint64 lastVersion = vaultFactory.lastVersion(); - - vm.expectRevert(IVault.MissingRoles.selector); - vault = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: epochDuration, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: address(0), - depositWhitelistSetRoleHolder: address(0), - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: address(0) - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - } - - function test_SetDelegator() public { - uint64 lastVersion = vaultFactory.lastVersion(); - - vault = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: 7 days, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - - assertEq(vault.isDelegatorInitialized(), false); - - address[] memory networkLimitSetRoleHolders = new address[](1); - networkLimitSetRoleHolders[0] = alice; - address[] memory operatorNetworkLimitSetRoleHolders = new address[](1); - operatorNetworkLimitSetRoleHolders[0] = alice; - delegator = FullRestakeDelegator( - delegatorFactory.create( - 1, - abi.encode( - address(vault), - abi.encode( - IFullRestakeDelegator.InitParams({ - baseParams: IBaseDelegator.BaseParams({ - defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice - }), - networkLimitSetRoleHolders: networkLimitSetRoleHolders, - operatorNetworkLimitSetRoleHolders: operatorNetworkLimitSetRoleHolders - }) - ) - ) - ) - ); - - vault.setDelegator(address(delegator)); - - assertEq(vault.delegator(), address(delegator)); - assertEq(vault.isDelegatorInitialized(), true); - assertEq(vault.isInitialized(), false); - } - - function test_SetDelegatorRevertDelegatorAlreadyInitialized() public { - uint64 lastVersion = vaultFactory.lastVersion(); - - vault = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: 7 days, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - - address[] memory networkLimitSetRoleHolders = new address[](1); - networkLimitSetRoleHolders[0] = alice; - address[] memory operatorNetworkLimitSetRoleHolders = new address[](1); - operatorNetworkLimitSetRoleHolders[0] = alice; - delegator = FullRestakeDelegator( - delegatorFactory.create( - 1, - abi.encode( - address(vault), - abi.encode( - IFullRestakeDelegator.InitParams({ - baseParams: IBaseDelegator.BaseParams({ - defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice - }), - networkLimitSetRoleHolders: networkLimitSetRoleHolders, - operatorNetworkLimitSetRoleHolders: operatorNetworkLimitSetRoleHolders - }) - ) - ) - ) - ); - - vault.setDelegator(address(delegator)); - - vm.expectRevert(IVault.DelegatorAlreadyInitialized.selector); - vault.setDelegator(address(delegator)); - } - - function test_SetDelegatorRevertNotDelegator() public { - uint64 lastVersion = vaultFactory.lastVersion(); - - vault = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: 7 days, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - - vm.expectRevert(IVault.NotDelegator.selector); - vault.setDelegator(address(1)); - } - - function test_SetDelegatorRevertInvalidDelegator() public { - uint64 lastVersion = vaultFactory.lastVersion(); - - vault = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: 7 days, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - - VaultTokenized vault2 = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: 7 days, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - - address[] memory networkLimitSetRoleHolders = new address[](1); - networkLimitSetRoleHolders[0] = alice; - address[] memory operatorNetworkLimitSetRoleHolders = new address[](1); - operatorNetworkLimitSetRoleHolders[0] = alice; - delegator = FullRestakeDelegator( - delegatorFactory.create( - 1, - abi.encode( - address(vault2), - abi.encode( - IFullRestakeDelegator.InitParams({ - baseParams: IBaseDelegator.BaseParams({ - defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice - }), - networkLimitSetRoleHolders: networkLimitSetRoleHolders, - operatorNetworkLimitSetRoleHolders: operatorNetworkLimitSetRoleHolders - }) - ) - ) - ) - ); - - vm.expectRevert(IVault.InvalidDelegator.selector); - vault.setDelegator(address(delegator)); - } - - function test_SetSlasher() public { - uint64 lastVersion = vaultFactory.lastVersion(); - - vault = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: 7 days, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - - assertEq(vault.isSlasherInitialized(), false); - - slasher = Slasher( - slasherFactory.create( - 0, - abi.encode( - address(vault), - abi.encode(ISlasher.InitParams({baseParams: IBaseSlasher.BaseParams({isBurnerHook: false})})) - ) - ) - ); - - vault.setSlasher(address(slasher)); - - assertEq(vault.slasher(), address(slasher)); - assertEq(vault.isSlasherInitialized(), true); - assertEq(vault.isInitialized(), false); - } - - function test_SetSlasherRevertSlasherAlreadyInitialized() public { - uint64 lastVersion = vaultFactory.lastVersion(); - - vault = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: 7 days, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - - slasher = Slasher( - slasherFactory.create( - 0, - abi.encode( - address(vault), - abi.encode(ISlasher.InitParams({baseParams: IBaseSlasher.BaseParams({isBurnerHook: false})})) - ) - ) - ); - - vault.setSlasher(address(slasher)); - - vm.expectRevert(IVault.SlasherAlreadyInitialized.selector); - vault.setSlasher(address(slasher)); - } - - function test_SetSlasherRevertNotSlasher() public { - uint64 lastVersion = vaultFactory.lastVersion(); - - vault = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: 7 days, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - - slasher = Slasher( - slasherFactory.create( - 0, - abi.encode( - address(vault), - abi.encode(ISlasher.InitParams({baseParams: IBaseSlasher.BaseParams({isBurnerHook: false})})) - ) - ) - ); - - vm.expectRevert(IVault.NotSlasher.selector); - vault.setSlasher(address(1)); - } - - function test_SetSlasherRevertInvalidSlasher() public { - uint64 lastVersion = vaultFactory.lastVersion(); - - vault = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: 7 days, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - - VaultTokenized vault2 = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: 7 days, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - - slasher = Slasher( - slasherFactory.create( - 0, - abi.encode( - address(vault2), - abi.encode(ISlasher.InitParams({baseParams: IBaseSlasher.BaseParams({isBurnerHook: false})})) - ) - ) - ); - - vm.expectRevert(IVault.InvalidSlasher.selector); - vault.setSlasher(address(slasher)); - } - - function test_SetSlasherZeroAddress() public { - uint64 lastVersion = vaultFactory.lastVersion(); - - vault = VaultTokenized( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: 7 days, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ) - ) - ); - - vault.setSlasher(address(0)); - } - - function test_DepositTwice(uint256 amount1, uint256 amount2) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - amount2 = bound(amount2, 1, 100 * 10 ** 18); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - uint256 tokensBefore = collateral.balanceOf(address(vault)); - uint256 shares1 = amount1 * 10 ** 0; - { - (uint256 depositedAmount, uint256 mintedShares) = _deposit(alice, amount1); - assertEq(depositedAmount, amount1); - assertEq(mintedShares, shares1); - - assertEq(vault.balanceOf(alice), shares1); - assertEq(vault.totalSupply(), shares1); - } - assertEq(collateral.balanceOf(address(vault)) - tokensBefore, amount1); - - assertEq(vault.totalStake(), amount1); - assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), 0); - assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares1); - assertEq(vault.activeShares(), shares1); - assertEq(vault.activeStakeAt(uint48(blockTimestamp - 1), ""), 0); - assertEq(vault.activeStakeAt(uint48(blockTimestamp), ""), amount1); - assertEq(vault.activeStake(), amount1); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1), ""), 0); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp), ""), shares1); - assertEq(vault.activeSharesOf(alice), shares1); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), 0); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1); - assertEq(vault.activeBalanceOf(alice), amount1); - assertEq(vault.slashableBalanceOf(alice), amount1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - uint256 shares2 = amount2 * (shares1 + 10 ** 0) / (amount1 + 1); - { - (uint256 depositedAmount, uint256 mintedShares) = _deposit(alice, amount2); - assertEq(depositedAmount, amount2); - assertEq(mintedShares, shares2); - - assertEq(vault.balanceOf(alice), shares1 + shares2); - assertEq(vault.totalSupply(), shares1 + shares2); - } - - assertEq(vault.totalStake(), amount1 + amount2); - assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares1); - assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares1 + shares2); - assertEq(vault.activeShares(), shares1 + shares2); - uint256 gasLeft = gasleft(); - assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), abi.encode(1)), shares1); - uint256 gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), abi.encode(0)), shares1); - assertGt(gasSpent, gasLeft - gasleft()); - gasLeft = gasleft(); - assertEq(vault.activeSharesAt(uint48(blockTimestamp), abi.encode(0)), shares1 + shares2); - gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq(vault.activeSharesAt(uint48(blockTimestamp), abi.encode(1)), shares1 + shares2); - assertGt(gasSpent, gasLeft - gasleft()); - assertEq(vault.activeStakeAt(uint48(blockTimestamp - 1), ""), amount1); - assertEq(vault.activeStakeAt(uint48(blockTimestamp), ""), amount1 + amount2); - assertEq(vault.activeStake(), amount1 + amount2); - gasLeft = gasleft(); - assertEq(vault.activeStakeAt(uint48(blockTimestamp - 1), abi.encode(1)), amount1); - gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq(vault.activeStakeAt(uint48(blockTimestamp - 1), abi.encode(0)), amount1); - assertGt(gasSpent, gasLeft - gasleft()); - gasLeft = gasleft(); - assertEq(vault.activeStakeAt(uint48(blockTimestamp), abi.encode(0)), amount1 + amount2); - gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq(vault.activeStakeAt(uint48(blockTimestamp), abi.encode(1)), amount1 + amount2); - assertGt(gasSpent, gasLeft - gasleft()); - assertEq(vault.activeStakeAt(uint48(blockTimestamp - 1), ""), shares1); - assertEq(vault.activeStakeAt(uint48(blockTimestamp), ""), shares1 + shares2); - assertEq(vault.activeSharesOf(alice), shares1 + shares2); - gasLeft = gasleft(); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1), abi.encode(1)), shares1); - gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1), abi.encode(0)), shares1); - assertGt(gasSpent, gasLeft - gasleft()); - gasLeft = gasleft(); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp), abi.encode(0)), shares1 + shares2); - gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp), abi.encode(1)), shares1 + shares2); - assertGt(gasSpent, gasLeft - gasleft()); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), amount1); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 + amount2); - assertEq(vault.activeBalanceOf(alice), amount1 + amount2); - assertEq(vault.slashableBalanceOf(alice), amount1 + amount2); - gasLeft = gasleft(); - assertEq( - vault.activeBalanceOfAt( - alice, - uint48(blockTimestamp - 1), - abi.encode( - IVault.ActiveBalanceOfHints({ - activeSharesOfHint: abi.encode(1), - activeStakeHint: abi.encode(1), - activeSharesHint: abi.encode(1) - }) - ) - ), - amount1 - ); - gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq( - vault.activeBalanceOfAt( - alice, - uint48(blockTimestamp - 1), - abi.encode( - IVault.ActiveBalanceOfHints({ - activeSharesOfHint: abi.encode(0), - activeStakeHint: abi.encode(0), - activeSharesHint: abi.encode(0) - }) - ) - ), - amount1 - ); - assertGt(gasSpent, gasLeft - gasleft()); - gasLeft = gasleft(); - assertEq( - vault.activeBalanceOfAt( - alice, - uint48(blockTimestamp), - abi.encode( - IVault.ActiveBalanceOfHints({ - activeSharesOfHint: abi.encode(0), - activeStakeHint: abi.encode(0), - activeSharesHint: abi.encode(0) - }) - ) - ), - amount1 + amount2 - ); - gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq( - vault.activeBalanceOfAt( - alice, - uint48(blockTimestamp), - abi.encode( - IVault.ActiveBalanceOfHints({ - activeSharesOfHint: abi.encode(1), - activeStakeHint: abi.encode(1), - activeSharesHint: abi.encode(1) - }) - ) - ), - amount1 + amount2 - ); - assertGt(gasSpent, gasLeft - gasleft()); - } - - function test_DepositTwiceFeeOnTransferCollateral(uint256 amount1, uint256 amount2) public { - amount1 = bound(amount1, 2, 100 * 10 ** 18); - amount2 = bound(amount2, 2, 100 * 10 ** 18); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - uint48 epochDuration = 1; - { - address[] memory networkLimitSetRoleHolders = new address[](1); - networkLimitSetRoleHolders[0] = alice; - address[] memory operatorNetworkSharesSetRoleHolders = new address[](1); - operatorNetworkSharesSetRoleHolders[0] = alice; - (address vault_,,) = vaultConfigurator.create( - IVaultConfigurator.InitParams({ - version: vaultFactory.lastVersion(), - owner: alice, - vaultParams: abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(feeOnTransferCollateral), - burner: address(0xdEaD), - epochDuration: epochDuration, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ), - delegatorIndex: 0, - delegatorParams: abi.encode( - INetworkRestakeDelegator.InitParams({ - baseParams: IBaseDelegator.BaseParams({ - defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice - }), - networkLimitSetRoleHolders: networkLimitSetRoleHolders, - operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders - }) - ), - withSlasher: false, - slasherIndex: 0, - slasherParams: "" - }) - ); - - vault = VaultTokenized(vault_); - } - - uint256 tokensBefore = feeOnTransferCollateral.balanceOf(address(vault)); - uint256 shares1 = (amount1 - 1) * 10 ** 0; - feeOnTransferCollateral.transfer(alice, amount1 + 1); - vm.startPrank(alice); - feeOnTransferCollateral.approve(address(vault), amount1); - { - (uint256 depositedAmount, uint256 mintedShares) = vault.deposit(alice, amount1); - assertEq(depositedAmount, amount1 - 1); - assertEq(mintedShares, shares1); - } - vm.stopPrank(); - assertEq(feeOnTransferCollateral.balanceOf(address(vault)) - tokensBefore, amount1 - 1); - - assertEq(vault.totalStake(), amount1 - 1); - assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), 0); - assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares1); - assertEq(vault.activeShares(), shares1); - assertEq(vault.activeStakeAt(uint48(blockTimestamp - 1), ""), 0); - assertEq(vault.activeStakeAt(uint48(blockTimestamp), ""), amount1 - 1); - assertEq(vault.activeStake(), amount1 - 1); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1), ""), 0); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp), ""), shares1); - assertEq(vault.activeSharesOf(alice), shares1); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), 0); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 - 1); - assertEq(vault.activeBalanceOf(alice), amount1 - 1); - assertEq(vault.slashableBalanceOf(alice), amount1 - 1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - uint256 shares2 = (amount2 - 1) * (shares1 + 10 ** 0) / (amount1 - 1 + 1); - feeOnTransferCollateral.transfer(alice, amount2 + 1); - vm.startPrank(alice); - feeOnTransferCollateral.approve(address(vault), amount2); - { - (uint256 depositedAmount, uint256 mintedShares) = vault.deposit(alice, amount2); - assertEq(depositedAmount, amount2 - 1); - assertEq(mintedShares, shares2); - } - vm.stopPrank(); - - assertEq(vault.totalStake(), amount1 - 1 + amount2 - 1); - assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares1); - assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares1 + shares2); - assertEq(vault.activeShares(), shares1 + shares2); - uint256 gasLeft = gasleft(); - assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), abi.encode(1)), shares1); - uint256 gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), abi.encode(0)), shares1); - assertGt(gasSpent, gasLeft - gasleft()); - gasLeft = gasleft(); - assertEq(vault.activeSharesAt(uint48(blockTimestamp), abi.encode(0)), shares1 + shares2); - gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq(vault.activeSharesAt(uint48(blockTimestamp), abi.encode(1)), shares1 + shares2); - assertGt(gasSpent, gasLeft - gasleft()); - assertEq(vault.activeStakeAt(uint48(blockTimestamp - 1), ""), amount1 - 1); - assertEq(vault.activeStakeAt(uint48(blockTimestamp), ""), amount1 - 1 + amount2 - 1); - assertEq(vault.activeStake(), amount1 - 1 + amount2 - 1); - gasLeft = gasleft(); - assertEq(vault.activeStakeAt(uint48(blockTimestamp - 1), abi.encode(1)), amount1 - 1); - gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq(vault.activeStakeAt(uint48(blockTimestamp - 1), abi.encode(0)), amount1 - 1); - assertGt(gasSpent, gasLeft - gasleft()); - gasLeft = gasleft(); - assertEq(vault.activeStakeAt(uint48(blockTimestamp), abi.encode(0)), amount1 - 1 + amount2 - 1); - gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq(vault.activeStakeAt(uint48(blockTimestamp), abi.encode(1)), amount1 - 1 + amount2 - 1); - assertGt(gasSpent, gasLeft - gasleft()); - assertEq(vault.activeStakeAt(uint48(blockTimestamp - 1), ""), shares1); - assertEq(vault.activeStakeAt(uint48(blockTimestamp), ""), shares1 + shares2); - assertEq(vault.activeSharesOf(alice), shares1 + shares2); - gasLeft = gasleft(); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1), abi.encode(1)), shares1); - gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1), abi.encode(0)), shares1); - assertGt(gasSpent, gasLeft - gasleft()); - gasLeft = gasleft(); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp), abi.encode(0)), shares1 + shares2); - gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp), abi.encode(1)), shares1 + shares2); - assertGt(gasSpent, gasLeft - gasleft()); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), amount1 - 1); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 - 1 + amount2 - 1); - assertEq(vault.activeBalanceOf(alice), amount1 - 1 + amount2 - 1); - assertEq(vault.slashableBalanceOf(alice), amount1 - 1 + amount2 - 1); - gasLeft = gasleft(); - assertEq( - vault.activeBalanceOfAt( - alice, - uint48(blockTimestamp - 1), - abi.encode( - IVault.ActiveBalanceOfHints({ - activeSharesOfHint: abi.encode(1), - activeStakeHint: abi.encode(1), - activeSharesHint: abi.encode(1) - }) - ) - ), - amount1 - 1 - ); - gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq( - vault.activeBalanceOfAt( - alice, - uint48(blockTimestamp - 1), - abi.encode( - IVault.ActiveBalanceOfHints({ - activeSharesOfHint: abi.encode(0), - activeStakeHint: abi.encode(0), - activeSharesHint: abi.encode(0) - }) - ) - ), - amount1 - 1 - ); - assertGt(gasSpent, gasLeft - gasleft()); - gasLeft = gasleft(); - assertEq( - vault.activeBalanceOfAt( - alice, - uint48(blockTimestamp), - abi.encode( - IVault.ActiveBalanceOfHints({ - activeSharesOfHint: abi.encode(0), - activeStakeHint: abi.encode(0), - activeSharesHint: abi.encode(0) - }) - ) - ), - amount1 - 1 + amount2 - 1 - ); - gasSpent = gasLeft - gasleft(); - gasLeft = gasleft(); - assertEq( - vault.activeBalanceOfAt( - alice, - uint48(blockTimestamp), - abi.encode( - IVault.ActiveBalanceOfHints({ - activeSharesOfHint: abi.encode(1), - activeStakeHint: abi.encode(1), - activeSharesHint: abi.encode(1) - }) - ) - ), - amount1 - 1 + amount2 - 1 - ); - assertGt(gasSpent, gasLeft - gasleft()); - } - - function test_DepositBoth(uint256 amount1, uint256 amount2) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - amount2 = bound(amount2, 1, 100 * 10 ** 18); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - uint256 shares1 = amount1 * 10 ** 0; - { - (uint256 depositedAmount, uint256 mintedShares) = _deposit(alice, amount1); - assertEq(depositedAmount, amount1); - assertEq(mintedShares, shares1); - - assertEq(vault.balanceOf(alice), shares1); - assertEq(vault.totalSupply(), shares1); - } - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - uint256 shares2 = amount2 * (shares1 + 10 ** 0) / (amount1 + 1); - { - (uint256 depositedAmount, uint256 mintedShares) = _deposit(bob, amount2); - assertEq(depositedAmount, amount2); - assertEq(mintedShares, shares2); - - assertEq(vault.balanceOf(bob), shares2); - assertEq(vault.totalSupply(), shares1 + shares2); - } - - assertEq(vault.totalStake(), amount1 + amount2); - assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares1); - assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares1 + shares2); - assertEq(vault.activeShares(), shares1 + shares2); - assertEq(vault.activeStakeAt(uint48(blockTimestamp - 1), ""), amount1); - assertEq(vault.activeStakeAt(uint48(blockTimestamp), ""), amount1 + amount2); - assertEq(vault.activeStake(), amount1 + amount2); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1), ""), shares1); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp), ""), shares1); - assertEq(vault.activeSharesOf(alice), shares1); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), amount1); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1); - assertEq(vault.activeBalanceOf(alice), amount1); - assertEq(vault.slashableBalanceOf(alice), amount1); - assertEq(vault.activeSharesOfAt(bob, uint48(blockTimestamp - 1), ""), 0); - assertEq(vault.activeSharesOfAt(bob, uint48(blockTimestamp), ""), shares2); - assertEq(vault.activeSharesOf(bob), shares2); - assertEq(vault.activeBalanceOfAt(bob, uint48(blockTimestamp - 1), ""), 0); - assertEq(vault.activeBalanceOfAt(bob, uint48(blockTimestamp), ""), amount2); - assertEq(vault.activeBalanceOf(bob), amount2); - assertEq(vault.slashableBalanceOf(bob), amount2); - } - - function test_DepositRevertInvalidOnBehalfOf(uint256 amount1) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - vm.startPrank(alice); - vm.expectRevert(IVault.InvalidOnBehalfOf.selector); - vault.deposit(address(0), amount1); - vm.stopPrank(); - } - - function test_DepositRevertInsufficientDeposit() public { - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - vm.startPrank(alice); - vm.expectRevert(IVault.InsufficientDeposit.selector); - vault.deposit(alice, 0); - vm.stopPrank(); - } - - function test_WithdrawTwice(uint256 amount1, uint256 amount2, uint256 amount3) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - amount2 = bound(amount2, 1, 100 * 10 ** 18); - amount3 = bound(amount3, 1, 100 * 10 ** 18); - vm.assume(amount1 >= amount2 + amount3); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - // uint48 epochDuration = 1; - vault = _getVault(1); - - (, uint256 shares) = _deposit(alice, amount1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - uint256 burnedShares = amount2 * (shares + 10 ** 0) / (amount1 + 1); - uint256 mintedShares = amount2 * 10 ** 0; - (uint256 burnedShares_, uint256 mintedShares_) = _withdraw(alice, amount2); - assertEq(burnedShares_, burnedShares); - assertEq(mintedShares_, mintedShares); - - assertEq(vault.balanceOf(alice), amount1 - burnedShares_); - assertEq(vault.totalSupply(), amount1 - burnedShares_); - - assertEq(vault.totalStake(), amount1); - assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares); - assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares - burnedShares); - assertEq(vault.activeShares(), shares - burnedShares); - assertEq(vault.activeStakeAt(uint48(blockTimestamp - 1), ""), amount1); - assertEq(vault.activeStakeAt(uint48(blockTimestamp), ""), amount1 - amount2); - assertEq(vault.activeStake(), amount1 - amount2); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1), ""), shares); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp), ""), shares - burnedShares); - assertEq(vault.activeSharesOf(alice), shares - burnedShares); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), amount1); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 - amount2); - assertEq(vault.activeBalanceOf(alice), amount1 - amount2); - assertEq(vault.withdrawals(vault.currentEpoch()), 0); - assertEq(vault.withdrawals(vault.currentEpoch() + 1), amount2); - assertEq(vault.withdrawals(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch()), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 1), mintedShares); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch(), alice), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 1, alice), mintedShares); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 2, alice), 0); - assertEq(vault.slashableBalanceOf(alice), amount1); - - shares -= burnedShares; - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - burnedShares = amount3 * (shares + 10 ** 0) / (amount1 - amount2 + 1); - mintedShares = amount3 * 10 ** 0; - (burnedShares_, mintedShares_) = _withdraw(alice, amount3); - assertEq(burnedShares_, burnedShares); - assertEq(mintedShares_, mintedShares); - - assertEq(vault.balanceOf(alice), amount1 - amount2 - amount3); - assertEq(vault.totalSupply(), amount1 - amount2 - amount3); - - assertEq(vault.totalStake(), amount1); - assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares); - assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares - burnedShares); - assertEq(vault.activeShares(), shares - burnedShares); - assertEq(vault.activeStakeAt(uint48(blockTimestamp - 1), ""), amount1 - amount2); - assertEq(vault.activeStakeAt(uint48(blockTimestamp), ""), amount1 - amount2 - amount3); - assertEq(vault.activeStake(), amount1 - amount2 - amount3); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1), ""), shares); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp), ""), shares - burnedShares); - assertEq(vault.activeSharesOf(alice), shares - burnedShares); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), amount1 - amount2); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 - amount2 - amount3); - assertEq(vault.activeBalanceOf(alice), amount1 - amount2 - amount3); - assertEq(vault.withdrawals(vault.currentEpoch() - 1), 0); - assertEq(vault.withdrawals(vault.currentEpoch()), amount2); - assertEq(vault.withdrawals(vault.currentEpoch() + 1), amount3); - assertEq(vault.withdrawals(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() - 1), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch()), amount2 * 10 ** 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 1), amount3 * 10 ** 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() - 1, alice), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch(), alice), amount2 * 10 ** 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 1, alice), amount3 * 10 ** 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 2, alice), 0); - assertEq(vault.slashableBalanceOf(alice), amount1); - - shares -= burnedShares; - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - assertEq(vault.totalStake(), amount1 - amount2); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - assertEq(vault.totalStake(), amount1 - amount2 - amount3); - } - - function test_WithdrawRevertInvalidClaimer(uint256 amount1) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - vm.expectRevert(IVault.InvalidClaimer.selector); - vm.startPrank(alice); - vault.withdraw(address(0), amount1); - vm.stopPrank(); - } - - function test_WithdrawRevertInsufficientWithdrawal(uint256 amount1) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - vm.expectRevert(IVault.InsufficientWithdrawal.selector); - _withdraw(alice, 0); - } - - function test_WithdrawRevertTooMuchWithdraw(uint256 amount1) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - vm.expectRevert(IVault.TooMuchWithdraw.selector); - _withdraw(alice, amount1 + 1); - } - - function test_RedeemTwice(uint256 amount1, uint256 amount2, uint256 amount3) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - amount2 = bound(amount2, 1, 100 * 10 ** 18); - amount3 = bound(amount3, 1, 100 * 10 ** 18); - vm.assume(amount1 >= amount2 + amount3); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - // uint48 epochDuration = 1; - vault = _getVault(1); - - (, uint256 shares) = _deposit(alice, amount1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - uint256 withdrawnAssets2 = amount2 * (amount1 + 1) / (shares + 10 ** 0); - uint256 mintedShares = amount2 * 10 ** 0; - (uint256 withdrawnAssets_, uint256 mintedShares_) = _redeem(alice, amount2); - assertEq(withdrawnAssets_, withdrawnAssets2); - assertEq(mintedShares_, mintedShares); - - assertEq(vault.totalStake(), amount1); - assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares); - assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares - amount2); - assertEq(vault.activeShares(), shares - amount2); - assertEq(vault.activeStakeAt(uint48(blockTimestamp - 1), ""), amount1); - assertEq(vault.activeStakeAt(uint48(blockTimestamp), ""), amount1 - withdrawnAssets2); - assertEq(vault.activeStake(), amount1 - withdrawnAssets2); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1), ""), shares); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp), ""), shares - amount2); - assertEq(vault.activeSharesOf(alice), shares - amount2); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), amount1); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 - withdrawnAssets2); - assertEq(vault.activeBalanceOf(alice), amount1 - withdrawnAssets2); - assertEq(vault.withdrawals(vault.currentEpoch()), 0); - assertEq(vault.withdrawals(vault.currentEpoch() + 1), withdrawnAssets2); - assertEq(vault.withdrawals(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch()), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 1), mintedShares); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch(), alice), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 1, alice), mintedShares); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 2, alice), 0); - assertEq(vault.slashableBalanceOf(alice), amount1); - - shares -= amount2; - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - uint256 withdrawnAssets3 = amount3 * (amount1 - withdrawnAssets2 + 1) / (shares + 10 ** 0); - mintedShares = amount3 * 10 ** 0; - (withdrawnAssets_, mintedShares_) = _redeem(alice, amount3); - assertEq(withdrawnAssets_, withdrawnAssets3); - assertEq(mintedShares_, mintedShares); - - assertEq(vault.totalStake(), amount1); - assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares); - assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares - amount3); - assertEq(vault.activeShares(), shares - amount3); - assertEq(vault.activeStakeAt(uint48(blockTimestamp - 1), ""), amount1 - withdrawnAssets2); - assertEq(vault.activeStakeAt(uint48(blockTimestamp), ""), amount1 - withdrawnAssets2 - withdrawnAssets3); - assertEq(vault.activeStake(), amount1 - withdrawnAssets2 - withdrawnAssets3); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1), ""), shares); - assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp), ""), shares - amount3); - assertEq(vault.activeSharesOf(alice), shares - amount3); - assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), amount1 - withdrawnAssets2); - assertEq( - vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 - withdrawnAssets2 - withdrawnAssets3 - ); - assertEq(vault.activeBalanceOf(alice), amount1 - withdrawnAssets2 - withdrawnAssets3); - assertEq(vault.withdrawals(vault.currentEpoch() - 1), 0); - assertEq(vault.withdrawals(vault.currentEpoch()), withdrawnAssets2); - assertEq(vault.withdrawals(vault.currentEpoch() + 1), withdrawnAssets3); - assertEq(vault.withdrawals(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() - 1), 0); - assertEq(vault.withdrawalShares(vault.currentEpoch()), withdrawnAssets2 * 10 ** 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 1), withdrawnAssets3 * 10 ** 0); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 2), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() - 1, alice), 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch(), alice), withdrawnAssets2 * 10 ** 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 1, alice), withdrawnAssets3 * 10 ** 0); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 2, alice), 0); - assertEq(vault.slashableBalanceOf(alice), amount1); - - shares -= amount3; - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - assertEq(vault.totalStake(), amount1 - withdrawnAssets2); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - assertEq(vault.totalStake(), amount1 - withdrawnAssets2 - withdrawnAssets3); - } - - function test_RedeemRevertInvalidClaimer(uint256 amount1) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - vm.expectRevert(IVault.InvalidClaimer.selector); - vm.startPrank(alice); - vault.redeem(address(0), amount1); - vm.stopPrank(); - } - - function test_RedeemRevertInsufficientRedeemption(uint256 amount1) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - vm.expectRevert(IVault.InsufficientRedemption.selector); - _redeem(alice, 0); - } - - function test_RedeemRevertTooMuchRedeem(uint256 amount1) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - vm.expectRevert(IVault.TooMuchRedeem.selector); - _redeem(alice, amount1 + 1); - } - - function test_Claim(uint256 amount1, uint256 amount2) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - amount2 = bound(amount2, 1, 100 * 10 ** 18); - vm.assume(amount1 >= amount2); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount2); - - blockTimestamp = blockTimestamp + 2; - vm.warp(blockTimestamp); - - uint256 tokensBefore = collateral.balanceOf(address(vault)); - uint256 tokensBeforeAlice = collateral.balanceOf(alice); - assertEq(_claim(alice, vault.currentEpoch() - 1), amount2); - assertEq(tokensBefore - collateral.balanceOf(address(vault)), amount2); - assertEq(collateral.balanceOf(alice) - tokensBeforeAlice, amount2); - - assertEq(vault.isWithdrawalsClaimed(vault.currentEpoch() - 1, alice), true); - } - - function test_ClaimRevertInvalidRecipient(uint256 amount1, uint256 amount2) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - amount2 = bound(amount2, 1, 100 * 10 ** 18); - vm.assume(amount1 >= amount2); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount2); - - blockTimestamp = blockTimestamp + 2; - vm.warp(blockTimestamp); - - vm.startPrank(alice); - uint256 currentEpoch = vault.currentEpoch(); - vm.expectRevert(IVault.InvalidRecipient.selector); - vault.claim(address(0), currentEpoch - 1); - vm.stopPrank(); - } - - function test_ClaimRevertInvalidEpoch(uint256 amount1, uint256 amount2) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - amount2 = bound(amount2, 1, 100 * 10 ** 18); - vm.assume(amount1 >= amount2); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount2); - - blockTimestamp = blockTimestamp + 2; - vm.warp(blockTimestamp); - - uint256 currentEpoch = vault.currentEpoch(); - vm.expectRevert(IVault.InvalidEpoch.selector); - _claim(alice, currentEpoch); - } - - function test_ClaimRevertAlreadyClaimed(uint256 amount1, uint256 amount2) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - amount2 = bound(amount2, 1, 100 * 10 ** 18); - vm.assume(amount1 >= amount2); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount2); - - blockTimestamp = blockTimestamp + 2; - vm.warp(blockTimestamp); - - uint256 currentEpoch = vault.currentEpoch(); - _claim(alice, currentEpoch - 1); - - vm.expectRevert(IVault.AlreadyClaimed.selector); - _claim(alice, currentEpoch - 1); - } - - function test_ClaimRevertInsufficientClaim(uint256 amount1, uint256 amount2) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - amount2 = bound(amount2, 1, 100 * 10 ** 18); - vm.assume(amount1 >= amount2); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount2); - - blockTimestamp = blockTimestamp + 2; - vm.warp(blockTimestamp); - - uint256 currentEpoch = vault.currentEpoch(); - vm.expectRevert(IVault.InsufficientClaim.selector); - _claim(alice, currentEpoch - 2); - } - - function test_ClaimBatch(uint256 amount1, uint256 amount2, uint256 amount3) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - amount2 = bound(amount2, 1, 100 * 10 ** 18); - amount3 = bound(amount3, 1, 100 * 10 ** 18); - vm.assume(amount1 >= amount2 + amount3); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount2); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount3); - - blockTimestamp = blockTimestamp + 2; - vm.warp(blockTimestamp); - - uint256[] memory epochs = new uint256[](2); - epochs[0] = vault.currentEpoch() - 1; - epochs[1] = vault.currentEpoch() - 2; - - uint256 tokensBefore = collateral.balanceOf(address(vault)); - uint256 tokensBeforeAlice = collateral.balanceOf(alice); - assertEq(_claimBatch(alice, epochs), amount2 + amount3); - assertEq(tokensBefore - collateral.balanceOf(address(vault)), amount2 + amount3); - assertEq(collateral.balanceOf(alice) - tokensBeforeAlice, amount2 + amount3); - - assertEq(vault.isWithdrawalsClaimed(vault.currentEpoch() - 1, alice), true); - } - - function test_ClaimBatchRevertInvalidRecipient(uint256 amount1, uint256 amount2, uint256 amount3) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - amount2 = bound(amount2, 1, 100 * 10 ** 18); - amount3 = bound(amount3, 1, 100 * 10 ** 18); - vm.assume(amount1 >= amount2 + amount3); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount2); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount3); - - blockTimestamp = blockTimestamp + 2; - vm.warp(blockTimestamp); - - uint256[] memory epochs = new uint256[](2); - epochs[0] = vault.currentEpoch() - 1; - epochs[1] = vault.currentEpoch() - 2; - - vm.expectRevert(IVault.InvalidRecipient.selector); - vm.startPrank(alice); - vault.claimBatch(address(0), epochs); - vm.stopPrank(); - } - - function test_ClaimBatchRevertInvalidLengthEpochs(uint256 amount1, uint256 amount2, uint256 amount3) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - amount2 = bound(amount2, 1, 100 * 10 ** 18); - amount3 = bound(amount3, 1, 100 * 10 ** 18); - vm.assume(amount1 >= amount2 + amount3); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount2); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount3); - - blockTimestamp = blockTimestamp + 2; - vm.warp(blockTimestamp); - - uint256[] memory epochs = new uint256[](0); - vm.expectRevert(IVault.InvalidLengthEpochs.selector); - _claimBatch(alice, epochs); - } - - function test_ClaimBatchRevertInvalidEpoch(uint256 amount1, uint256 amount2, uint256 amount3) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - amount2 = bound(amount2, 1, 100 * 10 ** 18); - amount3 = bound(amount3, 1, 100 * 10 ** 18); - vm.assume(amount1 >= amount2 + amount3); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount2); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount3); - - blockTimestamp = blockTimestamp + 2; - vm.warp(blockTimestamp); - - uint256[] memory epochs = new uint256[](2); - epochs[0] = vault.currentEpoch() - 1; - epochs[1] = vault.currentEpoch(); - - vm.expectRevert(IVault.InvalidEpoch.selector); - _claimBatch(alice, epochs); - } - - function test_ClaimBatchRevertAlreadyClaimed(uint256 amount1, uint256 amount2, uint256 amount3) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - amount2 = bound(amount2, 1, 100 * 10 ** 18); - amount3 = bound(amount3, 1, 100 * 10 ** 18); - vm.assume(amount1 >= amount2 + amount3); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount2); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount3); - - blockTimestamp = blockTimestamp + 2; - vm.warp(blockTimestamp); - - uint256[] memory epochs = new uint256[](2); - epochs[0] = vault.currentEpoch() - 1; - epochs[1] = vault.currentEpoch() - 1; - - vm.expectRevert(IVault.AlreadyClaimed.selector); - _claimBatch(alice, epochs); - } - - function test_ClaimBatchRevertInsufficientClaim(uint256 amount1, uint256 amount2, uint256 amount3) public { - amount1 = bound(amount1, 1, 100 * 10 ** 18); - amount2 = bound(amount2, 1, 100 * 10 ** 18); - amount3 = bound(amount3, 1, 100 * 10 ** 18); - vm.assume(amount1 >= amount2 + amount3); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - uint48 epochDuration = 1; - vault = _getVault(epochDuration); - - _deposit(alice, amount1); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount2); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - _withdraw(alice, amount3); - - blockTimestamp = blockTimestamp + 2; - vm.warp(blockTimestamp); - - uint256[] memory epochs = new uint256[](2); - epochs[0] = vault.currentEpoch() - 1; - epochs[1] = vault.currentEpoch() - 3; - - vm.expectRevert(IVault.InsufficientClaim.selector); - _claimBatch(alice, epochs); - } - - function test_SetDepositWhitelist() public { - uint48 epochDuration = 1; - - vault = _getVault(epochDuration); - - _grantDepositWhitelistSetRole(alice, alice); - _setDepositWhitelist(alice, true); - assertEq(vault.depositWhitelist(), true); - - _setDepositWhitelist(alice, false); - assertEq(vault.depositWhitelist(), false); - } - - function test_SetDepositWhitelistRevertNotWhitelistedDepositor() public { - uint48 epochDuration = 1; - - vault = _getVault(epochDuration); - - _deposit(alice, 1); - - _grantDepositWhitelistSetRole(alice, alice); - _setDepositWhitelist(alice, true); - - vm.startPrank(alice); - vm.expectRevert(IVault.NotWhitelistedDepositor.selector); - vault.deposit(alice, 1); - vm.stopPrank(); - } - - function test_SetDepositWhitelistRevertAlreadySet() public { - uint48 epochDuration = 1; - - vault = _getVault(epochDuration); - - _grantDepositWhitelistSetRole(alice, alice); - _setDepositWhitelist(alice, true); - - vm.expectRevert(IVault.AlreadySet.selector); - _setDepositWhitelist(alice, true); - } - - function test_SetDepositorWhitelistStatus() public { - uint48 epochDuration = 1; - - vault = _getVault(epochDuration); - - _grantDepositWhitelistSetRole(alice, alice); - _setDepositWhitelist(alice, true); - - _grantDepositorWhitelistRole(alice, alice); - - _setDepositorWhitelistStatus(alice, bob, true); - assertEq(vault.isDepositorWhitelisted(bob), true); - - _deposit(bob, 1); - - _setDepositWhitelist(alice, false); - - _deposit(bob, 1); - } - - function test_SetDepositorWhitelistStatusRevertInvalidAccount() public { - uint48 epochDuration = 1; - - vault = _getVault(epochDuration); - - _grantDepositWhitelistSetRole(alice, alice); - _setDepositWhitelist(alice, true); - - _grantDepositorWhitelistRole(alice, alice); - - vm.expectRevert(IVault.InvalidAccount.selector); - _setDepositorWhitelistStatus(alice, address(0), true); - } - - function test_SetDepositorWhitelistStatusRevertAlreadySet() public { - uint48 epochDuration = 1; - - vault = _getVault(epochDuration); - - _grantDepositWhitelistSetRole(alice, alice); - _setDepositWhitelist(alice, true); - - _grantDepositorWhitelistRole(alice, alice); - - _setDepositorWhitelistStatus(alice, bob, true); - - vm.expectRevert(IVault.AlreadySet.selector); - _setDepositorWhitelistStatus(alice, bob, true); - } - - function test_SetIsDepositLimit() public { - uint48 epochDuration = 1; - - vault = _getVault(epochDuration); - - _grantIsDepositLimitSetRole(alice, alice); - _setIsDepositLimit(alice, true); - assertEq(vault.isDepositLimit(), true); - - _setIsDepositLimit(alice, false); - assertEq(vault.isDepositLimit(), false); - } - - function test_SetIsDepositLimitRevertAlreadySet() public { - uint48 epochDuration = 1; - - vault = _getVault(epochDuration); - - _grantIsDepositLimitSetRole(alice, alice); - _setIsDepositLimit(alice, true); - - vm.expectRevert(IVault.AlreadySet.selector); - _setIsDepositLimit(alice, true); - } - - function test_SetDepositLimit(uint256 limit1, uint256 limit2, uint256 depositAmount) public { - uint48 epochDuration = 1; - - vault = _getVault(epochDuration); - - _grantIsDepositLimitSetRole(alice, alice); - _setIsDepositLimit(alice, true); - assertEq(vault.depositLimit(), 0); - - limit1 = bound(limit1, 1, type(uint256).max); - _grantDepositLimitSetRole(alice, alice); - _setDepositLimit(alice, limit1); - assertEq(vault.depositLimit(), limit1); - - limit2 = bound(limit2, 1, 1000 ether); - vm.assume(limit2 != limit1); - _setDepositLimit(alice, limit2); - assertEq(vault.depositLimit(), limit2); - - depositAmount = bound(depositAmount, 1, limit2); - _deposit(alice, depositAmount); - } - - function test_SetDepositLimitToNull(uint256 limit1) public { - uint48 epochDuration = 1; - - vault = _getVault(epochDuration); - - limit1 = bound(limit1, 1, type(uint256).max); - _grantIsDepositLimitSetRole(alice, alice); - _setIsDepositLimit(alice, true); - _grantDepositLimitSetRole(alice, alice); - _setDepositLimit(alice, limit1); - - _setIsDepositLimit(alice, false); - - _setDepositLimit(alice, 0); +import {IVaultTokenized} from "../../src/interfaces/vault/IVaultTokenized.sol"; +import {VaultTokenized} from "../../src/contracts/vault/VaultTokenized.sol"; +import "./Vault.t.sol"; - assertEq(vault.depositLimit(), 0); +contract VaultTokenizedTest is VaultTest { + function setUp() public override { + super.setUp(); } - function test_SetDepositLimitRevertDepositLimitReached(uint256 depositAmount, uint256 limit) public { - uint48 epochDuration = 1; - - vault = _getVault(epochDuration); - - _deposit(alice, 1); - - limit = bound(limit, 2, 1000 ether); - _grantIsDepositLimitSetRole(alice, alice); - _setIsDepositLimit(alice, true); - _grantDepositLimitSetRole(alice, alice); - _setDepositLimit(alice, limit); - - depositAmount = bound(depositAmount, limit, 2000 ether); + function test_Create2( + address burner, + uint48 epochDuration, + bool depositWhitelist, + bool isDepositLimit, + uint256 depositLimit + ) public override { + epochDuration = uint48(bound(epochDuration, 1, 50 weeks)); + super.test_Create2(burner, epochDuration, depositWhitelist, isDepositLimit, depositLimit); - collateral.transfer(alice, depositAmount); - vm.startPrank(alice); - collateral.approve(address(vault), depositAmount); - vm.expectRevert(IVault.DepositLimitReached.selector); - vault.deposit(alice, depositAmount); - vm.stopPrank(); + assertEq(vault.balanceOf(alice), 0); + assertEq(vault.totalSupply(), 0); + assertEq(vault.allowance(alice, alice), 0); + assertEq(vault.decimals(), collateral.decimals()); + assertEq(vault.symbol(), "TEST"); + assertEq(vault.name(), "Test"); } - function test_SetDepositLimitRevertAlreadySet(uint256 limit) public { - uint48 epochDuration = 1; - - vault = _getVault(epochDuration); + function test_DepositTwice(uint256 amount1, uint256 amount2) public override { + amount1 = bound(amount1, 1, 100 * 10 ** 18); + amount2 = bound(amount2, 1, 100 * 10 ** 18); - limit = bound(limit, 1, type(uint256).max); - _grantIsDepositLimitSetRole(alice, alice); - _setIsDepositLimit(alice, true); - _grantDepositLimitSetRole(alice, alice); - _setDepositLimit(alice, limit); + super.test_DepositTwice(amount1, amount2); + uint256 shares1 = amount1 * 10 ** 0; + uint256 shares2 = amount2 * (shares1 + 10 ** 0) / (amount1 + 1); - vm.expectRevert(IVault.AlreadySet.selector); - _setDepositLimit(alice, limit); + assertEq(vault.balanceOf(alice), shares1 + shares2); + assertEq(vault.totalSupply(), shares1 + shares2); } - function test_OnSlashRevertNotSlasher() public { - uint48 epochDuration = 1; - - vault = _getVault(epochDuration); + function test_DepositBoth(uint256 amount1, uint256 amount2) public override { + amount1 = bound(amount1, 1, 100 * 10 ** 18); + amount2 = bound(amount2, 1, 100 * 10 ** 18); - vm.startPrank(alice); - vm.expectRevert(IVault.NotSlasher.selector); - vault.onSlash(0, 0); - vm.stopPrank(); - } + super.test_DepositBoth(amount1, amount2); + uint256 shares1 = amount1 * 10 ** 0; + uint256 shares2 = amount2 * (shares1 + 10 ** 0) / (amount1 + 1); - struct Test_SlashStruct { - uint256 slashAmountReal1; - uint256 tokensBeforeBurner; - uint256 activeStake1; - uint256 withdrawals1; - uint256 nextWithdrawals1; - uint256 slashAmountSlashed2; + assertEq(vault.balanceOf(alice), shares1); + assertEq(vault.balanceOf(bob), shares2); + assertEq(vault.totalSupply(), shares1 + shares2); } - function test_Slash( - // uint48 epochDuration, - uint256 depositAmount, - uint256 withdrawAmount1, - uint256 withdrawAmount2, - uint256 slashAmount1, - uint256 slashAmount2, - uint256 captureAgo - ) public { - // epochDuration = uint48(bound(epochDuration, 2, 10 days)); - depositAmount = bound(depositAmount, 1, 100 * 10 ** 18); - withdrawAmount1 = bound(withdrawAmount1, 1, 100 * 10 ** 18); - withdrawAmount2 = bound(withdrawAmount2, 1, 100 * 10 ** 18); - slashAmount1 = bound(slashAmount1, 1, type(uint256).max / 2); - slashAmount2 = bound(slashAmount2, 1, type(uint256).max / 2); - captureAgo = bound(captureAgo, 1, 10 days); - vm.assume(depositAmount > withdrawAmount1 + withdrawAmount2); - vm.assume(depositAmount > slashAmount1); - vm.assume(captureAgo <= 7 days); - - uint256 blockTimestamp = vm.getBlockTimestamp(); - blockTimestamp = blockTimestamp + 1_720_700_948; - vm.warp(blockTimestamp); - - (vault, delegator, slasher) = _getVaultAndDelegatorAndSlasher(7 days); - - // address network = alice; - _registerNetwork(alice, alice); - _setMaxNetworkLimit(alice, 0, type(uint256).max); - - _registerOperator(alice); - _registerOperator(bob); - - _optInOperatorVault(alice); - _optInOperatorVault(bob); - - _optInOperatorNetwork(alice, address(alice)); - _optInOperatorNetwork(bob, address(alice)); - - _setNetworkLimit(alice, alice, type(uint256).max); - - _setOperatorNetworkLimit(alice, alice, alice, type(uint256).max / 2); - _setOperatorNetworkLimit(alice, alice, bob, type(uint256).max / 2); - - _deposit(alice, depositAmount); - _withdraw(alice, withdrawAmount1); - - blockTimestamp = blockTimestamp + vault.epochDuration(); - vm.warp(blockTimestamp); - - _withdraw(alice, withdrawAmount2); - - assertEq(vault.totalStake(), depositAmount); - assertEq(vault.activeStake(), depositAmount - withdrawAmount1 - withdrawAmount2); - assertEq(vault.withdrawals(vault.currentEpoch()), withdrawAmount1); - assertEq(vault.withdrawals(vault.currentEpoch() + 1), withdrawAmount2); - - blockTimestamp = blockTimestamp + 1; - vm.warp(blockTimestamp); - - Test_SlashStruct memory test_SlashStruct; - - if (vault.epochAt(uint48(blockTimestamp - captureAgo)) != vault.currentEpoch()) { - test_SlashStruct.slashAmountReal1 = Math.min(slashAmount1, depositAmount - withdrawAmount1); - test_SlashStruct.tokensBeforeBurner = collateral.balanceOf(address(vault.burner())); - assertEq( - _slash(alice, alice, alice, slashAmount1, uint48(blockTimestamp - captureAgo), ""), - test_SlashStruct.slashAmountReal1 - ); - assertEq( - collateral.balanceOf(address(vault.burner())) - test_SlashStruct.tokensBeforeBurner, - test_SlashStruct.slashAmountReal1 - ); - - test_SlashStruct.activeStake1 = depositAmount - withdrawAmount1 - withdrawAmount2 - - (depositAmount - withdrawAmount1 - withdrawAmount2) - .mulDiv(test_SlashStruct.slashAmountReal1, depositAmount); - test_SlashStruct.withdrawals1 = - withdrawAmount1 - withdrawAmount1.mulDiv(test_SlashStruct.slashAmountReal1, depositAmount); - test_SlashStruct.nextWithdrawals1 = - withdrawAmount2 - withdrawAmount2.mulDiv(test_SlashStruct.slashAmountReal1, depositAmount); - assertEq(vault.totalStake(), depositAmount - test_SlashStruct.slashAmountReal1); - assertTrue(test_SlashStruct.withdrawals1 - vault.withdrawals(vault.currentEpoch()) <= 2); - assertTrue(test_SlashStruct.nextWithdrawals1 - vault.withdrawals(vault.currentEpoch() + 1) <= 1); - assertEq(vault.activeStake(), test_SlashStruct.activeStake1); - - test_SlashStruct.slashAmountSlashed2 = Math.min( - depositAmount - test_SlashStruct.slashAmountReal1, - Math.min(slashAmount2, depositAmount - withdrawAmount1) - ); - test_SlashStruct.tokensBeforeBurner = collateral.balanceOf(address(vault.burner())); - assertEq( - _slash(alice, alice, bob, slashAmount2, uint48(blockTimestamp - captureAgo), ""), - Math.min(slashAmount2, depositAmount - withdrawAmount1) - ); - assertEq( - collateral.balanceOf(address(vault.burner())) - test_SlashStruct.tokensBeforeBurner, - test_SlashStruct.slashAmountSlashed2 - ); - - assertEq( - vault.totalStake(), - depositAmount - test_SlashStruct.slashAmountReal1 - test_SlashStruct.slashAmountSlashed2 - ); - assertTrue( - (test_SlashStruct.withdrawals1 - - test_SlashStruct.withdrawals1 - .mulDiv( - test_SlashStruct.slashAmountSlashed2, - depositAmount - test_SlashStruct.slashAmountReal1 - )) - vault.withdrawals(vault.currentEpoch()) <= 4 - ); - assertTrue( - (test_SlashStruct.nextWithdrawals1 - - test_SlashStruct.nextWithdrawals1 - .mulDiv( - test_SlashStruct.slashAmountSlashed2, - depositAmount - test_SlashStruct.slashAmountReal1 - )) - vault.withdrawals(vault.currentEpoch() + 1) <= 2 - ); - assertEq( - vault.activeStake(), - test_SlashStruct.activeStake1 - - test_SlashStruct.activeStake1 - .mulDiv(test_SlashStruct.slashAmountSlashed2, depositAmount - test_SlashStruct.slashAmountReal1) - ); - } else { - test_SlashStruct.slashAmountReal1 = - Math.min(slashAmount1, depositAmount - withdrawAmount1 - withdrawAmount2); - test_SlashStruct.tokensBeforeBurner = collateral.balanceOf(address(vault.burner())); - assertEq( - _slash(alice, alice, alice, slashAmount1, uint48(blockTimestamp - captureAgo), ""), - test_SlashStruct.slashAmountReal1 - ); - assertEq( - collateral.balanceOf(address(vault.burner())) - test_SlashStruct.tokensBeforeBurner, - test_SlashStruct.slashAmountReal1 - ); - - test_SlashStruct.activeStake1 = depositAmount - withdrawAmount1 - withdrawAmount2 - - (depositAmount - withdrawAmount1 - withdrawAmount2) - .mulDiv(test_SlashStruct.slashAmountReal1, depositAmount - withdrawAmount1); - test_SlashStruct.withdrawals1 = withdrawAmount1; - test_SlashStruct.nextWithdrawals1 = withdrawAmount2 - - withdrawAmount2.mulDiv(test_SlashStruct.slashAmountReal1, depositAmount - withdrawAmount1); - assertEq(vault.totalStake(), depositAmount - test_SlashStruct.slashAmountReal1); - assertEq(vault.withdrawals(vault.currentEpoch()), test_SlashStruct.withdrawals1); - assertTrue(test_SlashStruct.nextWithdrawals1 - vault.withdrawals(vault.currentEpoch() + 1) <= 1); - assertEq(vault.activeStake(), test_SlashStruct.activeStake1); + function test_WithdrawTwice(uint256 amount1, uint256 amount2, uint256 amount3) public override { + amount1 = bound(amount1, 1, 100 * 10 ** 18); + amount2 = bound(amount2, 1, 100 * 10 ** 18); + amount3 = bound(amount3, 1, 100 * 10 ** 18); + vm.assume(amount1 >= amount2 + amount3); - test_SlashStruct.slashAmountSlashed2 = Math.min( - depositAmount - withdrawAmount1 - test_SlashStruct.slashAmountReal1, - Math.min(slashAmount2, depositAmount - withdrawAmount1 - withdrawAmount2) - ); - test_SlashStruct.tokensBeforeBurner = collateral.balanceOf(address(vault.burner())); - assertEq( - _slash(alice, alice, bob, slashAmount2, uint48(blockTimestamp - captureAgo), ""), - Math.min(slashAmount2, depositAmount - withdrawAmount1 - withdrawAmount2) - ); - assertEq( - collateral.balanceOf(address(vault.burner())) - test_SlashStruct.tokensBeforeBurner, - test_SlashStruct.slashAmountSlashed2 - ); + super.test_WithdrawTwice(amount1, amount2, amount3); - assertEq( - vault.totalStake(), - depositAmount - test_SlashStruct.slashAmountReal1 - test_SlashStruct.slashAmountSlashed2 - ); - assertEq(vault.withdrawals(vault.currentEpoch()), test_SlashStruct.withdrawals1); - assertTrue( - (test_SlashStruct.nextWithdrawals1 - - test_SlashStruct.nextWithdrawals1 - .mulDiv( - test_SlashStruct.slashAmountSlashed2, - depositAmount - withdrawAmount1 - test_SlashStruct.slashAmountReal1 - )) - vault.withdrawals(vault.currentEpoch() + 1) <= 2 - ); - assertEq( - vault.activeStake(), - test_SlashStruct.activeStake1 - - test_SlashStruct.activeStake1 - .mulDiv( - test_SlashStruct.slashAmountSlashed2, - depositAmount - withdrawAmount1 - test_SlashStruct.slashAmountReal1 - ) - ); - } + assertEq(vault.balanceOf(alice), amount1 - amount2 - amount3); + assertEq(vault.totalSupply(), amount1 - amount2 - amount3); } - // struct GasStruct { - // uint256 gasSpent1; - // uint256 gasSpent2; - // } - - // struct HintStruct { - // uint256 num; - // bool back; - // uint256 secondsAgo; - // } - - // function test_ActiveSharesHint(uint256 amount1, uint48 epochDuration, HintStruct memory hintStruct) public { - // amount1 = bound(amount1, 1, 100 * 10 ** 18); - // epochDuration = uint48(bound(epochDuration, 1, 7 days)); - // hintStruct.num = bound(hintStruct.num, 0, 25); - // hintStruct.secondsAgo = bound(hintStruct.secondsAgo, 0, 1_720_700_948); - - // uint256 blockTimestamp = vm.getBlockTimestamp(); - // blockTimestamp = blockTimestamp + 1_720_700_948; - // vm.warp(blockTimestamp); - - // vault = _getVault(epochDuration); - - // for (uint256 i; i < hintStruct.num; ++i) { - // _deposit(alice, amount1); - - // blockTimestamp = blockTimestamp + epochDuration; - // vm.warp(blockTimestamp); - // } - - // uint48 timestamp = - // uint48(hintStruct.back ? blockTimestamp - hintStruct.secondsAgo : blockTimestamp + hintStruct.secondsAgo); - - // VaultHints vaultHints = new VaultHints(); - // bytes memory hint = vaultHints.activeSharesHint(address(vault), timestamp); - - // GasStruct memory gasStruct = GasStruct({gasSpent1: 1, gasSpent2: 1}); - // vault.activeSharesAt(timestamp, new bytes(0)); - // gasStruct.gasSpent1 = vm.lastCallGas().gasTotalUsed; - // vault.activeSharesAt(timestamp, hint); - // gasStruct.gasSpent2 = vm.lastCallGas().gasTotalUsed; - // assertApproxEqRel(gasStruct.gasSpent1, gasStruct.gasSpent2, 0.05e18); - // } - - // function test_ActiveStakeHint(uint256 amount1, uint48 epochDuration, HintStruct memory hintStruct) public { - // amount1 = bound(amount1, 1, 100 * 10 ** 18); - // epochDuration = uint48(bound(epochDuration, 1, 7 days)); - // hintStruct.num = bound(hintStruct.num, 0, 25); - // hintStruct.secondsAgo = bound(hintStruct.secondsAgo, 0, 1_720_700_948); - - // uint256 blockTimestamp = vm.getBlockTimestamp(); - // blockTimestamp = blockTimestamp + 1_720_700_948; - // vm.warp(blockTimestamp); - - // vault = _getVault(epochDuration); - - // for (uint256 i; i < hintStruct.num; ++i) { - // _deposit(alice, amount1); - - // blockTimestamp = blockTimestamp + epochDuration; - // vm.warp(blockTimestamp); - // } - - // uint48 timestamp = - // uint48(hintStruct.back ? blockTimestamp - hintStruct.secondsAgo : blockTimestamp + hintStruct.secondsAgo); - - // VaultHints vaultHints = new VaultHints(); - // bytes memory hint = vaultHints.activeStakeHint(address(vault), timestamp); - - // GasStruct memory gasStruct = GasStruct({gasSpent1: 1, gasSpent2: 1}); - // vault.activeStakeAt(timestamp, new bytes(0)); - // gasStruct.gasSpent1 = vm.lastCallGas().gasTotalUsed; - // vault.activeStakeAt(timestamp, hint); - // gasStruct.gasSpent2 = vm.lastCallGas().gasTotalUsed; - // assertGe(gasStruct.gasSpent1, gasStruct.gasSpent2); - // } - - // function test_ActiveSharesOfHint(uint256 amount1, uint48 epochDuration, HintStruct memory hintStruct) public { - // amount1 = bound(amount1, 1, 100 * 10 ** 18); - // epochDuration = uint48(bound(epochDuration, 1, 7 days)); - // hintStruct.num = bound(hintStruct.num, 0, 25); - // hintStruct.secondsAgo = bound(hintStruct.secondsAgo, 0, 1_720_700_948); - - // uint256 blockTimestamp = vm.getBlockTimestamp(); - // blockTimestamp = blockTimestamp + 1_720_700_948; - // vm.warp(blockTimestamp); - - // vault = _getVault(epochDuration); - - // for (uint256 i; i < hintStruct.num; ++i) { - // _deposit(alice, amount1); - - // blockTimestamp = blockTimestamp + epochDuration; - // vm.warp(blockTimestamp); - // } - - // uint48 timestamp = - // uint48(hintStruct.back ? blockTimestamp - hintStruct.secondsAgo : blockTimestamp + hintStruct.secondsAgo); - - // VaultHints vaultHints = new VaultHints(); - // bytes memory hint = vaultHints.activeSharesOfHint(address(vault), alice, timestamp); - - // GasStruct memory gasStruct = GasStruct({gasSpent1: 1, gasSpent2: 1}); - // vault.activeSharesOfAt(alice, timestamp, new bytes(0)); - // gasStruct.gasSpent1 = vm.lastCallGas().gasTotalUsed; - // vault.activeSharesOfAt(alice, timestamp, hint); - // gasStruct.gasSpent2 = vm.lastCallGas().gasTotalUsed; - // assertGe(gasStruct.gasSpent1, gasStruct.gasSpent2); - // } - - // struct ActiveBalanceOfHintsUint32 { - // uint32 activeSharesOfHint; - // uint32 activeStakeHint; - // uint32 activeSharesHint; - // } - - // function test_ActiveBalanceOfHint( - // uint256 amount1, - // uint48 epochDuration, - // HintStruct memory hintStruct, - // ActiveBalanceOfHintsUint32 memory activeBalanceOfHintsUint32 - // ) public { - // amount1 = bound(amount1, 1, 100 * 10 ** 18); - // epochDuration = uint48(bound(epochDuration, 1, 7 days)); - // hintStruct.num = bound(hintStruct.num, 0, 25); - // hintStruct.secondsAgo = bound(hintStruct.secondsAgo, 0, 1_720_700_948); - - // uint256 blockTimestamp = vm.getBlockTimestamp(); - // blockTimestamp = blockTimestamp + 1_720_700_948; - // vm.warp(blockTimestamp); - - // vault = _getVault(epochDuration); - - // for (uint256 i; i < hintStruct.num; ++i) { - // _deposit(alice, amount1); - - // blockTimestamp = blockTimestamp + epochDuration; - // vm.warp(blockTimestamp); - // } - - // uint48 timestamp = - // uint48(hintStruct.back ? blockTimestamp - hintStruct.secondsAgo : blockTimestamp + hintStruct.secondsAgo); - - // VaultHints vaultHints = new VaultHints(); - // bytes memory hint = vaultHints.activeBalanceOfHints(address(vault), alice, timestamp); - - // GasStruct memory gasStruct = GasStruct({gasSpent1: 1, gasSpent2: 1}); - // bytes memory activeBalanceOfHints = abi.encode( - // IVault.ActiveBalanceOfHints({ - // activeSharesOfHint: abi.encode(activeBalanceOfHintsUint32.activeSharesOfHint), - // activeStakeHint: abi.encode(activeBalanceOfHintsUint32.activeStakeHint), - // activeSharesHint: abi.encode(activeBalanceOfHintsUint32.activeSharesHint) - // }) - // ); - // try vault.activeBalanceOfAt(alice, timestamp, activeBalanceOfHints) { - // gasStruct.gasSpent1 = vm.lastCallGas().gasTotalUsed; - // } catch { - // vault.activeBalanceOfAt(alice, timestamp, ""); - // gasStruct.gasSpent1 = vm.lastCallGas().gasTotalUsed; - // } - - // vault.activeBalanceOfAt(alice, timestamp, hint); - // gasStruct.gasSpent2 = vm.lastCallGas().gasTotalUsed; - // assertGe(gasStruct.gasSpent1, gasStruct.gasSpent2); - // } - - // function test_ActiveBalanceOfHintMany( - // uint256 amount1, - // uint48 epochDuration, - // HintStruct memory hintStruct - // ) public { - // amount1 = bound(amount1, 1, 1 * 10 ** 18); - // epochDuration = uint48(bound(epochDuration, 1, 7 days)); - // hintStruct.num = 500; - // hintStruct.secondsAgo = bound(hintStruct.secondsAgo, 0, 1_720_700_948); - - // uint256 blockTimestamp = vm.getBlockTimestamp(); - // blockTimestamp = blockTimestamp + 1_720_700_948; - // vm.warp(blockTimestamp); - - // vault = _getVault(epochDuration); - - // for (uint256 i; i < hintStruct.num; ++i) { - // _deposit(alice, amount1); - - // blockTimestamp = blockTimestamp + epochDuration; - // vm.warp(blockTimestamp); - // } - - // uint48 timestamp = - // uint48(hintStruct.back ? blockTimestamp - hintStruct.secondsAgo : blockTimestamp + hintStruct.secondsAgo); - - // VaultHints vaultHints = new VaultHints(); - // bytes memory hint = vaultHints.activeBalanceOfHints(address(vault), alice, timestamp); - - // GasStruct memory gasStruct = GasStruct({gasSpent1: 1, gasSpent2: 1}); - // vault.activeBalanceOfAt(alice, timestamp, ""); - // gasStruct.gasSpent1 = vm.lastCallGas().gasTotalUsed; - // vault.activeBalanceOfAt(alice, timestamp, hint); - // gasStruct.gasSpent2 = vm.lastCallGas().gasTotalUsed; - // assertGe(gasStruct.gasSpent1, gasStruct.gasSpent2); - - // assertLt(gasStruct.gasSpent1 - gasStruct.gasSpent2, 10_000); - // } - function test_Transfer(uint256 amount1, uint256 amount2) public { amount1 = bound(amount1, 1, 100 * 10 ** 18); amount2 = bound(amount2, 1, 100 * 10 ** 18); @@ -2855,93 +122,38 @@ contract VaultTokenizedTest is Test { } } - function _getVault(uint48 epochDuration) internal returns (VaultTokenized) { - address[] memory networkLimitSetRoleHolders = new address[](1); - networkLimitSetRoleHolders[0] = alice; - address[] memory operatorNetworkSharesSetRoleHolders = new address[](1); - operatorNetworkSharesSetRoleHolders[0] = alice; - (address vault_,,) = vaultConfigurator.create( - IVaultConfigurator.InitParams({ - version: vaultFactory.lastVersion(), - owner: alice, - vaultParams: abi.encode( - IVaultTokenized.InitParamsTokenized({ - baseParams: IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: epochDuration, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }), - name: "Test", - symbol: "TEST" - }) - ), - delegatorIndex: 0, - delegatorParams: abi.encode( - INetworkRestakeDelegator.InitParams({ - baseParams: IBaseDelegator.BaseParams({ - defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice - }), - networkLimitSetRoleHolders: networkLimitSetRoleHolders, - operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders - }) - ), - withSlasher: false, - slasherIndex: 0, - slasherParams: abi.encode( - ISlasher.InitParams({baseParams: IBaseSlasher.BaseParams({isBurnerHook: false})}) - ) - }) - ); - - return VaultTokenized(vault_); - } - - struct GetVaultAndDelegatorAndSlasherLocalVariables { - uint48 epochDuration; - address[] networkLimitSetRoleHolders; - address[] operatorNetworkLimitSetRoleHolders; - address vault_; - address delegator_; - address slasher_; - } - - function _getVaultAndDelegatorAndSlasher(uint48 epochDuration) + function _createVaultImpl(address delegatorFactory, address slasherFactory, address vaultFactory) internal - returns (VaultTokenized, FullRestakeDelegator, Slasher) + virtual + override + returns (address) { - GetVaultAndDelegatorAndSlasherLocalVariables memory vars; - - // Set parameters - vars.epochDuration = epochDuration; - - // Create role holders - vars.networkLimitSetRoleHolders = new address[](1); - vars.networkLimitSetRoleHolders[0] = alice; - vars.operatorNetworkLimitSetRoleHolders = new address[](1); - vars.operatorNetworkLimitSetRoleHolders[0] = alice; + return address(new VaultTokenized(delegatorFactory, slasherFactory, vaultFactory)); + } - // Create vault, delegator, and slasher - (vars.vault_, vars.delegator_, vars.slasher_) = vaultConfigurator.create( + function _createInitializedVault( + uint48 epochDuration, + address[] memory networkLimitSetRoleHolders, + address[] memory operatorNetworkSharesSetRoleHolders, + uint64 version, + address burner, + bool depositWhitelist, + bool isDepositLimit, + uint256 depositLimit + ) internal virtual override returns (IVaultFull, address, address) { + (address vault_, address delegator_, address slasher_) = vaultConfigurator.create( IVaultConfigurator.InitParams({ - version: vaultFactory.lastVersion(), - owner: alice, + version: version, + owner: address(0), vaultParams: abi.encode( IVaultTokenized.InitParamsTokenized({ baseParams: IVault.InitParams({ collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: vars.epochDuration, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, + burner: burner, + epochDuration: epochDuration, + depositWhitelist: depositWhitelist, + isDepositLimit: isDepositLimit, + depositLimit: depositLimit, defaultAdminRoleHolder: alice, depositWhitelistSetRoleHolder: alice, depositorWhitelistRoleHolder: alice, @@ -2954,12 +166,12 @@ contract VaultTokenizedTest is Test { ), delegatorIndex: 1, delegatorParams: abi.encode( - IFullRestakeDelegator.InitParams({ + INetworkRestakeDelegator.InitParams({ baseParams: IBaseDelegator.BaseParams({ defaultAdminRoleHolder: alice, hook: address(0), hookSetRoleHolder: alice }), - networkLimitSetRoleHolders: vars.networkLimitSetRoleHolders, - operatorNetworkLimitSetRoleHolders: vars.operatorNetworkLimitSetRoleHolders + networkLimitSetRoleHolders: networkLimitSetRoleHolders, + operatorNetworkSharesSetRoleHolders: operatorNetworkSharesSetRoleHolders }) ), withSlasher: true, @@ -2970,154 +182,34 @@ contract VaultTokenizedTest is Test { }) ); - return (VaultTokenized(vars.vault_), FullRestakeDelegator(vars.delegator_), Slasher(vars.slasher_)); - } - - function _registerOperator(address user) internal { - vm.startPrank(user); - operatorRegistry.registerOperator(); - vm.stopPrank(); + return (IVaultFull(vault_), address(delegator_), address(slasher_)); } - function _registerNetwork(address user, address middleware) internal { - vm.startPrank(user); - networkRegistry.registerNetwork(); - networkMiddlewareService.setMiddleware(middleware); - vm.stopPrank(); + function _getEncodedVaultParams(IVault.InitParams memory params) internal pure override returns (bytes memory) { + return abi.encode(IVaultTokenized.InitParamsTokenized({baseParams: params, name: "Test", symbol: "TEST"})); } - function _grantDepositorWhitelistRole(address user, address account) internal { + function _grantDepositorWhitelistRole(address user, address account) internal virtual override { vm.startPrank(user); - VaultTokenized(address(vault)).grantRole(vault.DEPOSITOR_WHITELIST_ROLE(), account); + VaultTokenized(address(vault)).grantRole(VaultTokenized(address(vault)).DEPOSITOR_WHITELIST_ROLE(), account); vm.stopPrank(); } - function _grantDepositWhitelistSetRole(address user, address account) internal { + function _grantDepositWhitelistSetRole(address user, address account) internal virtual override { vm.startPrank(user); VaultTokenized(address(vault)).grantRole(vault.DEPOSIT_WHITELIST_SET_ROLE(), account); vm.stopPrank(); } - function _grantIsDepositLimitSetRole(address user, address account) internal { + function _grantIsDepositLimitSetRole(address user, address account) internal virtual override { vm.startPrank(user); VaultTokenized(address(vault)).grantRole(vault.IS_DEPOSIT_LIMIT_SET_ROLE(), account); vm.stopPrank(); } - function _grantDepositLimitSetRole(address user, address account) internal { + function _grantDepositLimitSetRole(address user, address account) internal virtual override { vm.startPrank(user); VaultTokenized(address(vault)).grantRole(vault.DEPOSIT_LIMIT_SET_ROLE(), account); vm.stopPrank(); } - - function _deposit(address user, uint256 amount) internal returns (uint256 depositedAmount, uint256 mintedShares) { - collateral.transfer(user, amount); - vm.startPrank(user); - collateral.approve(address(vault), amount); - (depositedAmount, mintedShares) = vault.deposit(user, amount); - vm.stopPrank(); - } - - function _withdraw(address user, uint256 amount) internal returns (uint256 burnedShares, uint256 mintedShares) { - vm.startPrank(user); - (burnedShares, mintedShares) = vault.withdraw(user, amount); - vm.stopPrank(); - } - - function _redeem(address user, uint256 shares) internal returns (uint256 withdrawnAssets, uint256 mintedShares) { - vm.startPrank(user); - (withdrawnAssets, mintedShares) = vault.redeem(user, shares); - vm.stopPrank(); - } - - function _claim(address user, uint256 epoch) internal returns (uint256 amount) { - vm.startPrank(user); - amount = vault.claim(user, epoch); - vm.stopPrank(); - } - - function _claimBatch(address user, uint256[] memory epochs) internal returns (uint256 amount) { - vm.startPrank(user); - amount = vault.claimBatch(user, epochs); - vm.stopPrank(); - } - - function _optInOperatorVault(address user) internal { - vm.startPrank(user); - operatorVaultOptInService.optIn(address(vault)); - vm.stopPrank(); - } - - function _optOutOperatorVault(address user) internal { - vm.startPrank(user); - operatorVaultOptInService.optOut(address(vault)); - vm.stopPrank(); - } - - function _optInOperatorNetwork(address user, address network) internal { - vm.startPrank(user); - operatorNetworkOptInService.optIn(network); - vm.stopPrank(); - } - - function _optOutOperatorNetwork(address user, address network) internal { - vm.startPrank(user); - operatorNetworkOptInService.optOut(network); - vm.stopPrank(); - } - - function _setDepositWhitelist(address user, bool status) internal { - vm.startPrank(user); - vault.setDepositWhitelist(status); - vm.stopPrank(); - } - - function _setDepositorWhitelistStatus(address user, address depositor, bool status) internal { - vm.startPrank(user); - vault.setDepositorWhitelistStatus(depositor, status); - vm.stopPrank(); - } - - function _setIsDepositLimit(address user, bool status) internal { - vm.startPrank(user); - vault.setIsDepositLimit(status); - vm.stopPrank(); - } - - function _setDepositLimit(address user, uint256 amount) internal { - vm.startPrank(user); - vault.setDepositLimit(amount); - vm.stopPrank(); - } - - function _setNetworkLimit(address user, address network, uint256 amount) internal { - vm.startPrank(user); - delegator.setNetworkLimit(network.subnetwork(0), amount); - vm.stopPrank(); - } - - function _setOperatorNetworkLimit(address user, address network, address operator, uint256 amount) internal { - vm.startPrank(user); - delegator.setOperatorNetworkLimit(network.subnetwork(0), operator, amount); - vm.stopPrank(); - } - - function _slash( - address user, - address network, - address operator, - uint256 amount, - uint48 captureTimestamp, - bytes memory hints - ) internal returns (uint256 slashAmount) { - vm.startPrank(user); - slashAmount = slasher.slash(network.subnetwork(0), operator, amount, captureTimestamp, hints); - vm.stopPrank(); - } - - function _setMaxNetworkLimit(address user, uint96 identifier, uint256 amount) internal { - vm.startPrank(user); - delegator.setMaxNetworkLimit(identifier, amount); - vm.stopPrank(); - } } From 1fa291ff6b5002a38ccbb0cd5c3be07e654dde4e Mon Sep 17 00:00:00 2001 From: Sergii Liakh Date: Wed, 10 Dec 2025 21:05:16 -0300 Subject: [PATCH 5/9] test: vault invariant tests --- test/invariant/VaultInvariants.t.sol | 38 +++ test/invariant/handlers/VaultHandler.sol | 345 +++++++++++++++++++++++ 2 files changed, 383 insertions(+) create mode 100644 test/invariant/VaultInvariants.t.sol create mode 100644 test/invariant/handlers/VaultHandler.sol diff --git a/test/invariant/VaultInvariants.t.sol b/test/invariant/VaultInvariants.t.sol new file mode 100644 index 00000000..ddfd880e --- /dev/null +++ b/test/invariant/VaultInvariants.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {Test} from "forge-std/Test.sol"; + +import {VaultHandler} from "./handlers/VaultHandler.sol"; + +contract VaultInvariantsTest is Test { + VaultHandler public handler; + + function setUp() public { + handler = new VaultHandler(); + + bytes4[] memory selectors = new bytes4[](5); + selectors[0] = VaultHandler.deposit.selector; + selectors[1] = VaultHandler.withdraw.selector; + selectors[2] = VaultHandler.redeem.selector; + selectors[3] = VaultHandler.claim.selector; + selectors[4] = VaultHandler.slash.selector; + + targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); + targetContract(address(handler)); + } + + function invariant_Totals() public { + assert(handler.totalDeposited() >= handler.totalWithdrawn()); + assert(handler.totalWithdrawn() >= handler.totalClaimed()); + assert(handler.vaultBalance() + handler.totalClaimed() + handler.totalSlashed() == handler.totalDeposited()); + } + + function invariant_UserBalance() public { + address[] memory depositors = handler.getDepositors(); + for (uint256 i = 0; i < depositors.length; i++) { + address account = depositors[i]; + assert(handler.totalClaimedOf(account) <= handler.totalDepositOf(account)); + } + } +} diff --git a/test/invariant/handlers/VaultHandler.sol b/test/invariant/handlers/VaultHandler.sol new file mode 100644 index 00000000..5cde6c6c --- /dev/null +++ b/test/invariant/handlers/VaultHandler.sol @@ -0,0 +1,345 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {Test} from "forge-std/Test.sol"; + +import {Vault} from "../../../src/contracts/vault/Vault.sol"; +import {VaultConfigurator} from "../../../src/contracts/VaultConfigurator.sol"; +import {VaultFactory} from "../../../src/contracts/VaultFactory.sol"; +import {DelegatorFactory} from "../../../src/contracts/DelegatorFactory.sol"; +import {SlasherFactory} from "../../../src/contracts/SlasherFactory.sol"; +import {NetworkRegistry} from "../../../src/contracts/NetworkRegistry.sol"; +import {OperatorRegistry} from "../../../src/contracts/OperatorRegistry.sol"; +import {MetadataService} from "../../../src/contracts/service/MetadataService.sol"; +import {NetworkMiddlewareService} from "../../../src/contracts/service/NetworkMiddlewareService.sol"; +import {OptInService} from "../../../src/contracts/service/OptInService.sol"; +import {IBaseDelegator} from "../../../src/interfaces/delegator/IBaseDelegator.sol"; +import {IBaseSlasher} from "../../../src/interfaces/slasher/IBaseSlasher.sol"; +import {FullRestakeDelegator} from "../../../src/contracts/delegator/FullRestakeDelegator.sol"; +import {NetworkRestakeDelegator} from "../../../src/contracts/delegator/NetworkRestakeDelegator.sol"; +import {Slasher} from "../../../src/contracts/slasher/Slasher.sol"; + +import {IVault} from "../../../src/interfaces/vault/IVault.sol"; +import {IVaultConfigurator} from "../../../src/interfaces/IVaultConfigurator.sol"; +import {INetworkRestakeDelegator} from "../../../src/interfaces/delegator/INetworkRestakeDelegator.sol"; +import {IFullRestakeDelegator} from "../../../src/interfaces/delegator/IFullRestakeDelegator.sol"; +import {ISlasher} from "../../../src/interfaces/slasher/ISlasher.sol"; + +import {Subnetwork} from "../../../src/contracts/libraries/Subnetwork.sol"; + +import {Token} from "../../mocks/Token.sol"; + +contract VaultHandler is Test { + using Subnetwork for address; + + uint256 public totalDeposited; + uint256 public totalClaimed; + uint256 public totalSlashed; + uint256 public totalWithdrawn; + + Vault public vault; + FullRestakeDelegator public delegator; + Slasher public slasher; + Token public collateral; + + VaultConfigurator internal configurator; + VaultFactory internal vaultFactory; + DelegatorFactory internal delegatorFactory; + SlasherFactory internal slasherFactory; + NetworkRegistry internal networkRegistry; + OperatorRegistry internal operatorRegistry; + NetworkMiddlewareService internal networkMiddlewareService; + OptInService internal operatorVaultOptInService; + OptInService internal operatorNetworkOptInService; + + address internal network; + address internal operator; + bytes32 internal subnetwork; + + address[] public depositors; + mapping(address account => uint256 withdrawalCount) internal withdrawalsCreated; + mapping(address account => uint256 totalClaimed) public totalClaimedOf; + mapping(address account => uint256 totalDeposited) public totalDepositOf; + + modifier adjustTimestamp(uint256 timeJumpSeed) { + uint256 timeJump = _bound(timeJumpSeed, 2 minutes, 1 days); + vm.warp(block.timestamp + timeJump); + _; + } + + constructor() { + _initialize(); + } + + function activeStake() external view returns (uint256) { + return vault.activeStake(); + } + + function getDepositors() external view returns (address[] memory) { + return depositors; + } + + function vaultBalance() external view returns (uint256) { + return collateral.balanceOf(address(vault)); + } + + function _initialize() internal { + network = makeAddr("network"); + operator = makeAddr("operator"); + + vaultFactory = new VaultFactory(address(this)); + delegatorFactory = new DelegatorFactory(address(this)); + slasherFactory = new SlasherFactory(address(this)); + networkRegistry = new NetworkRegistry(); + operatorRegistry = new OperatorRegistry(); + networkMiddlewareService = new NetworkMiddlewareService(address(networkRegistry)); + operatorVaultOptInService = + new OptInService(address(operatorRegistry), address(vaultFactory), "OperatorVaultOptInService"); + operatorNetworkOptInService = + new OptInService(address(operatorRegistry), address(networkRegistry), "OperatorNetworkOptInService"); + + // Metadata services are required by delegators/slashers but not used in invariants. + new MetadataService(address(operatorRegistry)); + new MetadataService(address(networkRegistry)); + + address vaultImpl = + address(new Vault(address(delegatorFactory), address(slasherFactory), address(vaultFactory))); + vaultFactory.whitelist(vaultImpl); + + address networkRestakeDelegatorImpl = address( + new NetworkRestakeDelegator( + address(networkRegistry), + address(vaultFactory), + address(operatorVaultOptInService), + address(operatorNetworkOptInService), + address(delegatorFactory), + delegatorFactory.totalTypes() + ) + ); + delegatorFactory.whitelist(networkRestakeDelegatorImpl); + + address fullRestakeDelegatorImpl = address( + new FullRestakeDelegator( + address(networkRegistry), + address(vaultFactory), + address(operatorVaultOptInService), + address(operatorNetworkOptInService), + address(delegatorFactory), + delegatorFactory.totalTypes() + ) + ); + delegatorFactory.whitelist(fullRestakeDelegatorImpl); + + address slasherImpl = address( + new Slasher( + address(vaultFactory), + address(networkMiddlewareService), + address(slasherFactory), + slasherFactory.totalTypes() + ) + ); + slasherFactory.whitelist(slasherImpl); + + configurator = new VaultConfigurator(address(vaultFactory), address(delegatorFactory), address(slasherFactory)); + + collateral = new Token("InvariantToken"); + + vm.prank(network); + networkRegistry.registerNetwork(); + + vm.prank(operator); + operatorRegistry.registerOperator(); + + vm.prank(network); + networkMiddlewareService.setMiddleware(address(this)); + + (address vaultAddr, address delegatorAddr, address slasherAddr) = configurator.create( + IVaultConfigurator.InitParams({ + version: vaultFactory.lastVersion(), + owner: address(0), + vaultParams: abi.encode( + IVault.InitParams({ + collateral: address(collateral), + burner: address(0xBEEF), + epochDuration: 6 hours, + depositWhitelist: false, + isDepositLimit: false, + depositLimit: 0, + defaultAdminRoleHolder: address(this), + depositWhitelistSetRoleHolder: address(0), + depositorWhitelistRoleHolder: address(0), + isDepositLimitSetRoleHolder: address(0), + depositLimitSetRoleHolder: address(0) + }) + ), + delegatorIndex: 1, // FullRestakeDelegator + delegatorParams: abi.encode( + IFullRestakeDelegator.InitParams({ + baseParams: IBaseDelegator.BaseParams({ + defaultAdminRoleHolder: address(this), hook: address(0), hookSetRoleHolder: address(this) + }), + networkLimitSetRoleHolders: _asSingletonArray(address(this)), + operatorNetworkLimitSetRoleHolders: _asSingletonArray(address(this)) + }) + ), + withSlasher: true, + slasherIndex: 0, + slasherParams: abi.encode( + ISlasher.InitParams({baseParams: IBaseSlasher.BaseParams({isBurnerHook: false})}) + ) + }) + ); + + vault = Vault(vaultAddr); + delegator = FullRestakeDelegator(delegatorAddr); + slasher = Slasher(slasherAddr); + + subnetwork = network.subnetwork(0); + + _bootstrapLimitsAndOptIns(); + } + + function deposit(address depositor, uint256 amount, uint256 timeJumpSeed) external adjustTimestamp(timeJumpSeed) { + if (depositor == address(0) || depositor == address(vault)) { + depositor = address(1); + } + + amount = _bound(amount, 1 ether, 1_000_000 ether); + + deal(address(collateral), depositor, amount); + + vm.startPrank(depositor); + collateral.approve(address(vault), amount); + (uint256 depositedAmount, uint256 mintedShares) = vault.deposit(depositor, amount); + totalDeposited += depositedAmount; + totalDepositOf[depositor] += depositedAmount; + _rememberDepositor(depositor); + vm.stopPrank(); + } + + function withdraw(uint256 claimerSeed, uint256 amount, uint256 timeJumpSeed) + external + adjustTimestamp(timeJumpSeed) + { + address account = _selectDepositor(claimerSeed); + if (account == address(0)) { + return; + } + + uint256 balance = vault.activeBalanceOf(account); + if (balance == 0) { + return; + } + + amount = _bound(amount, 1, balance); + + vm.startPrank(account); + vault.withdraw(account, amount); + withdrawalsCreated[account] += 1; + totalWithdrawn += amount; + vm.stopPrank(); + } + + function redeem(uint256 claimerSeed, uint256 amount, uint256 timeJumpSeed) external adjustTimestamp(timeJumpSeed) { + address account = _selectDepositor(claimerSeed); + if (account == address(0)) { + return; + } + + uint256 shares = vault.activeSharesOf(account); + if (shares == 0) { + return; + } + + amount = _bound(amount, 1, shares); + + vm.startPrank(account); + (uint256 withdrawnAssets, uint256 mintedShares) = vault.redeem(account, amount); + totalWithdrawn += withdrawnAssets; + withdrawalsCreated[account] += 1; + vm.stopPrank(); + } + + function claim(uint256 claimerSeed, uint256 indexSeed, uint256 timeJumpSeed) + external + adjustTimestamp(timeJumpSeed) + { + address account = _selectDepositor(claimerSeed); + if (account == address(0)) { + return; + } + + uint256 created = withdrawalsCreated[account]; + if (created == 0) { + return; + } + + uint256 index = _bound(indexSeed, 0, created - 1); + + vm.startPrank(account); + uint256 amount = vault.claim(account, index); + totalClaimed += amount; + totalClaimedOf[account] += amount; + + vm.stopPrank(); + } + + function slash(uint256 amount, uint256 captureTimestampSeed, uint256 timeJumpSeed) + external + adjustTimestamp(timeJumpSeed) + { + // Reduce slashing frequency. + if (captureTimestampSeed % 5 != 0) { + return; + } + + uint256 stake = vault.totalStake(); + if (stake == 0) { + return; + } + + uint48 captureTimestamp = uint48(block.timestamp) - 1; + amount = _bound(amount, 1, stake/2); + + uint256 slashedAmount = slasher.slash(subnetwork, operator, amount, captureTimestamp, ""); + totalSlashed += slashedAmount; + } + + function _bootstrapLimitsAndOptIns() internal { + uint256 limit = type(uint256).max / 4; + + vm.prank(network); + delegator.setMaxNetworkLimit(0, limit); + + // Allow delegator to stake entire vault balance to the single subnetwork/operator. + delegator.setNetworkLimit(subnetwork, limit); + delegator.setOperatorNetworkLimit(subnetwork, operator, limit); + + vm.prank(operator); + operatorVaultOptInService.optIn(address(vault)); + + vm.prank(operator); + operatorNetworkOptInService.optIn(network); + } + + function _selectDepositor(uint256 claimerSeed) internal view returns (address) { + if (depositors.length == 0) { + return address(0); + } + + uint256 index = _bound(claimerSeed, 0, depositors.length - 1); + return depositors[index]; + } + + function _rememberDepositor(address account) internal { + if (totalDepositOf[account] > 0) { + return; + } + depositors.push(account); + } + + function _asSingletonArray(address value) internal pure returns (address[] memory arr) { + arr = new address[](1); + arr[0] = value; + } +} From 50a4afe51ccc3b13a74f20097aefc9fd66a3ad89 Mon Sep 17 00:00:00 2001 From: Sergii Liakh Date: Thu, 11 Dec 2025 17:18:12 -0300 Subject: [PATCH 6/9] feat: vault migration --- src/contracts/vault/Vault.sol | 55 ++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/contracts/vault/Vault.sol b/src/contracts/vault/Vault.sol index 0d8aa13c..3c20f5d9 100644 --- a/src/contracts/vault/Vault.sol +++ b/src/contracts/vault/Vault.sol @@ -370,6 +370,20 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau emit SetSlasher(slasher_); } + function migrateWithdrawals() public { + uint48 epochDurationInit_ = epochDurationInit; + uint48 epochDuration_ = epochDuration; + for (uint48 i = epochDurationInit_; i < block.timestamp + epochDuration_; i += epochDuration_) { + uint256 epoch = (i - epochDurationInit_) / epochDuration_; + uint256 shares = _epochWithdrawalSharesOf[epoch][msg.sender]; + if (shares > 0) { + _withdrawalsOf[msg.sender].push( + Withdrawal(_isEpochWithdrawalsClaimed[epoch][msg.sender], i + epochDuration_, shares) + ); + } + } + } + function _withdraw(address claimer, uint256 withdrawnAssets, uint256 burnedShares) internal virtual @@ -483,6 +497,45 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau internal override { - revert(); + uint256 assetPerShare; + uint256 currentAssetPerShare; + uint256 withdrawals; + uint256 withdrawalShares; + uint256 bucketWithdrawals; + uint256 bucketWithdrawalShares; + uint208 latestBucket; + uint48 epochDurationInit_ = epochDurationInit; + uint48 epochDuration_ = epochDuration; + for (uint48 i = epochDurationInit_; i < block.timestamp + epochDuration_; i += epochDuration_) { + uint256 epoch = (i - epochDurationInit_) / epochDuration_; + withdrawalShares = _epochWithdrawalShares[epoch]; + if (withdrawalShares == 0) { + continue; + } + withdrawals = _epochWithdrawals[epoch]; + withdrawalsPrefixes.push(i + epochDuration_, withdrawalsPrefixes.latest() + withdrawals); + withdrawalSharesPrefixes.push(i + epochDuration_, withdrawalSharesPrefixes.latest() + withdrawalShares); + + currentAssetPerShare = withdrawals.mulDiv(1e27, withdrawalShares); + if(assetPerShare == 0) { + assetPerShare = currentAssetPerShare; + timeToBucket.push(i + epochDuration_, latestBucket); + } + // in case of slashing we need to create a new bucket + if (assetPerShare != currentAssetPerShare) { + _withdrawals[latestBucket] = bucketWithdrawals; + _withdrawalShares[latestBucket] = bucketWithdrawalShares; + assetPerShare = currentAssetPerShare; + bucketWithdrawals = withdrawals; + bucketWithdrawalShares = withdrawalShares; + latestBucket++; + timeToBucket.push(i + epochDuration_, latestBucket); + } else { + bucketWithdrawals += withdrawals; + bucketWithdrawalShares += withdrawalShares; + } + } + _withdrawals[latestBucket] = bucketWithdrawals; + _withdrawalShares[latestBucket] = bucketWithdrawalShares; } } From f571645534c5acad9bb2177ba8a618a01726c2ac Mon Sep 17 00:00:00 2001 From: Sergii Liakh Date: Mon, 15 Dec 2025 21:59:13 -0300 Subject: [PATCH 7/9] feat: migration update --- src/contracts/vault/Vault.sol | 85 +++++++++++++++-------------------- 1 file changed, 37 insertions(+), 48 deletions(-) diff --git a/src/contracts/vault/Vault.sol b/src/contracts/vault/Vault.sol index 3c20f5d9..1b1701f0 100644 --- a/src/contracts/vault/Vault.sol +++ b/src/contracts/vault/Vault.sol @@ -104,6 +104,16 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau return activeBalanceOf(account) + amount; } + function getWithdrawalsAt(uint48 timestamp) public view returns (uint256, uint256) { + // if timestamp is before the migration, use the epoch withdrawals + if(timestamp < timeToBucket.at(0)._key) { + uint48 epoch = (timestamp - epochDurationInit) / epochDuration; + return (_epochWithdrawals[epoch], _epochWithdrawalShares[epoch]); + } + uint256 bucketIndex = timeToBucket.upperLookupRecent(timestamp); + return (_withdrawals[bucketIndex], _withdrawalShares[bucketIndex]); + } + /** * @inheritdoc IVault */ @@ -370,16 +380,14 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau emit SetSlasher(slasher_); } - function migrateWithdrawals() public { - uint48 epochDurationInit_ = epochDurationInit; - uint48 epochDuration_ = epochDuration; - for (uint48 i = epochDurationInit_; i < block.timestamp + epochDuration_; i += epochDuration_) { - uint256 epoch = (i - epochDurationInit_) / epochDuration_; - uint256 shares = _epochWithdrawalSharesOf[epoch][msg.sender]; + function migrateWithdrawalsOf(uint48[] calldata epochs, address account) public { + uint256 length = epochs.length; + for (uint256 i; i < length; ++i) { + uint48 epoch = epochs[i]; + uint256 shares = _epochWithdrawalSharesOf[epoch][account]; + uint48 unlockAt = epochDurationInit + (epoch + 1) * epochDuration; if (shares > 0) { - _withdrawalsOf[msg.sender].push( - Withdrawal(_isEpochWithdrawalsClaimed[epoch][msg.sender], i + epochDuration_, shares) - ); + _withdrawalsOf[account].push(Withdrawal(_isEpochWithdrawalsClaimed[epoch][account], unlockAt, shares)); } } } @@ -420,9 +428,9 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau if (withdrawal.unlockAt >= block.timestamp) { revert WithdrawalNotMatured(); } - uint256 bucketIndex = timeToBucket.upperLookupRecent(withdrawal.unlockAt); _withdrawalsOf[msg.sender][index].claimed = true; - amount = ERC4626Math.previewRedeem(withdrawal.shares, _withdrawals[bucketIndex], _withdrawalShares[bucketIndex]); + (uint256 withdrawals, uint256 withdrawalShares) = getWithdrawalsAt(withdrawal.unlockAt); + amount = ERC4626Math.previewRedeem(withdrawal.shares, withdrawals, withdrawalShares); } function _initialize(uint64, address, bytes memory data) internal virtual override { @@ -497,45 +505,26 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau internal override { - uint256 assetPerShare; - uint256 currentAssetPerShare; uint256 withdrawals; uint256 withdrawalShares; - uint256 bucketWithdrawals; - uint256 bucketWithdrawalShares; - uint208 latestBucket; - uint48 epochDurationInit_ = epochDurationInit; uint48 epochDuration_ = epochDuration; - for (uint48 i = epochDurationInit_; i < block.timestamp + epochDuration_; i += epochDuration_) { - uint256 epoch = (i - epochDurationInit_) / epochDuration_; - withdrawalShares = _epochWithdrawalShares[epoch]; - if (withdrawalShares == 0) { - continue; - } - withdrawals = _epochWithdrawals[epoch]; - withdrawalsPrefixes.push(i + epochDuration_, withdrawalsPrefixes.latest() + withdrawals); - withdrawalSharesPrefixes.push(i + epochDuration_, withdrawalSharesPrefixes.latest() + withdrawalShares); - - currentAssetPerShare = withdrawals.mulDiv(1e27, withdrawalShares); - if(assetPerShare == 0) { - assetPerShare = currentAssetPerShare; - timeToBucket.push(i + epochDuration_, latestBucket); - } - // in case of slashing we need to create a new bucket - if (assetPerShare != currentAssetPerShare) { - _withdrawals[latestBucket] = bucketWithdrawals; - _withdrawalShares[latestBucket] = bucketWithdrawalShares; - assetPerShare = currentAssetPerShare; - bucketWithdrawals = withdrawals; - bucketWithdrawalShares = withdrawalShares; - latestBucket++; - timeToBucket.push(i + epochDuration_, latestBucket); - } else { - bucketWithdrawals += withdrawals; - bucketWithdrawalShares += withdrawalShares; - } - } - _withdrawals[latestBucket] = bucketWithdrawals; - _withdrawalShares[latestBucket] = bucketWithdrawalShares; + uint48 epochDurationInit_ = epochDurationInit; + + uint48 epoch = (block.timestamp - epochDurationInit_).toUint48() / epochDuration_; + withdrawalShares = _epochWithdrawalShares[epoch]; + withdrawals = _epochWithdrawals[epoch]; + + uint48 nextEpochStart = epochDurationInit_ + (epoch + 1) * epochDuration_; + withdrawalsPrefixes.push(nextEpochStart, withdrawals); + withdrawalSharesPrefixes.push(nextEpochStart, withdrawalShares); + + withdrawalShares += _epochWithdrawalShares[epoch + 1]; + withdrawals += _epochWithdrawals[epoch + 1]; + withdrawalsPrefixes.push(nextEpochStart + epochDuration_, withdrawals); + withdrawalSharesPrefixes.push(nextEpochStart + epochDuration_, withdrawalShares); + + timeToBucket.push(nextEpochStart, epoch); + _withdrawals[epoch] = withdrawals; + _withdrawalShares[epoch] = withdrawalShares; } } From ddeb61351add40f40cd3be7f723fb636f3e6e4b8 Mon Sep 17 00:00:00 2001 From: Sergii Liakh Date: Wed, 17 Dec 2025 17:08:02 -0300 Subject: [PATCH 8/9] feat: new shares calculation during migration --- src/contracts/vault/Vault.sol | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/contracts/vault/Vault.sol b/src/contracts/vault/Vault.sol index 1b1701f0..a30401d2 100644 --- a/src/contracts/vault/Vault.sol +++ b/src/contracts/vault/Vault.sol @@ -382,11 +382,17 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau function migrateWithdrawalsOf(uint48[] calldata epochs, address account) public { uint256 length = epochs.length; + uint48 epochDuration_ = epochDuration; + uint48 epochDurationInit_ = epochDurationInit; + uint48 migrationEpoch = (timeToBucket.at(0)._key - epochDurationInit_) / epochDuration_ - 1; for (uint256 i; i < length; ++i) { uint48 epoch = epochs[i]; uint256 shares = _epochWithdrawalSharesOf[epoch][account]; - uint48 unlockAt = epochDurationInit + (epoch + 1) * epochDuration; + uint48 unlockAt = epochDurationInit_ + (epoch + 1) * epochDuration_; if (shares > 0) { + if (epoch >= migrationEpoch) { + shares = ERC4626Math.previewRedeem(shares, _epochWithdrawals[epoch], _epochWithdrawalShares[epoch]); + } _withdrawalsOf[account].push(Withdrawal(_isEpochWithdrawalsClaimed[epoch][account], unlockAt, shares)); } } @@ -506,25 +512,22 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau override { uint256 withdrawals; - uint256 withdrawalShares; uint48 epochDuration_ = epochDuration; uint48 epochDurationInit_ = epochDurationInit; uint48 epoch = (block.timestamp - epochDurationInit_).toUint48() / epochDuration_; - withdrawalShares = _epochWithdrawalShares[epoch]; withdrawals = _epochWithdrawals[epoch]; uint48 nextEpochStart = epochDurationInit_ + (epoch + 1) * epochDuration_; withdrawalsPrefixes.push(nextEpochStart, withdrawals); - withdrawalSharesPrefixes.push(nextEpochStart, withdrawalShares); + withdrawalSharesPrefixes.push(nextEpochStart, withdrawals); - withdrawalShares += _epochWithdrawalShares[epoch + 1]; withdrawals += _epochWithdrawals[epoch + 1]; withdrawalsPrefixes.push(nextEpochStart + epochDuration_, withdrawals); - withdrawalSharesPrefixes.push(nextEpochStart + epochDuration_, withdrawalShares); + withdrawalSharesPrefixes.push(nextEpochStart + epochDuration_, withdrawals); timeToBucket.push(nextEpochStart, epoch); _withdrawals[epoch] = withdrawals; - _withdrawalShares[epoch] = withdrawalShares; + _withdrawalShares[epoch] = withdrawals; } } From 6b7f28cf1f57a8b6f6b7ce73f85f21521c8674f0 Mon Sep 17 00:00:00 2001 From: Andrey Date: Sun, 28 Dec 2025 23:07:19 +0400 Subject: [PATCH 9/9] refactor: simplify legacy support & polish --- lib/forge-std | 2 +- lib/openzeppelin-contracts | 2 +- lib/openzeppelin-contracts-upgradeable | 2 +- script/utils/interfaces/ICreateX.sol | 20 +- .../delegator/NetworkRestakeDelegator.sol | 5 +- src/contracts/slasher/VetoSlasher.sol | 3 +- src/contracts/vault/Vault.sol | 186 +++--- src/contracts/vault/VaultStorage.sol | 90 +-- src/interfaces/delegator/IBaseDelegator.sol | 9 +- src/interfaces/delegator/IDelegatorHook.sol | 9 +- src/interfaces/vault/IVault.sol | 20 +- src/interfaces/vault/IVaultStorage.sol | 84 +-- test/delegator/FullRestakeDelegator.t.sol | 2 +- test/delegator/NetworkRestakeDelegator.t.sol | 2 +- .../delegator/OperatorSpecificDelegator.t.sol | 2 +- test/helpers/VaultTestHelper.sol | 104 +++ test/helpers/v1/IVaultStorageV1.sol | 223 +++++++ test/helpers/v1/IVaultV1.sol | 300 +++++++++ test/helpers/v1/VaultStorageV1.sol | 223 +++++++ test/helpers/v1/VaultV1.sol | 482 ++++++++++++++ test/integration/SymbioticCoreIntegration.sol | 10 +- .../SymbioticCoreIntegrationExample.t.sol | 2 +- .../base/SymbioticCoreBindingsBase.sol | 10 +- .../base/SymbioticCoreInitBase.sol | 6 +- test/invariant/handlers/VaultHandler.sol | 2 +- test/vault/IVaultFull.sol | 20 - test/vault/Vault.t.sol | 621 ++++++++++++++---- test/vault/VaultTokenized.t.sol | 59 +- 28 files changed, 2035 insertions(+), 465 deletions(-) create mode 100644 test/helpers/VaultTestHelper.sol create mode 100644 test/helpers/v1/IVaultStorageV1.sol create mode 100644 test/helpers/v1/IVaultV1.sol create mode 100644 test/helpers/v1/VaultStorageV1.sol create mode 100644 test/helpers/v1/VaultV1.sol delete mode 100644 test/vault/IVaultFull.sol diff --git a/lib/forge-std b/lib/forge-std index 8e40513d..7117c90c 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 8e40513d678f392f398620b3ef2b418648b33e89 +Subproject commit 7117c90c8cf6c68e5acce4f09a6b24715cea4de6 diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index dbb6104c..c64a1edb 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 +Subproject commit c64a1edb67b6e3f4a15cca8909c9482ad33a02b0 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable index 723f8cab..e725abdd 160000 --- a/lib/openzeppelin-contracts-upgradeable +++ b/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit 723f8cab09cdae1aca9ec9cc1cfa040c2d4b06c1 +Subproject commit e725abddf1e01cf05ace496e950fc8e243cc7cab diff --git a/script/utils/interfaces/ICreateX.sol b/script/utils/interfaces/ICreateX.sol index ad12fcb8..f96b3ef4 100644 --- a/script/utils/interfaces/ICreateX.sol +++ b/script/utils/interfaces/ICreateX.sol @@ -77,12 +77,10 @@ interface ICreateX { payable returns (address newContract); - function deployCreate2AndInit( - bytes memory initCode, - bytes memory data, - Values memory values, - address refundAddress - ) external payable returns (address newContract); + function deployCreate2AndInit(bytes memory initCode, bytes memory data, Values memory values, address refundAddress) + external + payable + returns (address newContract); function deployCreate2AndInit(bytes memory initCode, bytes memory data, Values memory values) external @@ -124,12 +122,10 @@ interface ICreateX { payable returns (address newContract); - function deployCreate3AndInit( - bytes memory initCode, - bytes memory data, - Values memory values, - address refundAddress - ) external payable returns (address newContract); + function deployCreate3AndInit(bytes memory initCode, bytes memory data, Values memory values, address refundAddress) + external + payable + returns (address newContract); function deployCreate3AndInit(bytes memory initCode, bytes memory data, Values memory values) external diff --git a/src/contracts/delegator/NetworkRestakeDelegator.sol b/src/contracts/delegator/NetworkRestakeDelegator.sol index 58529a54..6d1fd875 100644 --- a/src/contracts/delegator/NetworkRestakeDelegator.sol +++ b/src/contracts/delegator/NetworkRestakeDelegator.sol @@ -130,8 +130,9 @@ contract NetworkRestakeDelegator is BaseDelegator, INetworkRestakeDelegator { revert AlreadySet(); } - _totalOperatorNetworkShares[subnetwork] - .push(Time.timestamp(), totalOperatorNetworkShares(subnetwork) - operatorNetworkShares_ + shares); + _totalOperatorNetworkShares[subnetwork].push( + Time.timestamp(), totalOperatorNetworkShares(subnetwork) - operatorNetworkShares_ + shares + ); _operatorNetworkShares[subnetwork][operator].push(Time.timestamp(), shares); emit SetOperatorNetworkShares(subnetwork, operator, shares); diff --git a/src/contracts/slasher/VetoSlasher.sol b/src/contracts/slasher/VetoSlasher.sol index e90ce07d..1f18bb91 100644 --- a/src/contracts/slasher/VetoSlasher.sol +++ b/src/contracts/slasher/VetoSlasher.sol @@ -252,8 +252,7 @@ contract VetoSlasher is BaseSlasher, IVetoSlasher { } if (resolver_ != address(uint160(_resolver[subnetwork].latest()))) { - _resolver[subnetwork] - .push( + _resolver[subnetwork].push( (IVault(vault_).currentEpochStart() + resolverSetEpochsDelay * IVault(vault_).epochDuration()) .toUint48(), uint160(resolver_) diff --git a/src/contracts/vault/Vault.sol b/src/contracts/vault/Vault.sol index a30401d2..8c263b0d 100644 --- a/src/contracts/vault/Vault.sol +++ b/src/contracts/vault/Vault.sol @@ -41,12 +41,14 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau * @inheritdoc IVault */ function totalStake() public view returns (uint256) { - uint208 lastBucket = timeToBucket.latest(); - uint256 lastWithdrawalShares = _withdrawalShares[lastBucket]; - uint256 unmaturedWithdrawals = lastWithdrawalShares == 0 - ? 0 - : unmaturedWithdrawalShares(uint48(block.timestamp)).mulDiv(_withdrawals[lastBucket], lastWithdrawalShares); - return activeStake() + unmaturedWithdrawals; + uint208 lastBucket = _timeToBucket.latest(); + uint256 lastWithdrawalShares = withdrawalShares[lastBucket]; + return activeStake() + + (lastWithdrawalShares > 0 + ? (_withdrawalSharesPrefixes.latest() + - _withdrawalSharesPrefixes.upperLookupRecent(uint48(block.timestamp))) + .mulDiv(withdrawals[lastBucket], lastWithdrawalShares) + : 0); } /** @@ -75,43 +77,10 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau * @inheritdoc IVault */ function withdrawalsOf(uint256 index, address account) public view returns (uint256) { - Withdrawal memory withdrawal = _withdrawalsOf[account][index]; - uint256 bucketIndex = timeToBucket.upperLookupRecent(withdrawal.unlockAt); - return ERC4626Math.previewRedeem(withdrawal.shares, _withdrawals[bucketIndex], _withdrawalShares[bucketIndex]); - } - - /** - * @inheritdoc IVault - */ - function slashableBalanceOf(address account) external view returns (uint256) { - uint256 amount; - Withdrawal[] storage withdrawals_ = _withdrawalsOf[account]; - if (withdrawals_.length == 0) { - return activeBalanceOf(account); - } - for (uint256 i = withdrawals_.length; i > 0;) { - --i; - Withdrawal memory withdrawal = withdrawals_[i]; - if (withdrawal.unlockAt <= block.timestamp) { - break; - } - uint256 bucketIndex = timeToBucket.upperLookupRecent(withdrawal.unlockAt); - amount += ERC4626Math.previewRedeem( - withdrawal.shares, _withdrawals[bucketIndex], _withdrawalShares[bucketIndex] - ); - } - - return activeBalanceOf(account) + amount; - } - - function getWithdrawalsAt(uint48 timestamp) public view returns (uint256, uint256) { - // if timestamp is before the migration, use the epoch withdrawals - if(timestamp < timeToBucket.at(0)._key) { - uint48 epoch = (timestamp - epochDurationInit) / epochDuration; - return (_epochWithdrawals[epoch], _epochWithdrawalShares[epoch]); - } - uint256 bucketIndex = timeToBucket.upperLookupRecent(timestamp); - return (_withdrawals[bucketIndex], _withdrawalShares[bucketIndex]); + uint256 bucketIndex = _timeToBucket.upperLookupRecent(withdrawalUnlockAt(index, account)); + return ERC4626Math.previewRedeem( + withdrawalSharesOf(index, account), withdrawals[bucketIndex], withdrawalShares[bucketIndex] + ); } /** @@ -249,32 +218,33 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau if (msg.sender != slasher) { revert NotSlasher(); } - if (captureTimestamp + epochDuration < uint48(block.timestamp) || captureTimestamp >= uint48(block.timestamp)) { revert InvalidCaptureEpoch(); } - uint208 lastBucket = timeToBucket.latest(); - uint256 lastWithdrawals = _withdrawals[lastBucket]; - uint256 lastWithdrawalShares = _withdrawalShares[lastBucket]; - - uint256 unmaturedWithdrawalShares = unmaturedWithdrawalShares(uint48(block.timestamp)); + uint208 lastBucket = _timeToBucket.latest(); + uint256 lastWithdrawals = withdrawals[lastBucket]; + uint256 lastWithdrawalShares = withdrawalShares[lastBucket]; + uint256 unmaturedWithdrawalShares = + _withdrawalSharesPrefixes.latest() - _withdrawalSharesPrefixes.upperLookupRecent(uint48(block.timestamp)); uint256 unmaturedWithdrawals = - lastWithdrawalShares == 0 ? 0 : unmaturedWithdrawalShares.mulDiv(lastWithdrawals, lastWithdrawalShares); - - timeToBucket.push(uint48(block.timestamp), lastBucket + 1); - _withdrawals[lastBucket] = lastWithdrawals - unmaturedWithdrawals; - _withdrawalShares[lastBucket] = lastWithdrawalShares - unmaturedWithdrawalShares; - _withdrawalShares[lastBucket + 1] = unmaturedWithdrawalShares; + lastWithdrawalShares > 0 ? unmaturedWithdrawalShares.mulDiv(lastWithdrawals, lastWithdrawalShares) : 0; uint256 activeStake_ = activeStake(); uint256 slashableStake = activeStake_ + unmaturedWithdrawals; slashedAmount = Math.min(amount, slashableStake); - uint256 activeSlashed = slashedAmount.mulDiv(activeStake_, slashableStake); - _activeStake.push(uint48(block.timestamp), activeStake_ - activeSlashed); + if (slashedAmount > 0) { + _timeToBucket.push(uint48(block.timestamp), lastBucket + 1); + withdrawals[lastBucket] = lastWithdrawals - unmaturedWithdrawals; + withdrawalShares[lastBucket] = lastWithdrawalShares - unmaturedWithdrawalShares; + withdrawalShares[lastBucket + 1] = unmaturedWithdrawalShares; + + uint256 activeSlashed = slashedAmount.mulDiv(activeStake_, slashableStake); + _activeStake.push(uint48(block.timestamp), activeStake_ - activeSlashed); + withdrawals[lastBucket + 1] = unmaturedWithdrawals - (slashedAmount - activeSlashed); - _withdrawals[lastBucket + 1] = unmaturedWithdrawals - (slashedAmount - activeSlashed); + IERC20(collateral).safeTransfer(burner, slashedAmount); + } - IERC20(collateral).safeTransfer(burner, slashedAmount); emit OnSlash(amount, captureTimestamp, slashedAmount); } @@ -380,24 +350,6 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau emit SetSlasher(slasher_); } - function migrateWithdrawalsOf(uint48[] calldata epochs, address account) public { - uint256 length = epochs.length; - uint48 epochDuration_ = epochDuration; - uint48 epochDurationInit_ = epochDurationInit; - uint48 migrationEpoch = (timeToBucket.at(0)._key - epochDurationInit_) / epochDuration_ - 1; - for (uint256 i; i < length; ++i) { - uint48 epoch = epochs[i]; - uint256 shares = _epochWithdrawalSharesOf[epoch][account]; - uint48 unlockAt = epochDurationInit_ + (epoch + 1) * epochDuration_; - if (shares > 0) { - if (epoch >= migrationEpoch) { - shares = ERC4626Math.previewRedeem(shares, _epochWithdrawals[epoch], _epochWithdrawalShares[epoch]); - } - _withdrawalsOf[account].push(Withdrawal(_isEpochWithdrawalsClaimed[epoch][account], unlockAt, shares)); - } - } - } - function _withdraw(address claimer, uint256 withdrawnAssets, uint256 burnedShares) internal virtual @@ -407,36 +359,32 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau _activeShares.push(uint48(block.timestamp), activeShares() - burnedShares); _activeStake.push(uint48(block.timestamp), activeStake() - withdrawnAssets); - uint256 lastBucket = timeToBucket.latest(); + uint256 lastBucket = _timeToBucket.latest(); mintedShares = - ERC4626Math.previewDeposit(withdrawnAssets, _withdrawalShares[lastBucket], _withdrawals[lastBucket]); - _withdrawals[lastBucket] += withdrawnAssets; - _withdrawalShares[lastBucket] += mintedShares; + ERC4626Math.previewDeposit(withdrawnAssets, withdrawalShares[lastBucket], withdrawals[lastBucket]); + withdrawals[lastBucket] += withdrawnAssets; + withdrawalShares[lastBucket] += mintedShares; uint48 unlockAt = uint48(block.timestamp) + epochDuration; - _withdrawalsOf[msg.sender].push(Withdrawal(false, unlockAt, mintedShares)); - withdrawalsPrefixes.push(unlockAt, withdrawalsPrefixes.latest() + withdrawnAssets); - withdrawalSharesPrefixes.push(unlockAt, withdrawalSharesPrefixes.latest() + mintedShares); + _withdrawalsOf[claimer].push(Withdrawal(false, unlockAt, mintedShares)); + _withdrawalSharesPrefixes.push(unlockAt, _withdrawalSharesPrefixes.latest() + mintedShares); emit Withdraw(msg.sender, claimer, withdrawnAssets, burnedShares, mintedShares); } function _claim(uint256 index) internal returns (uint256 amount) { - if (index >= _withdrawalsOf[msg.sender].length) { - revert InvalidEpoch(); - } - Withdrawal memory withdrawal = _withdrawalsOf[msg.sender][index]; - + Withdrawal storage withdrawal = _withdrawalsOf[msg.sender][index]; if (withdrawal.claimed) { revert AlreadyClaimed(); } - if (withdrawal.unlockAt >= block.timestamp) { revert WithdrawalNotMatured(); } - _withdrawalsOf[msg.sender][index].claimed = true; - (uint256 withdrawals, uint256 withdrawalShares) = getWithdrawalsAt(withdrawal.unlockAt); - amount = ERC4626Math.previewRedeem(withdrawal.shares, withdrawals, withdrawalShares); + amount = withdrawalsOf(index, msg.sender); + if (amount == 0) { + revert InsufficientClaim(); + } + withdrawal.claimed = true; } function _initialize(uint64, address, bytes memory data) internal virtual override { @@ -476,7 +424,6 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau burner = params.burner; - epochDurationInit = uint48(block.timestamp); epochDuration = params.epochDuration; depositWhitelist = params.depositWhitelist; @@ -501,6 +448,27 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau } } + /** + * @inheritdoc IVault + */ + function migrateWithdrawalsOf(address account, uint48 epoch) public { + if (_isEpochWithdrawalsClaimed[epoch][account]) { + revert(); + } + uint256 shares = _epochWithdrawalSharesOf[epoch][account]; + uint48 unlockAt = _epochDurationInit + (epoch + 1) * epochDuration; + if (unlockAt >= _withdrawalSharesPrefixes.at(0)._key) { + shares = ERC4626Math.previewRedeem(shares, _epochWithdrawals[epoch], _epochWithdrawalShares[epoch]); + } else if (withdrawalShares[epoch] == 0) { + withdrawals[epoch] = _epochWithdrawals[epoch]; + withdrawalShares[epoch] = _epochWithdrawalShares[epoch]; + _timeToBucket._trace._checkpoints[epoch]._key = unlockAt; + _timeToBucket._trace._checkpoints[epoch]._value = epoch; + } + _withdrawalsOf[account].push(Withdrawal(false, unlockAt, shares)); + _isEpochWithdrawalsClaimed[epoch][account] = true; + } + function _migrate( uint64, /* oldVersion */ @@ -511,23 +479,17 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau internal override { - uint256 withdrawals; - uint48 epochDuration_ = epochDuration; - uint48 epochDurationInit_ = epochDurationInit; - - uint48 epoch = (block.timestamp - epochDurationInit_).toUint48() / epochDuration_; - withdrawals = _epochWithdrawals[epoch]; - - uint48 nextEpochStart = epochDurationInit_ + (epoch + 1) * epochDuration_; - withdrawalsPrefixes.push(nextEpochStart, withdrawals); - withdrawalSharesPrefixes.push(nextEpochStart, withdrawals); - - withdrawals += _epochWithdrawals[epoch + 1]; - withdrawalsPrefixes.push(nextEpochStart + epochDuration_, withdrawals); - withdrawalSharesPrefixes.push(nextEpochStart + epochDuration_, withdrawals); - - timeToBucket.push(nextEpochStart, epoch); - _withdrawals[epoch] = withdrawals; - _withdrawalShares[epoch] = withdrawals; + uint48 epoch = (block.timestamp - _epochDurationInit).toUint48() / epochDuration; + uint256 epochWithdrawals = _epochWithdrawals[epoch]; + uint48 nextEpochStart = _epochDurationInit + (epoch + 1) * epochDuration; + _withdrawalSharesPrefixes.push(nextEpochStart, epochWithdrawals); + epochWithdrawals += _epochWithdrawals[epoch + 1]; + _withdrawalSharesPrefixes.push(nextEpochStart + epochDuration, epochWithdrawals); + assembly ("memory-safe") { + sstore(_timeToBucket.slot, epoch) + } + _timeToBucket.push(nextEpochStart, epoch); + withdrawals[epoch] = epochWithdrawals; + withdrawalShares[epoch] = epochWithdrawals; } } diff --git a/src/contracts/vault/VaultStorage.sol b/src/contracts/vault/VaultStorage.sol index 1a7ad0ca..36f7d8fc 100644 --- a/src/contracts/vault/VaultStorage.sol +++ b/src/contracts/vault/VaultStorage.sol @@ -65,10 +65,7 @@ abstract contract VaultStorage is StaticDelegateCallable, IVaultStorage { */ address public burner; - /** - * @inheritdoc IVaultStorage - */ - uint48 public epochDurationInit; + uint48 internal _epochDurationInit; /** * @inheritdoc IVaultStorage @@ -132,58 +129,31 @@ abstract contract VaultStorage is StaticDelegateCallable, IVaultStorage { mapping(address account => Checkpoints.Trace256 shares) internal _activeSharesOf; mapping(address account => Withdrawal[] withdrawals) internal _withdrawalsOf; - mapping(uint256 => uint256) internal _withdrawals; - mapping(uint256 => uint256) internal _withdrawalShares; - - Checkpoints.Trace208 public timeToBucket; - Checkpoints.Trace256 public withdrawalsPrefixes; - Checkpoints.Trace256 public withdrawalSharesPrefixes; - - constructor(address delegatorFactory, address slasherFactory) { - DELEGATOR_FACTORY = delegatorFactory; - SLASHER_FACTORY = slasherFactory; - } /** * @inheritdoc IVaultStorage */ - function epochAt(uint48 timestamp) public view returns (uint256) { - if (timestamp < epochDurationInit) { - revert InvalidTimestamp(); - } - return timeToBucket.upperLookupRecent(timestamp); - } + mapping(uint256 bucketIndex => uint256 value) public withdrawalShares; /** * @inheritdoc IVaultStorage */ - function currentEpoch() public view returns (uint256) { - return timeToBucket.upperLookupRecent(uint48(block.timestamp)); - } + mapping(uint256 bucketIndex => uint256 value) public withdrawals; - /** - * @inheritdoc IVaultStorage - */ - function currentEpochStart() public view returns (uint48) { - return (epochDurationInit + currentEpoch() * epochDuration).toUint48(); - } + Checkpoints.Trace256 internal _withdrawalSharesPrefixes; - /** - * @inheritdoc IVaultStorage - */ - function previousEpochStart() public view returns (uint48) { - uint256 epoch = currentEpoch(); - if (epoch == 0) { - revert NoPreviousEpoch(); - } - return (epochDurationInit + (epoch - 1) * epochDuration).toUint48(); + Checkpoints.Trace208 internal _timeToBucket; + + constructor(address delegatorFactory, address slasherFactory) { + DELEGATOR_FACTORY = delegatorFactory; + SLASHER_FACTORY = slasherFactory; } /** * @inheritdoc IVaultStorage */ - function nextEpochStart() public view returns (uint48) { - return (epochDurationInit + (currentEpoch() + 1) * epochDuration).toUint48(); + function currentEpochStart() public view returns (uint48) { + return uint48(block.timestamp); } /** @@ -228,29 +198,33 @@ abstract contract VaultStorage is StaticDelegateCallable, IVaultStorage { return _activeSharesOf[account].latest(); } - function isWithdrawalsClaimed(uint256 index, address account) external view returns (bool) { - return _withdrawalsOf[account][index].claimed; - } - - function withdrawalShares(uint256 index) external view returns (uint256) { - return _withdrawalShares[index]; - } - - function withdrawalSharesOf(uint256 index, address account) external view returns (uint256) { + /** + * @inheritdoc IVaultStorage + */ + function withdrawalSharesOf(uint256 index, address account) public view returns (uint256) { return _withdrawalsOf[account][index].shares; } - - function withdrawals(uint256 index) external view returns (uint256) { - return _withdrawals[index]; + + /** + * @inheritdoc IVaultStorage + */ + function isWithdrawalsClaimed(uint256 index, address account) public view returns (bool) { + return _withdrawalsOf[account][index].claimed; } - function latestWithdrawalBucket() external view returns (uint256) { - return timeToBucket.latest(); + /** + * @inheritdoc IVaultStorage + */ + function withdrawalUnlockAt(uint256 index, address account) public view returns (uint48) { + return _withdrawalsOf[account][index].unlockAt; } - function unmaturedWithdrawalShares(uint48 timestamp) public view returns (uint256) { - return withdrawalSharesPrefixes.length() == 1 ? withdrawalSharesPrefixes.latest() : withdrawalSharesPrefixes.latest() - withdrawalSharesPrefixes.upperLookupRecent(timestamp); + /** + * @inheritdoc IVaultStorage + */ + function withdrawalsLength(address account) public view returns (uint256) { + return _withdrawalsOf[account].length; } - uint256[50] private __gap; + uint256[45] private __gap; } diff --git a/src/interfaces/delegator/IBaseDelegator.sol b/src/interfaces/delegator/IBaseDelegator.sol index 91b129ac..0340f212 100644 --- a/src/interfaces/delegator/IBaseDelegator.sol +++ b/src/interfaces/delegator/IBaseDelegator.sol @@ -174,11 +174,6 @@ interface IBaseDelegator is IEntity { * @param data some additional data * @dev Only the vault's slasher can call this function. */ - function onSlash( - bytes32 subnetwork, - address operator, - uint256 amount, - uint48 captureTimestamp, - bytes calldata data - ) external; + function onSlash(bytes32 subnetwork, address operator, uint256 amount, uint48 captureTimestamp, bytes calldata data) + external; } diff --git a/src/interfaces/delegator/IDelegatorHook.sol b/src/interfaces/delegator/IDelegatorHook.sol index d2241fca..61333955 100644 --- a/src/interfaces/delegator/IDelegatorHook.sol +++ b/src/interfaces/delegator/IDelegatorHook.sol @@ -10,11 +10,6 @@ interface IDelegatorHook { * @param captureTimestamp time point when the stake was captured * @param data some additional data */ - function onSlash( - bytes32 subnetwork, - address operator, - uint256 amount, - uint48 captureTimestamp, - bytes calldata data - ) external; + function onSlash(bytes32 subnetwork, address operator, uint256 amount, uint48 captureTimestamp, bytes calldata data) + external; } diff --git a/src/interfaces/vault/IVault.sol b/src/interfaces/vault/IVault.sol index be01eeee..b5121283 100644 --- a/src/interfaces/vault/IVault.sol +++ b/src/interfaces/vault/IVault.sol @@ -4,6 +4,11 @@ pragma solidity ^0.8.0; import {IMigratableEntity} from "../common/IMigratableEntity.sol"; import {IVaultStorage} from "./IVaultStorage.sol"; +/** + * @title IVault + * @dev Deprecated signatures: + * slashableBalanceOf() + */ interface IVault is IMigratableEntity, IVaultStorage { error AlreadyClaimed(); error AlreadySet(); @@ -18,7 +23,6 @@ interface IVault is IMigratableEntity, IVaultStorage { error InvalidClaimer(); error InvalidCollateral(); error InvalidDelegator(); - error InvalidEpoch(); error InvalidEpochDuration(); error InvalidLengthEpochs(); error InvalidOnBehalfOf(); @@ -195,13 +199,6 @@ interface IVault is IMigratableEntity, IVaultStorage { */ function withdrawalsOf(uint256 index, address account) external view returns (uint256); - /** - * @notice Get a total amount of the collateral that can be slashed for a given account. - * @param account account to get the slashable collateral for - * @return total amount of the account's slashable collateral - */ - function slashableBalanceOf(address account) external view returns (uint256); - /** * @notice Deposit collateral into the vault. * @param onBehalfOf account the deposit is made on behalf of @@ -298,4 +295,11 @@ interface IVault is IMigratableEntity, IVaultStorage { * @dev Can be set only once. */ function setSlasher(address slasher) external; + + /** + * @dev Migrates a epoch-based withdawal to the fixed-delay-based one. + * @param epoch An epoch to migrate the withdrawal for. + * @param account An account to migrate the withdrawal of. + */ + function migrateWithdrawalsOf(address account, uint48 epoch) external; } diff --git a/src/interfaces/vault/IVaultStorage.sol b/src/interfaces/vault/IVaultStorage.sol index 8eddaaaf..b02b2917 100644 --- a/src/interfaces/vault/IVaultStorage.sol +++ b/src/interfaces/vault/IVaultStorage.sol @@ -1,6 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +/** + * @title IVaultStorage + * @dev Deprecated signatures: + * epochDurationInit() + * epochAt(uint48) + * currentEpoch() + * previousEpochStart() + * nextEpochStart() + */ interface IVaultStorage { error InvalidTimestamp(); error NoPreviousEpoch(); @@ -84,55 +93,18 @@ interface IVaultStorage { function isSlasherInitialized() external view returns (bool); /** - * @notice Get a time point of the epoch duration set. - * @return time point of the epoch duration set - */ - function epochDurationInit() external view returns (uint48); - - /** - * @notice Get a duration of the vault epoch. - * @return duration of the epoch + * @notice Get a duration of the vault withdrawal delay. + * @return duration of the withdrawal delay */ function epochDuration() external view returns (uint48); - /** - * @notice Get an epoch at a given timestamp. - * @param timestamp time point to get the epoch at - * @return epoch at the timestamp - * @dev Reverts if the timestamp is less than the start of the epoch 0. - * @dev DEPRECATED: Epoch-related functionality is deprecated. - */ - function epochAt(uint48 timestamp) external view returns (uint256); - - /** - * @notice Get a current vault epoch. - * @return current epoch - * @dev DEPRECATED: Epoch-related functionality is deprecated. - */ - function currentEpoch() external view returns (uint256); - /** * @notice Get a start of the current vault epoch. * @return start of the current epoch - * @dev DEPRECATED: Epoch-related functionality is deprecated. + * @dev DEPRECATED: Returns current timestamp. */ function currentEpochStart() external view returns (uint48); - /** - * @notice Get a start of the previous vault epoch. - * @return start of the previous epoch - * @dev Reverts if the current epoch is 0. - * @dev DEPRECATED: Epoch-related functionality is deprecated. - */ - function previousEpochStart() external view returns (uint48); - - /** - * @notice Get a start of the next vault epoch. - * @return start of the next epoch - * @dev DEPRECATED: Epoch-related functionality is deprecated. - */ - function nextEpochStart() external view returns (uint48); - /** * @notice Get if the deposit whitelist is enabled. * @return if the deposit whitelist is enabled @@ -203,16 +175,16 @@ interface IVaultStorage { function activeSharesOf(address account) external view returns (uint256); /** - * @notice Get a total amount of the withdrawals at a given index. + * @notice Get a total amount of the withdrawals at a given bucket index. * @param index index to get the total amount of the withdrawals at - * @return total amount of the withdrawals at the index + * @return total amount of the withdrawals at the bucket index */ function withdrawals(uint256 index) external view returns (uint256); /** - * @notice Get a total number of withdrawal shares at a given index. + * @notice Get a total number of withdrawal shares at a given bucket index. * @param index index to get the total number of withdrawal shares at - * @return total number of withdrawal shares at the index + * @return total number of withdrawal shares at the bucket index */ function withdrawalShares(uint256 index) external view returns (uint256); @@ -225,23 +197,25 @@ interface IVaultStorage { function withdrawalSharesOf(uint256 index, address account) external view returns (uint256); /** - * @notice Get if the withdrawals are claimed for a particular account at a given index. - * @param index index to check the withdrawals for the account at - * @param account account to check the withdrawals for - * @return if the withdrawals are claimed for the account at the index + * @notice Get if the withdrawal is claimed for a particular account at a given index. + * @param index index to check the withdrawal for the account at + * @param account account to check the withdrawal for + * @return if the withdrawal is claimed for the account at the index */ function isWithdrawalsClaimed(uint256 index, address account) external view returns (bool); /** - * @notice Get a total number of unmatured withdrawal shares at a given timestamp. - * @param timestamp time point to get the total number of unmatured withdrawal shares at - * @return total number of unmatured withdrawal shares at the timestamp + * @notice Get when the withdrawal become claimable for a particular account at a given index. + * @param index index to check the withdrawals for the account at + * @param account account to check the withdrawal for + * @return when the withdrawal is claimable for the account at the index */ - function unmaturedWithdrawalShares(uint48 timestamp) external view returns (uint256); + function withdrawalUnlockAt(uint256 index, address account) external view returns (uint48); /** - * @notice Get the latest withdrawal bucket. - * @return latest withdrawal bucket + * @notice Get how many withdrawals a particular account requested. + * @param account account to check the withdrawals for + * @return the number of withdrawals requested by the account */ - function latestWithdrawalBucket() external view returns (uint256); + function withdrawalsLength(address account) external view returns (uint256); } diff --git a/test/delegator/FullRestakeDelegator.t.sol b/test/delegator/FullRestakeDelegator.t.sol index b9f5b719..11abbd96 100644 --- a/test/delegator/FullRestakeDelegator.t.sol +++ b/test/delegator/FullRestakeDelegator.t.sol @@ -640,7 +640,7 @@ contract FullRestakeDelegatorTest is Test { networkLimit1 ); - blockTimestamp = vault.currentEpochStart() + vault.epochDuration(); + blockTimestamp = blockTimestamp + vault.epochDuration(); vm.warp(blockTimestamp); assertEq( diff --git a/test/delegator/NetworkRestakeDelegator.t.sol b/test/delegator/NetworkRestakeDelegator.t.sol index 32999c3d..4e8ebe35 100644 --- a/test/delegator/NetworkRestakeDelegator.t.sol +++ b/test/delegator/NetworkRestakeDelegator.t.sol @@ -753,7 +753,7 @@ contract NetworkRestakeDelegatorTest is Test { networkLimit1 ); - blockTimestamp = vault.currentEpochStart() + vault.epochDuration(); + blockTimestamp = blockTimestamp + vault.epochDuration(); vm.warp(blockTimestamp); assertEq( diff --git a/test/delegator/OperatorSpecificDelegator.t.sol b/test/delegator/OperatorSpecificDelegator.t.sol index a681f282..75ebd316 100644 --- a/test/delegator/OperatorSpecificDelegator.t.sol +++ b/test/delegator/OperatorSpecificDelegator.t.sol @@ -463,7 +463,7 @@ contract OperatorSpecificDelegatorTest is Test { networkLimit1 ); - blockTimestamp = vault.currentEpochStart() + vault.epochDuration(); + blockTimestamp = blockTimestamp + vault.epochDuration(); vm.warp(blockTimestamp); assertEq( diff --git a/test/helpers/VaultTestHelper.sol b/test/helpers/VaultTestHelper.sol new file mode 100644 index 00000000..52a95ec4 --- /dev/null +++ b/test/helpers/VaultTestHelper.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import {Hints} from "../../src/contracts/hints/Hints.sol"; +import {VaultStorage} from "../../src/contracts/vault/VaultStorage.sol"; +import {Checkpoints} from "../../src/contracts/libraries/Checkpoints.sol"; + +contract VaultTestHelper is VaultStorage, Hints { + using Checkpoints for Checkpoints.Trace208; + using Checkpoints for Checkpoints.Trace256; + + constructor() VaultStorage(address(0), address(0)) {} + + function _timeToBucketLatestInternal() external view internalFunction returns (uint208) { + return _timeToBucket.latest(); + } + + function _timeToBucketAtInternal(uint32 pos) external view internalFunction returns (uint48, uint208) { + Checkpoints.Checkpoint208 memory checkpoint = _timeToBucket.at(pos); + return (checkpoint._key, checkpoint._value); + } + + function _timeToBucketLengthInternal() external view internalFunction returns (uint256) { + return _timeToBucket.length(); + } + + function _withdrawalSharesPrefixesLatestInternal() external view internalFunction returns (uint256) { + return _withdrawalSharesPrefixes.latest(); + } + + function _withdrawalSharesPrefixesUpperLookupRecentInternal(uint48 timestamp) + external + view + internalFunction + returns (uint256) + { + return _withdrawalSharesPrefixes.upperLookupRecent(timestamp); + } + + function _withdrawalSharesPrefixesAtInternal(uint32 pos) + external + view + internalFunction + returns (uint48, uint256) + { + Checkpoints.Checkpoint256 memory checkpoint = _withdrawalSharesPrefixes.at(pos); + return (checkpoint._key, checkpoint._value); + } + + function _withdrawalSharesPrefixesLengthInternal() external view internalFunction returns (uint256) { + return _withdrawalSharesPrefixes.length(); + } + + function timeToBucketLatest(address vault) external view returns (uint208) { + return abi.decode( + _selfStaticDelegateCall(vault, abi.encodeCall(VaultTestHelper._timeToBucketLatestInternal, ())), (uint208) + ); + } + + function timeToBucketAt(address vault, uint32 pos) external view returns (uint48, uint208) { + return abi.decode( + _selfStaticDelegateCall(vault, abi.encodeCall(VaultTestHelper._timeToBucketAtInternal, (pos))), + (uint48, uint208) + ); + } + + function timeToBucketLength(address vault) external view returns (uint256) { + return abi.decode( + _selfStaticDelegateCall(vault, abi.encodeCall(VaultTestHelper._timeToBucketLengthInternal, ())), (uint256) + ); + } + + function withdrawalSharesPrefixesLatest(address vault) external view returns (uint256) { + return abi.decode( + _selfStaticDelegateCall(vault, abi.encodeCall(VaultTestHelper._withdrawalSharesPrefixesLatestInternal, ())), + (uint256) + ); + } + + function withdrawalSharesPrefixesUpperLookupRecent(address vault, uint48 timestamp) external view returns (uint256) { + return abi.decode( + _selfStaticDelegateCall( + vault, abi.encodeCall(VaultTestHelper._withdrawalSharesPrefixesUpperLookupRecentInternal, (timestamp)) + ), + (uint256) + ); + } + + function withdrawalSharesPrefixesAt(address vault, uint32 pos) external view returns (uint48, uint256) { + return abi.decode( + _selfStaticDelegateCall(vault, abi.encodeCall(VaultTestHelper._withdrawalSharesPrefixesAtInternal, (pos))), + (uint48, uint256) + ); + } + + function withdrawalSharesPrefixesLength(address vault) external view returns (uint256) { + return abi.decode( + _selfStaticDelegateCall( + vault, abi.encodeCall(VaultTestHelper._withdrawalSharesPrefixesLengthInternal, ()) + ), + (uint256) + ); + } +} diff --git a/test/helpers/v1/IVaultStorageV1.sol b/test/helpers/v1/IVaultStorageV1.sol new file mode 100644 index 00000000..06ea6cb0 --- /dev/null +++ b/test/helpers/v1/IVaultStorageV1.sol @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IVaultStorageV1 { + error InvalidTimestamp(); + error NoPreviousEpoch(); + + /** + * @notice Get a deposit whitelist enabler/disabler's role. + * @return identifier of the whitelist enabler/disabler role + */ + function DEPOSIT_WHITELIST_SET_ROLE() external view returns (bytes32); + + /** + * @notice Get a depositor whitelist status setter's role. + * @return identifier of the depositor whitelist status setter role + */ + function DEPOSITOR_WHITELIST_ROLE() external view returns (bytes32); + + /** + * @notice Get a deposit limit enabler/disabler's role. + * @return identifier of the deposit limit enabler/disabler role + */ + function IS_DEPOSIT_LIMIT_SET_ROLE() external view returns (bytes32); + + /** + * @notice Get a deposit limit setter's role. + * @return identifier of the deposit limit setter role + */ + function DEPOSIT_LIMIT_SET_ROLE() external view returns (bytes32); + + /** + * @notice Get the delegator factory's address. + * @return address of the delegator factory + */ + function DELEGATOR_FACTORY() external view returns (address); + + /** + * @notice Get the slasher factory's address. + * @return address of the slasher factory + */ + function SLASHER_FACTORY() external view returns (address); + + /** + * @notice Get a vault collateral. + * @return address of the underlying collateral + */ + function collateral() external view returns (address); + + /** + * @notice Get a burner to issue debt to (e.g., 0xdEaD or some unwrapper contract). + * @return address of the burner + */ + function burner() external view returns (address); + + /** + * @notice Get a delegator (it delegates the vault's stake to networks and operators). + * @return address of the delegator + */ + function delegator() external view returns (address); + + /** + * @notice Get if the delegator is initialized. + * @return if the delegator is initialized + */ + function isDelegatorInitialized() external view returns (bool); + + /** + * @notice Get a slasher (it provides networks a slashing mechanism). + * @return address of the slasher + */ + function slasher() external view returns (address); + + /** + * @notice Get if the slasher is initialized. + * @return if the slasher is initialized + */ + function isSlasherInitialized() external view returns (bool); + + /** + * @notice Get a time point of the epoch duration set. + * @return time point of the epoch duration set + */ + function epochDurationInit() external view returns (uint48); + + /** + * @notice Get a duration of the vault epoch. + * @return duration of the epoch + */ + function epochDuration() external view returns (uint48); + + /** + * @notice Get an epoch at a given timestamp. + * @param timestamp time point to get the epoch at + * @return epoch at the timestamp + * @dev Reverts if the timestamp is less than the start of the epoch 0. + */ + function epochAt(uint48 timestamp) external view returns (uint256); + + /** + * @notice Get a current vault epoch. + * @return current epoch + */ + function currentEpoch() external view returns (uint256); + + /** + * @notice Get a start of the current vault epoch. + * @return start of the current epoch + */ + function currentEpochStart() external view returns (uint48); + + /** + * @notice Get a start of the previous vault epoch. + * @return start of the previous epoch + * @dev Reverts if the current epoch is 0. + */ + function previousEpochStart() external view returns (uint48); + + /** + * @notice Get a start of the next vault epoch. + * @return start of the next epoch + */ + function nextEpochStart() external view returns (uint48); + + /** + * @notice Get if the deposit whitelist is enabled. + * @return if the deposit whitelist is enabled + */ + function depositWhitelist() external view returns (bool); + + /** + * @notice Get if a given account is whitelisted as a depositor. + * @param account address to check + * @return if the account is whitelisted as a depositor + */ + function isDepositorWhitelisted(address account) external view returns (bool); + + /** + * @notice Get if the deposit limit is set. + * @return if the deposit limit is set + */ + function isDepositLimit() external view returns (bool); + + /** + * @notice Get a deposit limit (maximum amount of the active stake that can be in the vault simultaneously). + * @return deposit limit + */ + function depositLimit() external view returns (uint256); + + /** + * @notice Get a total number of active shares in the vault at a given timestamp using a hint. + * @param timestamp time point to get the total number of active shares at + * @param hint hint for the checkpoint index + * @return total number of active shares at the timestamp + */ + function activeSharesAt(uint48 timestamp, bytes memory hint) external view returns (uint256); + + /** + * @notice Get a total number of active shares in the vault. + * @return total number of active shares + */ + function activeShares() external view returns (uint256); + + /** + * @notice Get a total amount of active stake in the vault at a given timestamp using a hint. + * @param timestamp time point to get the total active stake at + * @param hint hint for the checkpoint index + * @return total amount of active stake at the timestamp + */ + function activeStakeAt(uint48 timestamp, bytes memory hint) external view returns (uint256); + + /** + * @notice Get a total amount of active stake in the vault. + * @return total amount of active stake + */ + function activeStake() external view returns (uint256); + + /** + * @notice Get a total number of active shares for a particular account at a given timestamp using a hint. + * @param account account to get the number of active shares for + * @param timestamp time point to get the number of active shares for the account at + * @param hint hint for the checkpoint index + * @return number of active shares for the account at the timestamp + */ + function activeSharesOfAt(address account, uint48 timestamp, bytes memory hint) external view returns (uint256); + + /** + * @notice Get a number of active shares for a particular account. + * @param account account to get the number of active shares for + * @return number of active shares for the account + */ + function activeSharesOf(address account) external view returns (uint256); + + /** + * @notice Get a total amount of the withdrawals at a given epoch. + * @param epoch epoch to get the total amount of the withdrawals at + * @return total amount of the withdrawals at the epoch + */ + function withdrawals(uint256 epoch) external view returns (uint256); + + /** + * @notice Get a total number of withdrawal shares at a given epoch. + * @param epoch epoch to get the total number of withdrawal shares at + * @return total number of withdrawal shares at the epoch + */ + function withdrawalShares(uint256 epoch) external view returns (uint256); + + /** + * @notice Get a number of withdrawal shares for a particular account at a given epoch (zero if claimed). + * @param epoch epoch to get the number of withdrawal shares for the account at + * @param account account to get the number of withdrawal shares for + * @return number of withdrawal shares for the account at the epoch + */ + function withdrawalSharesOf(uint256 epoch, address account) external view returns (uint256); + + /** + * @notice Get if the withdrawals are claimed for a particular account at a given epoch. + * @param epoch epoch to check the withdrawals for the account at + * @param account account to check the withdrawals for + * @return if the withdrawals are claimed for the account at the epoch + */ + function isWithdrawalsClaimed(uint256 epoch, address account) external view returns (bool); +} diff --git a/test/helpers/v1/IVaultV1.sol b/test/helpers/v1/IVaultV1.sol new file mode 100644 index 00000000..10a88e2f --- /dev/null +++ b/test/helpers/v1/IVaultV1.sol @@ -0,0 +1,300 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {IMigratableEntity} from "../../../src/interfaces/common/IMigratableEntity.sol"; +import {IVaultStorageV1} from "./IVaultStorageV1.sol"; + +interface IVaultV1 is IMigratableEntity, IVaultStorageV1 { + error AlreadyClaimed(); + error AlreadySet(); + error DelegatorAlreadyInitialized(); + error DepositLimitReached(); + error InsufficientClaim(); + error InsufficientDeposit(); + error InsufficientRedemption(); + error InsufficientWithdrawal(); + error InvalidAccount(); + error InvalidCaptureEpoch(); + error InvalidClaimer(); + error InvalidCollateral(); + error InvalidDelegator(); + error InvalidEpoch(); + error InvalidEpochDuration(); + error InvalidLengthEpochs(); + error InvalidOnBehalfOf(); + error InvalidRecipient(); + error InvalidSlasher(); + error MissingRoles(); + error NotDelegator(); + error NotSlasher(); + error NotWhitelistedDepositor(); + error SlasherAlreadyInitialized(); + error TooMuchRedeem(); + error TooMuchWithdraw(); + + /** + * @notice Initial parameters needed for a vault deployment. + * @param collateral vault's underlying collateral + * @param burner vault's burner to issue debt to (e.g., 0xdEaD or some unwrapper contract) + * @param epochDuration duration of the vault epoch (it determines sync points for withdrawals) + * @param depositWhitelist if enabling deposit whitelist + * @param isDepositLimit if enabling deposit limit + * @param depositLimit deposit limit (maximum amount of the collateral that can be in the vault simultaneously) + * @param defaultAdminRoleHolder address of the initial DEFAULT_ADMIN_ROLE holder + * @param depositWhitelistSetRoleHolder address of the initial DEPOSIT_WHITELIST_SET_ROLE holder + * @param depositorWhitelistRoleHolder address of the initial DEPOSITOR_WHITELIST_ROLE holder + * @param isDepositLimitSetRoleHolder address of the initial IS_DEPOSIT_LIMIT_SET_ROLE holder + * @param depositLimitSetRoleHolder address of the initial DEPOSIT_LIMIT_SET_ROLE holder + */ + struct InitParams { + address collateral; + address burner; + uint48 epochDuration; + bool depositWhitelist; + bool isDepositLimit; + uint256 depositLimit; + address defaultAdminRoleHolder; + address depositWhitelistSetRoleHolder; + address depositorWhitelistRoleHolder; + address isDepositLimitSetRoleHolder; + address depositLimitSetRoleHolder; + } + + /** + * @notice Hints for an active balance. + * @param activeSharesOfHint hint for the active shares of checkpoint + * @param activeStakeHint hint for the active stake checkpoint + * @param activeSharesHint hint for the active shares checkpoint + */ + struct ActiveBalanceOfHints { + bytes activeSharesOfHint; + bytes activeStakeHint; + bytes activeSharesHint; + } + + /** + * @notice Emitted when a deposit is made. + * @param depositor account that made the deposit + * @param onBehalfOf account the deposit was made on behalf of + * @param amount amount of the collateral deposited + * @param shares amount of the active shares minted + */ + event Deposit(address indexed depositor, address indexed onBehalfOf, uint256 amount, uint256 shares); + + /** + * @notice Emitted when a withdrawal is made. + * @param withdrawer account that made the withdrawal + * @param claimer account that needs to claim the withdrawal + * @param amount amount of the collateral withdrawn + * @param burnedShares amount of the active shares burned + * @param mintedShares amount of the epoch withdrawal shares minted + */ + event Withdraw( + address indexed withdrawer, address indexed claimer, uint256 amount, uint256 burnedShares, uint256 mintedShares + ); + + /** + * @notice Emitted when a claim is made. + * @param claimer account that claimed + * @param recipient account that received the collateral + * @param epoch epoch the collateral was claimed for + * @param amount amount of the collateral claimed + */ + event Claim(address indexed claimer, address indexed recipient, uint256 epoch, uint256 amount); + + /** + * @notice Emitted when a batch claim is made. + * @param claimer account that claimed + * @param recipient account that received the collateral + * @param epochs epochs the collateral was claimed for + * @param amount amount of the collateral claimed + */ + event ClaimBatch(address indexed claimer, address indexed recipient, uint256[] epochs, uint256 amount); + + /** + * @notice Emitted when a slash happens. + * @param amount amount of the collateral to slash + * @param captureTimestamp time point when the stake was captured + * @param slashedAmount real amount of the collateral slashed + */ + event OnSlash(uint256 amount, uint48 captureTimestamp, uint256 slashedAmount); + + /** + * @notice Emitted when a deposit whitelist status is enabled/disabled. + * @param status if enabled deposit whitelist + */ + event SetDepositWhitelist(bool status); + + /** + * @notice Emitted when a depositor whitelist status is set. + * @param account account for which the whitelist status is set + * @param status if whitelisted the account + */ + event SetDepositorWhitelistStatus(address indexed account, bool status); + + /** + * @notice Emitted when a deposit limit status is enabled/disabled. + * @param status if enabled deposit limit + */ + event SetIsDepositLimit(bool status); + + /** + * @notice Emitted when a deposit limit is set. + * @param limit deposit limit (maximum amount of the collateral that can be in the vault simultaneously) + */ + event SetDepositLimit(uint256 limit); + + /** + * @notice Emitted when a delegator is set. + * @param delegator vault's delegator to delegate the stake to networks and operators + * @dev Can be set only once. + */ + event SetDelegator(address indexed delegator); + + /** + * @notice Emitted when a slasher is set. + * @param slasher vault's slasher to provide a slashing mechanism to networks + * @dev Can be set only once. + */ + event SetSlasher(address indexed slasher); + + /** + * @notice Check if the vault is fully initialized (a delegator and a slasher are set). + * @return if the vault is fully initialized + */ + function isInitialized() external view returns (bool); + + /** + * @notice Get a total amount of the collateral that can be slashed. + * @return total amount of the slashable collateral + */ + function totalStake() external view returns (uint256); + + /** + * @notice Get an active balance for a particular account at a given timestamp using hints. + * @param account account to get the active balance for + * @param timestamp time point to get the active balance for the account at + * @param hints hints for checkpoints' indexes + * @return active balance for the account at the timestamp + */ + function activeBalanceOfAt(address account, uint48 timestamp, bytes calldata hints) external view returns (uint256); + + /** + * @notice Get an active balance for a particular account. + * @param account account to get the active balance for + * @return active balance for the account + */ + function activeBalanceOf(address account) external view returns (uint256); + + /** + * @notice Get withdrawals for a particular account at a given epoch (zero if claimed). + * @param epoch epoch to get the withdrawals for the account at + * @param account account to get the withdrawals for + * @return withdrawals for the account at the epoch + */ + function withdrawalsOf(uint256 epoch, address account) external view returns (uint256); + + /** + * @notice Get a total amount of the collateral that can be slashed for a given account. + * @param account account to get the slashable collateral for + * @return total amount of the account's slashable collateral + */ + function slashableBalanceOf(address account) external view returns (uint256); + + /** + * @notice Deposit collateral into the vault. + * @param onBehalfOf account the deposit is made on behalf of + * @param amount amount of the collateral to deposit + * @return depositedAmount real amount of the collateral deposited + * @return mintedShares amount of the active shares minted + */ + function deposit(address onBehalfOf, uint256 amount) + external + returns (uint256 depositedAmount, uint256 mintedShares); + + /** + * @notice Withdraw collateral from the vault (it will be claimable after the next epoch). + * @param claimer account that needs to claim the withdrawal + * @param amount amount of the collateral to withdraw + * @return burnedShares amount of the active shares burned + * @return mintedShares amount of the epoch withdrawal shares minted + */ + function withdraw(address claimer, uint256 amount) external returns (uint256 burnedShares, uint256 mintedShares); + + /** + * @notice Redeem collateral from the vault (it will be claimable after the next epoch). + * @param claimer account that needs to claim the withdrawal + * @param shares amount of the active shares to redeem + * @return withdrawnAssets amount of the collateral withdrawn + * @return mintedShares amount of the epoch withdrawal shares minted + */ + function redeem(address claimer, uint256 shares) external returns (uint256 withdrawnAssets, uint256 mintedShares); + + /** + * @notice Claim collateral from the vault. + * @param recipient account that receives the collateral + * @param epoch epoch to claim the collateral for + * @return amount amount of the collateral claimed + */ + function claim(address recipient, uint256 epoch) external returns (uint256 amount); + + /** + * @notice Claim collateral from the vault for multiple epochs. + * @param recipient account that receives the collateral + * @param epochs epochs to claim the collateral for + * @return amount amount of the collateral claimed + */ + function claimBatch(address recipient, uint256[] calldata epochs) external returns (uint256 amount); + + /** + * @notice Slash callback for burning collateral. + * @param amount amount to slash + * @param captureTimestamp time point when the stake was captured + * @return slashedAmount real amount of the collateral slashed + * @dev Only the slasher can call this function. + */ + function onSlash(uint256 amount, uint48 captureTimestamp) external returns (uint256 slashedAmount); + + /** + * @notice Enable/disable deposit whitelist. + * @param status if enabling deposit whitelist + * @dev Only a DEPOSIT_WHITELIST_SET_ROLE holder can call this function. + */ + function setDepositWhitelist(bool status) external; + + /** + * @notice Set a depositor whitelist status. + * @param account account for which the whitelist status is set + * @param status if whitelisting the account + * @dev Only a DEPOSITOR_WHITELIST_ROLE holder can call this function. + */ + function setDepositorWhitelistStatus(address account, bool status) external; + + /** + * @notice Enable/disable deposit limit. + * @param status if enabling deposit limit + * @dev Only a IS_DEPOSIT_LIMIT_SET_ROLE holder can call this function. + */ + function setIsDepositLimit(bool status) external; + + /** + * @notice Set a deposit limit. + * @param limit deposit limit (maximum amount of the collateral that can be in the vault simultaneously) + * @dev Only a DEPOSIT_LIMIT_SET_ROLE holder can call this function. + */ + function setDepositLimit(uint256 limit) external; + + /** + * @notice Set a delegator. + * @param delegator vault's delegator to delegate the stake to networks and operators + * @dev Can be set only once. + */ + function setDelegator(address delegator) external; + + /** + * @notice Set a slasher. + * @param slasher vault's slasher to provide a slashing mechanism to networks + * @dev Can be set only once. + */ + function setSlasher(address slasher) external; +} diff --git a/test/helpers/v1/VaultStorageV1.sol b/test/helpers/v1/VaultStorageV1.sol new file mode 100644 index 00000000..11bde895 --- /dev/null +++ b/test/helpers/v1/VaultStorageV1.sol @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.25; + +import {StaticDelegateCallable} from "../../../src/contracts/common/StaticDelegateCallable.sol"; + +import {Checkpoints} from "../../../src/contracts/libraries/Checkpoints.sol"; + +import {IVaultStorageV1} from "./IVaultStorageV1.sol"; + +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; + +abstract contract VaultStorageV1 is StaticDelegateCallable, IVaultStorageV1 { + using Checkpoints for Checkpoints.Trace256; + using SafeCast for uint256; + + /** + * @inheritdoc IVaultStorageV1 + */ + bytes32 public constant DEPOSIT_WHITELIST_SET_ROLE = keccak256("DEPOSIT_WHITELIST_SET_ROLE"); + + /** + * @inheritdoc IVaultStorageV1 + */ + bytes32 public constant DEPOSITOR_WHITELIST_ROLE = keccak256("DEPOSITOR_WHITELIST_ROLE"); + + /** + * @inheritdoc IVaultStorageV1 + */ + bytes32 public constant IS_DEPOSIT_LIMIT_SET_ROLE = keccak256("IS_DEPOSIT_LIMIT_SET_ROLE"); + + /** + * @inheritdoc IVaultStorageV1 + */ + bytes32 public constant DEPOSIT_LIMIT_SET_ROLE = keccak256("DEPOSIT_LIMIT_SET_ROLE"); + + /** + * @inheritdoc IVaultStorageV1 + */ + address public immutable DELEGATOR_FACTORY; + + /** + * @inheritdoc IVaultStorageV1 + */ + address public immutable SLASHER_FACTORY; + + /** + * @inheritdoc IVaultStorageV1 + */ + bool public depositWhitelist; + + /** + * @inheritdoc IVaultStorageV1 + */ + bool public isDepositLimit; + + /** + * @inheritdoc IVaultStorageV1 + */ + address public collateral; + + /** + * @inheritdoc IVaultStorageV1 + */ + address public burner; + + /** + * @inheritdoc IVaultStorageV1 + */ + uint48 public epochDurationInit; + + /** + * @inheritdoc IVaultStorageV1 + */ + uint48 public epochDuration; + + /** + * @inheritdoc IVaultStorageV1 + */ + address public delegator; + + /** + * @inheritdoc IVaultStorageV1 + */ + bool public isDelegatorInitialized; + + /** + * @inheritdoc IVaultStorageV1 + */ + address public slasher; + + /** + * @inheritdoc IVaultStorageV1 + */ + bool public isSlasherInitialized; + + /** + * @inheritdoc IVaultStorageV1 + */ + uint256 public depositLimit; + + /** + * @inheritdoc IVaultStorageV1 + */ + mapping(address account => bool value) public isDepositorWhitelisted; + + /** + * @inheritdoc IVaultStorageV1 + */ + mapping(uint256 epoch => uint256 amount) public withdrawals; + + /** + * @inheritdoc IVaultStorageV1 + */ + mapping(uint256 epoch => uint256 amount) public withdrawalShares; + + /** + * @inheritdoc IVaultStorageV1 + */ + mapping(uint256 epoch => mapping(address account => uint256 amount)) public withdrawalSharesOf; + + /** + * @inheritdoc IVaultStorageV1 + */ + mapping(uint256 epoch => mapping(address account => bool value)) public isWithdrawalsClaimed; + + Checkpoints.Trace256 internal _activeShares; + + Checkpoints.Trace256 internal _activeStake; + + mapping(address account => Checkpoints.Trace256 shares) internal _activeSharesOf; + + constructor(address delegatorFactory, address slasherFactory) { + DELEGATOR_FACTORY = delegatorFactory; + SLASHER_FACTORY = slasherFactory; + } + + /** + * @inheritdoc IVaultStorageV1 + */ + function epochAt(uint48 timestamp) public view returns (uint256) { + if (timestamp < epochDurationInit) { + revert InvalidTimestamp(); + } + return (timestamp - epochDurationInit) / epochDuration; + } + + /** + * @inheritdoc IVaultStorageV1 + */ + function currentEpoch() public view returns (uint256) { + return (Time.timestamp() - epochDurationInit) / epochDuration; + } + + /** + * @inheritdoc IVaultStorageV1 + */ + function currentEpochStart() public view returns (uint48) { + return (epochDurationInit + currentEpoch() * epochDuration).toUint48(); + } + + /** + * @inheritdoc IVaultStorageV1 + */ + function previousEpochStart() public view returns (uint48) { + uint256 epoch = currentEpoch(); + if (epoch == 0) { + revert NoPreviousEpoch(); + } + return (epochDurationInit + (epoch - 1) * epochDuration).toUint48(); + } + + /** + * @inheritdoc IVaultStorageV1 + */ + function nextEpochStart() public view returns (uint48) { + return (epochDurationInit + (currentEpoch() + 1) * epochDuration).toUint48(); + } + + /** + * @inheritdoc IVaultStorageV1 + */ + function activeSharesAt(uint48 timestamp, bytes memory hint) public view returns (uint256) { + return _activeShares.upperLookupRecent(timestamp, hint); + } + + /** + * @inheritdoc IVaultStorageV1 + */ + function activeShares() public view returns (uint256) { + return _activeShares.latest(); + } + + /** + * @inheritdoc IVaultStorageV1 + */ + function activeStakeAt(uint48 timestamp, bytes memory hint) public view returns (uint256) { + return _activeStake.upperLookupRecent(timestamp, hint); + } + + /** + * @inheritdoc IVaultStorageV1 + */ + function activeStake() public view returns (uint256) { + return _activeStake.latest(); + } + + /** + * @inheritdoc IVaultStorageV1 + */ + function activeSharesOfAt(address account, uint48 timestamp, bytes memory hint) public view returns (uint256) { + return _activeSharesOf[account].upperLookupRecent(timestamp, hint); + } + + /** + * @inheritdoc IVaultStorageV1 + */ + function activeSharesOf(address account) public view returns (uint256) { + return _activeSharesOf[account].latest(); + } + + uint256[50] private __gap; +} diff --git a/test/helpers/v1/VaultV1.sol b/test/helpers/v1/VaultV1.sol new file mode 100644 index 00000000..4c8b1900 --- /dev/null +++ b/test/helpers/v1/VaultV1.sol @@ -0,0 +1,482 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.25; + +import {MigratableEntity} from "../../../src/contracts/common/MigratableEntity.sol"; +import {VaultStorageV1} from "./VaultStorageV1.sol"; + +import {Checkpoints} from "../../../src/contracts/libraries/Checkpoints.sol"; +import {ERC4626Math} from "../../../src/contracts/libraries/ERC4626Math.sol"; + +import {IBaseDelegator} from "../../../src/interfaces/delegator/IBaseDelegator.sol"; +import {IBaseSlasher} from "../../../src/interfaces/slasher/IBaseSlasher.sol"; +import {IRegistry} from "../../../src/interfaces/common/IRegistry.sol"; +import {IVaultV1} from "./IVaultV1.sol"; + +import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {Time} from "@openzeppelin/contracts/utils/types/Time.sol"; + +contract VaultV1 is VaultStorageV1, MigratableEntity, AccessControlUpgradeable, IVaultV1 { + using Checkpoints for Checkpoints.Trace256; + using Math for uint256; + using SafeCast for uint256; + using SafeERC20 for IERC20; + + constructor(address delegatorFactory, address slasherFactory, address vaultFactory) + VaultStorageV1(delegatorFactory, slasherFactory) + MigratableEntity(vaultFactory) + {} + + /** + * @inheritdoc IVaultV1 + */ + function isInitialized() external view returns (bool) { + return isDelegatorInitialized && isSlasherInitialized; + } + + /** + * @inheritdoc IVaultV1 + */ + function totalStake() public view returns (uint256) { + uint256 epoch = currentEpoch(); + return activeStake() + withdrawals[epoch] + withdrawals[epoch + 1]; + } + + /** + * @inheritdoc IVaultV1 + */ + function activeBalanceOfAt(address account, uint48 timestamp, bytes calldata hints) public view returns (uint256) { + ActiveBalanceOfHints memory activeBalanceOfHints; + if (hints.length > 0) { + activeBalanceOfHints = abi.decode(hints, (ActiveBalanceOfHints)); + } + return ERC4626Math.previewRedeem( + activeSharesOfAt(account, timestamp, activeBalanceOfHints.activeSharesOfHint), + activeStakeAt(timestamp, activeBalanceOfHints.activeStakeHint), + activeSharesAt(timestamp, activeBalanceOfHints.activeSharesHint) + ); + } + + /** + * @inheritdoc IVaultV1 + */ + function activeBalanceOf(address account) public view returns (uint256) { + return ERC4626Math.previewRedeem(activeSharesOf(account), activeStake(), activeShares()); + } + + /** + * @inheritdoc IVaultV1 + */ + function withdrawalsOf(uint256 epoch, address account) public view returns (uint256) { + return + ERC4626Math.previewRedeem(withdrawalSharesOf[epoch][account], withdrawals[epoch], withdrawalShares[epoch]); + } + + /** + * @inheritdoc IVaultV1 + */ + function slashableBalanceOf(address account) external view returns (uint256) { + uint256 epoch = currentEpoch(); + return activeBalanceOf(account) + withdrawalsOf(epoch, account) + withdrawalsOf(epoch + 1, account); + } + + /** + * @inheritdoc IVaultV1 + */ + function deposit(address onBehalfOf, uint256 amount) + public + virtual + nonReentrant + returns (uint256 depositedAmount, uint256 mintedShares) + { + if (onBehalfOf == address(0)) { + revert InvalidOnBehalfOf(); + } + + if (depositWhitelist && !isDepositorWhitelisted[msg.sender]) { + revert NotWhitelistedDepositor(); + } + + uint256 balanceBefore = IERC20(collateral).balanceOf(address(this)); + IERC20(collateral).safeTransferFrom(msg.sender, address(this), amount); + depositedAmount = IERC20(collateral).balanceOf(address(this)) - balanceBefore; + + if (depositedAmount == 0) { + revert InsufficientDeposit(); + } + + if (isDepositLimit && activeStake() + depositedAmount > depositLimit) { + revert DepositLimitReached(); + } + + uint256 activeStake_ = activeStake(); + uint256 activeShares_ = activeShares(); + + mintedShares = ERC4626Math.previewDeposit(depositedAmount, activeShares_, activeStake_); + + _activeStake.push(Time.timestamp(), activeStake_ + depositedAmount); + _activeShares.push(Time.timestamp(), activeShares_ + mintedShares); + _activeSharesOf[onBehalfOf].push(Time.timestamp(), activeSharesOf(onBehalfOf) + mintedShares); + + emit Deposit(msg.sender, onBehalfOf, depositedAmount, mintedShares); + } + + /** + * @inheritdoc IVaultV1 + */ + function withdraw(address claimer, uint256 amount) + external + nonReentrant + returns (uint256 burnedShares, uint256 mintedShares) + { + if (claimer == address(0)) { + revert InvalidClaimer(); + } + + if (amount == 0) { + revert InsufficientWithdrawal(); + } + + burnedShares = ERC4626Math.previewWithdraw(amount, activeShares(), activeStake()); + + if (burnedShares > activeSharesOf(msg.sender)) { + revert TooMuchWithdraw(); + } + + mintedShares = _withdraw(claimer, amount, burnedShares); + } + + /** + * @inheritdoc IVaultV1 + */ + function redeem(address claimer, uint256 shares) + external + nonReentrant + returns (uint256 withdrawnAssets, uint256 mintedShares) + { + if (claimer == address(0)) { + revert InvalidClaimer(); + } + + if (shares > activeSharesOf(msg.sender)) { + revert TooMuchRedeem(); + } + + withdrawnAssets = ERC4626Math.previewRedeem(shares, activeStake(), activeShares()); + + if (withdrawnAssets == 0) { + revert InsufficientRedemption(); + } + + mintedShares = _withdraw(claimer, withdrawnAssets, shares); + } + + /** + * @inheritdoc IVaultV1 + */ + function claim(address recipient, uint256 epoch) external nonReentrant returns (uint256 amount) { + if (recipient == address(0)) { + revert InvalidRecipient(); + } + + amount = _claim(epoch); + + IERC20(collateral).safeTransfer(recipient, amount); + + emit Claim(msg.sender, recipient, epoch, amount); + } + + /** + * @inheritdoc IVaultV1 + */ + function claimBatch(address recipient, uint256[] calldata epochs) external nonReentrant returns (uint256 amount) { + if (recipient == address(0)) { + revert InvalidRecipient(); + } + + uint256 length = epochs.length; + if (length == 0) { + revert InvalidLengthEpochs(); + } + + for (uint256 i; i < length; ++i) { + amount += _claim(epochs[i]); + } + + IERC20(collateral).safeTransfer(recipient, amount); + + emit ClaimBatch(msg.sender, recipient, epochs, amount); + } + + /** + * @inheritdoc IVaultV1 + */ + function onSlash(uint256 amount, uint48 captureTimestamp) external nonReentrant returns (uint256 slashedAmount) { + if (msg.sender != slasher) { + revert NotSlasher(); + } + + uint256 currentEpoch_ = currentEpoch(); + uint256 captureEpoch = epochAt(captureTimestamp); + if ((currentEpoch_ > 0 && captureEpoch < currentEpoch_ - 1) || captureEpoch > currentEpoch_) { + revert InvalidCaptureEpoch(); + } + + uint256 activeStake_ = activeStake(); + uint256 nextWithdrawals = withdrawals[currentEpoch_ + 1]; + if (captureEpoch == currentEpoch_) { + uint256 slashableStake = activeStake_ + nextWithdrawals; + slashedAmount = Math.min(amount, slashableStake); + if (slashedAmount > 0) { + uint256 activeSlashed = slashedAmount.mulDiv(activeStake_, slashableStake); + uint256 nextWithdrawalsSlashed = slashedAmount - activeSlashed; + + _activeStake.push(Time.timestamp(), activeStake_ - activeSlashed); + withdrawals[captureEpoch + 1] = nextWithdrawals - nextWithdrawalsSlashed; + } + } else { + uint256 withdrawals_ = withdrawals[currentEpoch_]; + uint256 slashableStake = activeStake_ + withdrawals_ + nextWithdrawals; + slashedAmount = Math.min(amount, slashableStake); + if (slashedAmount > 0) { + uint256 activeSlashed = slashedAmount.mulDiv(activeStake_, slashableStake); + uint256 nextWithdrawalsSlashed = slashedAmount.mulDiv(nextWithdrawals, slashableStake); + uint256 withdrawalsSlashed = slashedAmount - activeSlashed - nextWithdrawalsSlashed; + + if (withdrawals_ < withdrawalsSlashed) { + nextWithdrawalsSlashed += withdrawalsSlashed - withdrawals_; + withdrawalsSlashed = withdrawals_; + } + + _activeStake.push(Time.timestamp(), activeStake_ - activeSlashed); + withdrawals[currentEpoch_ + 1] = nextWithdrawals - nextWithdrawalsSlashed; + withdrawals[currentEpoch_] = withdrawals_ - withdrawalsSlashed; + } + } + + if (slashedAmount > 0) { + IERC20(collateral).safeTransfer(burner, slashedAmount); + } + + emit OnSlash(amount, captureTimestamp, slashedAmount); + } + + /** + * @inheritdoc IVaultV1 + */ + function setDepositWhitelist(bool status) external nonReentrant onlyRole(DEPOSIT_WHITELIST_SET_ROLE) { + if (depositWhitelist == status) { + revert AlreadySet(); + } + + depositWhitelist = status; + + emit SetDepositWhitelist(status); + } + + /** + * @inheritdoc IVaultV1 + */ + function setDepositorWhitelistStatus(address account, bool status) + external + nonReentrant + onlyRole(DEPOSITOR_WHITELIST_ROLE) + { + if (account == address(0)) { + revert InvalidAccount(); + } + + if (isDepositorWhitelisted[account] == status) { + revert AlreadySet(); + } + + isDepositorWhitelisted[account] = status; + + emit SetDepositorWhitelistStatus(account, status); + } + + /** + * @inheritdoc IVaultV1 + */ + function setIsDepositLimit(bool status) external nonReentrant onlyRole(IS_DEPOSIT_LIMIT_SET_ROLE) { + if (isDepositLimit == status) { + revert AlreadySet(); + } + + isDepositLimit = status; + + emit SetIsDepositLimit(status); + } + + /** + * @inheritdoc IVaultV1 + */ + function setDepositLimit(uint256 limit) external nonReentrant onlyRole(DEPOSIT_LIMIT_SET_ROLE) { + if (depositLimit == limit) { + revert AlreadySet(); + } + + depositLimit = limit; + + emit SetDepositLimit(limit); + } + + function setDelegator(address delegator_) external nonReentrant { + if (isDelegatorInitialized) { + revert DelegatorAlreadyInitialized(); + } + + if (!IRegistry(DELEGATOR_FACTORY).isEntity(delegator_)) { + revert NotDelegator(); + } + + if (IBaseDelegator(delegator_).vault() != address(this)) { + revert InvalidDelegator(); + } + + delegator = delegator_; + + isDelegatorInitialized = true; + + emit SetDelegator(delegator_); + } + + function setSlasher(address slasher_) external nonReentrant { + if (isSlasherInitialized) { + revert SlasherAlreadyInitialized(); + } + + if (slasher_ != address(0)) { + if (!IRegistry(SLASHER_FACTORY).isEntity(slasher_)) { + revert NotSlasher(); + } + + if (IBaseSlasher(slasher_).vault() != address(this)) { + revert InvalidSlasher(); + } + + slasher = slasher_; + } + + isSlasherInitialized = true; + + emit SetSlasher(slasher_); + } + + function _withdraw(address claimer, uint256 withdrawnAssets, uint256 burnedShares) + internal + virtual + returns (uint256 mintedShares) + { + _activeSharesOf[msg.sender].push(Time.timestamp(), activeSharesOf(msg.sender) - burnedShares); + _activeShares.push(Time.timestamp(), activeShares() - burnedShares); + _activeStake.push(Time.timestamp(), activeStake() - withdrawnAssets); + + uint256 epoch = currentEpoch() + 1; + uint256 withdrawals_ = withdrawals[epoch]; + uint256 withdrawalsShares_ = withdrawalShares[epoch]; + + mintedShares = ERC4626Math.previewDeposit(withdrawnAssets, withdrawalsShares_, withdrawals_); + + withdrawals[epoch] = withdrawals_ + withdrawnAssets; + withdrawalShares[epoch] = withdrawalsShares_ + mintedShares; + withdrawalSharesOf[epoch][claimer] += mintedShares; + + emit Withdraw(msg.sender, claimer, withdrawnAssets, burnedShares, mintedShares); + } + + function _claim(uint256 epoch) internal returns (uint256 amount) { + if (epoch >= currentEpoch()) { + revert InvalidEpoch(); + } + + if (isWithdrawalsClaimed[epoch][msg.sender]) { + revert AlreadyClaimed(); + } + + amount = withdrawalsOf(epoch, msg.sender); + + if (amount == 0) { + revert InsufficientClaim(); + } + + isWithdrawalsClaimed[epoch][msg.sender] = true; + } + + function _initialize(uint64, address, bytes memory data) internal virtual override { + (InitParams memory params) = abi.decode(data, (InitParams)); + + if (params.collateral == address(0)) { + revert InvalidCollateral(); + } + + if (params.epochDuration == 0) { + revert InvalidEpochDuration(); + } + + if (params.defaultAdminRoleHolder == address(0)) { + if (params.depositWhitelistSetRoleHolder == address(0)) { + if (params.depositWhitelist) { + if (params.depositorWhitelistRoleHolder == address(0)) { + revert MissingRoles(); + } + } else if (params.depositorWhitelistRoleHolder != address(0)) { + revert MissingRoles(); + } + } + + if (params.isDepositLimitSetRoleHolder == address(0)) { + if (params.isDepositLimit) { + if (params.depositLimit == 0 && params.depositLimitSetRoleHolder == address(0)) { + revert MissingRoles(); + } + } else if (params.depositLimit != 0 || params.depositLimitSetRoleHolder != address(0)) { + revert MissingRoles(); + } + } + } + + collateral = params.collateral; + + burner = params.burner; + + epochDurationInit = Time.timestamp(); + epochDuration = params.epochDuration; + + depositWhitelist = params.depositWhitelist; + + isDepositLimit = params.isDepositLimit; + depositLimit = params.depositLimit; + + if (params.defaultAdminRoleHolder != address(0)) { + _grantRole(DEFAULT_ADMIN_ROLE, params.defaultAdminRoleHolder); + } + if (params.depositWhitelistSetRoleHolder != address(0)) { + _grantRole(DEPOSIT_WHITELIST_SET_ROLE, params.depositWhitelistSetRoleHolder); + } + if (params.depositorWhitelistRoleHolder != address(0)) { + _grantRole(DEPOSITOR_WHITELIST_ROLE, params.depositorWhitelistRoleHolder); + } + if (params.isDepositLimitSetRoleHolder != address(0)) { + _grantRole(IS_DEPOSIT_LIMIT_SET_ROLE, params.isDepositLimitSetRoleHolder); + } + if (params.depositLimitSetRoleHolder != address(0)) { + _grantRole(DEPOSIT_LIMIT_SET_ROLE, params.depositLimitSetRoleHolder); + } + } + + function _migrate( + uint64, + /* oldVersion */ + uint64, + /* newVersion */ + bytes calldata /* data */ + ) + internal + override + { + revert(); + } +} diff --git a/test/integration/SymbioticCoreIntegration.sol b/test/integration/SymbioticCoreIntegration.sol index 37525604..5431e41a 100644 --- a/test/integration/SymbioticCoreIntegration.sol +++ b/test/integration/SymbioticCoreIntegration.sol @@ -363,8 +363,9 @@ contract SymbioticCoreIntegration is SymbioticCoreInit { isPossibleOperatorForSubnetwork[ subnetwork ][vaults_SymbioticCore[k]][operators_SymbioticCore[l].addr] = true; - possibleOperatorsForSubnetwork[subnetwork][vaults_SymbioticCore[k]] - .push(operators_SymbioticCore[l].addr); + possibleOperatorsForSubnetwork[subnetwork][vaults_SymbioticCore[k]].push( + operators_SymbioticCore[l].addr + ); } if (_operatorConfirmedValidating_SymbioticCore( operators_SymbioticCore[l].addr, vaults_SymbioticCore[k], subnetwork @@ -372,8 +373,9 @@ contract SymbioticCoreIntegration is SymbioticCoreInit { isConfirmedOperatorForSubnetwork[ subnetwork ][vaults_SymbioticCore[k]][operators_SymbioticCore[l].addr] = true; - confirmedOperatorsForSubnetwork[subnetwork][vaults_SymbioticCore[k]] - .push(operators_SymbioticCore[l].addr); + confirmedOperatorsForSubnetwork[subnetwork][vaults_SymbioticCore[k]].push( + operators_SymbioticCore[l].addr + ); } } } diff --git a/test/integration/SymbioticCoreIntegrationExample.t.sol b/test/integration/SymbioticCoreIntegrationExample.t.sol index 80dca2dd..0c39ca4e 100644 --- a/test/integration/SymbioticCoreIntegrationExample.t.sol +++ b/test/integration/SymbioticCoreIntegrationExample.t.sol @@ -233,6 +233,6 @@ contract SymbioticCoreIntegrationExample is SymbioticCoreIntegration { ISymbioticBaseDelegator(ISymbioticVault(vault).delegator()).stake(subnetwork, newOperator.addr) ); console2.log("Total stake after new staker:", ISymbioticVault(vault).totalStake()); - console2.log("User stake:", ISymbioticVault(vault).slashableBalanceOf(newStaker.addr)); + console2.log("User stake:", ISymbioticVault(vault).activeBalanceOf(newStaker.addr)); } } diff --git a/test/integration/base/SymbioticCoreBindingsBase.sol b/test/integration/base/SymbioticCoreBindingsBase.sol index e8f6fecd..8e2dba8f 100644 --- a/test/integration/base/SymbioticCoreBindingsBase.sol +++ b/test/integration/base/SymbioticCoreBindingsBase.sol @@ -88,11 +88,11 @@ abstract contract SymbioticCoreBindingsBase is Test { _optInVault_SymbioticCore(symbioticCore, who, who, vault, deadline, signature); } - function _optInNetwork_SymbioticCore( - SymbioticCoreConstants.Core memory symbioticCore, - address who, - address network - ) internal virtual broadcast(who) { + function _optInNetwork_SymbioticCore(SymbioticCoreConstants.Core memory symbioticCore, address who, address network) + internal + virtual + broadcast(who) + { symbioticCore.operatorNetworkOptInService.optIn(network); } diff --git a/test/integration/base/SymbioticCoreInitBase.sol b/test/integration/base/SymbioticCoreInitBase.sol index e685cdf0..6e1792c6 100644 --- a/test/integration/base/SymbioticCoreInitBase.sol +++ b/test/integration/base/SymbioticCoreInitBase.sol @@ -610,9 +610,9 @@ abstract contract SymbioticCoreInitBase is SymbioticUtils, SymbioticCoreBindings } else if (type_ == 2) { delegatorSpecificCondition = ISymbioticOperatorSpecificDelegator(delegator).networkLimit(subnetwork) > 0; } else if (type_ == 3) { - delegatorSpecificCondition = - ISymbioticOperatorNetworkSpecificDelegator(delegator).network() == subnetwork.network() - && ISymbioticOperatorNetworkSpecificDelegator(delegator).maxNetworkLimit(subnetwork) > 0; + delegatorSpecificCondition = ISymbioticOperatorNetworkSpecificDelegator(delegator).network() + == subnetwork.network() + && ISymbioticOperatorNetworkSpecificDelegator(delegator).maxNetworkLimit(subnetwork) > 0; } return delegatorSpecificCondition; diff --git a/test/invariant/handlers/VaultHandler.sol b/test/invariant/handlers/VaultHandler.sol index 5cde6c6c..25ced271 100644 --- a/test/invariant/handlers/VaultHandler.sol +++ b/test/invariant/handlers/VaultHandler.sol @@ -299,7 +299,7 @@ contract VaultHandler is Test { } uint48 captureTimestamp = uint48(block.timestamp) - 1; - amount = _bound(amount, 1, stake/2); + amount = _bound(amount, 1, stake / 2); uint256 slashedAmount = slasher.slash(subnetwork, operator, amount, captureTimestamp, ""); totalSlashed += slashedAmount; diff --git a/test/vault/IVaultFull.sol b/test/vault/IVaultFull.sol deleted file mode 100644 index 8ef8fb1d..00000000 --- a/test/vault/IVaultFull.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {IVault} from "../../src/interfaces/vault/IVault.sol"; -import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; -import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; - -interface IVaultFull is IVault, IAccessControl, IERC20 { - function DEPOSITOR_WHITELIST_ROLE() external view returns (bytes32); - function DEPOSIT_WHITELIST_SET_ROLE() external view returns (bytes32); - function IS_DEPOSIT_LIMIT_SET_ROLE() external view returns (bytes32); - function DEPOSIT_LIMIT_SET_ROLE() external view returns (bytes32); - function DEFAULT_ADMIN_ROLE() external view returns (bytes32); - - function owner() external view returns (address); - function latestWithdrawalBucket() external view returns (uint256); - function decimals() external view returns (uint8); - function symbol() external view returns (string memory); - function name() external view returns (string memory); -} \ No newline at end of file diff --git a/test/vault/Vault.t.sol b/test/vault/Vault.t.sol index 93a9c5ed..03542a5f 100644 --- a/test/vault/Vault.t.sol +++ b/test/vault/Vault.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.25; -import {Test, console2} from "forge-std/Test.sol"; +import {Test, console2, stdError} from "forge-std/Test.sol"; import {VaultFactory} from "../../src/contracts/VaultFactory.sol"; import {DelegatorFactory} from "../../src/contracts/DelegatorFactory.sol"; @@ -14,6 +14,7 @@ import {OptInService} from "../../src/contracts/service/OptInService.sol"; import {Checkpoints} from "../../src/contracts/libraries/Checkpoints.sol"; import {Vault} from "../../src/contracts/vault/Vault.sol"; +import {VaultV1} from "../helpers/v1/VaultV1.sol"; import {NetworkRestakeDelegator} from "../../src/contracts/delegator/NetworkRestakeDelegator.sol"; import {FullRestakeDelegator} from "../../src/contracts/delegator/FullRestakeDelegator.sol"; import {OperatorSpecificDelegator} from "../../src/contracts/delegator/OperatorSpecificDelegator.sol"; @@ -22,7 +23,6 @@ import {Slasher} from "../../src/contracts/slasher/Slasher.sol"; import {VetoSlasher} from "../../src/contracts/slasher/VetoSlasher.sol"; import {IVault} from "../../src/interfaces/vault/IVault.sol"; -import {IVaultFull} from "./IVaultFull.sol"; import {Token} from "../mocks/Token.sol"; import {FeeOnTransferToken} from "../mocks/FeeOnTransferToken.sol"; @@ -39,6 +39,7 @@ import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; import {VaultHints} from "../../src/contracts/hints/VaultHints.sol"; import {Subnetwork} from "../../src/contracts/libraries/Subnetwork.sol"; +import {VaultTestHelper} from "../helpers/VaultTestHelper.sol"; contract VaultTest is Test { using Math for uint256; @@ -66,8 +67,9 @@ contract VaultTest is Test { Token collateral; FeeOnTransferToken feeOnTransferCollateral; VaultConfigurator vaultConfigurator; + VaultTestHelper vaultTestHelper; - IVaultFull vault; + IVault vault; FullRestakeDelegator delegator; Slasher slasher; @@ -89,6 +91,12 @@ contract VaultTest is Test { operatorNetworkOptInService = new OptInService(address(operatorRegistry), address(networkRegistry), "OperatorNetworkOptInService"); + vaultTestHelper = new VaultTestHelper(); + + address vaultImplV1 = + _createVaultV1Impl(address(delegatorFactory), address(slasherFactory), address(vaultFactory)); + vaultFactory.whitelist(vaultImplV1); + address vaultImpl = _createVaultImpl(address(delegatorFactory), address(slasherFactory), address(vaultFactory)); vaultFactory.whitelist(vaultImpl); @@ -188,8 +196,15 @@ contract VaultTest is Test { networkLimitSetRoleHolders[0] = alice; address[] memory operatorNetworkSharesSetRoleHolders = new address[](1); operatorNetworkSharesSetRoleHolders[0] = alice; - (IVaultFull vault_, address delegator_, address slasher_) = _createInitializedVault( - epochDuration, networkLimitSetRoleHolders, operatorNetworkSharesSetRoleHolders, vaultFactory.lastVersion(), burner, depositWhitelist, isDepositLimit, depositLimit + (IVault vault_, address delegator_, address slasher_) = _createInitializedVault( + epochDuration, + networkLimitSetRoleHolders, + operatorNetworkSharesSetRoleHolders, + vaultFactory.lastVersion(), + burner, + depositWhitelist, + isDepositLimit, + depositLimit ); vault = vault_; @@ -198,25 +213,17 @@ contract VaultTest is Test { assertEq(vault.DELEGATOR_FACTORY(), address(delegatorFactory)); assertEq(vault.SLASHER_FACTORY(), address(slasherFactory)); - assertEq(vault.owner(), address(0)); + assertEq(Vault(address(vault)).owner(), address(0)); assertEq(vault.collateral(), address(collateral)); assertEq(vault.delegator(), delegator_); assertEq(vault.slasher(), slasher_); assertEq(vault.burner(), burner); assertEq(vault.epochDuration(), epochDuration); assertEq(vault.depositWhitelist(), depositWhitelist); - assertEq(vault.hasRole(vault.DEFAULT_ADMIN_ROLE(), alice), true); - assertEq(vault.hasRole(vault.DEPOSITOR_WHITELIST_ROLE(), alice), true); - assertEq(vault.epochDurationInit(), blockTimestamp); + assertEq(Vault(address(vault)).hasRole(Vault(address(vault)).DEFAULT_ADMIN_ROLE(), alice), true); + assertEq(Vault(address(vault)).hasRole(vault.DEPOSITOR_WHITELIST_ROLE(), alice), true); assertEq(vault.epochDuration(), epochDuration); - vm.expectRevert(IVaultStorage.InvalidTimestamp.selector); - assertEq(vault.epochAt(0), 0); - assertEq(vault.epochAt(uint48(blockTimestamp)), 0); - assertEq(vault.currentEpoch(), 0); assertEq(vault.currentEpochStart(), blockTimestamp); - vm.expectRevert(IVaultStorage.NoPreviousEpoch.selector); - vault.previousEpochStart(); - assertEq(vault.nextEpochStart(), blockTimestamp + epochDuration); assertEq(vault.totalStake(), 0); assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), 0); assertEq(vault.activeShares(), 0); @@ -226,11 +233,11 @@ contract VaultTest is Test { assertEq(vault.activeSharesOf(alice), 0); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), 0); assertEq(vault.activeBalanceOf(alice), 0); + assertEq(vault.withdrawalsLength(alice), 0); assertEq(vault.withdrawals(0), 0); assertEq(vault.withdrawalShares(0), 0); assertEq(vault.depositWhitelist(), depositWhitelist); assertEq(vault.isDepositorWhitelisted(alice), false); - assertEq(vault.slashableBalanceOf(alice), 0); assertEq(vault.isDelegatorInitialized(), true); assertEq(vault.isSlasherInitialized(), true); assertEq(vault.isInitialized(), true); @@ -246,7 +253,14 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.InvalidEpochDuration.selector); _createInitializedVault( - epochDuration, networkLimitSetRoleHolders, operatorNetworkSharesSetRoleHolders, lastVersion, address(0xdEaD), false, false, 0 + epochDuration, + networkLimitSetRoleHolders, + operatorNetworkSharesSetRoleHolders, + lastVersion, + address(0xdEaD), + false, + false, + 0 ); } @@ -261,7 +275,14 @@ contract VaultTest is Test { collateral = Token(address(0)); vm.expectRevert(IVault.InvalidCollateral.selector); _createInitializedVault( - epochDuration, networkLimitSetRoleHolders, operatorNetworkSharesSetRoleHolders, lastVersion, address(0xdEaD), false, false, 0 + epochDuration, + networkLimitSetRoleHolders, + operatorNetworkSharesSetRoleHolders, + lastVersion, + address(0xdEaD), + false, + false, + 0 ); } @@ -271,7 +292,7 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.MissingRoles.selector); - vault = IVaultFull( + vault = IVault( vaultFactory.create( lastVersion, alice, @@ -300,7 +321,7 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.MissingRoles.selector); - vault = IVaultFull( + vault = IVault( vaultFactory.create( lastVersion, alice, @@ -329,7 +350,7 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.MissingRoles.selector); - vault = IVaultFull( + vault = IVault( vaultFactory.create( lastVersion, alice, @@ -358,7 +379,7 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.MissingRoles.selector); - vault = IVaultFull( + vault = IVault( vaultFactory.create( lastVersion, alice, @@ -387,7 +408,7 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.MissingRoles.selector); - vault = IVaultFull( + vault = IVault( vaultFactory.create( lastVersion, alice, @@ -413,7 +434,7 @@ contract VaultTest is Test { function test_SetDelegator() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = IVaultFull( + vault = IVault( vaultFactory.create( lastVersion, alice, @@ -469,7 +490,7 @@ contract VaultTest is Test { function test_SetDelegatorRevertDelegatorAlreadyInitialized() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = IVaultFull( + vault = IVault( vaultFactory.create( lastVersion, alice, @@ -522,7 +543,7 @@ contract VaultTest is Test { function test_SetDelegatorRevertNotDelegator() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = IVaultFull( + vault = IVault( vaultFactory.create( lastVersion, alice, @@ -551,7 +572,7 @@ contract VaultTest is Test { function test_SetDelegatorRevertInvalidDelegator() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = IVaultFull( + vault = IVault( vaultFactory.create( lastVersion, alice, @@ -573,26 +594,25 @@ contract VaultTest is Test { ) ); - address vault2 = - vaultFactory.create( - lastVersion, - alice, - _getEncodedVaultParams( - IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: 7 days, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }) - ) - ); + address vault2 = vaultFactory.create( + lastVersion, + alice, + _getEncodedVaultParams( + IVault.InitParams({ + collateral: address(collateral), + burner: address(0xdEaD), + epochDuration: 7 days, + depositWhitelist: false, + isDepositLimit: false, + depositLimit: 0, + defaultAdminRoleHolder: alice, + depositWhitelistSetRoleHolder: alice, + depositorWhitelistRoleHolder: alice, + isDepositLimitSetRoleHolder: alice, + depositLimitSetRoleHolder: alice + }) + ) + ); address[] memory networkLimitSetRoleHolders = new address[](1); networkLimitSetRoleHolders[0] = alice; @@ -623,7 +643,7 @@ contract VaultTest is Test { function test_SetSlasher() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = IVaultFull( + vault = IVault( vaultFactory.create( lastVersion, alice, @@ -667,7 +687,7 @@ contract VaultTest is Test { function test_SetSlasherRevertSlasherAlreadyInitialized() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = IVaultFull( + vault = IVault( vaultFactory.create( lastVersion, alice, @@ -708,7 +728,7 @@ contract VaultTest is Test { function test_SetSlasherRevertNotSlasher() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = IVaultFull( + vault = IVault( vaultFactory.create( lastVersion, alice, @@ -729,7 +749,6 @@ contract VaultTest is Test { ) ) ); - slasher = Slasher( slasherFactory.create( @@ -748,7 +767,7 @@ contract VaultTest is Test { function test_SetSlasherRevertInvalidSlasher() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = IVaultFull( + vault = IVault( vaultFactory.create( lastVersion, alice, @@ -770,26 +789,25 @@ contract VaultTest is Test { ) ); - address vault2 = - vaultFactory.create( - lastVersion, - alice, - _getEncodedVaultParams( - IVault.InitParams({ - collateral: address(collateral), - burner: address(0xdEaD), - epochDuration: 7 days, - depositWhitelist: false, - isDepositLimit: false, - depositLimit: 0, - defaultAdminRoleHolder: alice, - depositWhitelistSetRoleHolder: alice, - depositorWhitelistRoleHolder: alice, - isDepositLimitSetRoleHolder: alice, - depositLimitSetRoleHolder: alice - }) - ) - ); + address vault2 = vaultFactory.create( + lastVersion, + alice, + _getEncodedVaultParams( + IVault.InitParams({ + collateral: address(collateral), + burner: address(0xdEaD), + epochDuration: 7 days, + depositWhitelist: false, + isDepositLimit: false, + depositLimit: 0, + defaultAdminRoleHolder: alice, + depositWhitelistSetRoleHolder: alice, + depositorWhitelistRoleHolder: alice, + isDepositLimitSetRoleHolder: alice, + depositLimitSetRoleHolder: alice + }) + ) + ); slasher = Slasher( slasherFactory.create( @@ -808,7 +826,7 @@ contract VaultTest is Test { function test_SetSlasherZeroAddress() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = IVaultFull( + vault = IVault( vaultFactory.create( lastVersion, alice, @@ -853,7 +871,6 @@ contract VaultTest is Test { } assertEq(collateral.balanceOf(address(vault)) - tokensBefore, amount1); - assertEq(vault.totalStake(), amount1); assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), 0); assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares1); assertEq(vault.activeShares(), shares1); @@ -866,7 +883,6 @@ contract VaultTest is Test { assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), 0); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1); assertEq(vault.activeBalanceOf(alice), amount1); - assertEq(vault.slashableBalanceOf(alice), amount1); blockTimestamp = blockTimestamp + 1; vm.warp(blockTimestamp); @@ -878,7 +894,6 @@ contract VaultTest is Test { assertEq(mintedShares, shares2); } - assertEq(vault.totalStake(), amount1 + amount2); assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares1); assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares1 + shares2); assertEq(vault.activeShares(), shares1 + shares2); @@ -927,7 +942,6 @@ contract VaultTest is Test { assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), amount1); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 + amount2); assertEq(vault.activeBalanceOf(alice), amount1 + amount2); - assertEq(vault.slashableBalanceOf(alice), amount1 + amount2); gasLeft = gasleft(); assertEq( vault.activeBalanceOfAt( @@ -1034,7 +1048,6 @@ contract VaultTest is Test { vm.stopPrank(); assertEq(feeOnTransferCollateral.balanceOf(address(vault)) - tokensBefore, amount1 - 1); - assertEq(vault.totalStake(), amount1 - 1); assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), 0); assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares1); assertEq(vault.activeShares(), shares1); @@ -1047,7 +1060,6 @@ contract VaultTest is Test { assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), 0); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 - 1); assertEq(vault.activeBalanceOf(alice), amount1 - 1); - assertEq(vault.slashableBalanceOf(alice), amount1 - 1); blockTimestamp = blockTimestamp + 1; vm.warp(blockTimestamp); @@ -1063,7 +1075,6 @@ contract VaultTest is Test { } vm.stopPrank(); - assertEq(vault.totalStake(), amount1 - 1 + amount2 - 1); assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares1); assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares1 + shares2); assertEq(vault.activeShares(), shares1 + shares2); @@ -1112,7 +1123,6 @@ contract VaultTest is Test { assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), amount1 - 1); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 - 1 + amount2 - 1); assertEq(vault.activeBalanceOf(alice), amount1 - 1 + amount2 - 1); - assertEq(vault.slashableBalanceOf(alice), amount1 - 1 + amount2 - 1); gasLeft = gasleft(); assertEq( vault.activeBalanceOfAt( @@ -1207,7 +1217,6 @@ contract VaultTest is Test { assertEq(mintedShares, shares2); } - assertEq(vault.totalStake(), amount1 + amount2); assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares1); assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares1 + shares2); assertEq(vault.activeShares(), shares1 + shares2); @@ -1220,14 +1229,12 @@ contract VaultTest is Test { assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), amount1); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1); assertEq(vault.activeBalanceOf(alice), amount1); - assertEq(vault.slashableBalanceOf(alice), amount1); assertEq(vault.activeSharesOfAt(bob, uint48(blockTimestamp - 1), ""), 0); assertEq(vault.activeSharesOfAt(bob, uint48(blockTimestamp), ""), shares2); assertEq(vault.activeSharesOf(bob), shares2); assertEq(vault.activeBalanceOfAt(bob, uint48(blockTimestamp - 1), ""), 0); assertEq(vault.activeBalanceOfAt(bob, uint48(blockTimestamp), ""), amount2); assertEq(vault.activeBalanceOf(bob), amount2); - assertEq(vault.slashableBalanceOf(bob), amount2); } function test_DepositRevertInvalidOnBehalfOf(uint256 amount1) public { @@ -1276,7 +1283,7 @@ contract VaultTest is Test { assertEq(burnedShares_, burnedShares); assertEq(mintedShares_, mintedShares); - assertEq(vault.totalStake(), amount1); + assertEq(vault.totalStake(), _expectedTotalStake(uint48(blockTimestamp))); assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares); assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares - burnedShares); assertEq(vault.activeShares(), shares - burnedShares); @@ -1289,11 +1296,10 @@ contract VaultTest is Test { assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), amount1); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 - amount2); assertEq(vault.activeBalanceOf(alice), amount1 - amount2); - uint256 lastBucket = vault.latestWithdrawalBucket(); + uint256 lastBucket = _latestWithdrawalBucket(); assertEq(vault.withdrawals(lastBucket), amount2); assertEq(vault.withdrawalShares(lastBucket), mintedShares); assertEq(vault.withdrawalSharesOf(0, alice), amount2); - assertEq(vault.slashableBalanceOf(alice), amount1); shares -= burnedShares; @@ -1306,7 +1312,7 @@ contract VaultTest is Test { assertEq(burnedShares_, burnedShares); assertEq(mintedShares_, mintedShares); - assertEq(vault.totalStake(), amount1 - amount2); + assertEq(vault.totalStake(), _expectedTotalStake(uint48(blockTimestamp))); assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares); assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares - burnedShares); assertEq(vault.activeShares(), shares - burnedShares); @@ -1322,12 +1328,69 @@ contract VaultTest is Test { assertEq(vault.withdrawals(lastBucket), amount2 + amount3); assertEq(vault.withdrawalShares(lastBucket), amount2 + amount3); assertEq(vault.withdrawalSharesOf(1, alice), amount3); - assertEq(vault.slashableBalanceOf(alice), amount1 - amount2); blockTimestamp = blockTimestamp + 1; vm.warp(blockTimestamp); - assertEq(vault.totalStake(), amount1 - amount2 - amount3); + assertEq(vault.totalStake(), _expectedTotalStake(uint48(blockTimestamp))); + } + + function test_WithdrawUnlockAtAndLength(uint256 amount1, uint256 amount2, uint256 amount3) public { + amount1 = bound(amount1, 1, 100 * 10 ** 18); + amount2 = bound(amount2, 1, 100 * 10 ** 18); + amount3 = bound(amount3, 1, 100 * 10 ** 18); + vm.assume(amount1 >= amount2 + amount3); + + uint256 blockTimestamp = vm.getBlockTimestamp(); + blockTimestamp = blockTimestamp + 1_720_700_948; + vm.warp(blockTimestamp); + + uint48 epochDuration = 7; + vault = _getVault(epochDuration); + + _deposit(alice, amount1); + + blockTimestamp = blockTimestamp + 3; + vm.warp(blockTimestamp); + + _withdraw(alice, amount2); + + assertEq(vault.withdrawalsLength(alice), 1); + assertEq(vault.withdrawalUnlockAt(0, alice), uint48(blockTimestamp + epochDuration)); + + blockTimestamp = blockTimestamp + 2; + vm.warp(blockTimestamp); + + _withdraw(alice, amount3); + + assertEq(vault.withdrawalsLength(alice), 2); + assertEq(vault.withdrawalUnlockAt(1, alice), uint48(blockTimestamp + epochDuration)); + } + + function test_WithdrawRecordsClaimer(uint256 amount1, uint256 amount2) public { + amount1 = bound(amount1, 1, 100 * 10 ** 18); + amount2 = bound(amount2, 1, amount1); + + uint256 blockTimestamp = vm.getBlockTimestamp(); + blockTimestamp = blockTimestamp + 1_720_700_948; + vm.warp(blockTimestamp); + + uint48 epochDuration = 5; + vault = _getVault(epochDuration); + + _deposit(alice, amount1); + + blockTimestamp = blockTimestamp + 1; + vm.warp(blockTimestamp); + + vm.startPrank(alice); + (, uint256 mintedShares) = vault.withdraw(bob, amount2); + vm.stopPrank(); + + assertEq(vault.withdrawalsLength(alice), 0); + assertEq(vault.withdrawalsLength(bob), 1); + assertEq(vault.withdrawalUnlockAt(0, bob), uint48(blockTimestamp + epochDuration)); + assertEq(vault.withdrawalSharesOf(0, bob), mintedShares); } function test_WithdrawRevertInvalidClaimer(uint256 amount1) public { @@ -1392,7 +1455,7 @@ contract VaultTest is Test { assertEq(withdrawnAssets_, withdrawnAssets2); assertEq(mintedShares_, mintedShares); - assertEq(vault.totalStake(), amount1); + assertEq(vault.totalStake(), _expectedTotalStake(uint48(blockTimestamp))); assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares); assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares - amount2); assertEq(vault.activeShares(), shares - amount2); @@ -1405,11 +1468,10 @@ contract VaultTest is Test { assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1), ""), amount1); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp), ""), amount1 - withdrawnAssets2); assertEq(vault.activeBalanceOf(alice), amount1 - withdrawnAssets2); - uint256 lastBucket = vault.latestWithdrawalBucket(); + uint256 lastBucket = _latestWithdrawalBucket(); assertEq(vault.withdrawals(lastBucket), withdrawnAssets2); assertEq(vault.withdrawalShares(lastBucket), mintedShares); assertEq(vault.withdrawalSharesOf(0, alice), mintedShares); - assertEq(vault.slashableBalanceOf(alice), amount1); shares -= amount2; @@ -1422,7 +1484,7 @@ contract VaultTest is Test { assertEq(withdrawnAssets_, withdrawnAssets3); assertEq(mintedShares_, mintedShares); - assertEq(vault.totalStake(), amount1 - withdrawnAssets2); + assertEq(vault.totalStake(), _expectedTotalStake(uint48(blockTimestamp))); assertEq(vault.activeSharesAt(uint48(blockTimestamp - 1), ""), shares); assertEq(vault.activeSharesAt(uint48(blockTimestamp), ""), shares - amount3); assertEq(vault.activeShares(), shares - amount3); @@ -1440,12 +1502,11 @@ contract VaultTest is Test { assertEq(vault.withdrawals(lastBucket), withdrawnAssets2 + withdrawnAssets3); assertEq(vault.withdrawalShares(lastBucket), withdrawnAssets2 + withdrawnAssets3); assertEq(vault.withdrawalSharesOf(1, alice), withdrawnAssets3); - assertEq(vault.slashableBalanceOf(alice), amount1 - withdrawnAssets2); blockTimestamp = blockTimestamp + 1; vm.warp(blockTimestamp); - assertEq(vault.totalStake(), amount1 - withdrawnAssets2 - withdrawnAssets3); + assertEq(vault.totalStake(), _expectedTotalStake(uint48(blockTimestamp))); } function test_RedeemRevertInvalidClaimer(uint256 amount1) public { @@ -1567,7 +1628,7 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + 2; vm.warp(blockTimestamp); - vm.expectRevert(IVault.InvalidEpoch.selector); + vm.expectRevert(stdError.indexOOBError); _claim(alice, 10); } @@ -1769,7 +1830,7 @@ contract VaultTest is Test { indexes[0] = 0; indexes[1] = 2; - vm.expectRevert(IVault.InvalidEpoch.selector); + vm.expectRevert(stdError.indexOOBError); _claimBatch(alice, indexes); } @@ -1842,6 +1903,38 @@ contract VaultTest is Test { _claimBatch(alice, indexes); } + function test_TotalStakeUnlockBoundary(uint256 amount1, uint256 amount2) public { + amount1 = bound(amount1, 1, 100 * 10 ** 18); + amount2 = bound(amount2, 1, amount1); + + uint256 blockTimestamp = vm.getBlockTimestamp(); + blockTimestamp = blockTimestamp + 1_720_700_948; + vm.warp(blockTimestamp); + + uint48 epochDuration = 10; + vault = _getVault(epochDuration); + + _deposit(alice, amount1); + + blockTimestamp = blockTimestamp + 1; + vm.warp(blockTimestamp); + + _withdraw(alice, amount2); + + uint48 unlockAt = vault.withdrawalUnlockAt(0, alice); + assertEq(unlockAt, uint48(blockTimestamp + epochDuration)); + assertEq(vault.totalStake(), amount1); + + vm.warp(unlockAt); + assertEq(vault.totalStake(), amount1 - amount2); + + vm.expectRevert(IVault.WithdrawalNotMatured.selector); + _claim(alice, 0); + + vm.warp(uint256(unlockAt) + 1); + assertEq(_claim(alice, 0), amount2); + } + function test_SetDepositWhitelist() public { uint48 epochDuration = 1; @@ -2037,6 +2130,199 @@ contract VaultTest is Test { _setDepositLimit(alice, limit); } + function test_MigrateWithdrawals_FactoryUpgradePath() public { + uint48 epochDuration = 10; + + uint256 blockTimestamp = vm.getBlockTimestamp(); + blockTimestamp = blockTimestamp + 1_720_700_948; + vm.warp(blockTimestamp); + + address[] memory networkLimitSetRoleHolders = new address[](1); + networkLimitSetRoleHolders[0] = alice; + address[] memory operatorNetworkSharesSetRoleHolders = new address[](1); + operatorNetworkSharesSetRoleHolders[0] = alice; + (IVault vault_,, ) = _createInitializedVaultWithOwner( + epochDuration, + networkLimitSetRoleHolders, + operatorNetworkSharesSetRoleHolders, + 1, + address(0xdEaD), + false, + false, + 0, + address(this) + ); + VaultV1 vaultV1 = VaultV1(address(vault_)); + vault = IVault(address(vaultV1)); + + uint256 aliceDeposit = 1000; + uint256 bobDeposit = 500; + _deposit(alice, aliceDeposit); + _deposit(bob, bobDeposit); + + uint256 aliceWithdrawEpoch0 = 200; + uint256 bobWithdrawEpoch0 = 100; + _withdraw(alice, aliceWithdrawEpoch0); + _withdraw(bob, bobWithdrawEpoch0); + + uint256 epoch1Start = blockTimestamp + epochDuration; + vm.warp(epoch1Start + 1); + + uint256 aliceWithdrawEpoch1 = 150; + uint256 bobWithdrawEpoch1 = 60; + _withdraw(alice, aliceWithdrawEpoch1); + _withdraw(bob, bobWithdrawEpoch1); + + uint256 epoch2Start = blockTimestamp + 2 * epochDuration; + vm.warp(epoch2Start + 1); + + uint256 epoch1Withdrawals = aliceWithdrawEpoch0 + bobWithdrawEpoch0; + uint256 epoch2Withdrawals = aliceWithdrawEpoch1 + bobWithdrawEpoch1; + uint256 expectedAliceEpoch1 = Math.mulDiv(aliceWithdrawEpoch0, epoch1Withdrawals + 1, epoch1Withdrawals + 1); + + vm.startPrank(alice); + assertEq(vault.claim(alice, 1), expectedAliceEpoch1); + vm.stopPrank(); + + uint256 migrateTimestamp = epoch2Start + epochDuration / 2; + vm.warp(migrateTimestamp); + + vaultFactory.migrate(address(vaultV1), vaultFactory.lastVersion(), ""); + + IVault vaultV2 = IVault(address(vaultV1)); + + uint48 nextEpochStart = uint48(blockTimestamp + 3 * epochDuration); + assertEq(vaultTestHelper.withdrawalSharesPrefixesLength(address(vaultV2)), 2); + + (uint48 prefixKey0, uint256 prefixVal0) = vaultTestHelper.withdrawalSharesPrefixesAt(address(vaultV2), 0); + assertEq(prefixKey0, nextEpochStart); + assertEq(prefixVal0, epoch2Withdrawals); + + (uint48 prefixKey1, uint256 prefixVal1) = vaultTestHelper.withdrawalSharesPrefixesAt(address(vaultV2), 1); + assertEq(prefixKey1, uint48(nextEpochStart + epochDuration)); + assertEq(prefixVal1, epoch2Withdrawals); + + assertEq(vaultTestHelper.timeToBucketLength(address(vaultV2)), 3); + (uint48 bucketKey, uint208 bucketVal) = vaultTestHelper.timeToBucketAt(address(vaultV2), 2); + assertEq(bucketKey, nextEpochStart); + assertEq(bucketVal, 2); + + vm.expectRevert(); + vaultV2.migrateWithdrawalsOf(alice, 1); + + vaultV2.migrateWithdrawalsOf(bob, 1); + vaultV2.migrateWithdrawalsOf(alice, 2); + vaultV2.migrateWithdrawalsOf(bob, 2); + + assertEq(vaultV2.withdrawalsLength(bob), 2); + assertEq(vaultV2.withdrawalsLength(alice), 1); + + assertEq(vaultV2.withdrawalUnlockAt(0, bob), uint48(epoch2Start)); + assertEq(vaultV2.withdrawalUnlockAt(1, bob), nextEpochStart); + assertEq(vaultV2.withdrawalUnlockAt(0, alice), nextEpochStart); + + (uint48 bucketKeyPre, uint208 bucketValPre) = vaultTestHelper.timeToBucketAt(address(vaultV2), 1); + assertEq(bucketKeyPre, uint48(epoch2Start)); + assertEq(bucketValPre, 1); + + uint256 expectedBobEpoch1 = Math.mulDiv(bobWithdrawEpoch0, epoch1Withdrawals + 1, epoch1Withdrawals + 1); + uint256 expectedAliceEpoch2 = Math.mulDiv(aliceWithdrawEpoch1, epoch2Withdrawals + 1, epoch2Withdrawals + 1); + uint256 expectedBobEpoch2 = Math.mulDiv(bobWithdrawEpoch1, epoch2Withdrawals + 1, epoch2Withdrawals + 1); + + assertEq(vaultV2.withdrawalSharesOf(0, bob), bobWithdrawEpoch0); + assertEq(vaultV2.withdrawalSharesOf(1, bob), expectedBobEpoch2); + assertEq(vaultV2.withdrawalSharesOf(0, alice), expectedAliceEpoch2); + + assertEq(vaultV2.withdrawalsOf(0, bob), expectedBobEpoch1); + assertEq(vaultV2.withdrawalsOf(1, bob), expectedBobEpoch2); + assertEq(vaultV2.withdrawalsOf(0, alice), expectedAliceEpoch2); + + uint256 bobBalanceBefore = collateral.balanceOf(bob); + vm.startPrank(bob); + vaultV2.claim(bob, 0); + vm.stopPrank(); + assertEq(collateral.balanceOf(bob) - bobBalanceBefore, expectedBobEpoch1); + + vm.startPrank(alice); + vm.expectRevert(IVault.WithdrawalNotMatured.selector); + vaultV2.claim(alice, 0); + vm.stopPrank(); + + vm.warp(uint256(nextEpochStart) + 1); + uint256 aliceBalanceBefore = collateral.balanceOf(alice); + vm.startPrank(alice); + vaultV2.claim(alice, 0); + vm.stopPrank(); + assertEq(collateral.balanceOf(alice) - aliceBalanceBefore, expectedAliceEpoch2); + } + + function test_MigrateWithdrawals_ClaimAfterUpgrade() public { + uint48 epochDuration = 5; + + uint256 blockTimestamp = vm.getBlockTimestamp(); + blockTimestamp = blockTimestamp + 1_720_700_948; + vm.warp(blockTimestamp); + + address[] memory networkLimitSetRoleHolders = new address[](1); + networkLimitSetRoleHolders[0] = alice; + address[] memory operatorNetworkSharesSetRoleHolders = new address[](1); + operatorNetworkSharesSetRoleHolders[0] = alice; + (IVault vault_,, ) = _createInitializedVaultWithOwner( + epochDuration, + networkLimitSetRoleHolders, + operatorNetworkSharesSetRoleHolders, + 1, + address(0xdEaD), + false, + false, + 0, + address(this) + ); + VaultV1 vaultV1 = VaultV1(address(vault_)); + vault = IVault(address(vaultV1)); + + uint256 aliceDeposit = 1000; + _deposit(alice, aliceDeposit); + + uint256 withdrawEpoch0 = 250; + _withdraw(alice, withdrawEpoch0); + + uint256 epoch1Start = blockTimestamp + epochDuration; + vm.warp(epoch1Start + 1); + + uint256 withdrawEpoch1 = 180; + _withdraw(alice, withdrawEpoch1); + + uint256 epoch2Start = blockTimestamp + 2 * epochDuration; + vm.warp(epoch2Start + epochDuration / 2); + + vaultFactory.migrate(address(vaultV1), vaultFactory.lastVersion(), ""); + + IVault vaultV2 = IVault(address(vaultV1)); + vaultV2.migrateWithdrawalsOf(alice, 1); + vaultV2.migrateWithdrawalsOf(alice, 2); + + uint256 expectedEpoch1 = Math.mulDiv(withdrawEpoch0, withdrawEpoch0 + 1, withdrawEpoch0 + 1); + uint256 expectedEpoch2 = Math.mulDiv(withdrawEpoch1, withdrawEpoch1 + 1, withdrawEpoch1 + 1); + + uint256 aliceBalanceBefore = collateral.balanceOf(alice); + vm.startPrank(alice); + vaultV2.claim(alice, 0); + vm.stopPrank(); + assertEq(collateral.balanceOf(alice) - aliceBalanceBefore, expectedEpoch1); + assertEq(vaultV2.isWithdrawalsClaimed(0, alice), true); + + uint48 nextEpochStart = uint48(blockTimestamp + 3 * epochDuration); + vm.warp(uint256(nextEpochStart) + 1); + + aliceBalanceBefore = collateral.balanceOf(alice); + vm.startPrank(alice); + vaultV2.claim(alice, 1); + vm.stopPrank(); + assertEq(collateral.balanceOf(alice) - aliceBalanceBefore, expectedEpoch2); + assertEq(vaultV2.isWithdrawalsClaimed(1, alice), true); + } + function test_OnSlashRevertNotSlasher() public { uint48 epochDuration = 1; @@ -2083,7 +2369,6 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + captureAgo; vm.warp(blockTimestamp); - assertEq(vault.totalStake(), depositAmount); assertEq( _slash(alice, alice, alice, slashAmount1, uint48(blockTimestamp - captureAgo), ""), Math.min(slashAmount1, depositAmount) @@ -2128,15 +2413,15 @@ contract VaultTest is Test { vm.warp(blockTimestamp); uint256 activeStake = depositAmount - withdrawAmount1 - withdrawAmount2; - assertEq(vault.totalStake(), activeStake); + assertEq(vault.totalStake(), _expectedTotalStake(uint48(blockTimestamp))); assertEq(vault.activeStake(), activeStake); - uint256 lastBucket = vault.latestWithdrawalBucket(); + uint256 lastBucket = _latestWithdrawalBucket(); assertEq(vault.withdrawals(lastBucket), withdrawAmount1 + withdrawAmount2); blockTimestamp = blockTimestamp + vault.epochDuration(); vm.warp(blockTimestamp); - uint256 slashAmountReal = Math.min(slashAmount1, vault.totalStake()); + uint256 slashAmountReal = Math.min(slashAmount1, activeStake); uint256 tokensBeforeBurner = collateral.balanceOf(address(vault.burner())); assertEq(_slash(alice, alice, alice, slashAmount1, uint48(blockTimestamp - captureAgo), ""), slashAmountReal); assertEq(collateral.balanceOf(address(vault.burner())) - tokensBeforeBurner, slashAmountReal); @@ -2179,23 +2464,27 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + captureAgo; vm.warp(blockTimestamp); - uint256 slashAmountReal = Math.min(slashAmount1, vault.activeStake()); - uint256 tokensBeforeBurner = collateral.balanceOf(address(vault.burner())); - - uint256 totalStake = vault.totalStake(); uint256 activeStake = vault.activeStake(); - uint256 lastBucket = vault.latestWithdrawalBucket(); - uint256 withdrawals = vault.withdrawals(lastBucket); + uint256 lastBucket = _latestWithdrawalBucket(); + uint256 lastWithdrawals = vault.withdrawals(lastBucket); + uint256 lastWithdrawalShares = vault.withdrawalShares(lastBucket); + uint256 unmaturedWithdrawalShares = _unmaturedWithdrawalShares(uint48(blockTimestamp)); + uint256 unmaturedWithdrawals = + lastWithdrawalShares == 0 ? 0 : unmaturedWithdrawalShares.mulDiv(lastWithdrawals, lastWithdrawalShares); + uint256 slashableStake = activeStake + unmaturedWithdrawals; + uint256 slashAmountReal = Math.min(slashAmount1, activeStake); + uint256 tokensBeforeBurner = collateral.balanceOf(address(vault.burner())); console2.log("-------slasher", address(slasher)); assertEq(_slash(alice, alice, alice, slashAmount1, uint48(blockTimestamp - captureAgo), ""), slashAmountReal); assertEq(collateral.balanceOf(address(vault.burner())) - tokensBeforeBurner, slashAmountReal); - uint256 activeStakeAfter = - depositAmount - withdrawAmount1 - withdrawAmount2 - slashAmountReal.mulDiv(activeStake, totalStake); + uint256 activeSlashed = slashAmountReal.mulDiv(activeStake, slashableStake); + uint256 activeStakeAfter = activeStake - activeSlashed; assertApproxEqAbs(vault.activeStake(), activeStakeAfter, 10); - uint256 withdrawalsAfter = withdrawals - slashAmountReal.mulDiv(withdrawals, totalStake); + uint256 unmaturedSlashed = slashAmountReal - activeSlashed; + uint256 withdrawalsAfter = unmaturedWithdrawals - unmaturedSlashed; assertApproxEqAbs(vault.withdrawals(lastBucket + 1), withdrawalsAfter, 10); } @@ -2236,11 +2525,6 @@ contract VaultTest is Test { vm.warp(blockTimestamp); // First slash - uint256 totalStake1 = vault.totalStake(); - uint256 activeStake1 = vault.activeStake(); - uint256 lastBucket1 = vault.latestWithdrawalBucket(); - uint256 withdrawals1 = vault.withdrawals(lastBucket1); - uint256 slashAmountReal1 = _slash(alice, alice, alice, slashAmount1, uint48(blockTimestamp - captureAgo), ""); blockTimestamp = blockTimestamp + captureAgo; @@ -2248,10 +2532,12 @@ contract VaultTest is Test { // Second slash // Calculate unmatured withdrawals the same way the slash function does - uint256 lastBucket2 = vault.latestWithdrawalBucket(); + uint256 lastBucket2 = _latestWithdrawalBucket(); uint256 lastWithdrawals2 = vault.withdrawals(lastBucket2); uint256 lastWithdrawalShares2 = vault.withdrawalShares(lastBucket2); - uint256 unmaturedWithdrawalShares2 = vault.unmaturedWithdrawalShares(uint48(blockTimestamp)); + uint256 unmaturedWithdrawalShares2 = + vaultTestHelper.withdrawalSharesPrefixesLatest(address(vault)) + - vaultTestHelper.withdrawalSharesPrefixesUpperLookupRecent(address(vault), uint48(blockTimestamp)); uint256 unmaturedWithdrawals2 = lastWithdrawalShares2 == 0 ? 0 : unmaturedWithdrawalShares2.mulDiv(lastWithdrawals2, lastWithdrawalShares2); @@ -2261,15 +2547,36 @@ contract VaultTest is Test { uint256 slashAmountReal2 = _slash(alice, alice, alice, slashAmount2, uint48(blockTimestamp - captureAgo), ""); // Calculate state after second slash - uint256 activeStakeAfter = activeStake2 - slashAmountReal2.mulDiv(activeStake2, slashableStake2); + uint256 activeSlashed2 = slashAmountReal2.mulDiv(activeStake2, slashableStake2); + uint256 activeStakeAfter = activeStake2 - activeSlashed2; assertApproxEqAbs(vault.activeStake(), activeStakeAfter, 10); // The unmatured withdrawals are slashed proportionally - uint256 withdrawalsAfter = - unmaturedWithdrawals2 - slashAmountReal2.mulDiv(unmaturedWithdrawals2, slashableStake2); + uint256 unmaturedSlashed2 = slashAmountReal2 - activeSlashed2; + uint256 withdrawalsAfter = unmaturedWithdrawals2 - unmaturedSlashed2; assertApproxEqAbs(vault.withdrawals(lastBucket2 + 1), withdrawalsAfter, 10); } + function _latestWithdrawalBucket() internal view returns (uint256) { + return vaultTestHelper.timeToBucketLatest(address(vault)); + } + + function _unmaturedWithdrawalShares(uint48 timestamp) internal view returns (uint256) { + return vaultTestHelper.withdrawalSharesPrefixesLatest(address(vault)) + - vaultTestHelper.withdrawalSharesPrefixesUpperLookupRecent(address(vault), timestamp); + } + + function _expectedTotalStake(uint48 timestamp) internal view returns (uint256) { + uint256 lastBucket = _latestWithdrawalBucket(); + uint256 lastWithdrawalShares = vault.withdrawalShares(lastBucket); + uint256 activeStake_ = vault.activeStake(); + if (lastWithdrawalShares == 0) { + return activeStake_; + } + uint256 unmaturedShares = _unmaturedWithdrawalShares(timestamp); + return activeStake_ + unmaturedShares.mulDiv(vault.withdrawals(lastBucket), lastWithdrawalShares); + } + function _prepareVault() internal { _registerNetwork(alice, alice); _setMaxNetworkLimit(alice, 0, type(uint256).max); @@ -2289,31 +2596,44 @@ contract VaultTest is Test { _setOperatorNetworkLimit(alice, alice, bob, type(uint256).max / 2); } - - function _getVault(uint48 epochDuration) internal returns (IVaultFull) { + function _getVault(uint48 epochDuration) internal returns (IVault) { address[] memory networkLimitSetRoleHolders = new address[](1); networkLimitSetRoleHolders[0] = alice; address[] memory operatorNetworkSharesSetRoleHolders = new address[](1); operatorNetworkSharesSetRoleHolders[0] = alice; - (IVaultFull vault_,,) = _createInitializedVault( - epochDuration, networkLimitSetRoleHolders, operatorNetworkSharesSetRoleHolders, vaultFactory.lastVersion(), address(0xdEaD), false, false, 0 + (IVault vault_,,) = _createInitializedVault( + epochDuration, + networkLimitSetRoleHolders, + operatorNetworkSharesSetRoleHolders, + vaultFactory.lastVersion(), + address(0xdEaD), + false, + false, + 0 ); return vault_; } function _getVaultAndDelegatorAndSlasher(uint48 epochDuration) internal - returns (IVaultFull, FullRestakeDelegator, Slasher) + returns (IVault, FullRestakeDelegator, Slasher) { address[] memory networkLimitSetRoleHolders = new address[](1); networkLimitSetRoleHolders[0] = alice; address[] memory operatorNetworkLimitSetRoleHolders = new address[](1); operatorNetworkLimitSetRoleHolders[0] = alice; - (IVaultFull vault_, address delegator_, address slasher_) = _createInitializedVault( - epochDuration, networkLimitSetRoleHolders, operatorNetworkLimitSetRoleHolders, vaultFactory.lastVersion(), address(0xdEaD), false, false, 0 + (IVault vault_, address delegator_, address slasher_) = _createInitializedVault( + epochDuration, + networkLimitSetRoleHolders, + operatorNetworkLimitSetRoleHolders, + vaultFactory.lastVersion(), + address(0xdEaD), + false, + false, + 0 ); - return (IVaultFull(vault_), FullRestakeDelegator(delegator_), Slasher(slasher_)); + return (IVault(vault_), FullRestakeDelegator(delegator_), Slasher(slasher_)); } function _registerOperator(address user) internal { @@ -2472,6 +2792,14 @@ contract VaultTest is Test { return address(new Vault(delegatorFactory, slasherFactory, vaultFactory)); } + function _createVaultV1Impl(address delegatorFactory, address slasherFactory, address vaultFactory) + internal + virtual + returns (address) + { + return address(new VaultV1(delegatorFactory, slasherFactory, vaultFactory)); + } + function _createInitializedVault( uint48 epochDuration, address[] memory networkLimitSetRoleHolders, @@ -2481,11 +2809,35 @@ contract VaultTest is Test { bool depositWhitelist, bool isDepositLimit, uint256 depositLimit - ) internal virtual returns (IVaultFull, address, address) { + ) internal virtual returns (IVault, address, address) { + return _createInitializedVaultWithOwner( + epochDuration, + networkLimitSetRoleHolders, + operatorNetworkSharesSetRoleHolders, + version, + burner, + depositWhitelist, + isDepositLimit, + depositLimit, + address(0) + ); + } + + function _createInitializedVaultWithOwner( + uint48 epochDuration, + address[] memory networkLimitSetRoleHolders, + address[] memory operatorNetworkSharesSetRoleHolders, + uint64 version, + address burner, + bool depositWhitelist, + bool isDepositLimit, + uint256 depositLimit, + address owner_ + ) internal virtual returns (IVault, address, address) { (address vault_, address delegator_, address slasher_) = vaultConfigurator.create( IVaultConfigurator.InitParams({ version: version, - owner: address(0), + owner: owner_, vaultParams: abi.encode( IVault.InitParams({ collateral: address(collateral), @@ -2519,11 +2871,10 @@ contract VaultTest is Test { }) ); - return (IVaultFull(vault_), address(delegator_), address(slasher_)); + return (IVault(vault_), address(delegator_), address(slasher_)); } - function _getEncodedVaultParams(IVault.InitParams memory params) internal virtual pure returns (bytes memory) { + function _getEncodedVaultParams(IVault.InitParams memory params) internal pure virtual returns (bytes memory) { return abi.encode(params); } - } diff --git a/test/vault/VaultTokenized.t.sol b/test/vault/VaultTokenized.t.sol index 1b9c7da2..cf140605 100644 --- a/test/vault/VaultTokenized.t.sol +++ b/test/vault/VaultTokenized.t.sol @@ -20,12 +20,13 @@ contract VaultTokenizedTest is VaultTest { epochDuration = uint48(bound(epochDuration, 1, 50 weeks)); super.test_Create2(burner, epochDuration, depositWhitelist, isDepositLimit, depositLimit); - assertEq(vault.balanceOf(alice), 0); - assertEq(vault.totalSupply(), 0); - assertEq(vault.allowance(alice, alice), 0); - assertEq(vault.decimals(), collateral.decimals()); - assertEq(vault.symbol(), "TEST"); - assertEq(vault.name(), "Test"); + VaultTokenized tokenizedVault = VaultTokenized(address(vault)); + assertEq(tokenizedVault.balanceOf(alice), 0); + assertEq(tokenizedVault.totalSupply(), 0); + assertEq(tokenizedVault.allowance(alice, alice), 0); + assertEq(tokenizedVault.decimals(), collateral.decimals()); + assertEq(tokenizedVault.symbol(), "TEST"); + assertEq(tokenizedVault.name(), "Test"); } function test_DepositTwice(uint256 amount1, uint256 amount2) public override { @@ -36,8 +37,9 @@ contract VaultTokenizedTest is VaultTest { uint256 shares1 = amount1 * 10 ** 0; uint256 shares2 = amount2 * (shares1 + 10 ** 0) / (amount1 + 1); - assertEq(vault.balanceOf(alice), shares1 + shares2); - assertEq(vault.totalSupply(), shares1 + shares2); + VaultTokenized tokenizedVault = VaultTokenized(address(vault)); + assertEq(tokenizedVault.balanceOf(alice), shares1 + shares2); + assertEq(tokenizedVault.totalSupply(), shares1 + shares2); } function test_DepositBoth(uint256 amount1, uint256 amount2) public override { @@ -48,9 +50,10 @@ contract VaultTokenizedTest is VaultTest { uint256 shares1 = amount1 * 10 ** 0; uint256 shares2 = amount2 * (shares1 + 10 ** 0) / (amount1 + 1); - assertEq(vault.balanceOf(alice), shares1); - assertEq(vault.balanceOf(bob), shares2); - assertEq(vault.totalSupply(), shares1 + shares2); + VaultTokenized tokenizedVault = VaultTokenized(address(vault)); + assertEq(tokenizedVault.balanceOf(alice), shares1); + assertEq(tokenizedVault.balanceOf(bob), shares2); + assertEq(tokenizedVault.totalSupply(), shares1 + shares2); } function test_WithdrawTwice(uint256 amount1, uint256 amount2, uint256 amount3) public override { @@ -61,8 +64,9 @@ contract VaultTokenizedTest is VaultTest { super.test_WithdrawTwice(amount1, amount2, amount3); - assertEq(vault.balanceOf(alice), amount1 - amount2 - amount3); - assertEq(vault.totalSupply(), amount1 - amount2 - amount3); + VaultTokenized tokenizedVault = VaultTokenized(address(vault)); + assertEq(tokenizedVault.balanceOf(alice), amount1 - amount2 - amount3); + assertEq(tokenizedVault.totalSupply(), amount1 - amount2 - amount3); } function test_Transfer(uint256 amount1, uint256 amount2) public { @@ -78,8 +82,9 @@ contract VaultTokenizedTest is VaultTest { (, uint256 mintedShares) = _deposit(alice, amount1); - assertEq(vault.balanceOf(alice), mintedShares); - assertEq(vault.totalSupply(), mintedShares); + VaultTokenized tokenizedVault = VaultTokenized(address(vault)); + assertEq(tokenizedVault.balanceOf(alice), mintedShares); + assertEq(tokenizedVault.totalSupply(), mintedShares); assertEq(vault.activeSharesOf(alice), mintedShares); assertEq(vault.activeShares(), mintedShares); @@ -87,36 +92,36 @@ contract VaultTokenizedTest is VaultTest { vm.startPrank(alice); vm.expectRevert(); - vault.transfer(bob, amount2); + tokenizedVault.transfer(bob, amount2); vm.stopPrank(); } else { vm.startPrank(alice); - vault.transfer(bob, amount2); + tokenizedVault.transfer(bob, amount2); - assertEq(vault.balanceOf(alice), mintedShares - amount2); - assertEq(vault.totalSupply(), mintedShares); + assertEq(tokenizedVault.balanceOf(alice), mintedShares - amount2); + assertEq(tokenizedVault.totalSupply(), mintedShares); assertEq(vault.activeSharesOf(alice), mintedShares - amount2); assertEq(vault.activeShares(), mintedShares); - assertEq(vault.balanceOf(bob), amount2); + assertEq(tokenizedVault.balanceOf(bob), amount2); assertEq(vault.activeSharesOf(bob), amount2); vm.stopPrank(); vm.startPrank(bob); - vault.approve(alice, amount2); + tokenizedVault.approve(alice, amount2); vm.stopPrank(); - assertEq(vault.allowance(bob, alice), amount2); + assertEq(tokenizedVault.allowance(bob, alice), amount2); vm.startPrank(alice); - vault.transferFrom(bob, alice, amount2); + tokenizedVault.transferFrom(bob, alice, amount2); vm.stopPrank(); - assertEq(vault.balanceOf(alice), mintedShares); - assertEq(vault.totalSupply(), mintedShares); + assertEq(tokenizedVault.balanceOf(alice), mintedShares); + assertEq(tokenizedVault.totalSupply(), mintedShares); assertEq(vault.activeSharesOf(alice), mintedShares); assertEq(vault.activeShares(), mintedShares); } @@ -140,7 +145,7 @@ contract VaultTokenizedTest is VaultTest { bool depositWhitelist, bool isDepositLimit, uint256 depositLimit - ) internal virtual override returns (IVaultFull, address, address) { + ) internal virtual override returns (IVault, address, address) { (address vault_, address delegator_, address slasher_) = vaultConfigurator.create( IVaultConfigurator.InitParams({ version: version, @@ -182,7 +187,7 @@ contract VaultTokenizedTest is VaultTest { }) ); - return (IVaultFull(vault_), address(delegator_), address(slasher_)); + return (IVault(vault_), address(delegator_), address(slasher_)); } function _getEncodedVaultParams(IVault.InitParams memory params) internal pure override returns (bytes memory) {