diff --git a/src/contracts/delegator/BaseDelegator.sol b/src/contracts/delegator/BaseDelegator.sol index b262c391..ba761882 100644 --- a/src/contracts/delegator/BaseDelegator.sol +++ b/src/contracts/delegator/BaseDelegator.sol @@ -53,13 +53,6 @@ contract BaseDelegator is Entity, AccessControlUpgradeable, IBaseDelegator { */ mapping(address network => uint256 value) public maxNetworkLimit; - modifier onlySlasher() { - if (IVault(vault).slasher() != msg.sender) { - revert NotSlasher(); - } - _; - } - constructor( address networkRegistry, address vaultFactory, @@ -119,11 +112,11 @@ contract BaseDelegator is Entity, AccessControlUpgradeable, IBaseDelegator { uint48 delta = nextEpochStart - Time.timestamp(); if (Time.timestamp() + duration >= nextEpochStart) { minOperatorNetworkStakeDuring_ = - Math.min(minOperatorNetworkStakeDuring_, operatorNetworkStakeIn(operator, network, delta)); + Math.min(minOperatorNetworkStakeDuring_, operatorNetworkStakeIn(network, operator, delta)); } if (Time.timestamp() + duration >= nextEpochStart + epochDuration) { minOperatorNetworkStakeDuring_ = Math.min( - minOperatorNetworkStakeDuring_, operatorNetworkStakeIn(operator, network, delta + epochDuration) + minOperatorNetworkStakeDuring_, operatorNetworkStakeIn(network, operator, delta + epochDuration) ); } } @@ -150,7 +143,11 @@ contract BaseDelegator is Entity, AccessControlUpgradeable, IBaseDelegator { /** * @inheritdoc IBaseDelegator */ - function onSlash(address network, address operator, uint256 slashedAmount) external onlySlasher { + function onSlash(address network, address operator, uint256 slashedAmount) external { + if (IVault(vault).slasher() != msg.sender) { + revert NotSlasher(); + } + _onSlash(network, operator, slashedAmount); emit OnSlash(network, operator, slashedAmount); @@ -160,6 +157,24 @@ contract BaseDelegator is Entity, AccessControlUpgradeable, IBaseDelegator { function _onSlash(address network, address operator, uint256 slashedAmount) internal virtual {} + function _insertCheckpoint(Checkpoints.Trace256 storage checkpoints, uint48 key, uint256 value) internal { + (, uint48 latestTimestamp1, uint256 latestValue1) = checkpoints.latestCheckpoint(); + if (key < latestTimestamp1) { + checkpoints.pop(); + (, uint48 latestTimestamp2, uint256 latestValue2) = checkpoints.latestCheckpoint(); + if (key < latestTimestamp2) { + checkpoints.pop(); + checkpoints.push(key, value); + checkpoints.push(latestTimestamp2, latestValue2); + } else { + checkpoints.push(key, value); + } + checkpoints.push(latestTimestamp1, latestValue1); + } else { + checkpoints.push(key, value); + } + } + function _initializeInternal( address vault_, bytes memory data diff --git a/src/contracts/delegator/FullRestakeDelegator.sol b/src/contracts/delegator/FullRestakeDelegator.sol index a1281184..a230e6a5 100644 --- a/src/contracts/delegator/FullRestakeDelegator.sol +++ b/src/contracts/delegator/FullRestakeDelegator.sol @@ -119,7 +119,10 @@ contract FullRestakeDelegator is BaseDelegator, IFullRestakeDelegator { address operator, uint48 duration ) public view override(IBaseDelegator, BaseDelegator) returns (uint256) { - return Math.min(networkStakeIn(network, duration), operatorNetworkLimitIn(network, operator, duration)); + return Math.min( + IVault(vault).totalSupplyIn(duration), + Math.min(networkLimitIn(network, duration), operatorNetworkLimitIn(network, operator, duration)) + ); } /** @@ -129,7 +132,9 @@ contract FullRestakeDelegator is BaseDelegator, IFullRestakeDelegator { address network, address operator ) public view override(IBaseDelegator, BaseDelegator) returns (uint256) { - return Math.min(networkStake(network), operatorNetworkLimit(network, operator)); + return Math.min( + IVault(vault).totalSupply(), Math.min(networkLimit(network), operatorNetworkLimit(network, operator)) + ); } /** @@ -144,7 +149,7 @@ contract FullRestakeDelegator is BaseDelegator, IFullRestakeDelegator { ? Time.timestamp() : IVault(vault).currentEpochStart() + 2 * IVault(vault).epochDuration(); - _networkLimit[network].push(timestamp, amount); + _insertCheckpoint(_networkLimit[network], timestamp, amount); emit SetNetworkLimit(network, amount); } @@ -162,16 +167,16 @@ contract FullRestakeDelegator is BaseDelegator, IFullRestakeDelegator { if (amount > operatorNetworkLimit(network, operator)) { timestamp = Time.timestamp(); totalOperatorNetworkLimit_ = - totalOperatorNetworkLimit(network) + amount - operatorNetworkLimit(network, operator); + totalOperatorNetworkLimit(network) - operatorNetworkLimit(network, operator) + amount; } else { timestamp = IVault(vault).currentEpochStart() + 2 * IVault(vault).epochDuration(); - totalOperatorNetworkLimit_ = _totalOperatorNetworkLimit[network].latest() + amount - - _operatorNetworkLimit[network][operator].latest(); + totalOperatorNetworkLimit_ = _totalOperatorNetworkLimit[network].latest() + - _operatorNetworkLimit[network][operator].latest() + amount; } - _totalOperatorNetworkLimit[network].push(timestamp, totalOperatorNetworkLimit_); + _insertCheckpoint(_totalOperatorNetworkLimit[network], timestamp, totalOperatorNetworkLimit_); - _operatorNetworkLimit[network][operator].push(timestamp, amount); + _insertCheckpoint(_operatorNetworkLimit[network][operator], timestamp, amount); emit SetOperatorNetworkLimit(network, operator, amount); } diff --git a/src/contracts/delegator/NetworkRestakeDelegator.sol b/src/contracts/delegator/NetworkRestakeDelegator.sol index e85774e9..9871f042 100644 --- a/src/contracts/delegator/NetworkRestakeDelegator.sol +++ b/src/contracts/delegator/NetworkRestakeDelegator.sol @@ -101,6 +101,9 @@ contract NetworkRestakeDelegator is BaseDelegator, INetworkRestakeDelegator { address network, uint48 duration ) public view override(IBaseDelegator, BaseDelegator) returns (uint256) { + if (totalOperatorNetworkSharesIn(network, duration) == 0) { + return 0; + } return Math.min(IVault(vault).totalSupplyIn(duration), networkLimitIn(network, duration)); } @@ -108,6 +111,9 @@ contract NetworkRestakeDelegator is BaseDelegator, INetworkRestakeDelegator { * @inheritdoc IBaseDelegator */ function networkStake(address network) public view override(IBaseDelegator, BaseDelegator) returns (uint256) { + if (totalOperatorNetworkShares(network) == 0) { + return 0; + } return Math.min(IVault(vault).totalSupply(), networkLimit(network)); } @@ -119,8 +125,12 @@ contract NetworkRestakeDelegator is BaseDelegator, INetworkRestakeDelegator { address operator, uint48 duration ) public view override(IBaseDelegator, BaseDelegator) returns (uint256) { + uint256 totalOperatorNetworkSharesIn_ = totalOperatorNetworkSharesIn(network, duration); + if (totalOperatorNetworkSharesIn_ == 0) { + return 0; + } return operatorNetworkSharesIn(network, operator, duration).mulDiv( - networkStakeIn(network, duration), totalOperatorNetworkSharesIn(network, duration) + networkStakeIn(network, duration), totalOperatorNetworkSharesIn_ ); } @@ -131,8 +141,11 @@ contract NetworkRestakeDelegator is BaseDelegator, INetworkRestakeDelegator { address network, address operator ) public view override(IBaseDelegator, BaseDelegator) returns (uint256) { - return - operatorNetworkShares(network, operator).mulDiv(networkStake(network), totalOperatorNetworkShares(network)); + uint256 totalOperatorNetworkShares_ = totalOperatorNetworkShares(network); + if (totalOperatorNetworkShares_ == 0) { + return 0; + } + return operatorNetworkShares(network, operator).mulDiv(networkStake(network), totalOperatorNetworkShares_); } /** @@ -147,7 +160,7 @@ contract NetworkRestakeDelegator is BaseDelegator, INetworkRestakeDelegator { ? Time.timestamp() : IVault(vault).currentEpochStart() + 2 * IVault(vault).epochDuration(); - _networkLimit[network].push(timestamp, amount); + _insertCheckpoint(_networkLimit[network], timestamp, amount); emit SetNetworkLimit(network, amount); } @@ -164,7 +177,7 @@ contract NetworkRestakeDelegator is BaseDelegator, INetworkRestakeDelegator { _totalOperatorNetworkShares[network].push( timestamp, - _totalOperatorNetworkShares[network].latest() + shares - _operatorNetworkShares[network][operator].latest() + _totalOperatorNetworkShares[network].latest() - _operatorNetworkShares[network][operator].latest() + shares ); _operatorNetworkShares[network][operator].push(timestamp, shares); diff --git a/src/contracts/libraries/Checkpoints.sol b/src/contracts/libraries/Checkpoints.sol index 20166669..162c21b0 100644 --- a/src/contracts/libraries/Checkpoints.sol +++ b/src/contracts/libraries/Checkpoints.sol @@ -72,7 +72,7 @@ library Checkpoints { function upperLookupRecentCheckpoint( Trace208 storage self, uint48 key - ) internal view returns (bool, uint48, uint208) { + ) internal view returns (bool, uint48, uint208, uint256) { uint256 len = self._trace._checkpoints.length; uint256 low = 0; @@ -90,11 +90,11 @@ library Checkpoints { uint256 pos = _upperBinaryLookup(self._trace._checkpoints, key, low, high); if (pos == 0) { - return (false, 0, 0); + return (false, 0, 0, 0); } OZCheckpoints.Checkpoint208 memory checkpoint = _unsafeAccess(self._trace._checkpoints, pos - 1); - return (true, checkpoint._key, checkpoint._value); + return (true, checkpoint._key, checkpoint._value, pos - 1); } /** @@ -177,7 +177,7 @@ library Checkpoints { function upperLookupRecentCheckpoint( Trace256 storage self, uint48 key - ) internal view returns (bool, uint48, uint256) { + ) internal view returns (bool, uint48, uint256, uint256) { uint256 len = self._trace._checkpoints.length; uint256 low = 0; @@ -195,11 +195,11 @@ library Checkpoints { uint256 pos = _upperBinaryLookup(self._trace._checkpoints, key, low, high); if (pos == 0) { - return (false, 0, 0); + return (false, 0, 0, 0); } OZCheckpoints.Checkpoint208 memory checkpoint = _unsafeAccess(self._trace._checkpoints, pos - 1); - return (true, checkpoint._key, self._values[checkpoint._value]); + return (true, checkpoint._key, self._values[checkpoint._value], pos - 1); } /** diff --git a/src/contracts/libraries/ERC4626Math.sol b/src/contracts/libraries/ERC4626Math.sol index 9ee7f21d..8410c7c2 100644 --- a/src/contracts/libraries/ERC4626Math.sol +++ b/src/contracts/libraries/ERC4626Math.sol @@ -54,6 +54,6 @@ library ERC4626Math { } function _decimalsOffset() private pure returns (uint8) { - return 3; + return 0; } } diff --git a/src/contracts/slasher/VetoSlasher.sol b/src/contracts/slasher/VetoSlasher.sol index 9dd0c5a9..aecd3a7f 100644 --- a/src/contracts/slasher/VetoSlasher.sol +++ b/src/contracts/slasher/VetoSlasher.sol @@ -23,12 +23,7 @@ contract VetoSlasher is BaseSlasher, AccessControlUpgradeable, IVetoSlasher { /** * @inheritdoc IVetoSlasher */ - uint256 public SHARES_BASE = 10 ** 18; - - /** - * @inheritdoc IVetoSlasher - */ - bytes32 public constant RESOLVER_SHARES_SET_ROLE = keccak256("RESOLVER_SHARES_SET_ROLE"); + uint256 public constant SHARES_BASE = 10 ** 18; /** * @inheritdoc IVetoSlasher diff --git a/src/contracts/vault/Vault.sol b/src/contracts/vault/Vault.sol index e217aafd..6b8a6d31 100644 --- a/src/contracts/vault/Vault.sol +++ b/src/contracts/vault/Vault.sol @@ -23,13 +23,6 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau using SafeCast for uint256; using SafeERC20 for IERC20; - modifier onlySlasher() { - if (msg.sender != slasher()) { - revert NotSlasher(); - } - _; - } - /** * @inheritdoc IVault */ @@ -203,7 +196,11 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau /** * @inheritdoc IVault */ - function onSlash(uint256 slashedAmount) external onlySlasher { + function onSlash(uint256 slashedAmount) external { + if (msg.sender != slasher()) { + revert NotSlasher(); + } + if (slashedAmount == 0) { revert InsufficientSlash(); } @@ -226,11 +223,6 @@ contract Vault is VaultStorage, MigratableEntity, AccessControlUpgradeable, IVau if (activeSupply_ < activeSlashed) { withdrawalsSlashed += activeSlashed - activeSupply_; activeSlashed = activeSupply_; - - if (withdrawals_ < withdrawalsSlashed) { - nextWithdrawalsSlashed += withdrawalsSlashed - withdrawals_; - withdrawalsSlashed = withdrawals_; - } } _activeSupplies.push(Time.timestamp(), activeSupply_ - activeSlashed); diff --git a/src/contracts/vault/VaultStorage.sol b/src/contracts/vault/VaultStorage.sol index c4374d39..95e22562 100644 --- a/src/contracts/vault/VaultStorage.sol +++ b/src/contracts/vault/VaultStorage.sol @@ -195,7 +195,7 @@ contract VaultStorage is IVaultStorage { function activeSharesOfCheckpointAt( address account, uint48 timestamp - ) external view returns (bool, uint48, uint256) { + ) external view returns (bool, uint48, uint256, uint256) { return _activeSharesOf[account].upperLookupRecentCheckpoint(timestamp); } diff --git a/src/interfaces/IVaultConfigurator.sol b/src/interfaces/IVaultConfigurator.sol index 3e5f2fd3..d840951b 100644 --- a/src/interfaces/IVaultConfigurator.sol +++ b/src/interfaces/IVaultConfigurator.sol @@ -4,8 +4,6 @@ pragma solidity 0.8.25; import {IVault} from "src/interfaces/vault/IVault.sol"; interface IVaultConfigurator { - error InvalidSlashDuration(); - /** * @notice Initial parameters needed for a vault with a delegator and a slashher deployment. * @param version entity's version to use diff --git a/src/interfaces/slasher/IVetoSlasher.sol b/src/interfaces/slasher/IVetoSlasher.sol index 5f97ada2..70e4d402 100644 --- a/src/interfaces/slasher/IVetoSlasher.sol +++ b/src/interfaces/slasher/IVetoSlasher.sol @@ -96,12 +96,6 @@ interface IVetoSlasher { */ function SHARES_BASE() external view returns (uint256); - /** - * @notice Get a resolver shares setter's role. - * @return identifier of the resolver shares setter role - */ - function RESOLVER_SHARES_SET_ROLE() external view returns (bytes32); - /** * @notice Get the network registry's address. * @return address of the network registry diff --git a/src/interfaces/vault/IVaultStorage.sol b/src/interfaces/vault/IVaultStorage.sol index 2d00b4ed..4b7261a2 100644 --- a/src/interfaces/vault/IVaultStorage.sol +++ b/src/interfaces/vault/IVaultStorage.sol @@ -179,11 +179,12 @@ interface IVaultStorage { * @return if the checkpoint exists * @return timestamp time point of the checkpoint * @return amount of active shares at the checkpoint + * @return index of the checkpoint */ function activeSharesOfCheckpointAt( address account, uint48 timestamp - ) external view returns (bool, uint48, uint256); + ) external view returns (bool, uint48, uint256, uint256); /** * @notice Get an amount of active shares for a particular account. diff --git a/test/DelegatorFactory.t.sol b/test/DelegatorFactory.t.sol index 1af2f9ff..47d9db89 100644 --- a/test/DelegatorFactory.t.sol +++ b/test/DelegatorFactory.t.sol @@ -24,6 +24,7 @@ import {Token} from "./mocks/Token.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"; contract DelegatorFactoryTest is Test { @@ -179,10 +180,10 @@ contract DelegatorFactoryTest is Test { abi.encode( vault_, abi.encode( - INetworkRestakeDelegator.InitParams({ + IFullRestakeDelegator.InitParams({ baseParams: IBaseDelegator.BaseParams({defaultAdminRoleHolder: bob}), networkLimitSetRoleHolder: bob, - operatorNetworkSharesSetRoleHolder: bob + operatorNetworkLimitSetRoleHolder: bob }) ) ) diff --git a/test/SlasherFactory.t.sol b/test/SlasherFactory.t.sol index 3b3c7d48..bc86e50c 100644 --- a/test/SlasherFactory.t.sol +++ b/test/SlasherFactory.t.sol @@ -24,6 +24,7 @@ import {Token} from "./mocks/Token.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 {IVetoSlasher} from "src/interfaces/slasher/IVetoSlasher.sol"; diff --git a/test/VaultConfigurator.t.sol b/test/VaultConfigurator.t.sol index 72480d95..c3637880 100644 --- a/test/VaultConfigurator.t.sol +++ b/test/VaultConfigurator.t.sol @@ -24,6 +24,7 @@ import {Token} from "./mocks/Token.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"; contract VaultConfiguratorTest is Test { diff --git a/test/VaultFactory.t.sol b/test/VaultFactory.t.sol index 92a17885..bd5b277a 100644 --- a/test/VaultFactory.t.sol +++ b/test/VaultFactory.t.sol @@ -24,6 +24,7 @@ import {Token} from "./mocks/Token.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"; contract VaultFactoryTest is Test { diff --git a/test/delegator/FullRestakeDelegator.t.sol b/test/delegator/FullRestakeDelegator.t.sol index a204639c..0c587aa4 100644 --- a/test/delegator/FullRestakeDelegator.t.sol +++ b/test/delegator/FullRestakeDelegator.t.sol @@ -24,9 +24,11 @@ import {Token} from "test/mocks/Token.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 {IVaultStorage} from "src/interfaces/vault/IVaultStorage.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; contract FullRestakeDelegatorTest is Test { address owner; @@ -131,6 +133,597 @@ contract FullRestakeDelegatorTest is Test { new VaultConfigurator(address(vaultFactory), address(delegatorFactory), address(slasherFactory)); } + function test_Create(uint48 epochDuration) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + assertEq(delegator.VERSION(), 1); + assertEq(delegator.NETWORK_REGISTRY(), address(networkRegistry)); + assertEq(delegator.VAULT_FACTORY(), address(vaultFactory)); + assertEq(delegator.OPERATOR_VAULT_OPT_IN_SERVICE(), address(operatorVaultOptInService)); + assertEq(delegator.OPERATOR_NETWORK_OPT_IN_SERVICE(), address(operatorNetworkOptInService)); + assertEq(delegator.vault(), address(vault)); + assertEq(delegator.maxNetworkLimit(alice), 0); + assertEq(delegator.networkStakeIn(alice, 0), 0); + assertEq(delegator.networkStake(alice), 0); + assertEq(delegator.operatorNetworkStakeIn(alice, alice, 0), 0); + assertEq(delegator.operatorNetworkStake(alice, alice), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(alice, alice, 0), 0); + assertEq(delegator.NETWORK_LIMIT_SET_ROLE(), keccak256("NETWORK_LIMIT_SET_ROLE")); + assertEq(delegator.OPERATOR_NETWORK_LIMIT_SET_ROLE(), keccak256("OPERATOR_NETWORK_LIMIT_SET_ROLE")); + assertEq(delegator.networkLimitIn(alice, 0), 0); + assertEq(delegator.networkLimit(alice), 0); + assertEq(delegator.totalOperatorNetworkLimitIn(alice, 0), 0); + assertEq(delegator.totalOperatorNetworkLimit(alice), 0); + assertEq(delegator.operatorNetworkLimitIn(alice, alice, 0), 0); + assertEq(delegator.operatorNetworkLimit(alice, alice), 0); + } + + function test_CreateRevertNotVault(uint48 epochDuration) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + vm.expectRevert(IBaseDelegator.NotVault.selector); + delegatorFactory.create( + 1, + true, + abi.encode( + address(1), + abi.encode( + IFullRestakeDelegator.InitParams({ + baseParams: IBaseDelegator.BaseParams({defaultAdminRoleHolder: bob}), + networkLimitSetRoleHolder: bob, + operatorNetworkLimitSetRoleHolder: bob + }) + ) + ) + ); + } + + function test_CreateRevertMissingRoleHolders(uint48 epochDuration) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + vm.expectRevert(IFullRestakeDelegator.MissingRoleHolders.selector); + delegatorFactory.create( + 1, + true, + abi.encode( + address(vault), + abi.encode( + IFullRestakeDelegator.InitParams({ + baseParams: IBaseDelegator.BaseParams({defaultAdminRoleHolder: address(0)}), + networkLimitSetRoleHolder: address(0), + operatorNetworkLimitSetRoleHolder: bob + }) + ) + ) + ); + } + + function test_OnSlashRevertNotSlasher(uint48 epochDuration) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + vm.startPrank(alice); + vm.expectRevert(IBaseDelegator.NotSlasher.selector); + delegator.onSlash(address(0), address(0), 0); + vm.stopPrank(); + } + + function test_SetNetworkLimit( + uint48 epochDuration, + uint256 amount1, + uint256 amount2, + uint256 amount3, + uint256 amount4 + ) public { + epochDuration = uint48(bound(uint256(epochDuration), 1, 100 days)); + amount1 = bound(amount1, 1, type(uint256).max); + vm.assume(amount3 < amount2); + vm.assume(amount4 > amount2 && amount4 > amount1); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp; + + address network = bob; + _registerNetwork(network, bob); + + _setMaxNetworkLimit(network, type(uint256).max); + + _setNetworkLimit(alice, network, amount1); + + assertEq(delegator.networkLimitIn(network, 1), amount1); + assertEq(delegator.networkLimit(network), amount1); + + _setNetworkLimit(alice, network, amount2); + + if (amount1 >= amount2) { + assertEq(delegator.networkLimitIn(network, uint48(2 * vault.epochDuration() - 1)), amount1); + assertEq(delegator.networkLimit(network), amount1); + + blockTimestamp = vault.currentEpochStart() + vault.epochDuration(); + vm.warp(blockTimestamp); + + _setNetworkLimit(alice, network, amount1); + + assertEq(delegator.networkLimitIn(network, uint48(vault.epochDuration() - 1)), amount1); + assertEq(delegator.networkLimitIn(network, uint48(vault.epochDuration())), amount2); + assertEq(delegator.networkLimitIn(network, uint48(2 * vault.epochDuration())), amount1); + + blockTimestamp = vault.currentEpochStart() + vault.epochDuration() - 1; + vm.warp(blockTimestamp); + + assertEq(delegator.networkLimitIn(network, 1), amount2); + assertEq(delegator.networkLimit(network), amount1); + + _setNetworkLimit(alice, network, amount4); + + assertEq(delegator.networkLimitIn(network, 1), amount2); + assertEq(delegator.networkLimit(network), amount4); + + _setNetworkLimit(alice, network, amount2); + + assertEq(delegator.networkLimitIn(network, uint48(1)), amount2); + assertEq(delegator.networkLimitIn(network, uint48(vault.epochDuration() + 1)), amount2); + + blockTimestamp = blockTimestamp + 1; + vm.warp(blockTimestamp); + + _setNetworkLimit(alice, network, amount4); + + assertEq(delegator.networkLimitIn(network, uint48(vault.epochDuration())), amount2); + assertEq(delegator.networkLimit(network), amount4); + + blockTimestamp = blockTimestamp + vault.epochDuration(); + vm.warp(blockTimestamp); + + assertEq(delegator.networkLimitIn(network, 1), amount2); + assertEq(delegator.networkLimit(network), amount2); + } else { + assertEq(delegator.networkLimitIn(network, 1), amount2); + assertEq(delegator.networkLimit(network), amount2); + } + + _setNetworkLimit(alice, network, amount3); + + assertEq(delegator.networkLimitIn(network, uint48(2 * vault.epochDuration())), amount3); + assertEq(delegator.networkLimit(network), amount2); + + blockTimestamp = vault.currentEpochStart() + 2 * vault.epochDuration() - 1; + vm.warp(blockTimestamp); + + assertEq(delegator.networkLimitIn(network, 1), amount3); + assertEq(delegator.networkLimit(network), amount2); + + blockTimestamp = blockTimestamp + 1; + vm.warp(blockTimestamp); + + assertEq(delegator.networkLimitIn(network, 1), amount3); + assertEq(delegator.networkLimit(network), amount3); + + _setNetworkLimit(alice, network, amount2); + + assertEq(delegator.networkLimitIn(network, 1), amount2); + assertEq(delegator.networkLimit(network), amount2); + } + + function test_SetNetworkLimitRevertExceedsMaxNetworkLimit( + uint48 epochDuration, + uint256 amount1, + uint256 maxNetworkLimit + ) public { + epochDuration = uint48(bound(uint256(epochDuration), 1, 100 days)); + maxNetworkLimit = bound(maxNetworkLimit, 1, type(uint256).max); + vm.assume(amount1 > maxNetworkLimit); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + address network = bob; + _registerNetwork(network, bob); + + _setMaxNetworkLimit(network, maxNetworkLimit); + _optInNetworkVault(network); + + vm.expectRevert(IFullRestakeDelegator.ExceedsMaxNetworkLimit.selector); + _setNetworkLimit(alice, network, amount1); + } + + function test_SetOperatorNetworkLimit( + uint48 epochDuration, + uint256 amount1, + uint256 amount2, + uint256 amount3, + uint256 amount4 + ) public { + epochDuration = uint48(bound(uint256(epochDuration), 1, 100 days)); + amount1 = bound(amount1, 1, type(uint256).max); + vm.assume(amount3 < amount2); + vm.assume(amount4 > amount2 && amount4 > amount1); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp; + + address network = bob; + _registerNetwork(network, bob); + address operator = bob; + _registerOperator(operator); + + _setOperatorNetworkLimit(alice, network, operator, amount1); + + assertEq(delegator.operatorNetworkLimitIn(network, operator, 1), amount1); + assertEq(delegator.operatorNetworkLimit(network, operator), amount1); + assertEq(delegator.totalOperatorNetworkLimitIn(network, 1), amount1); + assertEq(delegator.totalOperatorNetworkLimit(network), amount1); + + _setOperatorNetworkLimit(alice, network, operator, amount2); + + if (amount1 >= amount2) { + assertEq( + delegator.operatorNetworkLimitIn(network, operator, uint48(2 * vault.epochDuration() - 1)), amount1 + ); + assertEq(delegator.operatorNetworkLimit(network, operator), amount1); + assertEq(delegator.totalOperatorNetworkLimitIn(network, uint48(2 * vault.epochDuration() - 1)), amount1); + assertEq(delegator.totalOperatorNetworkLimit(network), amount1); + + blockTimestamp = vault.currentEpochStart() + vault.epochDuration(); + vm.warp(blockTimestamp); + + _setOperatorNetworkLimit(alice, network, operator, amount1); + + assertEq(delegator.operatorNetworkLimitIn(network, operator, uint48(vault.epochDuration() - 1)), amount1); + assertEq(delegator.operatorNetworkLimitIn(network, operator, uint48(vault.epochDuration())), amount2); + assertEq(delegator.operatorNetworkLimitIn(network, operator, uint48(2 * vault.epochDuration())), amount1); + assertEq(delegator.totalOperatorNetworkLimitIn(network, uint48(vault.epochDuration() - 1)), amount1); + assertEq(delegator.totalOperatorNetworkLimitIn(network, uint48(vault.epochDuration())), amount2); + assertEq(delegator.totalOperatorNetworkLimitIn(network, uint48(2 * vault.epochDuration())), amount1); + + blockTimestamp = vault.currentEpochStart() + vault.epochDuration() - 1; + vm.warp(blockTimestamp); + + assertEq(delegator.operatorNetworkLimitIn(network, operator, 1), amount2); + assertEq(delegator.operatorNetworkLimit(network, operator), amount1); + assertEq(delegator.totalOperatorNetworkLimitIn(network, 1), amount2); + assertEq(delegator.totalOperatorNetworkLimit(network), amount1); + + _setOperatorNetworkLimit(alice, network, operator, amount4); + + assertEq(delegator.operatorNetworkLimitIn(network, operator, 1), amount2); + assertEq(delegator.operatorNetworkLimit(network, operator), amount4); + assertEq(delegator.totalOperatorNetworkLimitIn(network, 1), amount2); + assertEq(delegator.totalOperatorNetworkLimit(network), amount4); + + _setOperatorNetworkLimit(alice, network, operator, amount2); + + assertEq(delegator.operatorNetworkLimitIn(network, operator, uint48(1)), amount2); + assertEq(delegator.operatorNetworkLimitIn(network, operator, uint48(vault.epochDuration() + 1)), amount2); + assertEq(delegator.totalOperatorNetworkLimitIn(network, uint48(1)), amount2); + assertEq(delegator.totalOperatorNetworkLimitIn(network, uint48(vault.epochDuration() + 1)), amount2); + + blockTimestamp = blockTimestamp + 1; + vm.warp(blockTimestamp); + + _setOperatorNetworkLimit(alice, network, operator, amount4); + + assertEq(delegator.operatorNetworkLimitIn(network, operator, uint48(vault.epochDuration())), amount2); + assertEq(delegator.operatorNetworkLimit(network, operator), amount4); + assertEq(delegator.totalOperatorNetworkLimitIn(network, uint48(vault.epochDuration())), amount2); + assertEq(delegator.totalOperatorNetworkLimit(network), amount4); + + blockTimestamp = blockTimestamp + vault.epochDuration(); + vm.warp(blockTimestamp); + + assertEq(delegator.operatorNetworkLimitIn(network, operator, 1), amount2); + assertEq(delegator.operatorNetworkLimit(network, operator), amount2); + assertEq(delegator.totalOperatorNetworkLimitIn(network, 1), amount2); + assertEq(delegator.totalOperatorNetworkLimit(network), amount2); + } else { + assertEq(delegator.operatorNetworkLimitIn(network, operator, 1), amount2); + assertEq(delegator.operatorNetworkLimit(network, operator), amount2); + assertEq(delegator.totalOperatorNetworkLimitIn(network, 1), amount2); + assertEq(delegator.totalOperatorNetworkLimit(network), amount2); + } + + _setOperatorNetworkLimit(alice, network, operator, amount3); + + assertEq(delegator.operatorNetworkLimitIn(network, operator, uint48(2 * vault.epochDuration())), amount3); + assertEq(delegator.operatorNetworkLimit(network, operator), amount2); + assertEq(delegator.totalOperatorNetworkLimitIn(network, uint48(2 * vault.epochDuration())), amount3); + assertEq(delegator.totalOperatorNetworkLimit(network), amount2); + + blockTimestamp = vault.currentEpochStart() + 2 * vault.epochDuration() - 1; + vm.warp(blockTimestamp); + + assertEq(delegator.operatorNetworkLimitIn(network, operator, 1), amount3); + assertEq(delegator.operatorNetworkLimit(network, operator), amount2); + assertEq(delegator.totalOperatorNetworkLimitIn(network, 1), amount3); + assertEq(delegator.totalOperatorNetworkLimit(network), amount2); + + blockTimestamp = blockTimestamp + 1; + vm.warp(blockTimestamp); + + assertEq(delegator.operatorNetworkLimitIn(network, operator, 1), amount3); + assertEq(delegator.operatorNetworkLimit(network, operator), amount3); + assertEq(delegator.totalOperatorNetworkLimitIn(network, 1), amount3); + assertEq(delegator.totalOperatorNetworkLimit(network), amount3); + + _setOperatorNetworkLimit(alice, network, operator, amount2); + + assertEq(delegator.operatorNetworkLimitIn(network, operator, 1), amount2); + assertEq(delegator.operatorNetworkLimit(network, operator), amount2); + assertEq(delegator.totalOperatorNetworkLimitIn(network, 1), amount2); + assertEq(delegator.totalOperatorNetworkLimit(network), amount2); + } + + function test_SetOperatorNetworkLimitBoth( + uint48 epochDuration, + uint256 amount1, + uint256 amount2, + uint256 amount3 + ) public { + epochDuration = uint48(bound(uint256(epochDuration), 1, 100 days)); + amount1 = bound(amount1, 1, type(uint256).max / 2); + amount2 = bound(amount2, 1, type(uint256).max / 2); + vm.assume(amount3 < amount2); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + address network = bob; + _registerNetwork(network, bob); + _registerOperator(alice); + _registerOperator(bob); + + _setOperatorNetworkLimit(alice, network, alice, amount1); + + assertEq(delegator.operatorNetworkLimitIn(network, alice, 1), amount1); + assertEq(delegator.operatorNetworkLimit(network, alice), amount1); + assertEq(delegator.totalOperatorNetworkLimitIn(network, 1), amount1); + assertEq(delegator.totalOperatorNetworkLimit(network), amount1); + + _setOperatorNetworkLimit(alice, network, bob, amount2); + + assertEq(delegator.operatorNetworkLimitIn(network, bob, 1), amount2); + assertEq(delegator.operatorNetworkLimit(network, bob), amount2); + assertEq(delegator.totalOperatorNetworkLimitIn(network, 1), amount1 + amount2); + assertEq(delegator.totalOperatorNetworkLimit(network), amount1 + amount2); + + _setOperatorNetworkLimit(alice, network, bob, amount3); + + assertEq(delegator.operatorNetworkLimitIn(network, bob, uint48(2 * vault.epochDuration())), amount3); + assertEq(delegator.operatorNetworkLimit(network, bob), amount2); + assertEq(delegator.totalOperatorNetworkLimitIn(network, uint48(2 * vault.epochDuration())), amount1 + amount3); + assertEq(delegator.totalOperatorNetworkLimit(network), amount1 + amount2); + } + + function test_SetMaxNetworkLimit( + uint48 epochDuration, + uint256 maxNetworkLimit1, + uint256 maxNetworkLimit2, + uint256 networkLimit1 + ) public { + epochDuration = uint48(bound(epochDuration, 1, 100 days)); + maxNetworkLimit1 = bound(maxNetworkLimit1, 1, type(uint256).max); + vm.assume(maxNetworkLimit1 > maxNetworkLimit2); + vm.assume(maxNetworkLimit1 >= networkLimit1 && networkLimit1 >= maxNetworkLimit2); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp; + + address network = alice; + _registerNetwork(network, alice); + + _setMaxNetworkLimit(network, maxNetworkLimit1); + + assertEq(delegator.maxNetworkLimit(network), maxNetworkLimit1); + + _setNetworkLimit(alice, network, networkLimit1); + + assertEq(delegator.networkLimitIn(network, 2 * vault.epochDuration()), networkLimit1); + + blockTimestamp = vault.currentEpochStart() + vault.epochDuration(); + vm.warp(blockTimestamp); + + _setNetworkLimit(alice, network, networkLimit1); + + assertEq(delegator.networkLimitIn(network, vault.epochDuration()), networkLimit1); + assertEq(delegator.networkLimitIn(network, 2 * vault.epochDuration()), networkLimit1); + + _setMaxNetworkLimit(network, maxNetworkLimit2); + + assertEq(delegator.maxNetworkLimit(network), maxNetworkLimit2); + assertEq(delegator.networkLimitIn(network, vault.epochDuration()), maxNetworkLimit2); + assertEq(delegator.networkLimitIn(network, 2 * vault.epochDuration()), maxNetworkLimit2); + } + + function test_SetMaxNetworkLimitRevertNotNetwork(uint48 epochDuration, uint256 maxNetworkLimit) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + maxNetworkLimit = bound(maxNetworkLimit, 1, type(uint256).max); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + _registerNetwork(alice, alice); + + vm.expectRevert(IBaseDelegator.NotNetwork.selector); + _setMaxNetworkLimit(bob, maxNetworkLimit); + } + + function test_SetMaxNetworkLimitRevertAlreadySet(uint48 epochDuration, uint256 maxNetworkLimit) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + maxNetworkLimit = bound(maxNetworkLimit, 1, type(uint256).max); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + _registerNetwork(alice, alice); + + _setMaxNetworkLimit(alice, maxNetworkLimit); + + vm.expectRevert(IBaseDelegator.AlreadySet.selector); + _setMaxNetworkLimit(alice, maxNetworkLimit); + } + + function test_Stakes( + uint48 epochDuration, + uint256 depositAmount, + uint256 networkLimit, + uint256 operatorNetworkLimit1, + uint256 operatorNetworkLimit2, + uint256 operatorNetworkLimit3 + ) public { + epochDuration = uint48(bound(epochDuration, 1, 10 days)); + depositAmount = bound(depositAmount, 1, 100 * 10 ** 18); + networkLimit = bound(networkLimit, 1, type(uint256).max); + operatorNetworkLimit1 = bound(operatorNetworkLimit1, 1, type(uint256).max / 2); + operatorNetworkLimit2 = bound(operatorNetworkLimit2, 1, type(uint256).max / 2); + vm.assume(operatorNetworkLimit3 < operatorNetworkLimit2); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + address network = alice; + _registerNetwork(network, alice); + _setMaxNetworkLimit(network, type(uint256).max); + + _registerOperator(alice); + _registerOperator(bob); + + assertEq(delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), 0); + + _optInOperatorVault(alice); + _optInOperatorVault(bob); + + assertEq(delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), 0); + + _optInOperatorNetwork(alice, address(network)); + _optInOperatorNetwork(bob, address(network)); + + assertEq(delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), 0); + + _deposit(alice, depositAmount); + + assertEq(delegator.networkStakeIn(network, 1), 0); + assertEq(delegator.networkStake(network), 0); + assertEq(delegator.operatorNetworkStakeIn(network, alice, 1), 0); + assertEq(delegator.operatorNetworkStake(network, alice), 0); + assertEq(delegator.operatorNetworkStakeIn(network, bob, 1), 0); + assertEq(delegator.operatorNetworkStake(network, bob), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), 0); + + _setNetworkLimit(alice, network, networkLimit); + + assertEq(delegator.networkStakeIn(network, 1), 0); + assertEq(delegator.networkStake(network), 0); + assertEq(delegator.operatorNetworkStakeIn(network, alice, 1), 0); + assertEq(delegator.operatorNetworkStake(network, alice), 0); + assertEq(delegator.operatorNetworkStakeIn(network, bob, 1), 0); + assertEq(delegator.operatorNetworkStake(network, bob), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), 0); + + _setOperatorNetworkLimit(alice, network, alice, operatorNetworkLimit1); + + assertEq( + delegator.networkStakeIn(network, 1), Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit1)) + ); + assertEq( + delegator.networkStake(network), Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit1)) + ); + assertEq( + delegator.operatorNetworkStakeIn(network, alice, 1), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit1)) + ); + assertEq( + delegator.operatorNetworkStake(network, alice), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit1)) + ); + assertEq(delegator.operatorNetworkStakeIn(network, bob, 1), 0); + assertEq(delegator.operatorNetworkStake(network, bob), 0); + assertEq( + delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit1)) + ); + assertEq(delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), 0); + + _setOperatorNetworkLimit(alice, network, bob, operatorNetworkLimit2); + + assertEq( + delegator.networkStakeIn(network, 1), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit1 + operatorNetworkLimit2)) + ); + assertEq( + delegator.networkStake(network), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit1 + operatorNetworkLimit2)) + ); + assertEq( + delegator.operatorNetworkStakeIn(network, alice, 1), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit1)) + ); + assertEq( + delegator.operatorNetworkStake(network, alice), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit1)) + ); + assertEq( + delegator.operatorNetworkStakeIn(network, bob, 1), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit2)) + ); + assertEq( + delegator.operatorNetworkStake(network, bob), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit2)) + ); + assertEq( + delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit1)) + ); + assertEq( + delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit2)) + ); + + _setOperatorNetworkLimit(alice, network, bob, operatorNetworkLimit3); + + assertEq( + delegator.networkStakeIn(network, uint48(2 * vault.epochDuration())), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit1 + operatorNetworkLimit3)) + ); + assertEq( + delegator.networkStake(network), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit1 + operatorNetworkLimit2)) + ); + assertEq( + delegator.operatorNetworkStakeIn(network, alice, uint48(2 * vault.epochDuration())), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit1)) + ); + assertEq( + delegator.operatorNetworkStake(network, alice), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit1)) + ); + assertEq( + delegator.operatorNetworkStakeIn(network, bob, uint48(2 * vault.epochDuration())), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit3)) + ); + assertEq( + delegator.operatorNetworkStake(network, bob), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit2)) + ); + assertEq( + delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), + Math.min(depositAmount, Math.min(networkLimit, operatorNetworkLimit1)) + ); + assertEq( + delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), + Math.min(depositAmount, Math.min(networkLimit, Math.min(operatorNetworkLimit2, operatorNetworkLimit3))) + ); + } + function _getVaultAndDelegator(uint48 epochDuration) internal returns (Vault, FullRestakeDelegator) { (address vault_, address delegator_,) = vaultConfigurator.create( IVaultConfigurator.InitParams({ @@ -148,7 +741,7 @@ contract FullRestakeDelegatorTest is Test { slasherSetRoleHolder: alice, depositorWhitelistRoleHolder: alice }), - delegatorIndex: 0, + delegatorIndex: 1, delegatorParams: abi.encode( INetworkRestakeDelegator.InitParams({ baseParams: IBaseDelegator.BaseParams({defaultAdminRoleHolder: alice}), @@ -285,4 +878,10 @@ contract FullRestakeDelegatorTest is Test { slasher.slash(network, operator, amount); vm.stopPrank(); } + + function _setMaxNetworkLimit(address user, uint256 amount) internal { + vm.startPrank(user); + delegator.setMaxNetworkLimit(amount); + vm.stopPrank(); + } } diff --git a/test/delegator/NetworkRestakeDelegator.t.sol b/test/delegator/NetworkRestakeDelegator.t.sol index ce3c3f5f..a8661b89 100644 --- a/test/delegator/NetworkRestakeDelegator.t.sol +++ b/test/delegator/NetworkRestakeDelegator.t.sol @@ -24,11 +24,15 @@ import {Token} from "test/mocks/Token.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 {IVaultStorage} from "src/interfaces/vault/IVaultStorage.sol"; +import {Math} from "@openzeppelin/contracts/utils/math/Math.sol"; contract NetworkRestakeDelegatorTest is Test { + using Math for uint256; + address owner; address alice; uint256 alicePrivateKey; @@ -131,6 +135,532 @@ contract NetworkRestakeDelegatorTest is Test { new VaultConfigurator(address(vaultFactory), address(delegatorFactory), address(slasherFactory)); } + function test_Create(uint48 epochDuration) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + assertEq(delegator.VERSION(), 1); + assertEq(delegator.NETWORK_REGISTRY(), address(networkRegistry)); + assertEq(delegator.VAULT_FACTORY(), address(vaultFactory)); + assertEq(delegator.OPERATOR_VAULT_OPT_IN_SERVICE(), address(operatorVaultOptInService)); + assertEq(delegator.OPERATOR_NETWORK_OPT_IN_SERVICE(), address(operatorNetworkOptInService)); + assertEq(delegator.vault(), address(vault)); + assertEq(delegator.maxNetworkLimit(alice), 0); + assertEq(delegator.networkStakeIn(alice, 0), 0); + assertEq(delegator.networkStake(alice), 0); + assertEq(delegator.operatorNetworkStakeIn(alice, alice, 0), 0); + assertEq(delegator.operatorNetworkStake(alice, alice), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(alice, alice, 0), 0); + assertEq(delegator.NETWORK_LIMIT_SET_ROLE(), keccak256("NETWORK_LIMIT_SET_ROLE")); + assertEq(delegator.OPERATOR_NETWORK_SHARES_SET_ROLE(), keccak256("OPERATOR_NETWORK_SHARES_SET_ROLE")); + assertEq(delegator.networkLimitIn(alice, 0), 0); + assertEq(delegator.networkLimit(alice), 0); + assertEq(delegator.totalOperatorNetworkSharesIn(alice, 0), 0); + assertEq(delegator.totalOperatorNetworkShares(alice), 0); + assertEq(delegator.operatorNetworkSharesIn(alice, alice, 0), 0); + assertEq(delegator.operatorNetworkShares(alice, alice), 0); + } + + function test_CreateRevertNotVault(uint48 epochDuration) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + vm.expectRevert(IBaseDelegator.NotVault.selector); + delegatorFactory.create( + 0, + true, + abi.encode( + address(1), + abi.encode( + INetworkRestakeDelegator.InitParams({ + baseParams: IBaseDelegator.BaseParams({defaultAdminRoleHolder: bob}), + networkLimitSetRoleHolder: bob, + operatorNetworkSharesSetRoleHolder: bob + }) + ) + ) + ); + } + + function test_CreateRevertMissingRoleHolders(uint48 epochDuration) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + vm.expectRevert(INetworkRestakeDelegator.MissingRoleHolders.selector); + delegatorFactory.create( + 1, + true, + abi.encode( + address(vault), + abi.encode( + INetworkRestakeDelegator.InitParams({ + baseParams: IBaseDelegator.BaseParams({defaultAdminRoleHolder: address(0)}), + networkLimitSetRoleHolder: address(0), + operatorNetworkSharesSetRoleHolder: bob + }) + ) + ) + ); + } + + function test_OnSlashRevertNotSlasher(uint48 epochDuration) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + vm.startPrank(alice); + vm.expectRevert(IBaseDelegator.NotSlasher.selector); + delegator.onSlash(address(0), address(0), 0); + vm.stopPrank(); + } + + function test_SetNetworkLimit( + uint48 epochDuration, + uint256 amount1, + uint256 amount2, + uint256 amount3, + uint256 amount4 + ) public { + epochDuration = uint48(bound(uint256(epochDuration), 1, 100 days)); + amount1 = bound(amount1, 1, type(uint256).max); + vm.assume(amount3 < amount2); + vm.assume(amount4 > amount2 && amount4 > amount1); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp; + + address network = bob; + _registerNetwork(network, bob); + + _setMaxNetworkLimit(network, type(uint256).max); + + _setNetworkLimit(alice, network, amount1); + + assertEq(delegator.networkLimitIn(network, 1), amount1); + assertEq(delegator.networkLimit(network), amount1); + + _setNetworkLimit(alice, network, amount2); + + if (amount1 >= amount2) { + assertEq(delegator.networkLimitIn(network, uint48(2 * vault.epochDuration() - 1)), amount1); + assertEq(delegator.networkLimit(network), amount1); + + blockTimestamp = vault.currentEpochStart() + vault.epochDuration(); + vm.warp(blockTimestamp); + + _setNetworkLimit(alice, network, amount1); + + assertEq(delegator.networkLimitIn(network, uint48(vault.epochDuration() - 1)), amount1); + assertEq(delegator.networkLimitIn(network, uint48(vault.epochDuration())), amount2); + assertEq(delegator.networkLimitIn(network, uint48(2 * vault.epochDuration())), amount1); + + blockTimestamp = vault.currentEpochStart() + vault.epochDuration() - 1; + vm.warp(blockTimestamp); + + assertEq(delegator.networkLimitIn(network, 1), amount2); + assertEq(delegator.networkLimit(network), amount1); + + _setNetworkLimit(alice, network, amount4); + + assertEq(delegator.networkLimitIn(network, 1), amount2); + assertEq(delegator.networkLimit(network), amount4); + + _setNetworkLimit(alice, network, amount2); + + assertEq(delegator.networkLimitIn(network, uint48(1)), amount2); + assertEq(delegator.networkLimitIn(network, uint48(vault.epochDuration() + 1)), amount2); + + blockTimestamp = blockTimestamp + 1; + vm.warp(blockTimestamp); + + _setNetworkLimit(alice, network, amount4); + + assertEq(delegator.networkLimitIn(network, uint48(vault.epochDuration())), amount2); + assertEq(delegator.networkLimit(network), amount4); + + blockTimestamp = blockTimestamp + vault.epochDuration(); + vm.warp(blockTimestamp); + + assertEq(delegator.networkLimitIn(network, 1), amount2); + assertEq(delegator.networkLimit(network), amount2); + } else { + assertEq(delegator.networkLimitIn(network, 1), amount2); + assertEq(delegator.networkLimit(network), amount2); + } + + _setNetworkLimit(alice, network, amount3); + + assertEq(delegator.networkLimitIn(network, uint48(2 * vault.epochDuration())), amount3); + assertEq(delegator.networkLimit(network), amount2); + + blockTimestamp = vault.currentEpochStart() + 2 * vault.epochDuration() - 1; + vm.warp(blockTimestamp); + + assertEq(delegator.networkLimitIn(network, 1), amount3); + assertEq(delegator.networkLimit(network), amount2); + + blockTimestamp = blockTimestamp + 1; + vm.warp(blockTimestamp); + + assertEq(delegator.networkLimitIn(network, 1), amount3); + assertEq(delegator.networkLimit(network), amount3); + + _setNetworkLimit(alice, network, amount2); + + assertEq(delegator.networkLimitIn(network, 1), amount2); + assertEq(delegator.networkLimit(network), amount2); + } + + function test_SetNetworkLimitRevertExceedsMaxNetworkLimit( + uint48 epochDuration, + uint256 amount1, + uint256 maxNetworkLimit + ) public { + epochDuration = uint48(bound(uint256(epochDuration), 1, 100 days)); + maxNetworkLimit = bound(maxNetworkLimit, 1, type(uint256).max); + vm.assume(amount1 > maxNetworkLimit); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + address network = bob; + _registerNetwork(network, bob); + + _setMaxNetworkLimit(network, maxNetworkLimit); + _optInNetworkVault(network); + + vm.expectRevert(INetworkRestakeDelegator.ExceedsMaxNetworkLimit.selector); + _setNetworkLimit(alice, network, amount1); + } + + function test_SetOperatorNetworkSharesBoth( + uint48 epochDuration, + uint256 amount1, + uint256 amount2, + uint256 amount3 + ) public { + epochDuration = uint48(bound(uint256(epochDuration), 1, 100 days)); + amount1 = bound(amount1, 1, type(uint256).max / 2); + amount2 = bound(amount2, 1, type(uint256).max / 2); + amount3 = bound(amount3, 1, type(uint256).max / 2); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp; + + address network = bob; + _registerNetwork(network, bob); + _registerOperator(alice); + _registerOperator(bob); + + _setOperatorNetworkShares(alice, network, alice, amount1); + + assertEq(delegator.operatorNetworkSharesIn(network, alice, uint48(2 * vault.epochDuration())), amount1); + assertEq(delegator.operatorNetworkShares(network, alice), 0); + assertEq(delegator.totalOperatorNetworkSharesIn(network, uint48(2 * vault.epochDuration())), amount1); + assertEq(delegator.totalOperatorNetworkShares(network), 0); + + _setOperatorNetworkShares(alice, network, bob, amount2); + + assertEq(delegator.operatorNetworkSharesIn(network, bob, uint48(2 * vault.epochDuration())), amount2); + assertEq(delegator.operatorNetworkShares(network, bob), 0); + assertEq(delegator.totalOperatorNetworkSharesIn(network, uint48(2 * vault.epochDuration())), amount1 + amount2); + assertEq(delegator.totalOperatorNetworkShares(network), 0); + + blockTimestamp = blockTimestamp + vault.epochDuration(); + vm.warp(blockTimestamp); + + assertEq(delegator.operatorNetworkSharesIn(network, alice, uint48(vault.epochDuration())), amount1); + assertEq(delegator.operatorNetworkShares(network, alice), 0); + assertEq(delegator.operatorNetworkSharesIn(network, bob, uint48(vault.epochDuration())), amount2); + assertEq(delegator.operatorNetworkShares(network, bob), 0); + assertEq(delegator.totalOperatorNetworkSharesIn(network, uint48(vault.epochDuration())), amount1 + amount2); + assertEq(delegator.totalOperatorNetworkShares(network), 0); + + _setOperatorNetworkShares(alice, network, bob, amount3); + + assertEq(delegator.operatorNetworkSharesIn(network, bob, uint48(2 * vault.epochDuration())), amount3); + assertEq(delegator.operatorNetworkSharesIn(network, bob, uint48(vault.epochDuration())), amount2); + assertEq(delegator.operatorNetworkShares(network, bob), 0); + assertEq(delegator.totalOperatorNetworkSharesIn(network, uint48(2 * vault.epochDuration())), amount1 + amount3); + assertEq(delegator.totalOperatorNetworkSharesIn(network, uint48(vault.epochDuration())), amount1 + amount2); + assertEq(delegator.totalOperatorNetworkShares(network), 0); + + blockTimestamp = blockTimestamp + vault.epochDuration(); + vm.warp(blockTimestamp); + + assertEq(delegator.operatorNetworkSharesIn(network, bob, uint48(2 * vault.epochDuration())), amount3); + assertEq(delegator.operatorNetworkSharesIn(network, bob, uint48(vault.epochDuration())), amount3); + assertEq(delegator.operatorNetworkShares(network, bob), amount2); + assertEq(delegator.totalOperatorNetworkSharesIn(network, uint48(2 * vault.epochDuration())), amount1 + amount3); + assertEq(delegator.totalOperatorNetworkSharesIn(network, uint48(vault.epochDuration())), amount1 + amount3); + assertEq(delegator.totalOperatorNetworkShares(network), amount1 + amount2); + + blockTimestamp = blockTimestamp + vault.epochDuration(); + vm.warp(blockTimestamp); + + assertEq(delegator.operatorNetworkSharesIn(network, bob, uint48(2 * vault.epochDuration())), amount3); + assertEq(delegator.operatorNetworkSharesIn(network, bob, uint48(vault.epochDuration())), amount3); + assertEq(delegator.operatorNetworkShares(network, bob), amount3); + assertEq(delegator.totalOperatorNetworkSharesIn(network, uint48(2 * vault.epochDuration())), amount1 + amount3); + assertEq(delegator.totalOperatorNetworkSharesIn(network, uint48(vault.epochDuration())), amount1 + amount3); + assertEq(delegator.totalOperatorNetworkShares(network), amount1 + amount3); + } + + function test_SetMaxNetworkLimit( + uint48 epochDuration, + uint256 maxNetworkLimit1, + uint256 maxNetworkLimit2, + uint256 networkLimit1 + ) public { + epochDuration = uint48(bound(epochDuration, 1, 100 days)); + maxNetworkLimit1 = bound(maxNetworkLimit1, 1, type(uint256).max); + vm.assume(maxNetworkLimit1 > maxNetworkLimit2); + vm.assume(maxNetworkLimit1 >= networkLimit1 && networkLimit1 >= maxNetworkLimit2); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp; + + address network = alice; + _registerNetwork(network, alice); + + _setMaxNetworkLimit(network, maxNetworkLimit1); + + assertEq(delegator.maxNetworkLimit(network), maxNetworkLimit1); + + _setNetworkLimit(alice, network, networkLimit1); + + assertEq(delegator.networkLimitIn(network, 2 * vault.epochDuration()), networkLimit1); + + blockTimestamp = vault.currentEpochStart() + vault.epochDuration(); + vm.warp(blockTimestamp); + + _setNetworkLimit(alice, network, networkLimit1); + + assertEq(delegator.networkLimitIn(network, vault.epochDuration()), networkLimit1); + assertEq(delegator.networkLimitIn(network, 2 * vault.epochDuration()), networkLimit1); + + _setMaxNetworkLimit(network, maxNetworkLimit2); + + assertEq(delegator.maxNetworkLimit(network), maxNetworkLimit2); + assertEq(delegator.networkLimitIn(network, vault.epochDuration()), maxNetworkLimit2); + assertEq(delegator.networkLimitIn(network, 2 * vault.epochDuration()), maxNetworkLimit2); + } + + function test_SetMaxNetworkLimitRevertNotNetwork(uint48 epochDuration, uint256 maxNetworkLimit) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + maxNetworkLimit = bound(maxNetworkLimit, 1, type(uint256).max); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + _registerNetwork(alice, alice); + + vm.expectRevert(IBaseDelegator.NotNetwork.selector); + _setMaxNetworkLimit(bob, maxNetworkLimit); + } + + function test_SetMaxNetworkLimitRevertAlreadySet(uint48 epochDuration, uint256 maxNetworkLimit) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + maxNetworkLimit = bound(maxNetworkLimit, 1, type(uint256).max); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + _registerNetwork(alice, alice); + + _setMaxNetworkLimit(alice, maxNetworkLimit); + + vm.expectRevert(IBaseDelegator.AlreadySet.selector); + _setMaxNetworkLimit(alice, maxNetworkLimit); + } + + function test_Stakes( + uint48 epochDuration, + uint256 depositAmount, + uint256 networkLimit, + uint256 operatorNetworkShares1, + uint256 operatorNetworkShares2, + uint256 operatorNetworkShares3 + ) public { + epochDuration = uint48(bound(epochDuration, 1, 10 days)); + depositAmount = bound(depositAmount, 1, 100 * 10 ** 18); + networkLimit = bound(networkLimit, 1, type(uint256).max); + operatorNetworkShares1 = bound(operatorNetworkShares1, 1, type(uint256).max / 2); + operatorNetworkShares2 = bound(operatorNetworkShares2, 1, type(uint256).max / 2); + operatorNetworkShares3 = bound(operatorNetworkShares2, 0, type(uint256).max / 2); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp; + + address network = alice; + _registerNetwork(network, alice); + _setMaxNetworkLimit(network, type(uint256).max); + + _registerOperator(alice); + _registerOperator(bob); + + assertEq(delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), 0); + + _optInOperatorVault(alice); + _optInOperatorVault(bob); + + assertEq(delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), 0); + + _optInOperatorNetwork(alice, address(network)); + _optInOperatorNetwork(bob, address(network)); + + assertEq(delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), 0); + + _deposit(alice, depositAmount); + + assertEq(delegator.networkStakeIn(network, 1), 0); + assertEq(delegator.networkStake(network), 0); + assertEq(delegator.operatorNetworkStakeIn(network, alice, 1), 0); + assertEq(delegator.operatorNetworkStake(network, alice), 0); + assertEq(delegator.operatorNetworkStakeIn(network, bob, 1), 0); + assertEq(delegator.operatorNetworkStake(network, bob), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), 0); + + _setNetworkLimit(alice, network, networkLimit); + + assertEq(delegator.networkStakeIn(network, 1), 0); + assertEq(delegator.networkStake(network), 0); + assertEq(delegator.operatorNetworkStakeIn(network, alice, 1), 0); + assertEq(delegator.operatorNetworkStake(network, alice), 0); + assertEq(delegator.operatorNetworkStakeIn(network, bob, 1), 0); + assertEq(delegator.operatorNetworkStake(network, bob), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), 0); + assertEq(delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), 0); + + _setOperatorNetworkShares(alice, network, alice, operatorNetworkShares1); + + blockTimestamp = blockTimestamp + 2 * vault.epochDuration(); + vm.warp(blockTimestamp); + + assertEq(delegator.networkStakeIn(network, 1), Math.min(depositAmount, networkLimit)); + assertEq(delegator.networkStake(network), Math.min(depositAmount, networkLimit)); + assertEq( + delegator.operatorNetworkStakeIn(network, alice, 1), + operatorNetworkShares1.mulDiv(Math.min(depositAmount, networkLimit), operatorNetworkShares1) + ); + assertEq( + delegator.operatorNetworkStake(network, alice), + operatorNetworkShares1.mulDiv(Math.min(depositAmount, networkLimit), operatorNetworkShares1) + ); + assertEq(delegator.operatorNetworkStakeIn(network, bob, 1), 0); + assertEq(delegator.operatorNetworkStake(network, bob), 0); + assertEq( + delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), + operatorNetworkShares1.mulDiv(Math.min(depositAmount, networkLimit), operatorNetworkShares1) + ); + assertEq(delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), 0); + + _setOperatorNetworkShares(alice, network, bob, operatorNetworkShares2); + + blockTimestamp = blockTimestamp + 2 * vault.epochDuration(); + vm.warp(blockTimestamp); + + assertEq(delegator.networkStakeIn(network, 1), Math.min(depositAmount, networkLimit)); + assertEq(delegator.networkStake(network), Math.min(depositAmount, networkLimit)); + assertEq( + delegator.operatorNetworkStakeIn(network, alice, 1), + operatorNetworkShares1.mulDiv( + Math.min(depositAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares2 + ) + ); + assertEq( + delegator.operatorNetworkStake(network, alice), + operatorNetworkShares1.mulDiv( + Math.min(depositAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares2 + ) + ); + assertEq( + delegator.operatorNetworkStakeIn(network, bob, 1), + operatorNetworkShares2.mulDiv( + Math.min(depositAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares2 + ) + ); + assertEq( + delegator.operatorNetworkStake(network, bob), + operatorNetworkShares2.mulDiv( + Math.min(depositAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares2 + ) + ); + assertEq( + delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), + operatorNetworkShares1.mulDiv( + Math.min(depositAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares2 + ) + ); + assertEq( + delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), + operatorNetworkShares2.mulDiv( + Math.min(depositAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares2 + ) + ); + + _setOperatorNetworkShares(alice, network, bob, operatorNetworkShares3); + + assertEq( + delegator.networkStakeIn(network, uint48(2 * vault.epochDuration())), Math.min(depositAmount, networkLimit) + ); + assertEq(delegator.networkStake(network), Math.min(depositAmount, networkLimit)); + assertEq( + delegator.operatorNetworkStakeIn(network, alice, uint48(2 * vault.epochDuration())), + operatorNetworkShares1.mulDiv( + Math.min(depositAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares3 + ) + ); + assertEq( + delegator.operatorNetworkStake(network, alice), + operatorNetworkShares1.mulDiv( + Math.min(depositAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares3 + ) + ); + assertEq( + delegator.operatorNetworkStakeIn(network, bob, uint48(2 * vault.epochDuration())), + operatorNetworkShares3.mulDiv( + Math.min(depositAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares3 + ) + ); + assertEq( + delegator.operatorNetworkStake(network, bob), + operatorNetworkShares3.mulDiv( + Math.min(depositAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares3 + ) + ); + assertEq( + delegator.minOperatorNetworkStakeDuring(network, alice, 100 days), + Math.min( + operatorNetworkShares1.mulDiv( + Math.min(depositAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares2 + ), + operatorNetworkShares1.mulDiv( + Math.min(depositAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares3 + ) + ) + ); + assertEq( + delegator.minOperatorNetworkStakeDuring(network, bob, 100 days), + Math.min( + operatorNetworkShares2.mulDiv( + Math.min(depositAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares2 + ), + operatorNetworkShares3.mulDiv( + Math.min(depositAmount, networkLimit), operatorNetworkShares1 + operatorNetworkShares3 + ) + ) + ); + } + function _getVaultAndDelegator(uint48 epochDuration) internal returns (Vault, NetworkRestakeDelegator) { (address vault_, address delegator_,) = vaultConfigurator.create( IVaultConfigurator.InitParams({ @@ -285,4 +815,10 @@ contract NetworkRestakeDelegatorTest is Test { slasher.slash(network, operator, amount); vm.stopPrank(); } + + function _setMaxNetworkLimit(address user, uint256 amount) internal { + vm.startPrank(user); + delegator.setMaxNetworkLimit(amount); + vm.stopPrank(); + } } diff --git a/test/slasher/Slasher.t.sol b/test/slasher/Slasher.t.sol index 3325a123..e626603a 100644 --- a/test/slasher/Slasher.t.sol +++ b/test/slasher/Slasher.t.sol @@ -24,9 +24,11 @@ import {Token} from "test/mocks/Token.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 {IVaultStorage} from "src/interfaces/vault/IVaultStorage.sol"; +import {IBaseSlasher} from "src/interfaces/slasher/IBaseSlasher.sol"; contract SlasherTest is Test { address owner; @@ -131,6 +133,30 @@ contract SlasherTest is Test { new VaultConfigurator(address(vaultFactory), address(delegatorFactory), address(slasherFactory)); } + function test_Create(uint48 epochDuration) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + slasher = _getSlasher(address(vault)); + + assertEq(slasher.VAULT_FACTORY(), address(vaultFactory)); + assertEq(slasher.NETWORK_MIDDLEWARE_SERVICE(), address(networkMiddlewareService)); + assertEq(slasher.NETWORK_VAULT_OPT_IN_SERVICE(), address(networkVaultOptInService)); + assertEq(slasher.OPERATOR_VAULT_OPT_IN_SERVICE(), address(operatorVaultOptInService)); + assertEq(slasher.OPERATOR_NETWORK_OPT_IN_SERVICE(), address(operatorNetworkOptInService)); + assertEq(slasher.vault(), address(vault)); + } + + function test_CreateRevertNotVault(uint48 epochDuration) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + + (vault,) = _getVaultAndDelegator(epochDuration); + + vm.expectRevert(IBaseSlasher.NotVault.selector); + slasherFactory.create(0, true, abi.encode(address(1), "")); + } + function _getVaultAndDelegator(uint48 epochDuration) internal returns (Vault, FullRestakeDelegator) { (address vault_, address delegator_,) = vaultConfigurator.create( IVaultConfigurator.InitParams({ diff --git a/test/slasher/VetoSlasher.t.sol b/test/slasher/VetoSlasher.t.sol index 56e2a005..2f5a1ea0 100644 --- a/test/slasher/VetoSlasher.t.sol +++ b/test/slasher/VetoSlasher.t.sol @@ -24,10 +24,12 @@ import {Token} from "test/mocks/Token.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 {IVaultStorage} from "src/interfaces/vault/IVaultStorage.sol"; import {IVetoSlasher} from "src/interfaces/slasher/IVetoSlasher.sol"; +import {IBaseSlasher} from "src/interfaces/slasher/IBaseSlasher.sol"; contract VetoSlasherTest is Test { address owner; @@ -132,6 +134,157 @@ contract VetoSlasherTest is Test { new VaultConfigurator(address(vaultFactory), address(delegatorFactory), address(slasherFactory)); } + function test_Create(uint48 epochDuration, uint48 vetoDuration, uint48 executeDuration) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + vetoDuration = uint48(bound(vetoDuration, 0, type(uint48).max / 2)); + executeDuration = uint48(bound(executeDuration, 1, type(uint48).max / 2)); + vm.assume(vetoDuration + executeDuration <= epochDuration); + + (vault, delegator) = _getVaultAndDelegator(epochDuration); + + slasher = _getSlasher(address(vault), vetoDuration, executeDuration); + + assertEq(slasher.VAULT_FACTORY(), address(vaultFactory)); + assertEq(slasher.NETWORK_MIDDLEWARE_SERVICE(), address(networkMiddlewareService)); + assertEq(slasher.NETWORK_VAULT_OPT_IN_SERVICE(), address(networkVaultOptInService)); + assertEq(slasher.OPERATOR_VAULT_OPT_IN_SERVICE(), address(operatorVaultOptInService)); + assertEq(slasher.OPERATOR_NETWORK_OPT_IN_SERVICE(), address(operatorNetworkOptInService)); + assertEq(slasher.vault(), address(vault)); + assertEq(slasher.SHARES_BASE(), 1e18); + assertEq(slasher.NETWORK_REGISTRY(), address(networkRegistry)); + assertEq(slasher.vetoDuration(), vetoDuration); + assertEq(slasher.executeDuration(), executeDuration); + assertEq(slasher.slashRequestsLength(), 0); + vm.expectRevert(); + slasher.slashRequests(0); + assertEq(slasher.resolverSetEpochsDelay(), 3); + assertEq(slasher.resolverSharesAt(address(this), address(this), 0), 0); + assertEq(slasher.resolverShares(address(this), address(this)), 0); + } + + function test_CreateRevertNotVault( + uint48 epochDuration, + uint48 vetoDuration, + uint48 executeDuration, + uint256 resolverSetEpochsDelay + ) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + vetoDuration = uint48(bound(vetoDuration, 0, type(uint48).max / 2)); + executeDuration = uint48(bound(executeDuration, 1, type(uint48).max / 2)); + resolverSetEpochsDelay = bound(resolverSetEpochsDelay, 3, type(uint256).max); + vm.assume(vetoDuration + executeDuration <= epochDuration); + + (vault,) = _getVaultAndDelegator(epochDuration); + + vm.expectRevert(IBaseSlasher.NotVault.selector); + slasherFactory.create( + 1, + true, + abi.encode( + address(1), + abi.encode( + IVetoSlasher.InitParams({ + vetoDuration: vetoDuration, + executeDuration: executeDuration, + resolverSetEpochsDelay: resolverSetEpochsDelay + }) + ) + ) + ); + } + + function test_CreateRevertInvalidExecuteDuration( + uint48 epochDuration, + uint48 vetoDuration, + uint256 resolverSetEpochsDelay + ) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + vetoDuration = uint48(bound(vetoDuration, 0, type(uint48).max / 2)); + uint48 executeDuration = 0; + resolverSetEpochsDelay = bound(resolverSetEpochsDelay, 3, type(uint256).max); + vm.assume(vetoDuration + executeDuration <= epochDuration); + + (vault,) = _getVaultAndDelegator(epochDuration); + + vm.expectRevert(IVetoSlasher.InvalidExecuteDuration.selector); + slasherFactory.create( + 1, + true, + abi.encode( + address(vault), + abi.encode( + IVetoSlasher.InitParams({ + vetoDuration: vetoDuration, + executeDuration: executeDuration, + resolverSetEpochsDelay: resolverSetEpochsDelay + }) + ) + ) + ); + } + + function test_CreateRevertInvalidSlashDuration( + uint48 epochDuration, + uint48 vetoDuration, + uint48 executeDuration, + uint256 resolverSetEpochsDelay + ) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + vetoDuration = uint48(bound(vetoDuration, 0, type(uint48).max / 2)); + executeDuration = uint48(bound(executeDuration, 1, type(uint48).max / 2)); + resolverSetEpochsDelay = bound(resolverSetEpochsDelay, 3, type(uint256).max); + vm.assume(vetoDuration + executeDuration > epochDuration); + + (vault,) = _getVaultAndDelegator(epochDuration); + + vm.expectRevert(IVetoSlasher.InvalidSlashDuration.selector); + slasherFactory.create( + 1, + true, + abi.encode( + address(vault), + abi.encode( + IVetoSlasher.InitParams({ + vetoDuration: vetoDuration, + executeDuration: executeDuration, + resolverSetEpochsDelay: resolverSetEpochsDelay + }) + ) + ) + ); + } + + function test_CreateRevertInvalidResolverSetEpochsDelay( + uint48 epochDuration, + uint48 vetoDuration, + uint48 executeDuration, + uint256 resolverSetEpochsDelay + ) public { + epochDuration = uint48(bound(epochDuration, 1, type(uint48).max)); + vetoDuration = uint48(bound(vetoDuration, 0, type(uint48).max / 2)); + executeDuration = uint48(bound(executeDuration, 1, type(uint48).max / 2)); + resolverSetEpochsDelay = bound(resolverSetEpochsDelay, 0, 2); + vm.assume(vetoDuration + executeDuration <= epochDuration); + + (vault,) = _getVaultAndDelegator(epochDuration); + + vm.expectRevert(IVetoSlasher.InvalidResolverSetEpochsDelay.selector); + slasherFactory.create( + 1, + true, + abi.encode( + address(vault), + abi.encode( + IVetoSlasher.InitParams({ + vetoDuration: vetoDuration, + executeDuration: executeDuration, + resolverSetEpochsDelay: resolverSetEpochsDelay + }) + ) + ) + ); + } + function _getVaultAndDelegator(uint48 epochDuration) internal returns (Vault, FullRestakeDelegator) { (address vault_, address delegator_,) = vaultConfigurator.create( IVaultConfigurator.InitParams({ diff --git a/test/vault/Vault.t.sol b/test/vault/Vault.t.sol index 27eb861f..c0a0e6cd 100644 --- a/test/vault/Vault.t.sol +++ b/test/vault/Vault.t.sol @@ -24,6 +24,7 @@ import {Token} from "test/mocks/Token.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 {IVaultStorage} from "src/interfaces/vault/IVaultStorage.sol"; @@ -207,11 +208,12 @@ contract VaultTest is Test { assertEq(vault.activeSupply(), 0); assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp)), 0); assertEq(vault.activeSharesOf(alice), 0); - (bool checkpointExists, uint48 checkpointKey, uint256 checkpointValue) = + (bool checkpointExists, uint48 checkpointKey, uint256 checkpointValue, uint256 checkpointPos) = vault.activeSharesOfCheckpointAt(alice, uint48(blockTimestamp)); assertEq(checkpointExists, false); assertEq(checkpointKey, 0); assertEq(checkpointValue, 0); + assertEq(checkpointPos, 0); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp)), 0); assertEq(vault.activeBalanceOf(alice), 0); assertEq(vault.withdrawals(0), 0); @@ -443,7 +445,7 @@ contract VaultTest is Test { uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp; uint256 tokensBefore = collateral.balanceOf(address(vault)); - uint256 shares1 = amount1 * 10 ** 3; + uint256 shares1 = amount1 * 10 ** 0; assertEq(_deposit(alice, amount1), shares1); assertEq(collateral.balanceOf(address(vault)) - tokensBefore, amount1); @@ -460,11 +462,12 @@ contract VaultTest is Test { assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1)), 0); assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp)), shares1); assertEq(vault.activeSharesOf(alice), shares1); - (bool checkpointExists, uint48 checkpointKey, uint256 checkpointValue) = + (bool checkpointExists, uint48 checkpointKey, uint256 checkpointValue, uint256 checkpointPos) = vault.activeSharesOfCheckpointAt(alice, uint48(blockTimestamp)); assertEq(checkpointExists, true); assertEq(checkpointKey, blockTimestamp); assertEq(checkpointValue, shares1); + assertEq(checkpointPos, 0); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1)), 0); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp)), amount1); assertEq(vault.activeBalanceOf(alice), amount1); @@ -472,7 +475,7 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + 1; vm.warp(blockTimestamp); - uint256 shares2 = amount2 * (shares1 + 10 ** 3) / (amount1 + 1); + uint256 shares2 = amount2 * (shares1 + 10 ** 0) / (amount1 + 1); assertEq(_deposit(alice, amount2), shares2); assertEq(vault.totalSupplyIn(0), amount1 + amount2); @@ -488,16 +491,18 @@ contract VaultTest is Test { assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1)), shares1); assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp)), shares1 + shares2); assertEq(vault.activeSharesOf(alice), shares1 + shares2); - (checkpointExists, checkpointKey, checkpointValue) = + (checkpointExists, checkpointKey, checkpointValue, checkpointPos) = vault.activeSharesOfCheckpointAt(alice, uint48(blockTimestamp - 1)); assertEq(checkpointExists, true); assertEq(checkpointKey, blockTimestamp - 1); assertEq(checkpointValue, shares1); - (checkpointExists, checkpointKey, checkpointValue) = + assertEq(checkpointPos, 0); + (checkpointExists, checkpointKey, checkpointValue, checkpointPos) = vault.activeSharesOfCheckpointAt(alice, uint48(blockTimestamp)); assertEq(checkpointExists, true); assertEq(checkpointKey, blockTimestamp); assertEq(checkpointValue, shares1 + shares2); + assertEq(checkpointPos, 1); uint256 gasLeft = gasleft(); assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1), 1), shares1); uint256 gasSpent = gasLeft - gasleft(); @@ -524,13 +529,13 @@ contract VaultTest is Test { uint256 blockTimestamp = block.timestamp * block.timestamp / block.timestamp * block.timestamp / block.timestamp; - uint256 shares1 = amount1 * 10 ** 3; + uint256 shares1 = amount1 * 10 ** 0; assertEq(_deposit(alice, amount1), shares1); blockTimestamp = blockTimestamp + 1; vm.warp(blockTimestamp); - uint256 shares2 = amount2 * (shares1 + 10 ** 3) / (amount1 + 1); + uint256 shares2 = amount2 * (shares1 + 10 ** 0) / (amount1 + 1); assertEq(_deposit(bob, amount2), shares2); assertEq(vault.totalSupply(), amount1 + amount2); @@ -543,19 +548,22 @@ contract VaultTest is Test { assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1)), shares1); assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp)), shares1); assertEq(vault.activeSharesOf(alice), shares1); - (, uint48 checkpointKey, uint256 checkpointValue) = + (, uint48 checkpointKey, uint256 checkpointValue, uint256 checkpointPos) = vault.activeSharesOfCheckpointAt(alice, uint48(blockTimestamp)); assertEq(checkpointKey, blockTimestamp - 1); assertEq(checkpointValue, shares1); + assertEq(checkpointPos, 0); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1)), amount1); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp)), amount1); assertEq(vault.activeBalanceOf(alice), amount1); assertEq(vault.activeSharesOfAt(bob, uint48(blockTimestamp - 1)), 0); assertEq(vault.activeSharesOfAt(bob, uint48(blockTimestamp)), shares2); assertEq(vault.activeSharesOf(bob), shares2); - (, checkpointKey, checkpointValue) = vault.activeSharesOfCheckpointAt(bob, uint48(blockTimestamp)); + (, checkpointKey, checkpointValue, checkpointPos) = + vault.activeSharesOfCheckpointAt(bob, uint48(blockTimestamp)); assertEq(checkpointKey, blockTimestamp); assertEq(checkpointValue, shares2); + assertEq(checkpointPos, 0); assertEq(vault.activeBalanceOfAt(bob, uint48(blockTimestamp - 1)), 0); assertEq(vault.activeBalanceOfAt(bob, uint48(blockTimestamp)), amount2); assertEq(vault.activeBalanceOf(bob), amount2); @@ -599,13 +607,11 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + 1; vm.warp(blockTimestamp); - uint256 tokensBefore = collateral.balanceOf(address(vault)); - uint256 burnedShares = amount2 * (shares + 10 ** 3) / (amount1 + 1); - uint256 mintedShares = amount2 * 10 ** 3; + 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(tokensBefore - collateral.balanceOf(address(vault)), 0); assertEq(vault.totalSupplyIn(0), amount1); assertEq(vault.totalSupplyIn(1), amount1); @@ -620,10 +626,11 @@ contract VaultTest is Test { assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1)), shares); assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp)), shares - burnedShares); assertEq(vault.activeSharesOf(alice), shares - burnedShares); - (, uint48 checkpointKey, uint256 checkpointValue) = + (, uint48 checkpointKey, uint256 checkpointValue, uint256 checkpointPos) = vault.activeSharesOfCheckpointAt(alice, uint48(blockTimestamp)); assertEq(checkpointKey, blockTimestamp); assertEq(checkpointValue, shares - burnedShares); + assertEq(checkpointPos, 1); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp - 1)), amount1); assertEq(vault.activeBalanceOfAt(alice, uint48(blockTimestamp)), amount1 - amount2); assertEq(vault.activeBalanceOf(alice), amount1 - amount2); @@ -642,8 +649,8 @@ contract VaultTest is Test { blockTimestamp = blockTimestamp + 1; vm.warp(blockTimestamp); - burnedShares = amount3 * (shares + 10 ** 3) / (amount1 - amount2 + 1); - mintedShares = amount3 * 10 ** 3; + burnedShares = amount3 * (shares + 10 ** 0) / (amount1 - amount2 + 1); + mintedShares = amount3 * 10 ** 0; (burnedShares_, mintedShares_) = _withdraw(alice, amount3); assertEq(burnedShares_, burnedShares); assertEq(mintedShares_, mintedShares); @@ -661,9 +668,11 @@ contract VaultTest is Test { assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp - 1)), shares); assertEq(vault.activeSharesOfAt(alice, uint48(blockTimestamp)), shares - burnedShares); assertEq(vault.activeSharesOf(alice), shares - burnedShares); - (, checkpointKey, checkpointValue) = vault.activeSharesOfCheckpointAt(alice, uint48(blockTimestamp)); + (, checkpointKey, checkpointValue, checkpointPos) = + vault.activeSharesOfCheckpointAt(alice, uint48(blockTimestamp)); assertEq(checkpointKey, blockTimestamp); assertEq(checkpointValue, shares - burnedShares); + assertEq(checkpointPos, 2); 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); @@ -672,12 +681,12 @@ contract VaultTest is Test { 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 ** 3); - assertEq(vault.withdrawalShares(vault.currentEpoch() + 1), amount3 * 10 ** 3); + 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 ** 3); - assertEq(vault.withdrawalSharesOf(vault.currentEpoch() + 1, alice), amount3 * 10 ** 3); + 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); shares -= burnedShares; @@ -1045,6 +1054,17 @@ contract VaultTest is Test { _setSlasher(alice, address(1)); } + function test_OnSlashRevertNotSlasher() public { + uint48 epochDuration = 1; + + vault = _getVault(epochDuration); + + vm.startPrank(alice); + vm.expectRevert(IVault.NotSlasher.selector); + vault.onSlash(0); + vm.stopPrank(); + } + function _getVault(uint48 epochDuration) internal returns (Vault) { (address vault_,,) = vaultConfigurator.create( IVaultConfigurator.InitParams({