diff --git a/src/contracts/libraries/Snapshots.sol b/src/contracts/libraries/Snapshots.sol index dbf510c60..67936af85 100644 --- a/src/contracts/libraries/Snapshots.sol +++ b/src/contracts/libraries/Snapshots.sol @@ -1,11 +1,7 @@ // SPDX-License-Identifier: MIT - pragma solidity ^0.8.0; import "@openzeppelin-upgrades/contracts/utils/math/MathUpgradeable.sol"; -import "@openzeppelin-upgrades/contracts/utils/math/SafeCastUpgradeable.sol"; - -import "./SlashingLib.sol"; /** * @title Library for handling snapshots as part of allocating and slashing. @@ -22,6 +18,10 @@ import "./SlashingLib.sol"; * _Available since v4.5._ */ library Snapshots { + error SnapshotKeyDecreased(); + + uint64 internal constant WAD = 1 ether; + struct DefaultWadHistory { Snapshot[] _snapshots; } @@ -76,11 +76,12 @@ library Snapshots { uint256 pos = self.length; if (pos > 0) { + // Copying to memory is important here. Snapshot memory last = _unsafeAccess(self, pos - 1); // Snapshot keys must be non-decreasing. - require(last._key <= key, "Snapshot: decreasing keys"); + require(last._key <= key, SnapshotKeyDecreased()); // Update or push new snapshot if (last._key == key) { diff --git a/src/test/Delegation.t.sol b/src/test/Delegation.t.sol deleted file mode 100644 index 33e78fe97..000000000 --- a/src/test/Delegation.t.sol +++ /dev/null @@ -1,495 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.27; - -import "@openzeppelin/contracts/mocks/ERC1271WalletMock.sol"; -import "src/contracts/interfaces/ISignatureUtils.sol"; - -import "../test/EigenLayerTestHelper.t.sol"; - -contract DelegationTests is EigenLayerTestHelper { - uint256 public PRIVATE_KEY = 420; - - uint32 serveUntil = 100; - - address public registryCoordinator = address(uint160(uint256(keccak256("registryCoordinator")))); - uint8 defaultQuorumNumber = 0; - bytes32 defaultOperatorId = bytes32(uint256(0)); - - uint256 public constant DEFAULT_ALLOCATION_DELAY = 17.5 days; - - modifier fuzzedAmounts(uint256 ethAmount, uint256 eigenAmount) { - cheats.assume(ethAmount >= 0 && ethAmount <= 1e18); - cheats.assume(eigenAmount >= 0 && eigenAmount <= 1e18); - _; - } - - function setUp() public virtual override { - EigenLayerDeployer.setUp(); - } - - /// @notice testing if an operator can register to themselves. - function testSelfOperatorRegister() public { - _testRegisterAdditionalOperator(0); - } - - /// @notice testing if an operator can delegate to themselves. - /// @param sender is the address of the operator. - function testSelfOperatorDelegate(address sender) public { - cheats.assume(sender != address(0)); - cheats.assume(sender != address(eigenLayerProxyAdmin)); - IDelegationManagerTypes.OperatorDetails memory operatorDetails = IDelegationManagerTypes.OperatorDetails({ - __deprecated_earningsReceiver: sender, - delegationApprover: address(0), - __deprecated_stakerOptOutWindowBlocks: 0 - }); - _testRegisterAsOperator(sender, 1, operatorDetails); - } - - function testTwoSelfOperatorsRegister() public { - _testRegisterAdditionalOperator(0); - _testRegisterAdditionalOperator(1); - } - - /// @notice registers a fixed address as a delegate, delegates to it from a second address, - /// and checks that the delegate's voteWeights increase properly - /// @param operator is the operator being delegated to. - /// @param staker is the staker delegating stake to the operator. - function testDelegation( - address operator, - address staker, - uint96 ethAmount, - uint96 eigenAmount - ) public fuzzedAddress(operator) fuzzedAddress(staker) fuzzedAmounts(ethAmount, eigenAmount) { - cheats.assume(staker != operator); - // base strategy will revert if these amounts are too small on first deposit - cheats.assume(ethAmount >= 1); - cheats.assume(eigenAmount >= 2); - - // Set weights ahead of the helper function call - bytes memory quorumNumbers = new bytes(2); - quorumNumbers[0] = bytes1(uint8(0)); - quorumNumbers[0] = bytes1(uint8(1)); - _testDelegation(operator, staker, ethAmount, eigenAmount); - } - - /// @notice tests that a when an operator is delegated to, that delegation is properly accounted for. - function testDelegationReceived( - address _operator, - address staker, - uint64 ethAmount, - uint64 eigenAmount - ) public fuzzedAddress(_operator) fuzzedAddress(staker) fuzzedAmounts(ethAmount, eigenAmount) { - cheats.assume(staker != _operator); - cheats.assume(ethAmount >= 1); - cheats.assume(eigenAmount >= 2); - - // use storage to solve stack-too-deep - operator = _operator; - - IDelegationManagerTypes.OperatorDetails memory operatorDetails = IDelegationManagerTypes.OperatorDetails({ - __deprecated_earningsReceiver: operator, - delegationApprover: address(0), - __deprecated_stakerOptOutWindowBlocks: 0 - }); - if (!delegation.isOperator(operator)) { - _testRegisterAsOperator(operator, 1, operatorDetails); - } - - uint256 amountBefore = delegation.operatorShares(operator, wethStrat); - - //making additional deposits to the strategies - assertTrue(!delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate"); - _testDepositWeth(staker, ethAmount); - _testDepositEigen(staker, eigenAmount); - _testDelegateToOperator(staker, operator); - assertTrue(delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate"); - - (/*IStrategy[] memory updatedStrategies*/, uint256[] memory updatedShares) = strategyManager.getDeposits(staker); - - { - IStrategy _strat = wethStrat; - // IStrategy _strat = strategyManager.stakerStrats(staker, 0); - assertTrue(address(_strat) != address(0), "stakerStrats not updated correctly"); - - assertTrue( - delegation.operatorShares(operator, _strat) - updatedShares[0] == amountBefore, - "ETH operatorShares not updated correctly" - ); - - cheats.startPrank(address(strategyManager)); - - IDelegationManagerTypes.OperatorDetails memory expectedOperatorDetails = delegation.operatorDetails(operator); - assertTrue( - keccak256(abi.encode(expectedOperatorDetails)) == keccak256(abi.encode(operatorDetails)), - "failed to set correct operator details" - ); - } - } - - /// @notice tests that a when an operator is undelegated from, that the staker is properly classified as undelegated. - function testUndelegation( - address operator, - address staker, - uint96 ethAmount, - uint96 eigenAmount - ) public fuzzedAddress(operator) fuzzedAddress(staker) fuzzedAmounts(ethAmount, eigenAmount) { - cheats.assume(staker != operator); - // base strategy will revert if these amounts are too small on first deposit - cheats.assume(ethAmount >= 1); - cheats.assume(eigenAmount >= 1); - _testDelegation(operator, staker, ethAmount, eigenAmount); - - (IStrategy[] memory strategyArray, uint256[] memory shareAmounts) = strategyManager.getDeposits(staker); - uint256[] memory strategyIndexes = new uint256[](strategyArray.length); - - // withdraw shares - _testQueueWithdrawal(staker, strategyIndexes, strategyArray, shareAmounts, staker /*withdrawer*/); - - cheats.startPrank(staker); - delegation.undelegate(staker); - cheats.stopPrank(); - - require(delegation.delegatedTo(staker) == address(0), "undelegation unsuccessful"); - } - - /// @notice tests delegation from a staker to operator via ECDSA signature. - function testDelegateToBySignature( - address operator, - uint96 ethAmount, - uint96 eigenAmount, - uint256 expiry - ) public fuzzedAddress(operator) { - address staker = cheats.addr(PRIVATE_KEY); - _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); - - uint256 nonceBefore = delegation.stakerNonce(staker); - - bytes32 structHash = keccak256( - abi.encode(delegation.STAKER_DELEGATION_TYPEHASH(), staker, operator, nonceBefore, expiry) - ); - bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", delegation.domainSeparator(), structHash)); - - bytes memory signature; - { - (uint8 v, bytes32 r, bytes32 s) = cheats.sign(PRIVATE_KEY, digestHash); - signature = abi.encodePacked(r, s, v); - } - - if (expiry < block.timestamp) { - cheats.expectRevert(IDelegationManagerErrors.SignatureExpired.selector); - } - ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry = ISignatureUtils.SignatureWithExpiry({ - signature: signature, - expiry: expiry - }); - delegation.delegateToBySignature(staker, operator, signatureWithExpiry, signatureWithExpiry, bytes32(0)); - if (expiry >= block.timestamp) { - assertTrue(delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate"); - assertTrue(nonceBefore + 1 == delegation.stakerNonce(staker), "nonce not incremented correctly"); - assertTrue(delegation.delegatedTo(staker) == operator, "staker delegated to wrong operator"); - } - } - - /// @notice tries delegating using a signature and an EIP 1271 compliant wallet - function testDelegateToBySignature_WithContractWallet_Successfully( - address operator, - uint96 ethAmount, - uint96 eigenAmount - ) public fuzzedAddress(operator) { - address staker = cheats.addr(PRIVATE_KEY); - - // deploy ERC1271WalletMock for staker to use - cheats.startPrank(staker); - ERC1271WalletMock wallet = new ERC1271WalletMock(staker); - cheats.stopPrank(); - staker = address(wallet); - - _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); - - uint256 nonceBefore = delegation.stakerNonce(staker); - - bytes32 structHash = keccak256( - abi.encode(delegation.STAKER_DELEGATION_TYPEHASH(), staker, operator, nonceBefore, type(uint256).max) - ); - bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", delegation.domainSeparator(), structHash)); - - bytes memory signature; - { - (uint8 v, bytes32 r, bytes32 s) = cheats.sign(PRIVATE_KEY, digestHash); - signature = abi.encodePacked(r, s, v); - } - - ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry = ISignatureUtils.SignatureWithExpiry({ - signature: signature, - expiry: type(uint256).max - }); - delegation.delegateToBySignature(staker, operator, signatureWithExpiry, signatureWithExpiry, bytes32(0)); - assertTrue(delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate"); - assertTrue(nonceBefore + 1 == delegation.stakerNonce(staker), "nonce not incremented correctly"); - assertTrue(delegation.delegatedTo(staker) == operator, "staker delegated to wrong operator"); - } - - /// @notice tries delegating using a signature and an EIP 1271 compliant wallet, *but* providing a bad signature - function testDelegateToBySignature_WithContractWallet_BadSignature( - address operator, - uint96 ethAmount, - uint96 eigenAmount - ) public fuzzedAddress(operator) { - address staker = cheats.addr(PRIVATE_KEY); - - // deploy ERC1271WalletMock for staker to use - cheats.startPrank(staker); - ERC1271WalletMock wallet = new ERC1271WalletMock(staker); - cheats.stopPrank(); - staker = address(wallet); - - _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); - - uint256 nonceBefore = delegation.stakerNonce(staker); - - bytes32 structHash = keccak256( - abi.encode(delegation.STAKER_DELEGATION_TYPEHASH(), staker, operator, nonceBefore, type(uint256).max) - ); - bytes32 digestHash = keccak256(abi.encodePacked("\x19\x01", delegation.domainSeparator(), structHash)); - - bytes memory signature; - { - (uint8 v, bytes32 r, bytes32 s) = cheats.sign(PRIVATE_KEY, digestHash); - // mess up the signature by flipping v's parity - v = (v == 27 ? 28 : 27); - signature = abi.encodePacked(r, s, v); - } - - cheats.expectRevert(ISignatureUtils.InvalidSignature.selector); - ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry = ISignatureUtils.SignatureWithExpiry({ - signature: signature, - expiry: type(uint256).max - }); - delegation.delegateToBySignature(staker, operator, signatureWithExpiry, signatureWithExpiry, bytes32(0)); - } - - /// @notice tries delegating using a wallet that does not comply with EIP 1271 - function testDelegateToBySignature_WithContractWallet_NonconformingWallet( - address operator, - uint96 ethAmount, - uint96 eigenAmount, - uint8 v, - bytes32 r, - bytes32 s - ) public fuzzedAddress(operator) { - address staker = cheats.addr(PRIVATE_KEY); - - // deploy non ERC1271-compliant wallet for staker to use - cheats.startPrank(staker); - ERC1271MaliciousMock wallet = new ERC1271MaliciousMock(); - cheats.stopPrank(); - staker = address(wallet); - - _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); - - cheats.assume(staker != operator); - - bytes memory signature = abi.encodePacked(r, s, v); - - cheats.expectRevert(); - ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry = ISignatureUtils.SignatureWithExpiry({ - signature: signature, - expiry: type(uint256).max - }); - delegation.delegateToBySignature(staker, operator, signatureWithExpiry, signatureWithExpiry, bytes32(0)); - } - - /// @notice tests delegation to EigenLayer via an ECDSA signatures with invalid signature - /// @param operator is the operator being delegated to. - function testDelegateToByInvalidSignature( - address operator, - uint96 ethAmount, - uint96 eigenAmount, - uint8 v, - bytes32 r, - bytes32 s - ) public fuzzedAddress(operator) fuzzedAmounts(ethAmount, eigenAmount) { - address staker = cheats.addr(PRIVATE_KEY); - _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); - - bytes memory signature = abi.encodePacked(r, s, v); - - cheats.expectRevert(); - ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry = ISignatureUtils.SignatureWithExpiry({ - signature: signature, - expiry: type(uint256).max - }); - delegation.delegateToBySignature(staker, operator, signatureWithExpiry, signatureWithExpiry, bytes32(0)); - } - - /// @notice This function tests to ensure that a delegation contract - /// cannot be intitialized multiple times - function testCannotInitMultipleTimesDelegation() public cannotReinit { - //delegation has already been initialized in the Deployer test contract - delegation.initialize( - address(this), - eigenLayerPauserReg, - 0 - ); - } - - /// @notice This function tests to ensure that a you can't register as a delegate multiple times - /// @param operator is the operator being delegated to. - function testRegisterAsOperatorMultipleTimes(address operator) public fuzzedAddress(operator) { - IDelegationManagerTypes.OperatorDetails memory operatorDetails = IDelegationManagerTypes.OperatorDetails({ - __deprecated_earningsReceiver: operator, - delegationApprover: address(0), - __deprecated_stakerOptOutWindowBlocks: 0 - }); - _testRegisterAsOperator(operator, 1, operatorDetails); - cheats.expectRevert(IDelegationManagerErrors.ActivelyDelegated.selector); - _testRegisterAsOperator(operator, 1, operatorDetails); - } - - /// @notice This function tests to ensure that a staker cannot delegate to an unregistered operator - /// @param delegate is the unregistered operator - function testDelegationToUnregisteredDelegate(address delegate) public fuzzedAddress(delegate) { - //deposit into 1 strategy for getOperatorAddress(1), who is delegating to the unregistered operator - _testDepositStrategies(getOperatorAddress(1), 1e18, 1); - _testDepositEigen(getOperatorAddress(1), 1e18); - - cheats.expectRevert(IDelegationManagerErrors.OperatorNotRegistered.selector); - cheats.startPrank(getOperatorAddress(1)); - ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry; - delegation.delegateTo(delegate, signatureWithExpiry, bytes32(0)); - cheats.stopPrank(); - } - - /// @notice This function tests to ensure that a delegation contract - /// cannot be intitialized multiple times, test with different caller addresses - function testCannotInitMultipleTimesDelegation(address _attacker) public { - cheats.assume(_attacker != address(eigenLayerProxyAdmin)); - //delegation has already been initialized in the Deployer test contract - vm.prank(_attacker); - cheats.expectRevert(bytes("Initializable: contract is already initialized")); - delegation.initialize( - _attacker, - eigenLayerPauserReg, - 0 - ); - } - - /// @notice This function tests to ensure that an address can only call registerAsOperator() once - function testCannotRegisterAsOperatorTwice( - address _operator, - address _dt - ) public fuzzedAddress(_operator) fuzzedAddress(_dt) { - vm.assume(_dt != address(0)); - vm.startPrank(_operator); - IDelegationManagerTypes.OperatorDetails memory operatorDetails = IDelegationManagerTypes.OperatorDetails({ - __deprecated_earningsReceiver: msg.sender, - delegationApprover: address(0), - __deprecated_stakerOptOutWindowBlocks: 0 - }); - string memory emptyStringForMetadataURI; - delegation.registerAsOperator(operatorDetails, 1, emptyStringForMetadataURI); - cheats.expectRevert(IDelegationManagerErrors.ActivelyDelegated.selector); - delegation.registerAsOperator(operatorDetails, 1, emptyStringForMetadataURI); - cheats.stopPrank(); - } - - /// @notice this function checks that you can only delegate to an address that is already registered. - function testDelegateToInvalidOperator( - address _staker, - address _unregisteredoperator - ) public fuzzedAddress(_staker) { - vm.startPrank(_staker); - cheats.expectRevert(IDelegationManagerErrors.OperatorNotRegistered.selector); - ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry; - delegation.delegateTo(_unregisteredoperator, signatureWithExpiry, bytes32(0)); - cheats.expectRevert(IDelegationManagerErrors.OperatorNotRegistered.selector); - delegation.delegateTo(_staker, signatureWithExpiry, bytes32(0)); - cheats.stopPrank(); - } - - function testUndelegate_SigP_Version(address _operator, address _staker, address _dt) public { - vm.assume(_operator != address(0)); - vm.assume(_staker != address(0)); - vm.assume(_operator != _staker); - vm.assume(_dt != address(0)); - vm.assume(_operator != address(eigenLayerProxyAdmin)); - vm.assume(_staker != address(eigenLayerProxyAdmin)); - - // setup delegation - vm.prank(_operator); - IDelegationManagerTypes.OperatorDetails memory operatorDetails = IDelegationManagerTypes.OperatorDetails({ - __deprecated_earningsReceiver: _dt, - delegationApprover: address(0), - __deprecated_stakerOptOutWindowBlocks: 0 - }); - string memory emptyStringForMetadataURI; - delegation.registerAsOperator(operatorDetails, 1, emptyStringForMetadataURI); - vm.prank(_staker); - ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry; - delegation.delegateTo(_operator, signatureWithExpiry, bytes32(0)); - - // operators cannot undelegate from themselves - vm.prank(_operator); - cheats.expectRevert(IDelegationManagerErrors.OperatorsCannotUndelegate.selector); - delegation.undelegate(_operator); - - // assert still delegated - assertTrue(delegation.isDelegated(_staker)); - assertTrue(delegation.isOperator(_operator)); - - // _staker *can* undelegate themselves - vm.prank(_staker); - delegation.undelegate(_staker); - - // assert undelegated - assertTrue(!delegation.isDelegated(_staker)); - assertTrue(delegation.isOperator(_operator)); - } - - function _testRegisterAdditionalOperator(uint256 index) internal { - address sender = getOperatorAddress(index); - - //register as both ETH and EIGEN operator - uint256 wethToDeposit = 1e18; - uint256 eigenToDeposit = 1e10; - _testDepositWeth(sender, wethToDeposit); - _testDepositEigen(sender, eigenToDeposit); - IDelegationManagerTypes.OperatorDetails memory operatorDetails = IDelegationManagerTypes.OperatorDetails({ - __deprecated_earningsReceiver: sender, - delegationApprover: address(0), - __deprecated_stakerOptOutWindowBlocks: 0 - }); - _testRegisterAsOperator(sender, 1, operatorDetails); - cheats.startPrank(sender); - - cheats.stopPrank(); - } - - // registers the operator if they are not already registered, and deposits "WETH" + "EIGEN" on behalf of the staker. - function _registerOperatorAndDepositFromStaker( - address operator, - address staker, - uint96 ethAmount, - uint96 eigenAmount - ) internal { - cheats.assume(staker != operator); - - // if first deposit amount to base strategy is too small, it will revert. ignore that case here. - cheats.assume(ethAmount >= 1 && ethAmount <= 1e18); - cheats.assume(eigenAmount >= 1 && eigenAmount <= 1e18); - - if (!delegation.isOperator(operator)) { - IDelegationManagerTypes.OperatorDetails memory operatorDetails = IDelegationManagerTypes.OperatorDetails({ - __deprecated_earningsReceiver: operator, - delegationApprover: address(0), - __deprecated_stakerOptOutWindowBlocks: 0 - }); - _testRegisterAsOperator(operator, 1, operatorDetails); - } - - //making additional deposits to the strategies - assertTrue(!delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate"); - _testDepositWeth(staker, ethAmount); - _testDepositEigen(staker, eigenAmount); - } -} diff --git a/src/test/DepositWithdraw.t.sol b/src/test/DepositWithdraw.t.sol deleted file mode 100644 index 3e6033916..000000000 --- a/src/test/DepositWithdraw.t.sol +++ /dev/null @@ -1,485 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.27; - -import "./EigenLayerTestHelper.t.sol"; -import "../contracts/core/StrategyManagerStorage.sol"; -import "./mocks/ERC20_OneWeiFeeOnTransfer.sol"; - -contract DepositWithdrawTests is EigenLayerTestHelper { - uint256[] public emptyUintArray; - - uint32 constant MIN_WITHDRAWAL_DELAY = 17.5 days; - - /** - * @notice Verifies that it is possible to deposit WETH - * @param amountToDeposit Fuzzed input for amount of WETH to deposit - */ - function testWethDeposit(uint256 amountToDeposit) public returns (uint256 amountDeposited) { - // if first deposit amount to base strategy is too small, it will revert. ignore that case here. - cheats.assume(amountToDeposit >= 1); - cheats.assume(amountToDeposit <= 1e38 - 1); - return _testDepositWeth(getOperatorAddress(0), amountToDeposit); - } - - - /// @notice deploys 'numStratsToAdd' strategies using '_testAddStrategy' and then deposits '1e18' to each of them from 'getOperatorAddress(0)' - /// @param numStratsToAdd is the number of strategies being added and deposited into - function testDepositStrategies(uint8 numStratsToAdd) public { - _testDepositStrategies(getOperatorAddress(0), 1e18, numStratsToAdd); - } - - /// @notice Verifies that it is possible to deposit eigen. - /// @param eigenToDeposit is amount of eigen to deposit into the eigen strategy - function testDepositEigen(uint96 eigenToDeposit) public { - // sanity check for inputs; keeps fuzzed tests from failing - cheats.assume(eigenToDeposit < eigenTotalSupply); - // if first deposit amount to base strategy is too small, it will revert. ignore that case here. - cheats.assume(eigenToDeposit >= 1); - _testDepositEigen(getOperatorAddress(0), eigenToDeposit); - } - - /** - * @notice Tries to deposit an unsupported token into an `StrategyBase` contract by calling `strategyManager.depositIntoStrategy`. - * Verifies that reversion occurs correctly. - */ - function testDepositUnsupportedToken() public { - IERC20 token = new ERC20PresetFixedSupply( - "badToken", - "BADTOKEN", - 100, - address(this) - ); - token.approve(address(strategyManager), type(uint256).max); - - // whitelist the strategy for deposit - cheats.startPrank(strategyManager.strategyWhitelister()); - IStrategy[] memory _strategy = new IStrategy[](1); - _strategy[0] = wethStrat; - strategyManager.addStrategiesToDepositWhitelist(_strategy); - cheats.stopPrank(); - - cheats.expectRevert(IStrategyErrors.OnlyUnderlyingToken.selector); - strategyManager.depositIntoStrategy(wethStrat, token, 10); - } - - /** - * @notice Tries to deposit into an unsupported strategy by calling `strategyManager.depositIntoStrategy`. - * Verifies that reversion occurs correctly. - */ - function testDepositNonexistentStrategy(address nonexistentStrategy) public fuzzedAddress(nonexistentStrategy) { - // assume that the fuzzed address is not already a contract! - uint256 size; - assembly { - size := extcodesize(nonexistentStrategy) - } - cheats.assume(size == 0); - // check against calls from precompile addresses -- was getting fuzzy failures from this - cheats.assume(uint160(nonexistentStrategy) > 9); - - // harcoded input - uint256 testDepositAmount = 10; - - IERC20 token = new ERC20PresetFixedSupply( - "badToken", - "BADTOKEN", - 100, - address(this) - ); - token.approve(address(strategyManager), type(uint256).max); - - // whitelist the strategy for deposit - cheats.startPrank(strategyManager.strategyWhitelister()); - IStrategy[] memory _strategy = new IStrategy[](1); - _strategy[0] = IStrategy(nonexistentStrategy); - strategyManager.addStrategiesToDepositWhitelist(_strategy); - cheats.stopPrank(); - - cheats.expectRevert(); - strategyManager.depositIntoStrategy(IStrategy(nonexistentStrategy), token, testDepositAmount); - } - - /// @notice verify that trying to deposit an amount of zero will correctly revert - function testRevertOnZeroDeposit() public { - // whitelist the strategy for deposit - cheats.startPrank(strategyManager.strategyWhitelister()); - IStrategy[] memory _strategy = new IStrategy[](1); - _strategy[0] = wethStrat; - strategyManager.addStrategiesToDepositWhitelist(_strategy); - cheats.stopPrank(); - - cheats.expectRevert(IStrategyErrors.NewSharesZero.selector); - strategyManager.depositIntoStrategy(wethStrat, weth, 0); - } - - - /** - * @notice Modified from existing _createQueuedWithdrawal, skips delegation and deposit steps so that we can isolate the withdrawal step - * @notice Creates a queued withdrawal from `staker`, queues a withdrawal using - * `delegation.queueWithdrawal(strategyIndexes, strategyArray, tokensArray, shareAmounts, withdrawer)` - * @notice After initiating a queued withdrawal, this test checks that `delegation.canCompleteQueuedWithdrawal` immediately returns the correct - * response depending on whether `staker` is delegated or not. - * @param staker The address to initiate the queued withdrawal - * @param amountToDeposit The amount of WETH to deposit - */ - function _createOnlyQueuedWithdrawal( - address staker, - bool /*registerAsOperator*/, - uint256 amountToDeposit, - IStrategy[] memory strategyArray, - IERC20[] memory /*tokensArray*/, - uint256[] memory shareAmounts, - uint256[] memory /*strategyIndexes*/, - address withdrawer - ) - internal returns(bytes32 withdrawalRoot, IDelegationManagerTypes.Withdrawal memory queuedWithdrawal) - { - require(amountToDeposit >= shareAmounts[0], "_createQueuedWithdrawal: sanity check failed"); - - queuedWithdrawal = IDelegationManagerTypes.Withdrawal({ - strategies: strategyArray, - staker: staker, - withdrawer: withdrawer, - nonce: delegation.cumulativeWithdrawalsQueued(staker), - delegatedTo: delegation.delegatedTo(staker), - startTimestamp: uint32(block.timestamp), - scaledShares: shareAmounts - }); - - - IDelegationManagerTypes.QueuedWithdrawalParams[] memory params = new IDelegationManagerTypes.QueuedWithdrawalParams[](1); - - params[0] = IDelegationManagerTypes.QueuedWithdrawalParams({ - strategies: strategyArray, - shares: shareAmounts, - withdrawer: withdrawer - }); - - bytes32[] memory withdrawalRoots = new bytes32[](1); - - //queue the withdrawal - cheats.startPrank(staker); - withdrawalRoots = delegation.queueWithdrawals(params); - cheats.stopPrank(); - return (withdrawalRoots[0], queuedWithdrawal); - } - - - function testFrontrunFirstDepositor(/*uint256 depositAmount*/) public { - - //setup addresses - address attacker = address(100); - address user = address(200); - - //give 2 ether to attacker and user - weth.transfer(attacker,2 ether); - weth.transfer(user,2 ether); - - //attacker FRONTRUN: deposit 1 wei (receive 1 share) - StrategyManager _strategyManager = _whitelistStrategy(strategyManager, wethStrat); - - cheats.startPrank(attacker); - weth.approve(address(strategyManager), type(uint256).max); - _strategyManager.depositIntoStrategy(wethStrat, weth, 1 wei); - cheats.stopPrank(); - - //attacker FRONTRUN: transfer 1 ether into strategy directly to manipulate the value of shares - cheats.prank(attacker); - weth.transfer(address(wethStrat),1 ether); - - //user deposits 2 eth into strategy - only gets 1 share due to rounding - cheats.startPrank(user); - weth.approve(address(_strategyManager), type(uint256).max); - _strategyManager.depositIntoStrategy(wethStrat, weth, 2 ether); - cheats.stopPrank(); - - //attacker deposited 1 ether and 1 wei - received 1 share - //user deposited 2 ether - received X shares - //user has lost 0.5 ether? - (, uint256[] memory shares) = _strategyManager.getDeposits(attacker); - uint256 attackerValueWeth = wethStrat.sharesToUnderlyingView(shares[0]); - require(attackerValueWeth >= (1), "attacker got zero shares"); - - (, shares) = _strategyManager.getDeposits(user); - uint256 userValueWeth = wethStrat.sharesToUnderlyingView(shares[0]); - require(userValueWeth >= (1900000000000000000), "user has lost more than 0.1 eth from frontrunning"); - - uint256 attackerLossesWeth = (2 ether + 1 wei) - attackerValueWeth; - uint256 userLossesWeth = 2 ether - userValueWeth; - require(attackerLossesWeth > userLossesWeth, "griefing attack deals more damage than cost"); - } - - // fuzzed amounts user uint96's to be more realistic with amounts - function testFrontrunFirstDepositorFuzzed(uint96 firstDepositAmount, uint96 donationAmount, uint96 secondDepositAmount) public { - // want to only use nonzero amounts or else we'll get reverts - cheats.assume(firstDepositAmount != 0 && secondDepositAmount != 0); - - // setup addresses - address attacker = address(100); - address user = address(200); - - // attacker makes first deposit - _testDepositToStrategy(attacker, firstDepositAmount, weth, wethStrat); - - // transfer tokens into strategy directly to manipulate the value of shares - weth.transfer(address(wethStrat), donationAmount); - - // filter out calls that would revert for minting zero shares - cheats.assume(wethStrat.underlyingToShares(secondDepositAmount) != 0); - - // user makes 2nd deposit into strategy - gets diminished shares due to rounding - _testDepositToStrategy(user, secondDepositAmount, weth, wethStrat); - - // check for griefing - (, uint256[] memory shares) = strategyManager.getDeposits(attacker); - uint256 attackerValueWeth = wethStrat.sharesToUnderlyingView(shares[0]); - (, shares) = strategyManager.getDeposits(user); - uint256 userValueWeth = wethStrat.sharesToUnderlyingView(shares[0]); - - uint256 attackerCost = uint256(firstDepositAmount) + uint256(donationAmount); - require(attackerCost >= attackerValueWeth, "attacker gained value?"); - // uint256 attackerLossesWeth = attackerValueWeth > attackerCost ? 0 : (attackerCost - attackerValueWeth); - uint256 attackerLossesWeth = attackerCost - attackerValueWeth; - uint256 userLossesWeth = secondDepositAmount - userValueWeth; - - emit log_named_uint("attackerLossesWeth", attackerLossesWeth); - emit log_named_uint("userLossesWeth", userLossesWeth); - - // use '+1' here to account for rounding. given the attack will cost ETH in the form of gas, this is fine. - require(attackerLossesWeth + 1 >= userLossesWeth, "griefing attack deals more damage than cost"); - } - - - function testDepositTokenWithOneWeiFeeOnTransfer(address sender, uint64 amountToDeposit) public fuzzedAddress(sender) { - cheats.assume(amountToDeposit != 0); - - IERC20 underlyingToken; - - { - uint256 initSupply = 1e50; - address initOwner = address(this); - ERC20_OneWeiFeeOnTransfer oneWeiFeeOnTransferToken = new ERC20_OneWeiFeeOnTransfer(initSupply, initOwner); - underlyingToken = IERC20(address(oneWeiFeeOnTransferToken)); - } - - // need to transfer extra here because otherwise the `sender` won't have enough tokens - underlyingToken.transfer(sender, 1000); - - IStrategy oneWeiFeeOnTransferTokenStrategy = StrategyBase( - address( - new TransparentUpgradeableProxy( - address(baseStrategyImplementation), - address(eigenLayerProxyAdmin), - abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, eigenLayerPauserReg) - ) - ) - ); - - // REMAINDER OF CODE ADAPTED FROM `_testDepositToStrategy` - // _testDepositToStrategy(sender, amountToDeposit, underlyingToken, oneWeiFeeOnTransferTokenStrategy); - - // whitelist the strategy for deposit, in case it wasn't before - { - cheats.startPrank(strategyManager.strategyWhitelister()); - IStrategy[] memory _strategy = new IStrategy[](1); - _strategy[0] = oneWeiFeeOnTransferTokenStrategy; - strategyManager.addStrategiesToDepositWhitelist(_strategy); - cheats.stopPrank(); - } - - uint256 operatorSharesBefore = strategyManager.stakerDepositShares(sender, oneWeiFeeOnTransferTokenStrategy); - // check the expected output - uint256 expectedSharesOut = oneWeiFeeOnTransferTokenStrategy.underlyingToShares(amountToDeposit); - - underlyingToken.transfer(sender, amountToDeposit); - cheats.startPrank(sender); - underlyingToken.approve(address(strategyManager), type(uint256).max); - strategyManager.depositIntoStrategy(oneWeiFeeOnTransferTokenStrategy, underlyingToken, amountToDeposit); - - //check if depositor has never used this strat, that it is added correctly to stakerStrategyList array. - if (operatorSharesBefore == 0) { - // check that strategy is appropriately added to dynamic array of all of sender's strategies - assertTrue( - strategyManager.stakerStrategyList(sender, strategyManager.stakerStrategyListLength(sender) - 1) - == oneWeiFeeOnTransferTokenStrategy, - "_testDepositToStrategy: stakerStrategyList array updated incorrectly" - ); - } - - // check that the shares out match the expected amount out - // the actual transfer in will be lower by 1 wei than expected due to stETH's internal rounding - // to account for this we check approximate rather than strict equivalence here - { - uint256 actualSharesOut = strategyManager.stakerDepositShares(sender, oneWeiFeeOnTransferTokenStrategy) - operatorSharesBefore; - require((actualSharesOut * 1000) / expectedSharesOut > 998, "too few shares"); - require((actualSharesOut * 1000) / expectedSharesOut < 1002, "too many shares"); - - // additional sanity check for deposit not increasing in value - require(oneWeiFeeOnTransferTokenStrategy.sharesToUnderlying(actualSharesOut) <= amountToDeposit, "value cannot have increased"); - } - cheats.stopPrank(); - } - - /// @notice Shadow-forks mainnet and tests depositing stETH tokens into a "StrategyBase" contract. - function testForkMainnetDepositSteth() public { - // hard-coded inputs - // address sender = address(this); - uint64 amountToDeposit = 1e12; - - // shadow-fork mainnet - try cheats.createFork("mainnet") returns (uint256 forkId) { - cheats.selectFork(forkId); - // If RPC_MAINNET ENV not set, default to this mainnet RPC endpoint - } catch { - cheats.createSelectFork("https://eth.llamarpc.com"); - } - - // cast mainnet stETH address to IERC20 interface - // IERC20 steth = IERC20(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84); - IERC20 underlyingToken = IERC20(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84); - - // deploy necessary contracts on the shadow-forked network - // deploy proxy admin for ability to upgrade proxy contracts - eigenLayerProxyAdmin = new ProxyAdmin(); - //deploy pauser registry - address[] memory pausers = new address[](1); - pausers[0] = pauser; - eigenLayerPauserReg = new PauserRegistry(pausers, unpauser); - /** - * First, deploy upgradeable proxy contracts that **will point** to the implementations. Since the implementation contracts are - * not yet deployed, we give these proxies an empty contract as the initial implementation, to act as if they have no code. - */ - emptyContract = new EmptyContract(); - delegation = DelegationManager( - address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) - ); - strategyManager = StrategyManager( - address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) - ); - eigenPodManager = EigenPodManager( - address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) - ); - - ethPOSDeposit = new ETHPOSDepositMock(); - pod = new EigenPod(ethPOSDeposit, eigenPodManager, GOERLI_GENESIS_TIME); - - eigenPodBeacon = new UpgradeableBeacon(address(pod)); - - // Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs - DelegationManager delegationImplementation = new DelegationManager(avsDirectory, strategyManager, eigenPodManager, allocationManager, MIN_WITHDRAWAL_DELAY); - StrategyManager strategyManagerImplementation = new StrategyManager(delegation); - EigenPodManager eigenPodManagerImplementation = new EigenPodManager(ethPOSDeposit, eigenPodBeacon, strategyManager, delegation); - // Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them. - eigenLayerProxyAdmin.upgradeAndCall( - ITransparentUpgradeableProxy(payable(address(delegation))), - address(delegationImplementation), - abi.encodeWithSelector( - DelegationManager.initialize.selector, - eigenLayerReputedMultisig, - eigenLayerPauserReg, - 0 /*initialPausedStatus*/, - minWithdrawalDelayBlocks, - initializeStrategiesToSetDelayBlocks, - initializeWithdrawalDelayBlocks - ) - ); - eigenLayerProxyAdmin.upgradeAndCall( - ITransparentUpgradeableProxy(payable(address(strategyManager))), - address(strategyManagerImplementation), - abi.encodeWithSelector( - StrategyManager.initialize.selector, - eigenLayerReputedMultisig, - eigenLayerReputedMultisig, - eigenLayerPauserReg, - 0/*initialPausedStatus*/ - ) - ); - eigenLayerProxyAdmin.upgradeAndCall( - ITransparentUpgradeableProxy(payable(address(eigenPodManager))), - address(eigenPodManagerImplementation), - abi.encodeWithSelector( - EigenPodManager.initialize.selector, - eigenLayerReputedMultisig, - eigenLayerPauserReg, - 0/*initialPausedStatus*/ - ) - ); - - // cheat a bunch of ETH to this address - cheats.deal(address(this), 1e20); - // deposit a huge amount of ETH to get ample stETH - { - (bool success, bytes memory returnData) = address(underlyingToken).call{value: 1e20}(""); - require(success, "depositing stETH failed"); - returnData; - } - - // deploy StrategyBase contract implementation, then create upgradeable proxy that points to implementation and initialize it - baseStrategyImplementation = new StrategyBase(strategyManager); - IStrategy stethStrategy = StrategyBase( - address( - new TransparentUpgradeableProxy( - address(baseStrategyImplementation), - address(eigenLayerProxyAdmin), - abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, eigenLayerPauserReg) - ) - ) - ); - - // REMAINDER OF CODE ADAPTED FROM `_testDepositToStrategy` - // _testDepositToStrategy(sender, amountToDeposit, underlyingToken, stethStrategy); - - // whitelist the strategy for deposit, in case it wasn't before - { - cheats.startPrank(strategyManager.strategyWhitelister()); - IStrategy[] memory _strategy = new IStrategy[](1); - _strategy[0] = stethStrategy; - strategyManager.addStrategiesToDepositWhitelist(_strategy); - cheats.stopPrank(); - } - - uint256 operatorSharesBefore = strategyManager.stakerDepositShares(address(this), stethStrategy); - // check the expected output - uint256 expectedSharesOut = stethStrategy.underlyingToShares(amountToDeposit); - - underlyingToken.transfer(address(this), amountToDeposit); - cheats.startPrank(address(this)); - underlyingToken.approve(address(strategyManager), type(uint256).max); - strategyManager.depositIntoStrategy(stethStrategy, underlyingToken, amountToDeposit); - - //check if depositor has never used this strat, that it is added correctly to stakerStrategyList array. - if (operatorSharesBefore == 0) { - // check that strategy is appropriately added to dynamic array of all of sender's strategies - assertTrue( - strategyManager.stakerStrategyList(address(this), strategyManager.stakerStrategyListLength(address(this)) - 1) - == stethStrategy, - "_testDepositToStrategy: stakerStrategyList array updated incorrectly" - ); - } - - // check that the shares out match the expected amount out - // the actual transfer in will be lower by 1-2 wei than expected due to stETH's internal rounding - // to account for this we check approximate rather than strict equivalence here - { - uint256 actualSharesOut = strategyManager.stakerDepositShares(address(this), stethStrategy) - operatorSharesBefore; - require(actualSharesOut >= expectedSharesOut, "too few shares"); - require((actualSharesOut * 1000) / expectedSharesOut < 1003, "too many shares"); - - // additional sanity check for deposit not increasing in value - require(stethStrategy.sharesToUnderlying(actualSharesOut) <= amountToDeposit, "value cannot have increased"); - // slippage check - require((stethStrategy.sharesToUnderlying(actualSharesOut) * 1e6) / amountToDeposit >= (1e6 - 1), "bad slippage on first deposit"); - } - cheats.stopPrank(); - } - - function _whitelistStrategy(StrategyManager _strategyManager, StrategyBase _strategyBase) internal returns(StrategyManager) { - // whitelist the strategy for deposit - cheats.startPrank(strategyManager.strategyWhitelister()); - IStrategy[] memory _strategy = new IStrategy[](1); - _strategy[0] = IStrategy(_strategyBase); - _strategyManager.addStrategiesToDepositWhitelist(_strategy); - cheats.stopPrank(); - - return _strategyManager; - } -} diff --git a/src/test/EigenLayerDeployer.t.sol b/src/test/EigenLayerDeployer.t.sol deleted file mode 100644 index 31e51c982..000000000 --- a/src/test/EigenLayerDeployer.t.sol +++ /dev/null @@ -1,295 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.27; - -import "forge-std/Test.sol"; - -import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetFixedSupply.sol"; -import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import "@openzeppelin/contracts/proxy/beacon/IBeacon.sol"; -import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; - -import "src/contracts/core/AVSDirectory.sol"; -import "src/contracts/core/AllocationManager.sol"; -import "src/contracts/core/DelegationManager.sol"; -import "src/contracts/core/StrategyManager.sol"; -import "src/contracts/strategies/StrategyBase.sol"; -import "src/contracts/pods/EigenPod.sol"; -import "src/contracts/pods/EigenPodManager.sol"; -import "src/contracts/permissions/PauserRegistry.sol"; -import "src/contracts/interfaces/IETHPOSDeposit.sol"; - -import "src/test/utils/Operators.sol"; -import "src/test/mocks/ETHDepositMock.sol"; -import "src/test/mocks/EmptyContract.sol"; - -contract EigenLayerDeployer is Operators { - Vm cheats = Vm(VM_ADDRESS); - - // EigenLayer contracts - ProxyAdmin eigenLayerProxyAdmin; - PauserRegistry eigenLayerPauserReg; - - AVSDirectory avsDirectory; - AllocationManager allocationManager; - DelegationManager delegation; - StrategyManager strategyManager; - EigenPodManager eigenPodManager; - IEigenPod pod; - IETHPOSDeposit ethPOSDeposit; - IBeacon eigenPodBeacon; - - // testing/mock contracts - IERC20 eigenToken; - IERC20 weth; - StrategyBase wethStrat; - StrategyBase eigenStrat; - StrategyBase baseStrategyImplementation; - EmptyContract emptyContract; - - mapping(uint256 => IStrategy) strategies; - - uint32 DEALLOCATION_DELAY = 17.5 days; - uint32 ALLOCATION_CONFIGURATION_DELAY = 21 days; - - //from testing seed phrase - bytes32 priv_key_0 = 0x1234567812345678123456781234567812345678123456781234567812345678; - bytes32 priv_key_1 = 0x1234567812345678123456781234567812345698123456781234567812348976; - - //strategy indexes for undelegation (see commitUndelegation function) - uint256[] strategyIndexes; - address[2] stakers; - address sample_registrant = cheats.addr(436364636); - - address[] slashingContracts; - - uint256 wethInitialSupply = 10e50; - uint256 constant eigenTotalSupply = 1000e18; - uint256 nonce = 69; - uint256 gasLimit = 750000; - IStrategy[] initializeStrategiesToSetDelayBlocks; - uint256[] initializeWithdrawalDelayBlocks; - uint256 minWithdrawalDelayBlocks = 0; - uint32 PARTIAL_WITHDRAWAL_FRAUD_PROOF_PERIOD_BLOCKS = 7 days / 12 seconds; - uint256 REQUIRED_BALANCE_WEI = 32 ether; - uint64 MAX_PARTIAL_WTIHDRAWAL_AMOUNT_GWEI = 1 ether / 1e9; - uint64 GOERLI_GENESIS_TIME = 1616508000; - - address pauser; - address unpauser; - address theMultiSig = address(420); - address operator = address(0x4206904396bF2f8b173350ADdEc5007A52664293); //sk: e88d9d864d5d731226020c5d2f02b62a4ce2a4534a39c225d32d3db795f83319 - address acct_0 = cheats.addr(uint256(priv_key_0)); - address acct_1 = cheats.addr(uint256(priv_key_1)); - address _challenger = address(0x6966904396bF2f8b173350bCcec5007A52669873); - address eigenLayerReputedMultisig = address(this); - - address eigenLayerProxyAdminAddress; - address eigenLayerPauserRegAddress; - address delegationAddress; - address strategyManagerAddress; - address eigenPodManagerAddress; - address podAddress; - address eigenPodBeaconAddress; - address emptyContractAddress; - address operationsMultisig; - address executorMultisig; - - // addresses excluded from fuzzing due to abnormal behavior. TODO: @Sidu28 define this better and give it a clearer name - mapping(address => bool) fuzzedAddressMapping; - - //ensures that a passed in address is not set to true in the fuzzedAddressMapping - modifier fuzzedAddress(address addr) virtual { - cheats.assume(fuzzedAddressMapping[addr] == false); - _; - } - - modifier cannotReinit() { - cheats.expectRevert(bytes("Initializable: contract is already initialized")); - _; - } - - //performs basic deployment before each test - function setUp() public virtual { - try vm.envUint("CHAIN_ID") returns (uint256 chainId) { - if (chainId == 31337) { - _deployEigenLayerContractsLocal(); - } - // If CHAIN_ID ENV is not set, assume local deployment on 31337 - } catch { - _deployEigenLayerContractsLocal(); - } - - fuzzedAddressMapping[address(0)] = true; - fuzzedAddressMapping[address(avsDirectory)] = true; - fuzzedAddressMapping[address(allocationManager)] = true; - fuzzedAddressMapping[address(eigenLayerProxyAdmin)] = true; - fuzzedAddressMapping[address(strategyManager)] = true; - fuzzedAddressMapping[address(eigenPodManager)] = true; - fuzzedAddressMapping[address(delegation)] = true; - fuzzedAddressMapping[address(eigenLayerPauserReg)] = true; - } - - function _deployEigenLayerContractsLocal() internal { - pauser = address(69); - unpauser = address(489); - // deploy proxy admin for ability to upgrade proxy contracts - eigenLayerProxyAdmin = new ProxyAdmin(); - - //deploy pauser registry - address[] memory pausers = new address[](1); - pausers[0] = pauser; - eigenLayerPauserReg = new PauserRegistry(pausers, unpauser); - - /** - * First, deploy upgradeable proxy contracts that **will point** to the implementations. Since the implementation contracts are - * not yet deployed, we give these proxies an empty contract as the initial implementation, to act as if they have no code. - */ - emptyContract = new EmptyContract(); - avsDirectory = AVSDirectory( - address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) - ); - allocationManager = AllocationManager( - address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) - ); - delegation = DelegationManager( - address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) - ); - strategyManager = StrategyManager( - address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) - ); - eigenPodManager = EigenPodManager( - address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) - ); - ethPOSDeposit = new ETHPOSDepositMock(); - pod = new EigenPod( - ethPOSDeposit, - eigenPodManager, - GOERLI_GENESIS_TIME - ); - - eigenPodBeacon = new UpgradeableBeacon(address(pod)); - - // Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs - DelegationManager delegationImplementation = new DelegationManager( - avsDirectory, - strategyManager, - eigenPodManager, - allocationManager, - 17.5 days // min alloc delay - ); - - StrategyManager strategyManagerImplementation = new StrategyManager(delegation); - EigenPodManager eigenPodManagerImplementation = new EigenPodManager( - ethPOSDeposit, - eigenPodBeacon, - strategyManager, - delegation - ); - - - AVSDirectory avsDirectoryImplementation = new AVSDirectory( - delegation, - DEALLOCATION_DELAY - ); - - AllocationManager allocationManagerImplementation = new AllocationManager(delegation, avsDirectory, DEALLOCATION_DELAY, ALLOCATION_CONFIGURATION_DELAY); - - // Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them. - eigenLayerProxyAdmin.upgradeAndCall( - ITransparentUpgradeableProxy(payable(address(avsDirectory))), - address(avsDirectoryImplementation), - abi.encodeWithSelector( - AVSDirectory.initialize.selector, - eigenLayerReputedMultisig, - eigenLayerPauserReg, - 0 /*initialPausedStatus*/ - ) - ); - eigenLayerProxyAdmin.upgradeAndCall( - ITransparentUpgradeableProxy(payable(address(delegation))), - address(delegationImplementation), - abi.encodeWithSelector( - DelegationManager.initialize.selector, - eigenLayerReputedMultisig, - eigenLayerPauserReg, - 0 /*initialPausedStatus*/, - minWithdrawalDelayBlocks, - initializeStrategiesToSetDelayBlocks, - initializeWithdrawalDelayBlocks - ) - ); - eigenLayerProxyAdmin.upgradeAndCall( - ITransparentUpgradeableProxy(payable(address(strategyManager))), - address(strategyManagerImplementation), - abi.encodeWithSelector( - StrategyManager.initialize.selector, - eigenLayerReputedMultisig, - eigenLayerReputedMultisig, - eigenLayerPauserReg, - 0 /*initialPausedStatus*/ - ) - ); - eigenLayerProxyAdmin.upgradeAndCall( - ITransparentUpgradeableProxy(payable(address(eigenPodManager))), - address(eigenPodManagerImplementation), - abi.encodeWithSelector( - EigenPodManager.initialize.selector, - eigenLayerReputedMultisig, - eigenLayerPauserReg, - 0 /*initialPausedStatus*/ - ) - ); - eigenLayerProxyAdmin.upgradeAndCall( - ITransparentUpgradeableProxy(payable(address(allocationManager))), - address(allocationManagerImplementation), - abi.encodeWithSelector( - AllocationManager.initialize.selector, - eigenLayerReputedMultisig, - eigenLayerPauserReg, - 0 /*initialPausedStatus*/ - ) - ); - - //simple ERC20 (**NOT** WETH-like!), used in a test strategy - weth = new ERC20PresetFixedSupply("weth", "WETH", wethInitialSupply, address(this)); - - // deploy StrategyBase contract implementation, then create upgradeable proxy that points to implementation and initialize it - baseStrategyImplementation = new StrategyBase(strategyManager); - wethStrat = StrategyBase( - address( - new TransparentUpgradeableProxy( - address(baseStrategyImplementation), - address(eigenLayerProxyAdmin), - abi.encodeWithSelector(StrategyBase.initialize.selector, weth, eigenLayerPauserReg) - ) - ) - ); - - eigenToken = new ERC20PresetFixedSupply("eigen", "EIGEN", wethInitialSupply, address(this)); - - // deploy upgradeable proxy that points to StrategyBase implementation and initialize it - eigenStrat = StrategyBase( - address( - new TransparentUpgradeableProxy( - address(baseStrategyImplementation), - address(eigenLayerProxyAdmin), - abi.encodeWithSelector(StrategyBase.initialize.selector, eigenToken, eigenLayerPauserReg) - ) - ) - ); - - stakers = [acct_0, acct_1]; - } - - function _setAddresses(string memory config) internal { - eigenLayerProxyAdminAddress = stdJson.readAddress(config, ".addresses.eigenLayerProxyAdmin"); - eigenLayerPauserRegAddress = stdJson.readAddress(config, ".addresses.eigenLayerPauserReg"); - delegationAddress = stdJson.readAddress(config, ".addresses.delegation"); - strategyManagerAddress = stdJson.readAddress(config, ".addresses.strategyManager"); - eigenPodManagerAddress = stdJson.readAddress(config, ".addresses.eigenPodManager"); - emptyContractAddress = stdJson.readAddress(config, ".addresses.emptyContract"); - operationsMultisig = stdJson.readAddress(config, ".parameters.operationsMultisig"); - executorMultisig = stdJson.readAddress(config, ".parameters.executorMultisig"); - } -} diff --git a/src/test/EigenLayerTestHelper.t.sol b/src/test/EigenLayerTestHelper.t.sol deleted file mode 100644 index 282c23ed9..000000000 --- a/src/test/EigenLayerTestHelper.t.sol +++ /dev/null @@ -1,498 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.27; - -import "../test/EigenLayerDeployer.t.sol"; -import "../contracts/interfaces/ISignatureUtils.sol"; - -contract EigenLayerTestHelper is EigenLayerDeployer { - uint8 durationToInit = 2; - uint256 public SECP256K1N_MODULUS = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141; - uint256 public SECP256K1N_MODULUS_HALF = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0; - - uint256[] sharesBefore; - uint256[] balanceBefore; - uint256[] priorTotalShares; - uint256[] strategyTokenBalance; - - /** - * @notice Helper function to test `initiateDelegation` functionality. Handles registering as an operator, depositing tokens - * into both Weth and Eigen strategies, as well as delegating assets from "stakers" to the operator. - * @param operatorIndex is the index of the operator to use from the test-data/operators.json file - * @param amountEigenToDeposit amount of eigen token to deposit - * @param amountEthToDeposit amount of eth to deposit - */ - - function _testInitiateDelegation( - uint8 operatorIndex, - uint256 amountEigenToDeposit, - uint256 amountEthToDeposit - ) public returns (uint256 amountEthStaked, uint256 amountEigenStaked) { - address operator = getOperatorAddress(operatorIndex); - - //setting up operator's delegation terms - IDelegationManagerTypes.OperatorDetails memory operatorDetails = IDelegationManagerTypes.OperatorDetails({ - __deprecated_earningsReceiver: operator, - delegationApprover: address(0), - __deprecated_stakerOptOutWindowBlocks: 0 - }); - _testRegisterAsOperator(operator, 0, operatorDetails); - - for (uint256 i; i < stakers.length; i++) { - //initialize weth, eigen and eth balances for staker - eigenToken.transfer(stakers[i], amountEigenToDeposit); - weth.transfer(stakers[i], amountEthToDeposit); - - //deposit staker's eigen and weth into strategy manager - _testDepositEigen(stakers[i], amountEigenToDeposit); - _testDepositWeth(stakers[i], amountEthToDeposit); - - //delegate the staker's deposits to operator - uint256 operatorEigenSharesBefore = delegation.operatorShares(operator, eigenStrat); - uint256 operatorWETHSharesBefore = delegation.operatorShares(operator, wethStrat); - _testDelegateToOperator(stakers[i], operator); - //verify that `increaseOperatorShares` worked - assertTrue( - delegation.operatorShares(operator, eigenStrat) - operatorEigenSharesBefore == amountEigenToDeposit - ); - assertTrue(delegation.operatorShares(operator, wethStrat) - operatorWETHSharesBefore == amountEthToDeposit); - } - amountEthStaked += delegation.operatorShares(operator, wethStrat); - amountEigenStaked += delegation.operatorShares(operator, eigenStrat); - - return (amountEthStaked, amountEigenStaked); - } - - /** - * @notice Register 'sender' as an operator, setting their 'OperatorDetails' in DelegationManager to 'operatorDetails', verifies - * that the storage of DelegationManager contract is updated appropriately - * - * @param sender is the address being registered as an operator - * @param operatorDetails is the `sender`'s OperatorDetails struct - */ - function _testRegisterAsOperator( - address sender, - uint32 allocationDelay, - IDelegationManagerTypes.OperatorDetails memory operatorDetails - ) internal { - cheats.startPrank(sender); - string memory emptyStringForMetadataURI; - delegation.registerAsOperator(operatorDetails, allocationDelay, emptyStringForMetadataURI); - assertTrue(delegation.isOperator(sender), "testRegisterAsOperator: sender is not a operator"); - - assertTrue( - keccak256(abi.encode(delegation.operatorDetails(sender))) == keccak256(abi.encode(operatorDetails)), - "_testRegisterAsOperator: operatorDetails not set appropriately" - ); - - assertTrue(delegation.isDelegated(sender), "_testRegisterAsOperator: sender not marked as actively delegated"); - cheats.stopPrank(); - } - - /** - * @notice Deposits `amountToDeposit` of WETH from address `sender` into `wethStrat`. - * @param sender The address to spoof calls from using `cheats.startPrank(sender)` - * @param amountToDeposit Amount of WETH that is first *transferred from this contract to `sender`* and then deposited by `sender` into `stratToDepositTo` - */ - function _testDepositWeth(address sender, uint256 amountToDeposit) internal returns (uint256 amountDeposited) { - cheats.assume(amountToDeposit <= wethInitialSupply); - amountDeposited = _testDepositToStrategy(sender, amountToDeposit, weth, wethStrat); - } - - /** - * @notice Deposits `amountToDeposit` of EIGEN from address `sender` into `eigenStrat`. - * @param sender The address to spoof calls from using `cheats.startPrank(sender)` - * @param amountToDeposit Amount of EIGEN that is first *transferred from this contract to `sender`* and then deposited by `sender` into `stratToDepositTo` - */ - function _testDepositEigen(address sender, uint256 amountToDeposit) internal returns (uint256 amountDeposited) { - cheats.assume(amountToDeposit <= eigenTotalSupply); - amountDeposited = _testDepositToStrategy(sender, amountToDeposit, eigenToken, eigenStrat); - } - - /** - * @notice Deposits `amountToDeposit` of `underlyingToken` from address `sender` into `stratToDepositTo`. - * *If* `sender` has zero shares prior to deposit, *then* checks that `stratToDepositTo` is correctly added to their `stakerStrategyList` array. - * - * @param sender The address to spoof calls from using `cheats.startPrank(sender)` - * @param amountToDeposit Amount of WETH that is first *transferred from this contract to `sender`* and then deposited by `sender` into `stratToDepositTo` - */ - function _testDepositToStrategy( - address sender, - uint256 amountToDeposit, - IERC20 underlyingToken, - IStrategy stratToDepositTo - ) internal returns (uint256 amountDeposited) { - // deposits will revert when amountToDeposit is 0 - cheats.assume(amountToDeposit > 0); - - // whitelist the strategy for deposit, in case it wasn't before - { - cheats.startPrank(strategyManager.strategyWhitelister()); - IStrategy[] memory _strategy = new IStrategy[](1); - _strategy[0] = stratToDepositTo; - strategyManager.addStrategiesToDepositWhitelist(_strategy); - cheats.stopPrank(); - } - - uint256 operatorSharesBefore = strategyManager.stakerDepositShares(sender, stratToDepositTo); - // assumes this contract already has the underlying token! - uint256 contractBalance = underlyingToken.balanceOf(address(this)); - // check the expected output - uint256 expectedSharesOut = stratToDepositTo.underlyingToShares(amountToDeposit); - // logging and error for misusing this function (see assumption above) - if (amountToDeposit > contractBalance) { - emit log("amountToDeposit > contractBalance"); - emit log_named_uint("amountToDeposit is", amountToDeposit); - emit log_named_uint("while contractBalance is", contractBalance); - revert("_testDepositToStrategy failure"); - } else { - underlyingToken.transfer(sender, amountToDeposit); - cheats.startPrank(sender); - underlyingToken.approve(address(strategyManager), type(uint256).max); - strategyManager.depositIntoStrategy(stratToDepositTo, underlyingToken, amountToDeposit); - amountDeposited = amountToDeposit; - - //check if depositor has never used this strat, that it is added correctly to stakerStrategyList array. - if (operatorSharesBefore == 0) { - // check that strategy is appropriately added to dynamic array of all of sender's strategies - assertTrue( - strategyManager.stakerStrategyList(sender, strategyManager.stakerStrategyListLength(sender) - 1) == - stratToDepositTo, - "_testDepositToStrategy: stakerStrategyList array updated incorrectly" - ); - } - - // check that the shares out match the expected amount out - assertEq( - strategyManager.stakerDepositShares(sender, stratToDepositTo) - operatorSharesBefore, - expectedSharesOut, - "_testDepositToStrategy: actual shares out should match expected shares out" - ); - } - cheats.stopPrank(); - } - - /** - * @notice tries to delegate from 'staker' to 'operator', verifies that staker has at least some shares - * delegatedShares update correctly for 'operator' and delegated status is updated correctly for 'staker' - * @param staker the staker address to delegate from - * @param operator the operator address to delegate to - */ - function _testDelegateToOperator(address staker, address operator) internal { - //staker-specific information - (IStrategy[] memory delegateStrategies, uint256[] memory delegateShares) = strategyManager.getDeposits(staker); - - uint256 numStrats = delegateShares.length; - assertTrue(numStrats != 0, "_testDelegateToOperator: delegating from address with no deposits"); - uint256[] memory inititalSharesInStrats = new uint256[](numStrats); - for (uint256 i = 0; i < numStrats; ++i) { - inititalSharesInStrats[i] = delegation.operatorShares(operator, delegateStrategies[i]); - } - - cheats.startPrank(staker); - ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry; - delegation.delegateTo(operator, signatureWithExpiry, bytes32(0)); - cheats.stopPrank(); - - assertTrue( - delegation.delegatedTo(staker) == operator, - "_testDelegateToOperator: delegated address not set appropriately" - ); - assertTrue(delegation.isDelegated(staker), "_testDelegateToOperator: delegated status not set appropriately"); - - for (uint256 i = 0; i < numStrats; ++i) { - uint256 operatorSharesBefore = inititalSharesInStrats[i]; - uint256 operatorSharesAfter = delegation.operatorShares(operator, delegateStrategies[i]); - assertTrue( - operatorSharesAfter == (operatorSharesBefore + delegateShares[i]), - "_testDelegateToOperator: delegatedShares not increased correctly" - ); - } - } - - /** - * @notice deploys 'numStratsToAdd' strategies contracts and initializes them to treat `underlyingToken` as their underlying token - * and then deposits 'amountToDeposit' to each of them from 'sender' - * - * @param sender address that is depositing into the strategies - * @param amountToDeposit amount being deposited - * @param numStratsToAdd number of strategies that are being deployed and deposited into - */ - function _testDepositStrategies(address sender, uint256 amountToDeposit, uint8 numStratsToAdd) internal { - // hard-coded input - IERC20 underlyingToken = weth; - - cheats.assume(numStratsToAdd > 0 && numStratsToAdd <= 20); - IStrategy[] memory stratsToDepositTo = new IStrategy[](numStratsToAdd); - for (uint8 i = 0; i < numStratsToAdd; ++i) { - stratsToDepositTo[i] = StrategyBase( - address( - new TransparentUpgradeableProxy( - address(baseStrategyImplementation), - address(eigenLayerProxyAdmin), - abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, eigenLayerPauserReg) - ) - ) - ); - _testDepositToStrategy(sender, amountToDeposit, weth, StrategyBase(address(stratsToDepositTo[i]))); - } - for (uint8 i = 0; i < numStratsToAdd; ++i) { - // check that strategy is appropriately added to dynamic array of all of sender's strategies - assertTrue( - strategyManager.stakerStrategyList(sender, i) == stratsToDepositTo[i], - "stakerStrategyList array updated incorrectly" - ); - - // TODO: perhaps remove this is we can. seems brittle if we don't track the number of strategies somewhere - //store strategy in mapping of strategies - strategies[i] = IStrategy(address(stratsToDepositTo[i])); - } - } - - /** - * @notice Creates a queued withdrawal from `staker`. Begins by registering the staker as a delegate (if specified), then deposits `amountToDeposit` - * into the WETH strategy, and then queues a withdrawal using - * `strategyManager.queueWithdrawal(strategyIndexes, strategyArray, tokensArray, shareAmounts, withdrawer)` - * @notice After initiating a queued withdrawal, this test checks that `strategyManager.canCompleteQueuedWithdrawal` immediately returns the correct - * response depending on whether `staker` is delegated or not. - * @param staker The address to initiate the queued withdrawal - * @param registerAsOperator If true, `staker` will also register as a delegate in the course of this function - * @param amountToDeposit The amount of WETH to deposit - */ - function _createQueuedWithdrawal( - address staker, - bool registerAsOperator, - uint256 amountToDeposit, - IStrategy[] memory strategyArray, - uint256[] memory shareAmounts, - uint256[] memory strategyIndexes, - address withdrawer - ) internal returns (bytes32 withdrawalRoot, IDelegationManagerTypes.Withdrawal memory queuedWithdrawal) { - require(amountToDeposit >= shareAmounts[0], "_createQueuedWithdrawal: sanity check failed"); - - // we do this here to ensure that `staker` is delegated if `registerAsOperator` is true - if (registerAsOperator) { - assertTrue(!delegation.isDelegated(staker), "_createQueuedWithdrawal: staker is already delegated"); - IDelegationManagerTypes.OperatorDetails memory operatorDetails = IDelegationManagerTypes.OperatorDetails({ - __deprecated_earningsReceiver: staker, - delegationApprover: address(0), - __deprecated_stakerOptOutWindowBlocks: 0 - }); - _testRegisterAsOperator(staker, 0, operatorDetails); - assertTrue( - delegation.isDelegated(staker), - "_createQueuedWithdrawal: staker isn't delegated when they should be" - ); - } - - queuedWithdrawal = IDelegationManagerTypes.Withdrawal({ - strategies: strategyArray, - scaledShares: shareAmounts, - staker: staker, - withdrawer: withdrawer, - nonce: delegation.cumulativeWithdrawalsQueued(staker), - delegatedTo: delegation.delegatedTo(staker), - startTimestamp: uint32(block.timestamp) - }); - - { - //make deposit in WETH strategy - uint256 amountDeposited = _testDepositWeth(staker, amountToDeposit); - // We can't withdraw more than we deposit - if (shareAmounts[0] > amountDeposited) { - cheats.expectRevert("StrategyManager._removeShares: shareAmount too high"); - } - } - - //queue the withdrawal - withdrawalRoot = _testQueueWithdrawal(staker, strategyIndexes, strategyArray, shareAmounts, withdrawer); - return (withdrawalRoot, queuedWithdrawal); - } - - /** - * Helper for ECDSA signatures: combines V and S into VS - if S is greater than SECP256K1N_MODULUS_HALF, then we - * get the modulus, so that the leading bit of s is always 0. Then we set the leading - * bit to be either 0 or 1 based on the value of v, which is either 27 or 28 - */ - function getVSfromVandS(uint8 v, bytes32 s) internal view returns (bytes32) { - if (uint256(s) > SECP256K1N_MODULUS_HALF) { - s = bytes32(SECP256K1N_MODULUS - uint256(s)); - } - - bytes32 vs = s; - if (v == 28) { - vs = bytes32(uint256(s) ^ (1 << 255)); - } - - return vs; - } - - /// @notice registers a fixed address as an operator, delegates to it from a second address, - /// and checks that the operator's voteWeights increase properly - /// @param operator is the operator being delegated to. - /// @param staker is the staker delegating stake to the operator. - /// @param ethAmount is the amount of ETH to deposit into the operator's strategy. - /// @param eigenAmount is the amount of EIGEN to deposit into the operator's strategy. - function _testDelegation( - address operator, - address staker, - uint256 ethAmount, - uint256 eigenAmount - ) internal { - if (!delegation.isOperator(operator)) { - IDelegationManagerTypes.OperatorDetails memory operatorDetails = IDelegationManagerTypes.OperatorDetails({ - __deprecated_earningsReceiver: operator, - delegationApprover: address(0), - __deprecated_stakerOptOutWindowBlocks: 0 - }); - _testRegisterAsOperator(operator, 1, operatorDetails); - } - - uint256 amountBefore = delegation.operatorShares(operator, wethStrat); - - //making additional deposits to the strategies - assertTrue(!delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate"); - _testDepositWeth(staker, ethAmount); - _testDepositEigen(staker, eigenAmount); - _testDelegateToOperator(staker, operator); - assertTrue(delegation.isDelegated(staker) == true, "testDelegation: staker is not delegate"); - - (/*IStrategy[] memory updatedStrategies*/, uint256[] memory updatedShares) = strategyManager.getDeposits(staker); - - IStrategy _strat = wethStrat; - // IStrategy _strat = strategyManager.stakerStrategyList(staker, 0); - assertTrue(address(_strat) != address(0), "stakerStrategyList not updated correctly"); - - assertTrue( - delegation.operatorShares(operator, _strat) - updatedShares[0] == amountBefore, - "ETH operatorShares not updated correctly" - ); - } - - /** - * @notice Helper function to complete an existing queued withdrawal in shares - * @param depositor is the address of the staker who queued a withdrawal - * @param strategyArray is the array of strategies to withdraw from - * @param tokensArray is the array of tokens to withdraw from said strategies - * @param shareAmounts is the array of shares to be withdrawn from said strategies - * @param delegatedTo is the address the staker has delegated their shares to - * @param withdrawalStartTimestamp the block number of the original queued withdrawal - * @param middlewareTimesIndex index in the middlewareTimes array used to queue this withdrawal - */ - - function _testCompleteQueuedWithdrawalShares( - address depositor, - IStrategy[] memory strategyArray, - IERC20[] memory tokensArray, - uint256[] memory shareAmounts, - address delegatedTo, - address withdrawer, - uint256 nonce, - uint32 withdrawalStartTimestamp, - uint256 middlewareTimesIndex - ) internal { - cheats.startPrank(withdrawer); - - for (uint256 i = 0; i < strategyArray.length; i++) { - sharesBefore.push(strategyManager.stakerDepositShares(withdrawer, strategyArray[i])); - } - - IDelegationManagerTypes.Withdrawal memory queuedWithdrawal = IDelegationManagerTypes.Withdrawal({ - strategies: strategyArray, - scaledShares: shareAmounts, - staker: depositor, - withdrawer: withdrawer, - nonce: nonce, - startTimestamp: withdrawalStartTimestamp, - delegatedTo: delegatedTo - }); - - // complete the queued withdrawal - delegation.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, false); - - for (uint256 i = 0; i < strategyArray.length; i++) { - require( - strategyManager.stakerDepositShares(withdrawer, strategyArray[i]) == sharesBefore[i] + shareAmounts[i], - "_testCompleteQueuedWithdrawalShares: withdrawer shares not incremented" - ); - } - cheats.stopPrank(); - } - - /** - * @notice Helper function to complete an existing queued withdrawal in tokens - * @param depositor is the address of the staker who queued a withdrawal - * @param strategyArray is the array of strategies to withdraw from - * @param tokensArray is the array of tokens to withdraw from said strategies - * @param shareAmounts is the array of shares to be withdrawn from said strategies - * @param delegatedTo is the address the staker has delegated their shares to - * @param withdrawalStartTimestamp the block number of the original queued withdrawal - * @param middlewareTimesIndex index in the middlewareTimes array used to queue this withdrawal - */ - function _testCompleteQueuedWithdrawalTokens( - address depositor, - IStrategy[] memory strategyArray, - IERC20[] memory tokensArray, - uint256[] memory shareAmounts, - address delegatedTo, - address withdrawer, - uint256 nonce, - uint32 withdrawalStartTimestamp, - uint256 middlewareTimesIndex - ) internal { - cheats.startPrank(withdrawer); - - for (uint256 i = 0; i < strategyArray.length; i++) { - balanceBefore.push(strategyArray[i].underlyingToken().balanceOf(withdrawer)); - priorTotalShares.push(strategyArray[i].totalShares()); - strategyTokenBalance.push(strategyArray[i].underlyingToken().balanceOf(address(strategyArray[i]))); - } - - IDelegationManagerTypes.Withdrawal memory queuedWithdrawal = IDelegationManagerTypes.Withdrawal({ - strategies: strategyArray, - staker: depositor, - withdrawer: withdrawer, - nonce: nonce, - startTimestamp: withdrawalStartTimestamp, - delegatedTo: delegatedTo, - scaledShares: shareAmounts - }); - // complete the queued withdrawal - delegation.completeQueuedWithdrawal(queuedWithdrawal, tokensArray, true); - - for (uint256 i = 0; i < strategyArray.length; i++) { - //uint256 strategyTokenBalance = strategyArray[i].underlyingToken().balanceOf(address(strategyArray[i])); - uint256 tokenBalanceDelta = (strategyTokenBalance[i] * shareAmounts[i]) / priorTotalShares[i]; - - // filter out unrealistic case, where the withdrawer is the strategy contract itself - cheats.assume(withdrawer != address(strategyArray[i])); - require( - strategyArray[i].underlyingToken().balanceOf(withdrawer) == balanceBefore[i] + tokenBalanceDelta, - "_testCompleteQueuedWithdrawalTokens: withdrawer balance not incremented" - ); - } - cheats.stopPrank(); - } - - function _testQueueWithdrawal( - address depositor, - uint256[] memory /*strategyIndexes*/, - IStrategy[] memory strategyArray, - uint256[] memory shareAmounts, - address withdrawer - ) internal returns (bytes32) { - cheats.startPrank(depositor); - - IDelegationManagerTypes.QueuedWithdrawalParams[] memory params = new IDelegationManagerTypes.QueuedWithdrawalParams[](1); - - params[0] = IDelegationManagerTypes.QueuedWithdrawalParams({ - strategies: strategyArray, - shares: shareAmounts, - withdrawer: withdrawer - }); - - bytes32[] memory withdrawalRoots = new bytes32[](1); - withdrawalRoots = delegation.queueWithdrawals(params); - cheats.stopPrank(); - return withdrawalRoots[0]; - } -} diff --git a/src/test/Pausable.t.sol b/src/test/Pausable.t.sol deleted file mode 100644 index c946492cf..000000000 --- a/src/test/Pausable.t.sol +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.27; - -import "./EigenLayerTestHelper.t.sol"; - -contract PausableTests is EigenLayerTestHelper { - - /// @notice Emitted when the `pauserRegistry` is set to `newPauserRegistry`. - event PauserRegistrySet(IPauserRegistry pauserRegistry, IPauserRegistry newPauserRegistry); - - ///@dev test that pausing a contract works - function testPausingWithdrawalsFromStrategyManager(uint256 amountToDeposit, uint256 amountToWithdraw) public { - cheats.assume(amountToDeposit <= 1e38 - 1); - cheats.assume(amountToDeposit <= weth.balanceOf(address(this))); - // if first deposit amount to base strategy is too small, it will revert. ignore that case here. - cheats.assume(amountToDeposit >= 1); - cheats.assume(amountToWithdraw <= amountToDeposit); - - address sender = getOperatorAddress(0); - _testDepositToStrategy(sender, amountToDeposit, weth, wethStrat); - - cheats.startPrank(pauser); - strategyManager.pause(type(uint256).max); - cheats.stopPrank(); - - // uint256 strategyIndex = 0; - - cheats.prank(sender); - - // TODO: write this to work with completing a queued withdrawal - // cheats.expectRevert(bytes("Pausable: paused")); - // strategyManager.withdrawFromStrategy(strategyIndex, wethStrat, weth, amountToWithdraw); - // cheats.stopPrank(); - } - - function testUnauthorizedPauserStrategyManager(address unauthorizedPauser) - public - fuzzedAddress(unauthorizedPauser) - { - cheats.assume(!eigenLayerPauserReg.isPauser(unauthorizedPauser)); - cheats.startPrank(unauthorizedPauser); - cheats.expectRevert(IPausable.OnlyPauser.selector); - strategyManager.pause(type(uint256).max); - cheats.stopPrank(); - } - - function testSetPauser(address newPauser) public fuzzedAddress(newPauser) { - cheats.startPrank(unpauser); - eigenLayerPauserReg.setIsPauser(newPauser, true); - cheats.stopPrank(); - } - - function testSetUnpauser(address newUnpauser) public fuzzedAddress(newUnpauser) { - cheats.startPrank(unpauser); - eigenLayerPauserReg.setUnpauser(newUnpauser); - cheats.stopPrank(); - } - - function testSetPauserUnauthorized(address fakePauser, address newPauser) - public - fuzzedAddress(newPauser) - fuzzedAddress(fakePauser) - { - cheats.assume(fakePauser != eigenLayerPauserReg.unpauser()); - cheats.startPrank(fakePauser); - cheats.expectRevert(IPausable.OnlyUnpauser.selector); - eigenLayerPauserReg.setIsPauser(newPauser, true); - cheats.stopPrank(); - } - - function testSetPauserRegistryUnpauser(IPauserRegistry newPauserRegistry) public { - cheats.assume(address(newPauserRegistry) != address(0)); - IPauserRegistry oldPauserRegistry = strategyManager.pauserRegistry(); - cheats.prank(unpauser); - cheats.expectEmit(true, true, true, true, address(strategyManager)); - emit PauserRegistrySet(oldPauserRegistry, newPauserRegistry); - strategyManager.setPauserRegistry(newPauserRegistry); - - assertEq(address(newPauserRegistry), address(strategyManager.pauserRegistry())); - } - - function testSetPauserRegistyUnauthorized(IPauserRegistry newPauserRegistry, address notUnpauser) public fuzzedAddress(notUnpauser) { - cheats.assume(notUnpauser != eigenLayerPauserReg.unpauser()); - - cheats.prank(notUnpauser); - cheats.expectRevert(IPausable.OnlyUnpauser.selector); - strategyManager.setPauserRegistry(newPauserRegistry); - } -} diff --git a/src/test/Strategy.t.sol b/src/test/Strategy.t.sol deleted file mode 100644 index 6941dc968..000000000 --- a/src/test/Strategy.t.sol +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.27; - -import "./EigenLayerTestHelper.t.sol"; -import "../contracts/core/StrategyManagerStorage.sol"; - -contract StrategyTests is EigenLayerTestHelper { - /// @notice This function tests to ensure that a delegation contract - /// cannot be intitialized multiple times - function testCannotInitMultipleTimesDelegation() public cannotReinit { - wethStrat.initialize(weth, eigenLayerPauserReg); - } - - ///@notice This function tests to ensure that only the strategyManager - /// can deposit into a strategy - ///@param invalidDepositor is the non-registered depositor - function testInvalidCalltoDeposit(address invalidDepositor) public fuzzedAddress(invalidDepositor) { - IERC20 underlyingToken = wethStrat.underlyingToken(); - - cheats.startPrank(invalidDepositor); - cheats.expectRevert(IStrategyErrors.OnlyStrategyManager.selector); - wethStrat.deposit(underlyingToken, 1e18); - cheats.stopPrank(); - } - - ///@notice This function tests to ensure that only the strategyManager - /// can deposit into a strategy - ///@param invalidWithdrawer is the non-registered withdrawer - ///@param depositor is the depositor for which the shares are being withdrawn - function testInvalidCalltoWithdraw(address depositor, address invalidWithdrawer) - public - fuzzedAddress(invalidWithdrawer) - { - IERC20 underlyingToken = wethStrat.underlyingToken(); - - cheats.startPrank(invalidWithdrawer); - cheats.expectRevert(IStrategyErrors.OnlyStrategyManager.selector); - wethStrat.withdraw(depositor, underlyingToken, 1e18); - cheats.stopPrank(); - } - - ///@notice This function tests ensures that withdrawing for a depositor that never - /// actually deposited fails. - ///@param depositor is the depositor for which the shares are being withdrawn - function testWithdrawalExceedsTotalShares(address depositor, uint256 shares) public fuzzedAddress(depositor) { - cheats.assume(shares > strategyManager.stakerDepositShares(depositor, wethStrat)); - IERC20 underlyingToken = wethStrat.underlyingToken(); - - cheats.startPrank(address(strategyManager)); - cheats.expectRevert(IStrategyErrors.WithdrawalAmountExceedsTotalDeposits.selector); - wethStrat.withdraw(depositor, underlyingToken, shares); - - cheats.stopPrank(); - } -} diff --git a/src/test/Withdrawals.t.sol b/src/test/Withdrawals.t.sol deleted file mode 100644 index 5a23c59e3..000000000 --- a/src/test/Withdrawals.t.sol +++ /dev/null @@ -1,287 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.27; - -import "../test/EigenLayerTestHelper.t.sol"; - -contract WithdrawalTests is EigenLayerTestHelper { - // packed info used to help handle stack-too-deep errors - struct DataForTestWithdrawal { - IStrategy[] delegatorStrategies; - uint256[] delegatorShares; - address withdrawer; - uint96 nonce; - } - - bytes32 defaultOperatorId = bytes32(uint256(0)); - - function setUp() public virtual override { - EigenLayerDeployer.setUp(); - } - - //This function helps with stack too deep issues with "testWithdrawal" test - function testWithdrawalWrapper( - address operator, - address depositor, - uint96 ethAmount, - uint96 eigenAmount, - bool withdrawAsTokens, - bool RANDAO - ) public fuzzedAddress(operator) fuzzedAddress(depositor) { - cheats.assume(depositor != operator); - cheats.assume(ethAmount >= 1 && ethAmount <= 1e18); - cheats.assume(eigenAmount >= 1 && eigenAmount <= 1e18); - - address withdrawer = depositor; - - if (RANDAO) { - _testWithdrawalAndDeregistration(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsTokens); - } else { - _testWithdrawalWithStakeUpdate(operator, depositor, withdrawer, ethAmount, eigenAmount, withdrawAsTokens); - } - } - - /// @notice test staker's ability to undelegate/withdraw from an operator. - /// @param operator is the operator being delegated to. - /// @param depositor is the staker delegating stake to the operator. - function _testWithdrawalAndDeregistration( - address operator, - address depositor, - address withdrawer, - uint96 ethAmount, - uint96 eigenAmount, - bool withdrawAsTokens - ) internal { - _initiateDelegation(operator, depositor, ethAmount, eigenAmount); - - address delegatedTo = delegation.delegatedTo(depositor); - - // packed data structure to deal with stack-too-deep issues - DataForTestWithdrawal memory dataForTestWithdrawal; - - // scoped block to deal with stack-too-deep issues - { - //delegator-specific information - (IStrategy[] memory delegatorStrategies, uint256[] memory delegatorShares) = strategyManager.getDeposits( - depositor - ); - dataForTestWithdrawal.delegatorStrategies = delegatorStrategies; - dataForTestWithdrawal.delegatorShares = delegatorShares; - dataForTestWithdrawal.withdrawer = withdrawer; - // harcoded nonce value - dataForTestWithdrawal.nonce = 0; - } - - uint256[] memory strategyIndexes = new uint256[](2); - IERC20[] memory tokensArray = new IERC20[](2); - { - // hardcoded values - strategyIndexes[0] = 0; - strategyIndexes[1] = 0; - tokensArray[0] = weth; - tokensArray[1] = eigenToken; - } - - cheats.warp(uint32(block.timestamp) + 1 days); - cheats.roll(uint32(block.timestamp) + 1 days); - - _testQueueWithdrawal( - depositor, - strategyIndexes, - dataForTestWithdrawal.delegatorStrategies, - dataForTestWithdrawal.delegatorShares, - withdrawer - ); - uint32 queuedWithdrawalBlock = uint32(block.number); - - //now withdrawal block time is before deregistration - cheats.warp(uint32(block.timestamp) + 2 days); - cheats.roll(uint32(block.timestamp) + 2 days); - - // TODO: fix this to properly test the withdrawal - // { - // //warp past the serve until time, which is 3 days from the beginning. THis puts us at 4 days past that point - // cheats.warp(uint32(block.timestamp) + 4 days); - // cheats.roll(uint32(block.timestamp) + 4 days); - - // uint256 middlewareTimeIndex = 1; - // if (withdrawAsTokens) { - // _testCompleteQueuedWithdrawalTokens( - // depositor, - // dataForTestWithdrawal.delegatorStrategies, - // tokensArray, - // dataForTestWithdrawal.delegatorShares, - // delegatedTo, - // dataForTestWithdrawal.withdrawer, - // dataForTestWithdrawal.nonce, - // queuedWithdrawalBlock, - // middlewareTimeIndex - // ); - // } else { - // _testCompleteQueuedWithdrawalShares( - // depositor, - // dataForTestWithdrawal.delegatorStrategies, - // tokensArray, - // dataForTestWithdrawal.delegatorShares, - // delegatedTo, - // dataForTestWithdrawal.withdrawer, - // dataForTestWithdrawal.nonce, - // queuedWithdrawalBlock, - // middlewareTimeIndex - // ); - // } - // } - } - - /// @notice test staker's ability to undelegate/withdraw from an operator. - /// @param operator is the operator being delegated to. - /// @param depositor is the staker delegating stake to the operator. - function _testWithdrawalWithStakeUpdate( - address operator, - address depositor, - address withdrawer, - uint96 ethAmount, - uint96 eigenAmount, - bool withdrawAsTokens - ) public { - _initiateDelegation(operator, depositor, ethAmount, eigenAmount); - - // emit log_named_uint("Linked list element 1", uint256(uint160(address(generalServiceManager1)))); - // emit log_named_uint("Linked list element 2", uint256(uint160(address(generalServiceManager2)))); - // emit log("________________________________________________________________"); - // emit log_named_uint("Middleware 1 Update Block", uint32(block.number)); - - cheats.warp(uint32(block.timestamp) + 1 days); - cheats.roll(uint32(block.number) + 1); - - // emit log_named_uint("Middleware 2 Update Block", uint32(block.number)); - - address delegatedTo = delegation.delegatedTo(depositor); - - // packed data structure to deal with stack-too-deep issues - DataForTestWithdrawal memory dataForTestWithdrawal; - - // scoped block to deal with stack-too-deep issues - { - //delegator-specific information - (IStrategy[] memory delegatorStrategies, uint256[] memory delegatorShares) = strategyManager.getDeposits( - depositor - ); - dataForTestWithdrawal.delegatorStrategies = delegatorStrategies; - dataForTestWithdrawal.delegatorShares = delegatorShares; - dataForTestWithdrawal.withdrawer = withdrawer; - // harcoded nonce value - dataForTestWithdrawal.nonce = 0; - } - - uint256[] memory strategyIndexes = new uint256[](2); - IERC20[] memory tokensArray = new IERC20[](2); - { - // hardcoded values - strategyIndexes[0] = 0; - strategyIndexes[1] = 0; - tokensArray[0] = weth; - tokensArray[1] = eigenToken; - } - - cheats.warp(uint32(block.timestamp) + 1 days); - cheats.roll(uint32(block.number) + 1); - - _testQueueWithdrawal( - depositor, - strategyIndexes, - dataForTestWithdrawal.delegatorStrategies, - dataForTestWithdrawal.delegatorShares, - dataForTestWithdrawal.withdrawer - ); - uint32 queuedWithdrawalBlock = uint32(block.number); - - //now withdrawal block time is before deregistration - cheats.warp(uint32(block.timestamp) + 2 days); - cheats.roll(uint32(block.number) + 2); - - // uint256 prevElement = uint256(uint160(address(generalServiceManager2))); - - cheats.warp(uint32(block.timestamp) + 1 days); - cheats.roll(uint32(block.number) + 1); - - // prevElement = uint256(uint160(address(generalServiceManager1))); - - // TODO: update this to handle blockNumbers instead of timestamps - // { - // //warp past the serve until time, which is 3 days from the beginning. THis puts us at 4 days past that point - // cheats.warp(uint32(block.timestamp) + 4 days); - // cheats.roll(uint32(block.number) + 4); - - // uint256 middlewareTimeIndex = 3; - // if (withdrawAsTokens) { - // _testCompleteQueuedWithdrawalTokens( - // depositor, - // dataForTestWithdrawal.delegatorStrategies, - // tokensArray, - // dataForTestWithdrawal.delegatorShares, - // delegatedTo, - // dataForTestWithdrawal.withdrawer, - // dataForTestWithdrawal.nonce, - // queuedWithdrawalBlock, - // middlewareTimeIndex - // ); - // } else { - // _testCompleteQueuedWithdrawalShares( - // depositor, - // dataForTestWithdrawal.delegatorStrategies, - // tokensArray, - // dataForTestWithdrawal.delegatorShares, - // delegatedTo, - // dataForTestWithdrawal.withdrawer, - // dataForTestWithdrawal.nonce, - // queuedWithdrawalBlock, - // middlewareTimeIndex - // ); - // } - // } - } - - // @notice This function tests to ensure that a delegator can re-delegate to an operator after undelegating. - // @param operator is the operator being delegated to. - // @param staker is the staker delegating stake to the operator. - function testRedelegateAfterWithdrawal( - address operator, - address depositor, - uint96 ethAmount, - uint96 eigenAmount, - bool withdrawAsShares - ) public fuzzedAddress(operator) fuzzedAddress(depositor) { - cheats.assume(depositor != operator); - //this function performs delegation and subsequent withdrawal - testWithdrawalWrapper(operator, depositor, ethAmount, eigenAmount, withdrawAsShares, true); - - cheats.prank(depositor); - delegation.undelegate(depositor); - - //warps past fraudproof time interval - cheats.warp(block.timestamp + 7 days + 1); - _initiateDelegation(operator, depositor, ethAmount, eigenAmount); - } - - // Helper function to begin a delegation - function _initiateDelegation( - address operator, - address staker, - uint96 ethAmount, - uint96 eigenAmount - ) internal fuzzedAddress(operator) fuzzedAddress(staker) fuzzedAmounts(ethAmount, eigenAmount) { - cheats.assume(staker != operator); - // base strategy will revert if these amounts are too small on first deposit - cheats.assume(ethAmount >= 1); - cheats.assume(eigenAmount >= 2); - - _testDelegation(operator, staker, ethAmount, eigenAmount); - } - - modifier fuzzedAmounts(uint256 ethAmount, uint256 eigenAmount) { - cheats.assume(ethAmount >= 0 && ethAmount <= 1e18); - cheats.assume(eigenAmount >= 0 && eigenAmount <= 1e18); - _; - } -} - diff --git a/src/test/ext/slashing-lib.t.py b/src/test/ext/slashing-lib.t.py new file mode 100644 index 000000000..b51e7033f --- /dev/null +++ b/src/test/ext/slashing-lib.t.py @@ -0,0 +1,43 @@ +import sys +import json +from decimal import Decimal + +WAD = Decimal(1e18) + +class SlashingLib: + def mul_wad(self, x: int, y: int) -> int: + return int((Decimal(x) * Decimal(y) / WAD)) + + def div_wad(self, x: int, y: int) -> int: + return int((Decimal(x) * WAD / Decimal(y))) + + def mul_wad_round_up(self, x: int, y: int) -> int: + result = Decimal(x) * Decimal(y) / WAD + return int(result.to_integral_value(rounding="ROUND_UP")) + + def scale_shares_for_queued_withdrawal(self, shares_to_withdraw: int, beacon_chain_scaling_factor: int, operator_magnitude: int) -> int: + return int(Decimal(shares_to_withdraw) * WAD / Decimal(beacon_chain_scaling_factor)) * WAD / Decimal(operator_magnitude) + +class Dispatcher: + def __init__(self): + self.slashing_lib = SlashingLib() + self.args = json.loads(sys.argv[1]) + + def test_scale_shares_for_queued_withdrawal(self): + shares_to_withdraw = self.args['sharesToWithdraw'] + beacon_chain_scaling_factor = self.args['beaconChainScalingFactor'] + operator_magnitude = self.args['operatorMagnitude'] + result = self.slashing_lib.scale_shares_for_queued_withdrawal(shares_to_withdraw, beacon_chain_scaling_factor, operator_magnitude) + + # Encode the result as a 32-byte hex string. + # Equivilent to `abi.encode(uint256(result))`. + encoded_result = int(result).to_bytes(32, byteorder='big') + + # Foundry FFI return values must be printed to stdout. + print(f'0x{encoded_result.hex()}') + + def dispatch(self): + if self.args['method'] == 'scaleSharesForQueuedWithdrawal': + self.test_scale_shares_for_queued_withdrawal() + +Dispatcher().dispatch() \ No newline at end of file diff --git a/src/test/unit/DelegationUnit.t.sol b/src/test/unit/DelegationUnit.t.sol index f915ec4bc..69da2def8 100644 --- a/src/test/unit/DelegationUnit.t.sol +++ b/src/test/unit/DelegationUnit.t.sol @@ -631,8 +631,12 @@ contract DelegationManagerUnitTests_RegisterModifyOperator is DelegationManagerU assertEq(delegationManager.delegatedTo(operator), operator, "operator not delegated to self"); } + /// TODO: registerAsOperator 2 separate addresses + /// function testTwoSelfOperatorsRegister() public {} + + // @notice Verifies that a staker who is actively delegated to an operator cannot register as an operator (without first undelegating, at least) - function testFuzz_registerAsOperator_cannotRegisterWhileDelegated( + function testFuzz_Revert_registerAsOperator_cannotRegisterWhileDelegated( address staker, IDelegationManagerTypes.OperatorDetails memory operatorDetails ) public filterFuzzedAddressInputs(staker) { @@ -652,6 +656,12 @@ contract DelegationManagerUnitTests_RegisterModifyOperator is DelegationManagerU cheats.stopPrank(); } + + /// TODO: Add test for registerAsOperator where the operator has existing deposits in strategies + /// Assert: + /// depositShares == operatorShares == withdrawableShares + /// check operatorDetails hash encode matches the operatorDetails hash stored (call view function) + function testFuzz_registerAsOperator_withDeposits() public {} /** * @notice Tests that an operator can modify their OperatorDetails by calling `DelegationManager.modifyOperatorDetails` @@ -699,7 +709,7 @@ contract DelegationManagerUnitTests_RegisterModifyOperator is DelegationManagerU * @dev This is an important check to ensure that our definition of 'operator' remains consistent, in particular for preserving the * invariant that 'operators' are always delegated to themselves */ - function testFuzz_updateOperatorMetadataUri_revert_notOperator( + function testFuzz_Revert_updateOperatorMetadataUri_notOperator( IDelegationManagerTypes.OperatorDetails memory operatorDetails ) public { cheats.expectRevert(IDelegationManagerErrors.OperatorNotRegistered.selector); @@ -2174,6 +2184,38 @@ contract DelegationManagerUnitTests_delegateToBySignature is DelegationManagerUn cheats.stopPrank(); } + // TODO: Fix test from Delegation.t.sol + // /// @notice tries delegating using a wallet that does not comply with EIP 1271 + // function testDelegateToBySignature_WithContractWallet_NonconformingWallet( + // address operator, + // uint96 ethAmount, + // uint96 eigenAmount, + // uint8 v, + // bytes32 r, + // bytes32 s + // ) public fuzzedAddress(operator) { + // address staker = cheats.addr(PRIVATE_KEY); + + // // deploy non ERC1271-compliant wallet for staker to use + // cheats.startPrank(staker); + // ERC1271MaliciousMock wallet = new ERC1271MaliciousMock(); + // cheats.stopPrank(); + // staker = address(wallet); + + // _registerOperatorAndDepositFromStaker(operator, staker, ethAmount, eigenAmount); + + // cheats.assume(staker != operator); + + // bytes memory signature = abi.encodePacked(r, s, v); + + // cheats.expectRevert(); + // ISignatureUtils.SignatureWithExpiry memory signatureWithExpiry = ISignatureUtils.SignatureWithExpiry({ + // signature: signature, + // expiry: type(uint256).max + // }); + // delegation.delegateToBySignature(staker, operator, signatureWithExpiry, signatureWithExpiry, bytes32(0)); + // } + /// @notice Checks that `DelegationManager.delegateToBySignature` reverts when the staker is already delegated function test_Revert_Staker_WhenActivelyDelegated() public { address staker = cheats.addr(stakerPrivateKey); @@ -3552,6 +3594,29 @@ contract DelegationManagerUnitTests_Undelegate is DelegationManagerUnitTests { (uint256 newDepositScalingFactor,,) = delegationManager.stakerScalingFactor(defaultStaker, strategyMock); assertEq(newDepositScalingFactor, WAD, "staker scaling factor not reset correctly"); } + + // TODO: fix old Withdrawals.t.sol test + // // @notice This function tests to ensure that a delegator can re-delegate to an operator after undelegating. + // // @param operator is the operator being delegated to. + // // @param staker is the staker delegating stake to the operator. + // function testRedelegateAfterWithdrawal( + // address operator, + // address depositor, + // uint96 ethAmount, + // uint96 eigenAmount, + // bool withdrawAsShares + // ) public fuzzedAddress(operator) fuzzedAddress(depositor) { + // cheats.assume(depositor != operator); + // //this function performs delegation and subsequent withdrawal + // testWithdrawalWrapper(operator, depositor, ethAmount, eigenAmount, withdrawAsShares, true); + + // cheats.prank(depositor); + // delegation.undelegate(depositor); + + // //warps past fraudproof time interval + // cheats.warp(block.timestamp + 7 days + 1); + // _initiateDelegation(operator, depositor, ethAmount, eigenAmount); + // } } contract DelegationManagerUnitTests_queueWithdrawals is DelegationManagerUnitTests { @@ -4358,4 +4423,19 @@ contract DelegationManagerUnitTests_completeQueuedWithdrawal is DelegationManage } // TODO: add slashing cases for withdrawing as shares (can also be in integration tests) -} \ No newline at end of file +} + + +/** + * @notice TODO Lifecycle tests - These tests combine multiple functionalities of the DelegationManager + 1. Old SigP test - registerAsOperator, separate staker delegate to operator, as operator undelegate (reverts), + checks that staker is still delegated and operator still registered, staker undelegates, checks staker not delegated and operator + is still registered + 2. RegisterOperator, Deposit, Delegate, Queue, Complete + 3. RegisterOperator, Mock Slash(set maxMagnitudes), Deposit/Delegate, Queue, Complete + 4. RegisterOperator, Deposit/Delegate, Mock Slash(set maxMagnitudes), Queue, Complete + 5. RegisterOperator, Mock Slash(set maxMagnitudes), Deposit/Delegate, Queue, Mock Slash(set maxMagnitudes), Complete + 7. RegisterOperator, Deposit/Delegate, Mock Slash 100% (set maxMagnitudes), Undelegate, Complete non 100% slashed strategies + 8. RegisterOperator, Deposit/Delegate, Undelegate, Re delegate to another operator, Mock Slash 100% (set maxMagnitudes), Complete as shares + (withdrawals should have been slashed even though delegated to a new operator) + */ diff --git a/src/test/unit/PausableUnit.t.sol b/src/test/unit/PausableUnit.t.sol index 9a017e30a..32ea32689 100644 --- a/src/test/unit/PausableUnit.t.sol +++ b/src/test/unit/PausableUnit.t.sol @@ -25,6 +25,9 @@ contract PausableUnitTests is Test { /// @notice Emitted when the pause is lifted by `account`, and changed to `newPausedStatus`. event Unpaused(address indexed account, uint256 newPausedStatus); + /// @notice Emitted when the `pauserRegistry` is set to `newPauserRegistry`. + event PauserRegistrySet(IPauserRegistry pauserRegistry, IPauserRegistry newPauserRegistry); + function setUp() virtual public { address[] memory pausers = new address[](1); pausers[0] = pauser; @@ -173,4 +176,23 @@ contract PausableUnitTests is Test { cheats.stopPrank(); } + function testSetPauserRegistryUnpauser(IPauserRegistry newPauserRegistry) public { + cheats.assume(address(newPauserRegistry) != address(0)); + IPauserRegistry oldPauserRegistry = pausable.pauserRegistry(); + cheats.prank(unpauser); + cheats.expectEmit(true, true, true, true, address(pausable)); + emit PauserRegistrySet(oldPauserRegistry, newPauserRegistry); + pausable.setPauserRegistry(newPauserRegistry); + + assertEq(address(newPauserRegistry), address(pausable.pauserRegistry())); + } + + function testSetPauserRegistyUnauthorized(IPauserRegistry newPauserRegistry, address notUnpauser) public { + cheats.assume(notUnpauser != pausable.pauserRegistry().unpauser()); + + cheats.prank(notUnpauser); + cheats.expectRevert(IPausable.OnlyUnpauser.selector); + pausable.setPauserRegistry(newPauserRegistry); + } + } diff --git a/src/test/unit/StrategyManagerUnit.t.sol b/src/test/unit/StrategyManagerUnit.t.sol index fee8f4be3..3a443fbce 100644 --- a/src/test/unit/StrategyManagerUnit.t.sol +++ b/src/test/unit/StrategyManagerUnit.t.sol @@ -312,6 +312,325 @@ contract StrategyManagerUnitTests_depositIntoStrategy is StrategyManagerUnitTest testFuzz_depositIntoStrategySuccessfully(staker, amount); } + // TODO: test depositing into multiple strategies + // function testFuzz_depositIntoStrategy_MultipleStrategies() + // /// @notice deploys 'numStratsToAdd' strategies using '_testAddStrategy' and then deposits '1e18' to each of them from 'getOperatorAddress(0)' + // /// @param numStratsToAdd is the number of strategies being added and deposited into + // function testDepositStrategies(uint8 numStratsToAdd) public { + // _testDepositStrategies(getOperatorAddress(0), 1e18, numStratsToAdd); + // } + + // TODO: fix old stETH fork test + // /// @notice Shadow-forks mainnet and tests depositing stETH tokens into a "StrategyBase" contract. + // function testForkMainnetDepositSteth() public { + // // hard-coded inputs + // // address sender = address(this); + // uint64 amountToDeposit = 1e12; + + // // shadow-fork mainnet + // try cheats.createFork("mainnet") returns (uint256 forkId) { + // cheats.selectFork(forkId); + // // If RPC_MAINNET ENV not set, default to this mainnet RPC endpoint + // } catch { + // cheats.createSelectFork("https://eth.llamarpc.com"); + // } + + // // cast mainnet stETH address to IERC20 interface + // // IERC20 steth = IERC20(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84); + // IERC20 underlyingToken = IERC20(0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84); + + // // deploy necessary contracts on the shadow-forked network + // // deploy proxy admin for ability to upgrade proxy contracts + // eigenLayerProxyAdmin = new ProxyAdmin(); + // //deploy pauser registry + // address[] memory pausers = new address[](1); + // pausers[0] = pauser; + // eigenLayerPauserReg = new PauserRegistry(pausers, unpauser); + // /** + // * First, deploy upgradeable proxy contracts that **will point** to the implementations. Since the implementation contracts are + // * not yet deployed, we give these proxies an empty contract as the initial implementation, to act as if they have no code. + // */ + // emptyContract = new EmptyContract(); + // delegation = DelegationManager( + // address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + // ); + // strategyManager = StrategyManager( + // address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + // ); + // eigenPodManager = EigenPodManager( + // address(new TransparentUpgradeableProxy(address(emptyContract), address(eigenLayerProxyAdmin), "")) + // ); + + // ethPOSDeposit = new ETHPOSDepositMock(); + // pod = new EigenPod(ethPOSDeposit, eigenPodManager, GOERLI_GENESIS_TIME); + + // eigenPodBeacon = new UpgradeableBeacon(address(pod)); + + // // Second, deploy the *implementation* contracts, using the *proxy contracts* as inputs + // DelegationManager delegationImplementation = new DelegationManager(avsDirectory, strategyManager, eigenPodManager, allocationManager, MIN_WITHDRAWAL_DELAY); + // StrategyManager strategyManagerImplementation = new StrategyManager(delegation); + // EigenPodManager eigenPodManagerImplementation = new EigenPodManager(ethPOSDeposit, eigenPodBeacon, strategyManager, delegation); + // // Third, upgrade the proxy contracts to use the correct implementation contracts and initialize them. + // eigenLayerProxyAdmin.upgradeAndCall( + // ITransparentUpgradeableProxy(payable(address(delegation))), + // address(delegationImplementation), + // abi.encodeWithSelector( + // DelegationManager.initialize.selector, + // eigenLayerReputedMultisig, + // eigenLayerPauserReg, + // 0 /*initialPausedStatus*/, + // minWithdrawalDelayBlocks, + // initializeStrategiesToSetDelayBlocks, + // initializeWithdrawalDelayBlocks + // ) + // ); + // eigenLayerProxyAdmin.upgradeAndCall( + // ITransparentUpgradeableProxy(payable(address(strategyManager))), + // address(strategyManagerImplementation), + // abi.encodeWithSelector( + // StrategyManager.initialize.selector, + // eigenLayerReputedMultisig, + // eigenLayerReputedMultisig, + // eigenLayerPauserReg, + // 0/*initialPausedStatus*/ + // ) + // ); + // eigenLayerProxyAdmin.upgradeAndCall( + // ITransparentUpgradeableProxy(payable(address(eigenPodManager))), + // address(eigenPodManagerImplementation), + // abi.encodeWithSelector( + // EigenPodManager.initialize.selector, + // eigenLayerReputedMultisig, + // eigenLayerPauserReg, + // 0/*initialPausedStatus*/ + // ) + // ); + + // // cheat a bunch of ETH to this address + // cheats.deal(address(this), 1e20); + // // deposit a huge amount of ETH to get ample stETH + // { + // (bool success, bytes memory returnData) = address(underlyingToken).call{value: 1e20}(""); + // require(success, "depositing stETH failed"); + // returnData; + // } + + // // deploy StrategyBase contract implementation, then create upgradeable proxy that points to implementation and initialize it + // baseStrategyImplementation = new StrategyBase(strategyManager); + // IStrategy stethStrategy = StrategyBase( + // address( + // new TransparentUpgradeableProxy( + // address(baseStrategyImplementation), + // address(eigenLayerProxyAdmin), + // abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, eigenLayerPauserReg) + // ) + // ) + // ); + + // // REMAINDER OF CODE ADAPTED FROM `_testDepositToStrategy` + // // _testDepositToStrategy(sender, amountToDeposit, underlyingToken, stethStrategy); + + // // whitelist the strategy for deposit, in case it wasn't before + // { + // cheats.startPrank(strategyManager.strategyWhitelister()); + // IStrategy[] memory _strategy = new IStrategy[](1); + // _strategy[0] = stethStrategy; + // strategyManager.addStrategiesToDepositWhitelist(_strategy); + // cheats.stopPrank(); + // } + + // uint256 operatorSharesBefore = strategyManager.stakerDepositShares(address(this), stethStrategy); + // // check the expected output + // uint256 expectedSharesOut = stethStrategy.underlyingToShares(amountToDeposit); + + // underlyingToken.transfer(address(this), amountToDeposit); + // cheats.startPrank(address(this)); + // underlyingToken.approve(address(strategyManager), type(uint256).max); + // strategyManager.depositIntoStrategy(stethStrategy, underlyingToken, amountToDeposit); + + // //check if depositor has never used this strat, that it is added correctly to stakerStrategyList array. + // if (operatorSharesBefore == 0) { + // // check that strategy is appropriately added to dynamic array of all of sender's strategies + // assertTrue( + // strategyManager.stakerStrategyList(address(this), strategyManager.stakerStrategyListLength(address(this)) - 1) + // == stethStrategy, + // "_testDepositToStrategy: stakerStrategyList array updated incorrectly" + // ); + // } + + // // check that the shares out match the expected amount out + // // the actual transfer in will be lower by 1-2 wei than expected due to stETH's internal rounding + // // to account for this we check approximate rather than strict equivalence here + // { + // uint256 actualSharesOut = strategyManager.stakerDepositShares(address(this), stethStrategy) - operatorSharesBefore; + // require(actualSharesOut >= expectedSharesOut, "too few shares"); + // require((actualSharesOut * 1000) / expectedSharesOut < 1003, "too many shares"); + + // // additional sanity check for deposit not increasing in value + // require(stethStrategy.sharesToUnderlying(actualSharesOut) <= amountToDeposit, "value cannot have increased"); + // // slippage check + // require((stethStrategy.sharesToUnderlying(actualSharesOut) * 1e6) / amountToDeposit >= (1e6 - 1), "bad slippage on first deposit"); + // } + // cheats.stopPrank(); + // } + + // TODO: fix old frontrun depositor test + // function testFrontrunFirstDepositor(/*uint256 depositAmount*/) public { + + // //setup addresses + // address attacker = address(100); + // address user = address(200); + + // //give 2 ether to attacker and user + // weth.transfer(attacker,2 ether); + // weth.transfer(user,2 ether); + + // //attacker FRONTRUN: deposit 1 wei (receive 1 share) + // StrategyManager _strategyManager = _whitelistStrategy(strategyManager, wethStrat); + + // cheats.startPrank(attacker); + // weth.approve(address(strategyManager), type(uint256).max); + // _strategyManager.depositIntoStrategy(wethStrat, weth, 1 wei); + // cheats.stopPrank(); + + // //attacker FRONTRUN: transfer 1 ether into strategy directly to manipulate the value of shares + // cheats.prank(attacker); + // weth.transfer(address(wethStrat),1 ether); + + // //user deposits 2 eth into strategy - only gets 1 share due to rounding + // cheats.startPrank(user); + // weth.approve(address(_strategyManager), type(uint256).max); + // _strategyManager.depositIntoStrategy(wethStrat, weth, 2 ether); + // cheats.stopPrank(); + + // //attacker deposited 1 ether and 1 wei - received 1 share + // //user deposited 2 ether - received X shares + // //user has lost 0.5 ether? + // (, uint256[] memory shares) = _strategyManager.getDeposits(attacker); + // uint256 attackerValueWeth = wethStrat.sharesToUnderlyingView(shares[0]); + // require(attackerValueWeth >= (1), "attacker got zero shares"); + + // (, shares) = _strategyManager.getDeposits(user); + // uint256 userValueWeth = wethStrat.sharesToUnderlyingView(shares[0]); + // require(userValueWeth >= (1900000000000000000), "user has lost more than 0.1 eth from frontrunning"); + + // uint256 attackerLossesWeth = (2 ether + 1 wei) - attackerValueWeth; + // uint256 userLossesWeth = 2 ether - userValueWeth; + // require(attackerLossesWeth > userLossesWeth, "griefing attack deals more damage than cost"); + // } + + // TODO: fix old testFrontrunFirstDepositorFuzzed + // function testFrontrunFirstDepositorFuzzed(uint96 firstDepositAmount, uint96 donationAmount, uint96 secondDepositAmount) public { + // // want to only use nonzero amounts or else we'll get reverts + // cheats.assume(firstDepositAmount != 0 && secondDepositAmount != 0); + + // // setup addresses + // address attacker = address(100); + // address user = address(200); + + // // attacker makes first deposit + // _testDepositToStrategy(attacker, firstDepositAmount, weth, wethStrat); + + // // transfer tokens into strategy directly to manipulate the value of shares + // weth.transfer(address(wethStrat), donationAmount); + + // // filter out calls that would revert for minting zero shares + // cheats.assume(wethStrat.underlyingToShares(secondDepositAmount) != 0); + + // // user makes 2nd deposit into strategy - gets diminished shares due to rounding + // _testDepositToStrategy(user, secondDepositAmount, weth, wethStrat); + + // // check for griefing + // (, uint256[] memory shares) = strategyManager.getDeposits(attacker); + // uint256 attackerValueWeth = wethStrat.sharesToUnderlyingView(shares[0]); + // (, shares) = strategyManager.getDeposits(user); + // uint256 userValueWeth = wethStrat.sharesToUnderlyingView(shares[0]); + + // uint256 attackerCost = uint256(firstDepositAmount) + uint256(donationAmount); + // require(attackerCost >= attackerValueWeth, "attacker gained value?"); + // // uint256 attackerLossesWeth = attackerValueWeth > attackerCost ? 0 : (attackerCost - attackerValueWeth); + // uint256 attackerLossesWeth = attackerCost - attackerValueWeth; + // uint256 userLossesWeth = secondDepositAmount - userValueWeth; + + // emit log_named_uint("attackerLossesWeth", attackerLossesWeth); + // emit log_named_uint("userLossesWeth", userLossesWeth); + + // // use '+1' here to account for rounding. given the attack will cost ETH in the form of gas, this is fine. + // require(attackerLossesWeth + 1 >= userLossesWeth, "griefing attack deals more damage than cost"); + // } + + // TODO: testDepositTokenWithOneWeiFeeOnTransfer + // function testDepositTokenWithOneWeiFeeOnTransfer(address sender, uint64 amountToDeposit) public fuzzedAddress(sender) { + // cheats.assume(amountToDeposit != 0); + + // IERC20 underlyingToken; + + // { + // uint256 initSupply = 1e50; + // address initOwner = address(this); + // ERC20_OneWeiFeeOnTransfer oneWeiFeeOnTransferToken = new ERC20_OneWeiFeeOnTransfer(initSupply, initOwner); + // underlyingToken = IERC20(address(oneWeiFeeOnTransferToken)); + // } + + // // need to transfer extra here because otherwise the `sender` won't have enough tokens + // underlyingToken.transfer(sender, 1000); + + // IStrategy oneWeiFeeOnTransferTokenStrategy = StrategyBase( + // address( + // new TransparentUpgradeableProxy( + // address(baseStrategyImplementation), + // address(eigenLayerProxyAdmin), + // abi.encodeWithSelector(StrategyBase.initialize.selector, underlyingToken, eigenLayerPauserReg) + // ) + // ) + // ); + + // // REMAINDER OF CODE ADAPTED FROM `_testDepositToStrategy` + // // _testDepositToStrategy(sender, amountToDeposit, underlyingToken, oneWeiFeeOnTransferTokenStrategy); + + // // whitelist the strategy for deposit, in case it wasn't before + // { + // cheats.startPrank(strategyManager.strategyWhitelister()); + // IStrategy[] memory _strategy = new IStrategy[](1); + // _strategy[0] = oneWeiFeeOnTransferTokenStrategy; + // strategyManager.addStrategiesToDepositWhitelist(_strategy); + // cheats.stopPrank(); + // } + + // uint256 operatorSharesBefore = strategyManager.stakerDepositShares(sender, oneWeiFeeOnTransferTokenStrategy); + // // check the expected output + // uint256 expectedSharesOut = oneWeiFeeOnTransferTokenStrategy.underlyingToShares(amountToDeposit); + + // underlyingToken.transfer(sender, amountToDeposit); + // cheats.startPrank(sender); + // underlyingToken.approve(address(strategyManager), type(uint256).max); + // strategyManager.depositIntoStrategy(oneWeiFeeOnTransferTokenStrategy, underlyingToken, amountToDeposit); + + // //check if depositor has never used this strat, that it is added correctly to stakerStrategyList array. + // if (operatorSharesBefore == 0) { + // // check that strategy is appropriately added to dynamic array of all of sender's strategies + // assertTrue( + // strategyManager.stakerStrategyList(sender, strategyManager.stakerStrategyListLength(sender) - 1) + // == oneWeiFeeOnTransferTokenStrategy, + // "_testDepositToStrategy: stakerStrategyList array updated incorrectly" + // ); + // } + + // // check that the shares out match the expected amount out + // // the actual transfer in will be lower by 1 wei than expected due to stETH's internal rounding + // // to account for this we check approximate rather than strict equivalence here + // { + // uint256 actualSharesOut = strategyManager.stakerDepositShares(sender, oneWeiFeeOnTransferTokenStrategy) - operatorSharesBefore; + // require((actualSharesOut * 1000) / expectedSharesOut > 998, "too few shares"); + // require((actualSharesOut * 1000) / expectedSharesOut < 1002, "too many shares"); + + // // additional sanity check for deposit not increasing in value + // require(oneWeiFeeOnTransferTokenStrategy.sharesToUnderlying(actualSharesOut) <= amountToDeposit, "value cannot have increased"); + // } + // cheats.stopPrank(); + // } + + function test_Revert_WhenDepositsPaused() public { uint256 amount = 1e18; diff --git a/src/test/unit/libraries/SlashingLibUnit.sol b/src/test/unit/libraries/SlashingLibUnit.sol new file mode 100644 index 000000000..739806db4 --- /dev/null +++ b/src/test/unit/libraries/SlashingLibUnit.sol @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "forge-std/Test.sol"; +import "src/contracts/libraries/SlashingLib.sol"; + +contract SlashingLibUnitTests is Test { + using SlashingLib for StakerScalingFactors; + + /// ----------------------------------------------------------------------- + /// Wad Math + /// ----------------------------------------------------------------------- + + // NOTE: 128 bit values are used to avoid overflow. + + function test_MulWad_Correctness( + uint256 r + ) public { + uint256 x = bound(r, 0, type(uint128).max); + uint256 y = bound(r, 0, type(uint128).max); + + assertEq(SlashingLib.mulWad(x, y), x * y / WAD); + } + + function test_DivWad_Correctness( + uint256 r + ) public { + uint256 x = bound(r, 0, type(uint128).max); + uint256 y = bound(r, 0, type(uint128).max); + + if (y != 0) { + assertEq(SlashingLib.divWad(x, y), x * WAD / y); + } else { + vm.expectRevert(); // div by zero + SlashingLib.divWad(x, y); + } + } + + function test_MulWadRoundUp_Correctness( + uint256 r + ) public { + uint256 x = bound(r, 0, type(uint128).max); + uint256 y = bound(r, 0, type(uint128).max); + + uint256 result = SlashingLib.mulWadRoundUp(x, y); + uint256 expected = x * y / WAD; + + if (mulmod(x, y, WAD) != 0) { + assertEq(result, expected + 1); + } else { + assertEq(result, expected); + } + } + + /// ----------------------------------------------------------------------- + /// Getters + /// ----------------------------------------------------------------------- + + function test_GetDepositScalingFactor_InitiallyWad() public { + StakerScalingFactors memory ssf = StakerScalingFactors({ + depositScalingFactor: 0, + isBeaconChainScalingFactorSet: false, + beaconChainScalingFactor: 0 + }); + assertEq(SlashingLib.getDepositScalingFactor(ssf), WAD); + } + + function test_GetBeaconChainScalingFactor_InitiallyWad() public { + StakerScalingFactors memory ssf = StakerScalingFactors({ + depositScalingFactor: 0, + isBeaconChainScalingFactorSet: false, + beaconChainScalingFactor: 0 + }); + assertEq(SlashingLib.getBeaconChainScalingFactor(ssf), WAD); + } + + /// ----------------------------------------------------------------------- + /// Differential Test Helpers + /// ----------------------------------------------------------------------- + + function _differentialScaleSharesForQueuedWithdrawal( + uint256 sharesToWithdraw, + uint256 beaconChainScalingFactor, + uint64 operatorMagnitude + ) internal returns (uint256) { + string memory args; + args = vm.serializeString("args", "method", "scaleSharesForQueuedWithdrawal"); + args = vm.serializeUint("args", "sharesToWithdraw", sharesToWithdraw); + args = vm.serializeUint("args", "beaconChainScalingFactor", beaconChainScalingFactor); + args = vm.serializeUint("args", "operatorMagnitude", operatorMagnitude); + + string[] memory ffi = new string[](3); + ffi[0] = "python3"; + ffi[1] = "src/test/ext/slashing-lib.t.py"; + ffi[2] = args; + + return abi.decode(vm.ffi(ffi), (uint256)); + } + + function _differentialScaleSharesForCompleteWithdrawal( + uint256 scaledShares, + uint256 beaconChainScalingFactor, + uint64 operatorMagnitude + ) internal returns (uint256) { + string memory args; + args = vm.serializeString("args", "method", "scaleSharesForCompleteWithdrawal"); + args = vm.serializeUint("args", "scaledShares", scaledShares); + args = vm.serializeUint("args", "beaconChainScalingFactor", beaconChainScalingFactor); + args = vm.serializeUint("args", "operatorMagnitude", operatorMagnitude); + + string[] memory ffi = new string[](3); + ffi[0] = "python3"; + ffi[1] = "src/test/ext/slashing-lib.t.py"; + ffi[2] = args; + + return abi.decode(vm.ffi(ffi), (uint256)); + } + + /// ----------------------------------------------------------------------- + /// Differential Tests + /// ----------------------------------------------------------------------- + + function test_ScaleSharesForQueuedWithdrawal_Differential() public { + // TODO: Document the constraints of each variable and fuzz them. + uint256 sharesToWithdraw = 1e18; + uint64 beaconChainScalingFactor = 1e18; + uint64 operatorMagnitude = 1e18; + + uint256 result = + _differentialScaleSharesForQueuedWithdrawal(sharesToWithdraw, beaconChainScalingFactor, operatorMagnitude); + + assertEq( + SlashingLib.scaleSharesForQueuedWithdrawal( + sharesToWithdraw, + StakerScalingFactors({ + isBeaconChainScalingFactorSet: false, + beaconChainScalingFactor: uint64(beaconChainScalingFactor), + depositScalingFactor: 0 + }), + operatorMagnitude + ), + result + ); + } +} diff --git a/src/test/unit/libraries/SnapshotsUnit.t.sol b/src/test/unit/libraries/SnapshotsUnit.t.sol new file mode 100644 index 000000000..921149c03 --- /dev/null +++ b/src/test/unit/libraries/SnapshotsUnit.t.sol @@ -0,0 +1,44 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.27; + +import "forge-std/Test.sol"; +import "src/contracts/libraries/Snapshots.sol"; + +contract SnapshotsUnitTests is Test { + using Snapshots for Snapshots.DefaultWadHistory; + + Snapshots.DefaultWadHistory history; + + function test_Revert_SnapshotKeyDecreased(uint256 r) public { + uint32 key = uint32(bound(r, 1, type(uint32).max)); + uint32 smallerKey = uint32(bound(r, 0, key - 1)); + + history.push(key, 1); + + vm.expectRevert(Snapshots.SnapshotKeyDecreased.selector); + history.push(smallerKey, 2); + } + + function test_Push_Correctness(uint256 r) public { + uint32 key = uint32(bound(r, 0, type(uint32).max)); + uint64 value = uint32(bound(r, 0, type(uint64).max)); + + history.push(key, value); + + assertEq(history.upperLookup(key), value); + assertEq(history.latest(), value); + assertEq(history.length(), 1); + } + + function test_UpperLookup_InitiallyWad(uint32 r) public { + assertEq(history.upperLookup(r), Snapshots.WAD); + } + + function test_Latest_InitiallyWad() public { + assertEq(history.latest(), Snapshots.WAD); + } + + function test_Length_InitiallyZero() public { + assertEq(history.length(), 0); + } +} \ No newline at end of file