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 845f6753..8c263b0d 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; @@ -40,8 +41,14 @@ 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]; + uint208 lastBucket = _timeToBucket.latest(); + uint256 lastWithdrawalShares = withdrawalShares[lastBucket]; + return activeStake() + + (lastWithdrawalShares > 0 + ? (_withdrawalSharesPrefixes.latest() + - _withdrawalSharesPrefixes.upperLookupRecent(uint48(block.timestamp))) + .mulDiv(withdrawals[lastBucket], lastWithdrawalShares) + : 0); } /** @@ -69,17 +76,11 @@ 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]); - } - - /** - * @inheritdoc IVault - */ - function slashableBalanceOf(address account) external view returns (uint256) { - uint256 epoch = currentEpoch(); - return activeBalanceOf(account) + withdrawalsOf(epoch, account) + withdrawalsOf(epoch + 1, account); + function withdrawalsOf(uint256 index, address account) public view returns (uint256) { + uint256 bucketIndex = _timeToBucket.upperLookupRecent(withdrawalUnlockAt(index, account)); + return ERC4626Math.previewRedeem( + withdrawalSharesOf(index, account), withdrawals[bucketIndex], withdrawalShares[bucketIndex] + ); } /** @@ -116,9 +117,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); } @@ -176,38 +177,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); } /** @@ -217,46 +218,30 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau if (msg.sender != slasher) { revert NotSlasher(); } - - uint256 currentEpoch_ = currentEpoch(); - uint256 captureEpoch = epochAt(captureTimestamp); - if ((currentEpoch_ > 0 && captureEpoch < currentEpoch_ - 1) || captureEpoch > currentEpoch_) { + 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 = + _withdrawalSharesPrefixes.latest() - _withdrawalSharesPrefixes.upperLookupRecent(uint48(block.timestamp)); + uint256 unmaturedWithdrawals = + lastWithdrawalShares > 0 ? unmaturedWithdrawalShares.mulDiv(lastWithdrawals, lastWithdrawalShares) : 0; 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 slashableStake = activeStake_ + unmaturedWithdrawals; + slashedAmount = Math.min(amount, slashableStake); + if (slashedAmount > 0) { + _timeToBucket.push(uint48(block.timestamp), lastBucket + 1); + withdrawals[lastBucket] = lastWithdrawals - unmaturedWithdrawals; + withdrawalShares[lastBucket] = lastWithdrawalShares - unmaturedWithdrawalShares; + withdrawalShares[lastBucket + 1] = unmaturedWithdrawalShares; - _activeStake.push(Time.timestamp(), activeStake_ - activeSlashed); - withdrawals[currentEpoch_ + 1] = nextWithdrawals - nextWithdrawalsSlashed; - withdrawals[currentEpoch_] = withdrawals_ - withdrawalsSlashed; - } - } + uint256 activeSlashed = slashedAmount.mulDiv(activeStake_, slashableStake); + _activeStake.push(uint48(block.timestamp), activeStake_ - activeSlashed); + withdrawals[lastBucket + 1] = unmaturedWithdrawals - (slashedAmount - activeSlashed); - if (slashedAmount > 0) { IERC20(collateral).safeTransfer(burner, slashedAmount); } @@ -370,39 +355,36 @@ 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); - - uint256 epoch = currentEpoch() + 1; - uint256 withdrawals_ = withdrawals[epoch]; - uint256 withdrawalsShares_ = withdrawalShares[epoch]; + _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); - mintedShares = ERC4626Math.previewDeposit(withdrawnAssets, withdrawalsShares_, withdrawals_); + uint256 lastBucket = _timeToBucket.latest(); + mintedShares = + ERC4626Math.previewDeposit(withdrawnAssets, withdrawalShares[lastBucket], withdrawals[lastBucket]); + withdrawals[lastBucket] += withdrawnAssets; + withdrawalShares[lastBucket] += mintedShares; - withdrawals[epoch] = withdrawals_ + withdrawnAssets; - withdrawalShares[epoch] = withdrawalsShares_ + mintedShares; - withdrawalSharesOf[epoch][claimer] += mintedShares; + uint48 unlockAt = uint48(block.timestamp) + epochDuration; + _withdrawalsOf[claimer].push(Withdrawal(false, unlockAt, mintedShares)); + _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()) { - revert InvalidEpoch(); - } - - if (isWithdrawalsClaimed[epoch][msg.sender]) { + function _claim(uint256 index) internal returns (uint256 amount) { + Withdrawal storage withdrawal = _withdrawalsOf[msg.sender][index]; + if (withdrawal.claimed) { revert AlreadyClaimed(); } - - amount = withdrawalsOf(epoch, msg.sender); - + if (withdrawal.unlockAt >= block.timestamp) { + revert WithdrawalNotMatured(); + } + amount = withdrawalsOf(index, msg.sender); if (amount == 0) { revert InsufficientClaim(); } - - isWithdrawalsClaimed[epoch][msg.sender] = true; + withdrawal.claimed = true; } function _initialize(uint64, address, bytes memory data) internal virtual override { @@ -442,7 +424,6 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau burner = params.burner; - epochDurationInit = Time.timestamp(); epochDuration = params.epochDuration; depositWhitelist = params.depositWhitelist; @@ -467,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 */ @@ -477,6 +479,17 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau internal override { - revert(); + 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 2446ed02..36f7d8fc 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; /** @@ -64,10 +65,7 @@ abstract contract VaultStorage is StaticDelegateCallable, IVaultStorage { */ address public burner; - /** - * @inheritdoc IVaultStorage - */ - uint48 public epochDurationInit; + uint48 internal _epochDurationInit; /** * @inheritdoc IVaultStorage @@ -105,24 +103,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 +128,22 @@ abstract contract VaultStorage is StaticDelegateCallable, IVaultStorage { mapping(address account => Checkpoints.Trace256 shares) internal _activeSharesOf; + mapping(address account => Withdrawal[] withdrawals) internal _withdrawalsOf; + + /** + * @inheritdoc IVaultStorage + */ + mapping(uint256 bucketIndex => uint256 value) public withdrawalShares; + + /** + * @inheritdoc IVaultStorage + */ + mapping(uint256 bucketIndex => uint256 value) public withdrawals; + + Checkpoints.Trace256 internal _withdrawalSharesPrefixes; + + Checkpoints.Trace208 internal _timeToBucket; + constructor(address delegatorFactory, address slasherFactory) { DELEGATOR_FACTORY = delegatorFactory; SLASHER_FACTORY = slasherFactory; @@ -138,86 +152,79 @@ abstract contract VaultStorage is StaticDelegateCallable, IVaultStorage { /** * @inheritdoc IVaultStorage */ - function epochAt(uint48 timestamp) public view returns (uint256) { - if (timestamp < epochDurationInit) { - revert InvalidTimestamp(); - } - return (timestamp - epochDurationInit) / epochDuration; + function currentEpochStart() public view returns (uint48) { + return uint48(block.timestamp); } /** * @inheritdoc IVaultStorage */ - function currentEpoch() public view returns (uint256) { - return (Time.timestamp() - epochDurationInit) / epochDuration; + function activeSharesAt(uint48 timestamp, bytes memory hint) public view returns (uint256) { + return _activeShares.upperLookupRecent(timestamp, hint); } /** * @inheritdoc IVaultStorage */ - function currentEpochStart() public view returns (uint48) { - return (epochDurationInit + currentEpoch() * epochDuration).toUint48(); + function activeShares() public view returns (uint256) { + return _activeShares.latest(); } /** * @inheritdoc IVaultStorage */ - function previousEpochStart() public view returns (uint48) { - uint256 epoch = currentEpoch(); - if (epoch == 0) { - revert NoPreviousEpoch(); - } - return (epochDurationInit + (epoch - 1) * epochDuration).toUint48(); + function activeStakeAt(uint48 timestamp, bytes memory hint) public view returns (uint256) { + return _activeStake.upperLookupRecent(timestamp, hint); } /** * @inheritdoc IVaultStorage */ - function nextEpochStart() public view returns (uint48) { - return (epochDurationInit + (currentEpoch() + 1) * epochDuration).toUint48(); + function activeStake() public view returns (uint256) { + return _activeStake.latest(); } /** * @inheritdoc IVaultStorage */ - function activeSharesAt(uint48 timestamp, bytes memory hint) public view returns (uint256) { - return _activeShares.upperLookupRecent(timestamp, hint); + function activeSharesOfAt(address account, uint48 timestamp, bytes memory hint) public view returns (uint256) { + return _activeSharesOf[account].upperLookupRecent(timestamp, hint); } /** * @inheritdoc IVaultStorage */ - function activeShares() public view returns (uint256) { - return _activeShares.latest(); + function activeSharesOf(address account) public view returns (uint256) { + return _activeSharesOf[account].latest(); } /** * @inheritdoc IVaultStorage */ - function activeStakeAt(uint48 timestamp, bytes memory hint) public view returns (uint256) { - return _activeStake.upperLookupRecent(timestamp, hint); + function withdrawalSharesOf(uint256 index, address account) public view returns (uint256) { + return _withdrawalsOf[account][index].shares; } /** * @inheritdoc IVaultStorage */ - function activeStake() public view returns (uint256) { - return _activeStake.latest(); + function isWithdrawalsClaimed(uint256 index, address account) public view returns (bool) { + return _withdrawalsOf[account][index].claimed; } /** * @inheritdoc IVaultStorage */ - function activeSharesOfAt(address account, uint48 timestamp, bytes memory hint) public view returns (uint256) { - return _activeSharesOf[account].upperLookupRecent(timestamp, hint); + function withdrawalUnlockAt(uint256 index, address account) public view returns (uint48) { + return _withdrawalsOf[account][index].unlockAt; } /** * @inheritdoc IVaultStorage */ - function activeSharesOf(address account) public view returns (uint256) { - return _activeSharesOf[account].latest(); + 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 b7c96e3f..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(); @@ -31,6 +35,7 @@ interface IVault is IMigratableEntity, IVaultStorage { error SlasherAlreadyInitialized(); error TooMuchRedeem(); error TooMuchWithdraw(); + error WithdrawalNotMatured(); /** * @notice Initial parameters needed for a vault deployment. @@ -97,19 +102,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,19 +192,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); - - /** - * @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); + function withdrawalsOf(uint256 index, address account) external view returns (uint256); /** * @notice Deposit collateral into the vault. @@ -233,18 +231,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. @@ -297,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 d009eab9..b02b2917 100644 --- a/src/interfaces/vault/IVaultStorage.sol +++ b/src/interfaces/vault/IVaultStorage.sol @@ -1,10 +1,25 @@ // 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(); + 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 @@ -78,50 +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. - */ - 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 + * @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. - */ - 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 @@ -192,32 +175,47 @@ 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 bucket index. + * @param index index to get the total amount of the withdrawals at + * @return total amount of the withdrawals at the bucket 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 bucket index. + * @param index index to get the total number of withdrawal shares at + * @return total number of withdrawal shares at the bucket 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 index, address account) external view returns (uint256); + + /** + * @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 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 withdrawalSharesOf(uint256 epoch, address account) external view returns (uint256); + function withdrawalUnlockAt(uint256 index, address account) external view returns (uint48); /** - * @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 how many withdrawals a particular account requested. * @param account account to check the withdrawals for - * @return if the withdrawals are claimed for the account at the epoch + * @return the number of withdrawals requested by the account */ - function isWithdrawalsClaimed(uint256 epoch, address account) external view returns (bool); + 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/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..25ced271 --- /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; + } +} diff --git a/test/vault/Vault.t.sol b/test/vault/Vault.t.sol index 7d5e9a51..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"; @@ -11,8 +11,10 @@ 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 {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"; @@ -37,11 +39,13 @@ 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; using Subnetwork for bytes32; using Subnetwork for address; + using Checkpoints for Checkpoints.Trace208; address owner; address alice; @@ -63,12 +67,13 @@ contract VaultTest is Test { Token collateral; FeeOnTransferToken feeOnTransferCollateral; VaultConfigurator vaultConfigurator; + VaultTestHelper vaultTestHelper; - Vault vault; + IVault vault; FullRestakeDelegator delegator; Slasher slasher; - function setUp() public { + function setUp() public virtual { owner = address(this); (alice, alicePrivateKey) = makeAddrAndKey("alice"); (bob, bobPrivateKey) = makeAddrAndKey("bob"); @@ -86,8 +91,14 @@ contract VaultTest is Test { operatorNetworkOptInService = new OptInService(address(operatorRegistry), address(networkRegistry), "OperatorNetworkOptInService"); - address vaultImpl = - address(new Vault(address(delegatorFactory), address(slasherFactory), address(vaultFactory))); + 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); address networkRestakeDelegatorImpl = address( @@ -174,7 +185,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(); @@ -185,69 +196,34 @@ 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})}) - ) - }) + (IVault 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")); 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(), address(0)); + 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); @@ -257,46 +233,14 @@ 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.isWithdrawalsClaimed(0, alice), false); 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); - - 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 { @@ -308,41 +252,15 @@ 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 ); } @@ -354,42 +272,17 @@ 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 ); } @@ -399,11 +292,11 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.MissingRoles.selector); - vault = Vault( + vault = IVault( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -428,11 +321,11 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.MissingRoles.selector); - vault = Vault( + vault = IVault( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -457,11 +350,11 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.MissingRoles.selector); - vault = Vault( + vault = IVault( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -486,11 +379,11 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.MissingRoles.selector); - vault = Vault( + vault = IVault( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -515,11 +408,11 @@ contract VaultTest is Test { uint64 lastVersion = vaultFactory.lastVersion(); vm.expectRevert(IVault.MissingRoles.selector); - vault = Vault( + vault = IVault( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -541,11 +434,11 @@ contract VaultTest is Test { function test_SetDelegator() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVault( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -597,11 +490,11 @@ contract VaultTest is Test { function test_SetDelegatorRevertDelegatorAlreadyInitialized() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVault( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -650,11 +543,11 @@ contract VaultTest is Test { function test_SetDelegatorRevertNotDelegator() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVault( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -679,11 +572,11 @@ contract VaultTest is Test { function test_SetDelegatorRevertInvalidDelegator() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVault( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -701,25 +594,23 @@ contract VaultTest is Test { ) ); - Vault vault2 = Vault( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - 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 + }) ) ); @@ -752,11 +643,11 @@ contract VaultTest is Test { function test_SetSlasher() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVault( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -796,11 +687,11 @@ contract VaultTest is Test { function test_SetSlasherRevertSlasherAlreadyInitialized() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVault( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -837,11 +728,11 @@ contract VaultTest is Test { function test_SetSlasherRevertNotSlasher() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVault( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -876,11 +767,11 @@ contract VaultTest is Test { function test_SetSlasherRevertInvalidSlasher() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVault( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -898,25 +789,23 @@ contract VaultTest is Test { ) ); - Vault vault2 = Vault( - vaultFactory.create( - lastVersion, - alice, - abi.encode( - 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 + }) ) ); @@ -937,11 +826,11 @@ contract VaultTest is Test { function test_SetSlasherZeroAddress() public { uint64 lastVersion = vaultFactory.lastVersion(); - vault = Vault( + vault = IVault( vaultFactory.create( lastVersion, alice, - abi.encode( + _getEncodedVaultParams( IVault.InitParams({ collateral: address(collateral), burner: address(0xdEaD), @@ -962,7 +851,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); @@ -982,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); @@ -995,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); @@ -1007,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); @@ -1056,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( @@ -1137,42 +1022,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)); @@ -1188,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); @@ -1201,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); @@ -1217,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); @@ -1266,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( @@ -1333,7 +1189,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); @@ -1361,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); @@ -1374,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 { @@ -1406,7 +1259,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); @@ -1430,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); @@ -1443,16 +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); - 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); + uint256 lastBucket = _latestWithdrawalBucket(); + assertEq(vault.withdrawals(lastBucket), amount2); + assertEq(vault.withdrawalShares(lastBucket), mintedShares); + assertEq(vault.withdrawalSharesOf(0, alice), amount2); shares -= burnedShares; @@ -1465,7 +1312,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); @@ -1478,31 +1325,72 @@ 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; + assertEq(vault.withdrawals(lastBucket), amount2 + amount3); + assertEq(vault.withdrawalShares(lastBucket), amount2 + amount3); + assertEq(vault.withdrawalSharesOf(1, alice), amount3); blockTimestamp = blockTimestamp + 1; vm.warp(blockTimestamp); - assertEq(vault.totalStake(), amount1 - amount2); + 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); - assertEq(vault.totalStake(), amount1 - amount2 - amount3); + 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 { @@ -1567,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); @@ -1580,16 +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); - 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); + uint256 lastBucket = _latestWithdrawalBucket(); + assertEq(vault.withdrawals(lastBucket), withdrawnAssets2); + assertEq(vault.withdrawalShares(lastBucket), mintedShares); + assertEq(vault.withdrawalSharesOf(0, alice), mintedShares); shares -= amount2; @@ -1602,7 +1484,7 @@ contract VaultTest is Test { assertEq(withdrawnAssets_, withdrawnAssets3); 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 - amount3); assertEq(vault.activeShares(), shares - amount3); @@ -1617,31 +1499,14 @@ 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); blockTimestamp = blockTimestamp + 1; vm.warp(blockTimestamp); - assertEq(vault.totalStake(), amount1 - withdrawnAssets2 - withdrawnAssets3); + assertEq(vault.totalStake(), _expectedTotalStake(uint48(blockTimestamp))); } function test_RedeemRevertInvalidClaimer(uint256 amount1) public { @@ -1706,11 +1571,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 +1601,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 +1628,8 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + 2; vm.warp(blockTimestamp); - uint256 currentEpoch = vault.currentEpoch(); - vm.expectRevert(IVault.InvalidEpoch.selector); - _claim(alice, currentEpoch); + vm.expectRevert(stdError.indexOOBError); + _claim(alice, 10); } function test_ClaimRevertAlreadyClaimed(uint256 amount1, uint256 amount2) public { @@ -1791,14 +1654,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 +1669,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 +1682,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 +1714,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 +1755,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 +1793,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 +1826,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); + vm.expectRevert(stdError.indexOOBError); + _claimBatch(alice, indexes); } function test_ClaimBatchRevertAlreadyClaimed(uint256 amount1, uint256 amount2, uint256 amount3) public { @@ -2001,12 +1862,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 +1895,44 @@ contract VaultTest is Test { _withdraw(alice, amount3); - blockTimestamp = blockTimestamp + 2; + uint256[] memory indexes = new uint256[](2); + indexes[0] = 0; + indexes[1] = 1; + + vm.expectRevert(IVault.WithdrawalNotMatured.selector); + _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); - uint256[] memory epochs = new uint256[](2); - epochs[0] = vault.currentEpoch() - 1; - epochs[1] = vault.currentEpoch() - 3; + _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.expectRevert(IVault.InsufficientClaim.selector); - _claimBatch(alice, epochs); + vm.warp(uint256(unlockAt) + 1); + assertEq(_claim(alice, 0), amount2); } function test_SetDepositWhitelist() public { @@ -2240,19 +2130,212 @@ contract VaultTest is Test { _setDepositLimit(alice, limit); } - function test_OnSlashRevertNotSlasher() public { - uint48 epochDuration = 1; + function test_MigrateWithdrawals_FactoryUpgradePath() public { + uint48 epochDuration = 10; - vault = _getVault(epochDuration); + uint256 blockTimestamp = vm.getBlockTimestamp(); + blockTimestamp = blockTimestamp + 1_720_700_948; + vm.warp(blockTimestamp); - vm.startPrank(alice); - vm.expectRevert(IVault.NotSlasher.selector); - vault.onSlash(0, 0); - vm.stopPrank(); - } + 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)); - struct Test_SlashStruct { - uint256 slashAmountReal1; + 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; + + vault = _getVault(epochDuration); + + vm.startPrank(alice); + vm.expectRevert(IVault.NotSlasher.selector); + vault.onSlash(0, 0); + vm.stopPrank(); + } + + struct Test_SlashStruct { + uint256 slashAmountReal1; uint256 tokensBeforeBurner; uint256 activeStake1; uint256 withdrawals1; @@ -2260,8 +2343,39 @@ 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( + _slash(alice, alice, alice, slashAmount1, uint48(blockTimestamp - captureAgo), ""), + Math.min(slashAmount1, depositAmount) + ); + } + + function test_Slash_MaturedWithdrawals( uint256 depositAmount, uint256 withdrawAmount1, uint256 withdrawAmount2, @@ -2269,16 +2383,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,465 +2399,241 @@ 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(), _expectedTotalStake(uint48(blockTimestamp))); + assertEq(vault.activeStake(), activeStake); + uint256 lastBucket = _latestWithdrawalBucket(); + assertEq(vault.withdrawals(lastBucket), withdrawAmount1 + withdrawAmount2); + + blockTimestamp = blockTimestamp + vault.epochDuration(); + vm.warp(blockTimestamp); + + 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); + 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 + captureAgo; + vm.warp(blockTimestamp); + + uint256 activeStake = vault.activeStake(); + 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)); - blockTimestamp = blockTimestamp + 1; + assertEq(_slash(alice, alice, alice, slashAmount1, uint48(blockTimestamp - captureAgo), ""), slashAmountReal); + assertEq(collateral.balanceOf(address(vault.burner())) - tokensBeforeBurner, slashAmountReal); + + uint256 activeSlashed = slashAmountReal.mulDiv(activeStake, slashableStake); + uint256 activeStakeAfter = activeStake - activeSlashed; + assertApproxEqAbs(vault.activeStake(), activeStakeAfter, 10); + + uint256 unmaturedSlashed = slashAmountReal - activeSlashed; + uint256 withdrawalsAfter = unmaturedWithdrawals - unmaturedSlashed; + 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); - Test_SlashStruct memory test_SlashStruct; + (vault, delegator, slasher) = _getVaultAndDelegatorAndSlasher(7 days); - 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 - ); + _prepareVault(); - 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 - ); + _deposit(alice, depositAmount); + _withdraw(alice, withdrawAmount1); - 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 - ); + blockTimestamp = blockTimestamp + 10; + vm.warp(blockTimestamp); - 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 - ); + _withdraw(alice, withdrawAmount2); - 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 - ) - ); + blockTimestamp = blockTimestamp + captureAgo; + vm.warp(blockTimestamp); + + // First slash + 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 = _latestWithdrawalBucket(); + uint256 lastWithdrawals2 = vault.withdrawals(lastBucket2); + uint256 lastWithdrawalShares2 = vault.withdrawalShares(lastBucket2); + uint256 unmaturedWithdrawalShares2 = + vaultTestHelper.withdrawalSharesPrefixesLatest(address(vault)) + - vaultTestHelper.withdrawalSharesPrefixesUpperLookupRecent(address(vault), 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 activeSlashed2 = slashAmountReal2.mulDiv(activeStake2, slashableStake2); + uint256 activeStakeAfter = activeStake2 - activeSlashed2; + assertApproxEqAbs(vault.activeStake(), activeStakeAfter, 10); + + // The unmatured withdrawals are slashed proportionally + 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); } - // struct GasStruct { - // uint256 gasSpent1; - // uint256 gasSpent2; - // } + function _prepareVault() internal { + _registerNetwork(alice, alice); + _setMaxNetworkLimit(alice, 0, type(uint256).max); + + _registerOperator(alice); + _registerOperator(bob); - // struct HintStruct { - // uint256 num; - // bool back; - // uint256 secondsAgo; - // } + _optInOperatorVault(alice); + _optInOperatorVault(bob); - // 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); + _optInOperatorNetwork(alice, address(alice)); + _optInOperatorNetwork(bob, address(alice)); - // 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) { + _setNetworkLimit(alice, alice, type(uint256).max); + + _setOperatorNetworkLimit(alice, alice, alice, type(uint256).max / 2); + _setOperatorNetworkLimit(alice, alice, bob, type(uint256).max / 2); + } + + 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; - (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})}) - ) - }) + (IVault 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 (IVault, 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})}) - ) - }) + (IVault vault_, address delegator_, address slasher_) = _createInitializedVault( + epochDuration, + networkLimitSetRoleHolders, + operatorNetworkLimitSetRoleHolders, + vaultFactory.lastVersion(), + address(0xdEaD), + false, + false, + 0 ); - return (Vault(vault_), FullRestakeDelegator(delegator_), Slasher(slasher_)); + return (IVault(vault_), FullRestakeDelegator(delegator_), Slasher(slasher_)); } function _registerOperator(address user) internal { @@ -2760,25 +2649,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(); @@ -2810,9 +2699,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(); } @@ -2894,4 +2783,98 @@ 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 _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, + address[] memory operatorNetworkSharesSetRoleHolders, + uint64 version, + address burner, + bool depositWhitelist, + bool isDepositLimit, + uint256 depositLimit + ) 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: owner_, + 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 (IVault(vault_), address(delegator_), address(slasher_)); + } + + 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 d5b17cd8..cf140605 100644 --- a/test/vault/VaultTokenized.t.sol +++ b/test/vault/VaultTokenized.t.sol @@ -1,2803 +1,74 @@ // 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(); + 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_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); + VaultTokenized tokenizedVault = VaultTokenized(address(vault)); + assertEq(tokenizedVault.balanceOf(alice), shares1 + shares2); + assertEq(tokenizedVault.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; + VaultTokenized tokenizedVault = VaultTokenized(address(vault)); + assertEq(tokenizedVault.balanceOf(alice), shares1); + assertEq(tokenizedVault.balanceOf(bob), shares2); + assertEq(tokenizedVault.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 - ) - ); - } + VaultTokenized tokenizedVault = VaultTokenized(address(vault)); + assertEq(tokenizedVault.balanceOf(alice), amount1 - amount2 - amount3); + assertEq(tokenizedVault.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); @@ -2811,8 +82,9 @@ contract VaultTokenizedTest is Test { (, 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); @@ -2820,128 +92,73 @@ contract VaultTokenizedTest is Test { 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); } } - 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 (IVault, 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 +171,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 +187,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 (IVault(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(); - } }