From 166b0b90412e784546182a48f444db16eb84018d Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Tue, 21 Nov 2023 17:29:45 +0400 Subject: [PATCH 01/18] feat: bond curve mechanics --- script/DeployBase.s.sol | 5 +- src/CSAccounting.sol | 127 ++++--- src/CSBondCurve.sol | 134 +++++++ test/CSAccounting.blockedBond.t.sol | 11 +- test/CSAccounting.t.sol | 505 ++++++++++++++++++++++++- test/CSBondCurve.t.sol | 164 ++++++++ test/CSModule.t.sol | 5 +- test/integration/DepositInTokens.t.sol | 5 +- test/integration/StakingRouter.t.sol | 5 +- 9 files changed, 876 insertions(+), 85 deletions(-) create mode 100644 src/CSBondCurve.sol create mode 100644 test/CSBondCurve.t.sol diff --git a/script/DeployBase.s.sol b/script/DeployBase.s.sol index 310d9d3d..37f703fe 100644 --- a/script/DeployBase.s.sol +++ b/script/DeployBase.s.sol @@ -74,8 +74,11 @@ abstract contract DeployBase is Script { moduleType: "community-staking-module", locator: address(locator) }); + uint256[] memory curve = new uint256[](2); + curve[0] = 2 ether; + curve[1] = 4 ether; CSAccounting accounting = new CSAccounting({ - commonBondSize: 2 ether, + bondCurve: curve, admin: deployer, lidoLocator: address(locator), communityStakingModule: address(csm), diff --git a/src/CSAccounting.sol b/src/CSAccounting.sol index 8f27a3e8..ca13957c 100644 --- a/src/CSAccounting.sol +++ b/src/CSAccounting.sol @@ -5,6 +5,8 @@ pragma solidity 0.8.21; import { AccessControlEnumerable } from "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; +import { CSBondCurve } from "./CSBondCurve.sol"; + import { ILidoLocator } from "./interfaces/ILidoLocator.sol"; import { ICSModule } from "./interfaces/ICSModule.sol"; import { ILido } from "./interfaces/ILido.sol"; @@ -71,10 +73,13 @@ contract CSAccountingBase { error InvalidBlockedBondRetentionPeriod(); error InvalidStolenAmount(); error InvalidSender(); - error InvalidMultiplier(); } -contract CSAccounting is CSAccountingBase, AccessControlEnumerable { +contract CSAccounting is + CSAccountingBase, + CSBondCurve, + AccessControlEnumerable +{ struct PermitInput { uint256 value; uint256 deadline; @@ -93,6 +98,8 @@ contract CSAccounting is CSAccountingBase, AccessControlEnumerable { keccak256("EL_REWARDS_STEALING_PENALTY_INIT_ROLE"); // 0xcc2e7ce7be452f766dd24d55d87a3d42901c31ffa5b600cd1dff475abec91c1f bytes32 public constant EL_REWARDS_STEALING_PENALTY_SETTLE_ROLE = keccak256("EL_REWARDS_STEALING_PENALTY_SETTLE_ROLE"); // 0xdf6226649a1ca132f86d419e46892001284368a8f7445b5eb0d3fadf91329fe6 + bytes32 public constant SET_BOND_CURVE_ROLE = + keccak256("SET_BOND_CURVE_ROLE"); // 0x645c9e6d2a86805cb5a28b1e4751c0dab493df7cf935070ce405489ba1a7bf72 bytes32 public constant SET_BOND_MULTIPLIER_ROLE = keccak256("SET_BOND_MULTIPLIER_ROLE"); // 0x62131145aee19b18b85aa8ead52ba87f0efb6e61e249155edc68a2c24e8f79b5 @@ -102,10 +109,6 @@ contract CSAccounting is CSAccountingBase, AccessControlEnumerable { uint256 public constant MIN_BLOCKED_BOND_MANAGEMENT_PERIOD = 1 days; uint256 public constant MAX_BLOCKED_BOND_MANAGEMENT_PERIOD = 7 days; - uint256 public constant TOTAL_BASIS_POINTS = 10000; - - uint256 public immutable COMMON_BOND_SIZE; - ILidoLocator private immutable LIDO_LOCATOR; ICSModule private immutable CSM; IWstETH private immutable WSTETH; @@ -118,11 +121,8 @@ contract CSAccounting is CSAccountingBase, AccessControlEnumerable { mapping(uint256 => uint256) internal _bondShares; mapping(uint256 => BlockedBond) internal _blockedBondEther; - /// This mapping contains bond multiplier points (in basis points) for Node Operator's bond. - /// By default, all Node Operators have x1 multiplier (10000 basis points). - mapping(uint256 => uint256) internal _bondMultiplierBasisPoints; - /// @param commonBondSize common bond size in ETH for all node operators. + /// @param bondCurve initial bond curve /// @param admin admin role member address /// @param lidoLocator lido locator contract address /// @param wstETH wstETH contract address @@ -130,14 +130,14 @@ contract CSAccounting is CSAccountingBase, AccessControlEnumerable { /// @param _blockedBondRetentionPeriod retention period for blocked bond in seconds /// @param _blockedBondManagementPeriod management period for blocked bond in seconds constructor( - uint256 commonBondSize, + uint256[] memory bondCurve, address admin, address lidoLocator, address wstETH, address communityStakingModule, uint256 _blockedBondRetentionPeriod, uint256 _blockedBondManagementPeriod - ) { + ) CSBondCurve(bondCurve) { // check zero addresses require(admin != address(0), "admin is zero address"); require(lidoLocator != address(0), "lido locator is zero address"); @@ -156,8 +156,6 @@ contract CSAccounting is CSAccountingBase, AccessControlEnumerable { CSM = ICSModule(communityStakingModule); WSTETH = IWstETH(wstETH); - COMMON_BOND_SIZE = commonBondSize; - blockedBondRetentionPeriod = _blockedBondRetentionPeriod; blockedBondManagementPeriod = _blockedBondManagementPeriod; } @@ -177,6 +175,20 @@ contract CSAccounting is CSAccountingBase, AccessControlEnumerable { blockedBondManagementPeriod = management; } + function setBondCurve( + uint256[] memory bondCurve_ + ) external onlyRole(SET_BOND_CURVE_ROLE) { + _setBondCurve(bondCurve_); + } + + /// @notice Sets basis points of the bond multiplier for the given node operator. + function setBondMultiplier( + uint256 nodeOperatorId, + uint256 basisPoints + ) external onlyRole(SET_BOND_MULTIPLIER_ROLE) { + _setBondMultiplier(nodeOperatorId, basisPoints); + } + function _validateBlockedBondPeriods( uint256 retention, uint256 management @@ -200,23 +212,6 @@ contract CSAccounting is CSAccountingBase, AccessControlEnumerable { return _bondShares[nodeOperatorId]; } - /// @notice Returns basis points of the bond multiplier for the given node operator. - function getBondMultiplier( - uint256 nodeOperatorId - ) public view returns (uint256) { - uint256 basisPoints = _bondMultiplierBasisPoints[nodeOperatorId]; - return basisPoints > 0 ? basisPoints : TOTAL_BASIS_POINTS; - } - - /// @notice Sets basis points of the bond multiplier for the given node operator. - function setBondMultiplier( - uint256 nodeOperatorId, - uint256 basisPoints - ) external onlyRole(SET_BOND_MULTIPLIER_ROLE) { - if (basisPoints > TOTAL_BASIS_POINTS) revert InvalidMultiplier(); - _bondMultiplierBasisPoints[nodeOperatorId] = basisPoints; - } - /// @notice Returns total rewards (bond + fees) in ETH for the given node operator. /// @param nodeOperatorId id of the node operator to get rewards for. /// @return total rewards in ETH @@ -226,7 +221,7 @@ contract CSAccounting is CSAccountingBase, AccessControlEnumerable { uint256 cumulativeFeeShares ) public view returns (uint256) { (uint256 current, uint256 required) = _bondSharesSummary( - _getNodeOperatorActiveKeys(nodeOperatorId) + nodeOperatorId ); current += _feeDistributor().getFeesToDistribute( rewardsProof, @@ -346,22 +341,25 @@ contract CSAccounting is CSAccountingBase, AccessControlEnumerable { uint256 nodeOperatorId, uint256 additionalKeysCount ) public view returns (uint256) { + // todo: can be optimized. get active keys once (uint256 current, uint256 required) = _bondETHSummary(nodeOperatorId); - uint256 requiredForKeys = (getRequiredBondETHForKeys( - additionalKeysCount - ) * getBondMultiplier(nodeOperatorId)) / TOTAL_BASIS_POINTS; + uint256 currentKeysCount = _getNodeOperatorActiveKeys(nodeOperatorId); + uint256 requiredForNextKeys = _getCurveValueByKeysCount( + nodeOperatorId, + currentKeysCount + additionalKeysCount + ) - _getCurveValueByKeysCount(nodeOperatorId, currentKeysCount); uint256 missing = required > current ? required - current : 0; if (missing > 0) { - return missing + requiredForKeys; + return missing + requiredForNextKeys; } uint256 excess = current - required; - if (excess >= requiredForKeys) { + if (excess >= requiredForNextKeys) { return 0; } - return requiredForKeys - excess; + return requiredForNextKeys - excess; } /// @notice Returns the required bond stETH (inc. missed and excess) for the given node operator to upload new keys. @@ -394,7 +392,7 @@ contract CSAccounting is CSAccountingBase, AccessControlEnumerable { function getRequiredBondETHForKeys( uint256 keysCount ) public view returns (uint256) { - return keysCount * COMMON_BOND_SIZE; + return _getCurveValueByKeysCount(keysCount); } /// @notice Returns the required bond stETH for the given number of keys. @@ -412,13 +410,7 @@ contract CSAccounting is CSAccountingBase, AccessControlEnumerable { function getRequiredBondWstETHForKeys( uint256 keysCount ) public view returns (uint256) { - return _getRequiredBondSharesForKeys(keysCount); - } - - function _getRequiredBondSharesForKeys( - uint256 keysCount - ) internal view returns (uint256) { - return _sharesByEth(getRequiredBondETHForKeys(keysCount)); + return WSTETH.getWstETHByStETH(getRequiredBondStETHForKeys(keysCount)); } /// @dev unbonded meaning amount of keys with no bond at all @@ -428,30 +420,45 @@ contract CSAccounting is CSAccountingBase, AccessControlEnumerable { function getUnbondedKeysCount( uint256 nodeOperatorId ) public view returns (uint256) { - return - getRequiredBondETH(nodeOperatorId, 0) / - getRequiredBondETHForKeys(1); + uint256 activeKeys = _getNodeOperatorActiveKeys(nodeOperatorId); + uint256 currentBond = _ethByShares(_bondShares[nodeOperatorId]); + uint256 blockedBond = getBlockedBondETH(nodeOperatorId); + if (currentBond > blockedBond) { + currentBond -= blockedBond; + uint256 bondedKeys = _getKeysCountByCurveValue( + nodeOperatorId, + currentBond + ); + if ( + currentBond > + _getCurveValueByKeysCount(nodeOperatorId, bondedKeys) + ) { + bondedKeys += 1; + } + return activeKeys > bondedKeys ? activeKeys - bondedKeys : 0; + } + return activeKeys; } - /// @notice Returns the number of keys by the given bond ETH amount + /// @notice Returns the number of keys by the given bond stETH amount function getKeysCountByBondETH( uint256 ETHAmount ) public view returns (uint256) { - return ETHAmount / getRequiredBondETHForKeys(1); + return _getKeysCountByCurveValue(ETHAmount); } /// @notice Returns the number of keys by the given bond stETH amount function getKeysCountByBondStETH( uint256 stETHAmount ) public view returns (uint256) { - return stETHAmount / getRequiredBondStETHForKeys(1); + return getKeysCountByBondETH(stETHAmount); } /// @notice Returns the number of keys by the given bond wstETH amount function getKeysCountByBondWstETH( uint256 wstETHAmount ) public view returns (uint256) { - return wstETHAmount / getRequiredBondWstETHForKeys(1); + return getKeysCountByBondETH(WSTETH.getStETHByWstETH(wstETHAmount)); } /// @notice Stake user's ETH to Lido and make deposit in stETH to the bond @@ -925,9 +932,10 @@ contract CSAccounting is CSAccountingBase, AccessControlEnumerable { ) internal view returns (uint256 current, uint256 required) { current = _ethByShares(getBondShares(nodeOperatorId)); required = - ((getRequiredBondETHForKeys( + _getCurveValueByKeysCount( + nodeOperatorId, _getNodeOperatorActiveKeys(nodeOperatorId) - ) * getBondMultiplier(nodeOperatorId)) / TOTAL_BASIS_POINTS) + + ) + getBlockedBondETH(nodeOperatorId); } @@ -936,9 +944,12 @@ contract CSAccounting is CSAccountingBase, AccessControlEnumerable { ) internal view returns (uint256 current, uint256 required) { current = getBondShares(nodeOperatorId); required = - ((_getRequiredBondSharesForKeys( - _getNodeOperatorActiveKeys(nodeOperatorId) - ) * getBondMultiplier(nodeOperatorId)) / TOTAL_BASIS_POINTS) + + _sharesByEth( + _getCurveValueByKeysCount( + nodeOperatorId, + _getNodeOperatorActiveKeys(nodeOperatorId) + ) + ) + _sharesByEth(getBlockedBondETH(nodeOperatorId)); } diff --git a/src/CSBondCurve.sol b/src/CSBondCurve.sol new file mode 100644 index 00000000..394b563a --- /dev/null +++ b/src/CSBondCurve.sol @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.21; + +abstract contract CSBondCurve { + error InvalidBondCurveLength(); + error InvalidMultiplier(); + + // @dev Keys count to bond amount mapping + // x - keys count + // y - bond amount for x keys + uint256[] public bondCurve; + + uint256 internal constant MIN_CURVE_LENGTH = 2; + // todo: might be redefined in the future + uint256 internal constant MAX_CURVE_LENGTH = 20; + uint256 internal constant BASIS_POINTS = 10000; + uint256 internal constant MIN_BOND_MULTIPLIER = MAX_BOND_MULTIPLIER / 2; // x0.5 + uint256 internal constant MAX_BOND_MULTIPLIER = 10000; // x1 + + /// This mapping contains bond multiplier points (in basis points) for Node Operator's bond. + /// By default, all Node Operators have x1 multiplier (10000 basis points). + mapping(uint256 => uint256) internal _bondMultiplierBP; + + constructor(uint256[] memory _bondCurve) { + _checkCurveLength(_bondCurve); + bondCurve = _bondCurve; + } + + function _setBondCurve(uint256[] memory _bondCurve) internal { + _checkCurveLength(_bondCurve); + bondCurve = _bondCurve; + } + + function _setBondMultiplier( + uint256 nodeOperatorId, + uint256 basisPoints + ) internal { + _checkMultiplier(basisPoints); + _bondMultiplierBP[nodeOperatorId] = basisPoints; + } + + /// @notice Returns basis points of the bond multiplier for the given node operator. + /// if it isn't set, the multiplier is x1 (MAX_BOND_MULTIPLIER) + function getBondMultiplier( + uint256 nodeOperatorId + ) public view returns (uint256) { + uint256 basisPoints = _bondMultiplierBP[nodeOperatorId]; + return basisPoints > 0 ? basisPoints : MAX_BOND_MULTIPLIER; + } + + function _checkCurveLength(uint256[] memory xy) internal pure { + if (xy.length < MIN_CURVE_LENGTH || xy.length > MAX_CURVE_LENGTH) + revert InvalidBondCurveLength(); + } + + function _checkMultiplier(uint256 multiplier) internal pure { + if ( + multiplier < MIN_BOND_MULTIPLIER || multiplier > MAX_BOND_MULTIPLIER + ) revert InvalidMultiplier(); + } + + /// @notice Returns the amount of keys for the given bond amount. + function _getKeysCountByCurveValue( + uint256 amount + ) internal view returns (uint256) { + return _getKeysCountByCurveValue(type(uint256).max, amount); + } + + /// @notice Returns the amount of keys for the given bond amount for particular node operator. + function _getKeysCountByCurveValue( + uint256 nodeOperatorId, + uint256 amount + ) internal view returns (uint256) { + uint256 mult = getBondMultiplier(nodeOperatorId); + uint256 last = (bondCurve[bondCurve.length - 1] * mult) / BASIS_POINTS; + if (amount >= last) { + return + bondCurve.length + + ((amount - last) / + (last - + (bondCurve[bondCurve.length - 2] * mult) / + BASIS_POINTS)); + } + return _searchKeysByBond(amount, mult); + } + + function _searchKeysByBond( + uint256 value, + uint256 multiplier + ) internal view returns (uint256) { + uint256 low = 0; + uint256 high = bondCurve.length - 1; + while (low <= high) { + uint256 mid = (low + high) / 2; + if ( + (bondCurve[mid] * multiplier) / BASIS_POINTS > value && mid != 0 + ) { + high = mid - 1; + } else if ((bondCurve[mid] * multiplier) / BASIS_POINTS <= value) { + low = mid + 1; + } else { + return mid; + } + } + return low; + } + + function _getCurveValueByKeysCount( + uint256 keys + ) internal view returns (uint256) { + return _getCurveValueByKeysCount(type(uint256).max, keys); + } + + function _getCurveValueByKeysCount( + uint256 nodeOperatorId, + uint256 keys + ) internal view returns (uint256) { + uint256 mult = getBondMultiplier(nodeOperatorId); + if (keys == 0) return 0; + if (keys <= bondCurve.length) { + return (bondCurve[keys - 1] * mult) / BASIS_POINTS; + } else { + uint256 last = (bondCurve[bondCurve.length - 1] * mult) / + BASIS_POINTS; + return + last + + (keys - bondCurve.length) * + (last - + ((bondCurve[bondCurve.length - 2] * mult) / BASIS_POINTS)); + } + } +} diff --git a/test/CSAccounting.blockedBond.t.sol b/test/CSAccounting.blockedBond.t.sol index b6c5121e..7cf569cd 100644 --- a/test/CSAccounting.blockedBond.t.sol +++ b/test/CSAccounting.blockedBond.t.sol @@ -19,7 +19,7 @@ import { Fixtures } from "./helpers/Fixtures.sol"; contract CSAccounting_revealed is CSAccounting { constructor( - uint256 commonBondSize, + uint256[] memory bondCurve, address admin, address lidoLocator, address wstETH, @@ -28,7 +28,7 @@ contract CSAccounting_revealed is CSAccounting { uint256 blockedBondManagementPeriod ) CSAccounting( - commonBondSize, + bondCurve, admin, lidoLocator, wstETH, @@ -74,7 +74,7 @@ contract CSAccounting_revealed is CSAccounting { } } -contract CSAccountingTest is Test, Fixtures, CSAccountingBase { +contract CSAccounting_BlockedBondTest is Test, Fixtures, CSAccountingBase { using stdStorage for StdStorage; LidoLocatorMock internal locator; @@ -100,8 +100,11 @@ contract CSAccountingTest is Test, Fixtures, CSAccountingBase { (locator, wstETH, stETH, burner) = initLido(); stakingModule = new CommunityStakingModuleMock(); + uint256[] memory curve = new uint256[](2); + curve[0] = 2 ether; + curve[1] = 4 ether; accounting = new CSAccounting_revealed( - 2 ether, + curve, admin, address(locator), address(wstETH), diff --git a/test/CSAccounting.t.sol b/test/CSAccounting.t.sol index b320313d..2f1196b8 100644 --- a/test/CSAccounting.t.sol +++ b/test/CSAccounting.t.sol @@ -6,6 +6,7 @@ pragma solidity 0.8.21; import "forge-std/Test.sol"; import { CSAccountingBase, CSAccounting } from "../src/CSAccounting.sol"; +import { CSBondCurve } from "../src/CSBondCurve.sol"; import { PermitTokenBase } from "./helpers/Permit.sol"; import { Stub } from "./helpers/mocks/Stub.sol"; import { LidoMock } from "./helpers/mocks/LidoMock.sol"; @@ -17,6 +18,44 @@ import { WithdrawalQueueMockBase, WithdrawalQueueMock } from "./helpers/mocks/Wi import { Fixtures } from "./helpers/Fixtures.sol"; +contract CSAccounting_revealed is CSAccounting { + constructor( + uint256[] memory bondCurve, + address admin, + address lidoLocator, + address wstETH, + address communityStakingModule, + uint256 blockedBondRetentionPeriod, + uint256 blockedBondManagementPeriod + ) + CSAccounting( + bondCurve, + admin, + lidoLocator, + wstETH, + communityStakingModule, + blockedBondRetentionPeriod, + blockedBondManagementPeriod + ) + {} + + function setBondCurve() public { + uint256[] memory _bondCurve = new uint256[](11); + _bondCurve[0] = 2 ether; + _bondCurve[1] = 3.90 ether; // 1.9 + _bondCurve[2] = 5.70 ether; // 1.8 + _bondCurve[3] = 7.40 ether; // 1.7 + _bondCurve[4] = 9.00 ether; // 1.6 + _bondCurve[5] = 10.50 ether; // 1.5 + _bondCurve[6] = 11.90 ether; // 1.4 + _bondCurve[7] = 13.10 ether; // 1.3 + _bondCurve[8] = 14.30 ether; // 1.2 + _bondCurve[9] = 15.40 ether; // 1.1 + _bondCurve[10] = 16.40 ether; // 1.0 + bondCurve = _bondCurve; + } +} + contract CSAccountingTest is Test, Fixtures, @@ -31,7 +70,7 @@ contract CSAccountingTest is Stub internal burner; - CSAccounting public accounting; + CSAccounting_revealed public accounting; CommunityStakingModuleMock public stakingModule; CommunityStakingFeeDistributorMock public feeDistributor; @@ -48,8 +87,11 @@ contract CSAccountingTest is (locator, wstETH, stETH, burner) = initLido(); stakingModule = new CommunityStakingModuleMock(); - accounting = new CSAccounting( - 2 ether, + uint256[] memory curve = new uint256[](2); + curve[0] = 2 ether; + curve[1] = 4 ether; + accounting = new CSAccounting_revealed( + curve, admin, address(locator), address(wstETH), @@ -72,9 +114,50 @@ contract CSAccountingTest is accounting.EL_REWARDS_STEALING_PENALTY_SETTLE_ROLE(), admin ); + accounting.grantRole(accounting.SET_BOND_CURVE_ROLE(), admin); + accounting.grantRole(accounting.SET_BOND_MULTIPLIER_ROLE(), admin); vm.stopPrank(); } + function test_setBondCurve() public { + uint256[] memory _bondCurve = new uint256[](2); + _bondCurve[0] = 2 ether; + _bondCurve[1] = 4 ether; + + vm.prank(admin); + accounting.setBondCurve(_bondCurve); + + assertEq(accounting.bondCurve(0), 2 ether); + assertEq(accounting.bondCurve(1), 4 ether); + } + + function test_setBondCurve_RevertWhen_DoesNotHaveRole() public { + uint256[] memory _bondCurve = new uint256[](2); + _bondCurve[0] = 2 ether; + _bondCurve[1] = 4 ether; + + vm.expectRevert( + "AccessControl: account 0x7fa9385be102ac3eac297483dd6233d62b3e1496 is missing role 0x645c9e6d2a86805cb5a28b1e4751c0dab493df7cf935070ce405489ba1a7bf72" + ); + + accounting.setBondCurve(_bondCurve); + } + + function test_setBondMultiplier() public { + vm.prank(admin); + accounting.setBondMultiplier(0, 9500); // 0.95 + + assertEq(accounting.getBondMultiplier(0), 9500); + } + + function test_setBondMultiplier_RevertWhen_DoesNotHaveRole() public { + vm.expectRevert( + "AccessControl: account 0x7fa9385be102ac3eac297483dd6233d62b3e1496 is missing role 0x62131145aee19b18b85aa8ead52ba87f0efb6e61e249155edc68a2c24e8f79b5" + ); + + accounting.setBondMultiplier(0, 9500); // 0.95 + } + function test_totalBondShares() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 32 ether); @@ -622,10 +705,9 @@ contract CSAccountingTest is uint256 sharesAsFee = stETH.submit{ value: 0.1 ether }(address(0)); uint256 ETHAsFee = stETH.getPooledEthByShares(sharesAsFee); vm.deal(user, 32 ether); - vm.startPrank(user); + vm.prank(user); accounting.depositETH{ value: 32 ether }(user, 0); - // todo: should we think about simulate rebase? uint256 totalRewards = accounting.getTotalRewardsETH( new bytes32[](1), 0, @@ -633,6 +715,31 @@ contract CSAccountingTest is ); assertEq(totalRewards, ETHAsFee); + + // set sophisticated curve + accounting.setBondCurve(); + + totalRewards = accounting.getTotalRewardsETH( + new bytes32[](1), + 0, + sharesAsFee + ); + + // fee + excess after curve + assertEq(totalRewards, ETHAsFee + 10.6 ether); + + // set multiplier + vm.prank(admin); + accounting.setBondMultiplier(0, 9500); // 0.95 + + totalRewards = accounting.getTotalRewardsETH( + new bytes32[](1), + 0, + sharesAsFee + ); + + // fee + excess after curve + multiplier + assertEq(totalRewards, ETHAsFee + (32 ether - 21.4 ether * 0.95)); } function test_getTotalRewardsStETH() public { @@ -645,8 +752,8 @@ contract CSAccountingTest is vm.startPrank(user); stETH.submit{ value: 32 ether }({ _referal: address(0) }); accounting.depositStETH(user, 0, 32 ether); + vm.stopPrank(); - // todo: should we think about simulate rebase? uint256 totalRewards = accounting.getTotalRewardsStETH( new bytes32[](1), 0, @@ -654,6 +761,30 @@ contract CSAccountingTest is ); assertEq(totalRewards, stETHAsFee); + + // set sophisticated curve + accounting.setBondCurve(); + totalRewards = accounting.getTotalRewardsStETH( + new bytes32[](1), + 0, + sharesAsFee + ); + + // fee + excess after curve + assertEq(totalRewards, stETHAsFee + 10.6 ether); + + // set multiplier + vm.prank(admin); + accounting.setBondMultiplier(0, 9500); // 0.95 + + totalRewards = accounting.getTotalRewardsStETH( + new bytes32[](1), + 0, + sharesAsFee + ); + + // fee + excess after curve + multiplier + assertEq(totalRewards, stETHAsFee + (32 ether - 21.4 ether * 0.95)); } function test_getTotalRewardsWstETH() public { @@ -668,39 +799,100 @@ contract CSAccountingTest is vm.startPrank(user); stETH.submit{ value: 32 ether }({ _referal: address(0) }); accounting.depositStETH(user, 0, 32 ether); + vm.stopPrank(); - // todo: should we think about simulate rebase? uint256 totalRewards = accounting.getTotalRewardsWstETH( new bytes32[](1), 0, sharesAsFee ); - assertEq(totalRewards, wstETHAsFee); + assertApproxEqAbs(totalRewards, wstETHAsFee, 1); + + // set sophisticated curve + accounting.setBondCurve(); + totalRewards = accounting.getTotalRewardsWstETH( + new bytes32[](1), + 0, + sharesAsFee + ); + + // fee + excess after curve + assertApproxEqAbs( + totalRewards, + wstETHAsFee + wstETH.getWstETHByStETH(10.6 ether), + 1 + ); + + // set multiplier + vm.prank(admin); + accounting.setBondMultiplier(0, 9500); // 0.95 + + totalRewards = accounting.getTotalRewardsWstETH( + new bytes32[](1), + 0, + sharesAsFee + ); + + // fee + excess after curve + multiplier + assertEq( + totalRewards, + wstETHAsFee + wstETH.getWstETHByStETH(32 ether - 21.4 ether * 0.95) + ); } function test_getExcessBondETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 64 ether); - vm.startPrank(user); + vm.prank(user); accounting.depositETH{ value: 64 ether }(user, 0); assertApproxEqAbs(accounting.getExcessBondETH(0), 32 ether, 1); + + // set sophisticated curve + accounting.setBondCurve(); + + assertApproxEqAbs(accounting.getExcessBondETH(0), 42.6 ether, 1); + + // set multiplier + vm.prank(admin); + accounting.setBondMultiplier(0, 9500); // 0.95 + + assertApproxEqAbs( + accounting.getExcessBondETH(0), + 32 ether + (32 ether - 21.4 ether * 0.95), + 1 + ); } function test_getExcessBondStETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 64 ether); - vm.startPrank(user); + vm.prank(user); accounting.depositETH{ value: 64 ether }(user, 0); assertApproxEqAbs(accounting.getExcessBondStETH(0), 32 ether, 1); + + // set sophisticated curve + accounting.setBondCurve(); + + assertApproxEqAbs(accounting.getExcessBondStETH(0), 42.6 ether, 1); + + // set multiplier + vm.prank(admin); + accounting.setBondMultiplier(0, 9500); // 0.95 + + assertApproxEqAbs( + accounting.getExcessBondStETH(0), + 32 ether + (32 ether - 21.4 ether * 0.95), + 1 + ); } function test_getExcessBondWstETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 64 ether); - vm.startPrank(user); + vm.prank(user); accounting.depositETH{ value: 64 ether }(user, 0); assertApproxEqAbs( @@ -708,30 +900,79 @@ contract CSAccountingTest is wstETH.getWstETHByStETH(32 ether), 1 ); + + // set sophisticated curve + accounting.setBondCurve(); + + assertApproxEqAbs( + accounting.getExcessBondWstETH(0), + wstETH.getWstETHByStETH(42.6 ether), + 1 + ); + + // set multiplier + vm.prank(admin); + accounting.setBondMultiplier(0, 9500); // 0.95 + + assertApproxEqAbs( + accounting.getExcessBondWstETH(0), + wstETH.getWstETHByStETH(32 ether + (32 ether - 21.4 ether * 0.95)), + 1 + ); } function test_getMissingBondETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 16 ether); - vm.startPrank(user); + vm.prank(user); accounting.depositETH{ value: 16 ether }(user, 0); assertApproxEqAbs(accounting.getMissingBondETH(0), 16 ether, 1); + + // set sophisticated curve + accounting.setBondCurve(); + + assertApproxEqAbs(accounting.getMissingBondETH(0), 5.4 ether, 1); + + // set multiplier + vm.prank(admin); + accounting.setBondMultiplier(0, 9500); // 0.95 + + assertApproxEqAbs( + accounting.getMissingBondETH(0), + 16 ether - (32 ether - 21.4 ether * 0.95), + 1 + ); } function test_getMissingBondStETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 16 ether); - vm.startPrank(user); + vm.prank(user); accounting.depositETH{ value: 16 ether }(user, 0); assertApproxEqAbs(accounting.getMissingBondStETH(0), 16 ether, 1); + + // set sophisticated curve + accounting.setBondCurve(); + + assertApproxEqAbs(accounting.getMissingBondStETH(0), 5.4 ether, 1); + + // set multiplier + vm.prank(admin); + accounting.setBondMultiplier(0, 9500); // 0.95 + + assertApproxEqAbs( + accounting.getMissingBondStETH(0), + 16 ether - (32 ether - 21.4 ether * 0.95), + 1 + ); } function test_getMissingBondWstETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 16 ether); - vm.startPrank(user); + vm.prank(user); accounting.depositETH{ value: 16 ether }(user, 0); assertApproxEqAbs( @@ -739,15 +980,50 @@ contract CSAccountingTest is wstETH.getWstETHByStETH(16 ether), 1 ); + + // set sophisticated curve + accounting.setBondCurve(); + + assertApproxEqAbs( + accounting.getMissingBondWstETH(0), + wstETH.getWstETHByStETH(5.4 ether), + 1 + ); + + // set multiplier + vm.prank(admin); + accounting.setBondMultiplier(0, 9500); // 0.95 + + assertApproxEqAbs( + accounting.getMissingBondWstETH(0), + wstETH.getWstETHByStETH(16 ether - (32 ether - 21.4 ether * 0.95)), + 1 + ); } function test_getUnbondedKeysCount() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 17.57 ether); - vm.startPrank(user); - accounting.depositETH{ value: 17.57 ether }(user, 0); + vm.deal(user, 32 ether); + vm.prank(user); + accounting.depositETH{ value: 11.57 ether }(user, 0); + + assertEq(accounting.getUnbondedKeysCount(0), 10); + + vm.prank(user); + accounting.depositETH{ value: 2.43 ether }(user, 0); + + assertEq(accounting.getUnbondedKeysCount(0), 9); + + // set sophisticated curve + accounting.setBondCurve(); assertEq(accounting.getUnbondedKeysCount(0), 7); + + // set multiplier + vm.prank(admin); + accounting.setBondMultiplier(0, 9500); // 0.95 + + assertEq(accounting.getUnbondedKeysCount(0), 6); } function test_getKeysCountByBondETH() public { @@ -755,6 +1031,12 @@ contract CSAccountingTest is assertEq(accounting.getKeysCountByBondETH(1.99 ether), 0); assertEq(accounting.getKeysCountByBondETH(2 ether), 1); assertEq(accounting.getKeysCountByBondETH(4 ether), 2); + assertEq(accounting.getKeysCountByBondETH(16 ether), 8); + + // set sophisticated curve + accounting.setBondCurve(); + + assertEq(accounting.getKeysCountByBondETH(16 ether), 10); } function test_getKeysCountByBondStETH() public { @@ -762,6 +1044,12 @@ contract CSAccountingTest is assertEq(accounting.getKeysCountByBondStETH(1.99 ether), 0); assertEq(accounting.getKeysCountByBondStETH(2 ether), 1); assertEq(accounting.getKeysCountByBondStETH(4 ether), 2); + assertEq(accounting.getKeysCountByBondETH(16 ether), 8); + + // set sophisticated curve + accounting.setBondCurve(); + + assertEq(accounting.getKeysCountByBondStETH(16 ether), 10); } function test_getKeysCountByBondWstETH() public { @@ -774,16 +1062,32 @@ contract CSAccountingTest is ); assertEq( accounting.getKeysCountByBondWstETH( - wstETH.getWstETHByStETH(2 ether) + wstETH.getWstETHByStETH(2 ether + 1 wei) ), 1 ); assertEq( accounting.getKeysCountByBondWstETH( - wstETH.getWstETHByStETH(4 ether) + wstETH.getWstETHByStETH(4 ether + 1 wei) ), 2 ); + assertEq( + accounting.getKeysCountByBondWstETH( + wstETH.getWstETHByStETH(16 ether + 1 wei) + ), + 8 + ); + + // set sophisticated curve + accounting.setBondCurve(); + + assertEq( + accounting.getKeysCountByBondWstETH( + wstETH.getWstETHByStETH(16 ether + 1 wei) + ), + 10 + ); } function test_claimRewardsStETH() public { @@ -812,6 +1116,7 @@ contract CSAccountingTest is UINT256_MAX ); uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); assertEq( stETH.balanceOf(address(user)), @@ -828,6 +1133,54 @@ contract CSAccountingTest is bondSharesAfter, "bond manager after claim should be equal to before" ); + + // set sophisticated curve + accounting.setBondCurve(); + + vm.deal(address(feeDistributor), 0.1 ether); + vm.prank(address(feeDistributor)); + stETH.submit{ value: 0.1 ether }(address(0)); + + uint256 balanceBefore = stETH.balanceOf(address(user)); + vm.prank(user); + accounting.claimRewardsStETH( + new bytes32[](1), + 0, + sharesAsFee, + UINT256_MAX + ); + + // claimed fee before + fee + excess after curve + assertEq( + stETH.balanceOf(address(user)), + balanceBefore + stETHAsFee + 10.6 ether, + "user balance should be equal to fee reward + excess" + ); + + // set multiplier + vm.prank(admin); + accounting.setBondMultiplier(0, 9500); // 0.95 + + vm.deal(address(feeDistributor), 0.1 ether); + vm.prank(address(feeDistributor)); + stETH.submit{ value: 0.1 ether }(address(0)); + + balanceBefore = stETH.balanceOf(address(user)); + vm.prank(user); + accounting.claimRewardsStETH( + new bytes32[](1), + 0, + sharesAsFee, + UINT256_MAX + ); + + // claimed fee before x2 + fee + excess after multiplier + assertApproxEqAbs( + stETH.balanceOf(address(user)), + balanceBefore + stETHAsFee + (21.4 ether - 21.4 ether * 0.95), + 1, + "user balance should be equal to fee reward + excess" + ); } function test_claimRewardsStETH_WithDesirableValue() public { @@ -1023,6 +1376,7 @@ contract CSAccountingTest is UINT256_MAX ); uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); assertEq( wstETH.balanceOf(address(user)), @@ -1044,6 +1398,64 @@ contract CSAccountingTest is bondSharesBefore + 1 wei, "bond manager after claim should contain wrapped fee accuracy error" ); + + // set sophisticated curve + accounting.setBondCurve(); + + vm.deal(address(feeDistributor), 0.1 ether); + vm.prank(address(feeDistributor)); + stETH.submit{ value: 0.1 ether }(address(0)); + + uint256 balanceBefore = wstETH.balanceOf(address(user)); + vm.prank(user); + accounting.claimRewardsWstETH( + new bytes32[](1), + 0, + sharesAsFee, + UINT256_MAX + ); + + // claimed fee before + fee + excess after curve + assertApproxEqAbs( + wstETH.balanceOf(address(user)), + balanceBefore + + wstETH.getWstETHByStETH( + stETH.getPooledEthByShares(sharesAsFee) + 10.6 ether + ), + 1, + "user balance should be equal to fee reward + excess" + ); + + // set multiplier + vm.prank(admin); + accounting.setBondMultiplier(0, 9500); // 0.95 + + vm.deal(address(feeDistributor), 0.1 ether); + vm.prank(address(feeDistributor)); + stETH.submit{ value: 0.1 ether }(address(0)); + + balanceBefore = wstETH.balanceOf(address(user)); + vm.prank(user); + accounting.claimRewardsWstETH( + new bytes32[](1), + 0, + sharesAsFee, + UINT256_MAX + ); + + // claimed fee before x2 + fee + excess after multiplier + assertApproxEqAbs( + wstETH.balanceOf(address(user)), + balanceBefore + + wstETH.getWstETHByStETH( + stETH.getPooledEthByShares(sharesAsFee) + + 21.4 ether - + 21.4 ether * + 0.95 + ), + 1, + "user balance should be equal to fee reward + excess" + ); } function test_claimRewardsWstETH_WithDesirableValue() public { @@ -1151,6 +1563,7 @@ contract CSAccountingTest is UINT256_MAX ); uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); assertEq(requestIds.length, 1, "request ids length should be 1"); assertEq( @@ -1164,6 +1577,60 @@ contract CSAccountingTest is "shares of withdrawal queue should be equal to requested shares" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + + // set sophisticated curve + accounting.setBondCurve(); + + vm.deal(address(feeDistributor), 0.1 ether); + vm.prank(address(feeDistributor)); + stETH.submit{ value: 0.1 ether }(address(0)); + + uint256 balanceBefore = stETH.sharesOf( + address(locator.withdrawalQueue()) + ); + vm.prank(user); + accounting.requestRewardsETH( + new bytes32[](1), + 0, + sharesAsFee, + UINT256_MAX + ); + + // requested fee before + fee + excess after curve + assertEq( + stETH.sharesOf(address(locator.withdrawalQueue())), + balanceBefore + + requestedAsUnstETHAsShares + + stETH.getSharesByPooledEth(10.6 ether), + "shares of withdrawal queue should be equal to requested shares + excess" + ); + + // set multiplier + vm.prank(admin); + accounting.setBondMultiplier(0, 9500); // 0.95 + + vm.deal(address(feeDistributor), 0.1 ether); + vm.prank(address(feeDistributor)); + stETH.submit{ value: 0.1 ether }(address(0)); + + balanceBefore = stETH.sharesOf(address(locator.withdrawalQueue())); + vm.prank(user); + accounting.requestRewardsETH( + new bytes32[](1), + 0, + sharesAsFee, + UINT256_MAX + ); + + // claimed fee before x2 + fee + excess after multiplier + assertApproxEqAbs( + stETH.sharesOf(address(locator.withdrawalQueue())), + balanceBefore + + requestedAsUnstETHAsShares + + stETH.getSharesByPooledEth(21.4 ether - 21.4 ether * 0.95), + 1, + "shares of withdrawal queue should be equal to requested shares + excess" + ); } function test_requestRewardsETH_WithDesirableValue() public { diff --git a/test/CSBondCurve.t.sol b/test/CSBondCurve.t.sol new file mode 100644 index 00000000..af4a5ce0 --- /dev/null +++ b/test/CSBondCurve.t.sol @@ -0,0 +1,164 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.21; + +import "forge-std/Test.sol"; + +import { CSBondCurve } from "../src/CSBondCurve.sol"; + +contract CSBondCurveTestable is CSBondCurve { + constructor(uint256[] memory _bondCurve) CSBondCurve(_bondCurve) {} + + function setBondCurve(uint256[] memory _bondCurve) external { + _setBondCurve(_bondCurve); + } + + function setBondMultiplier( + uint256 nodeOperatorId, + uint256 basisPoints + ) external { + _setBondMultiplier(nodeOperatorId, basisPoints); + } + + function getKeysCountByCurveValue( + uint256 amount + ) external view returns (uint256) { + return _getKeysCountByCurveValue(amount); + } + + function getCurveValueByKeysCount( + uint256 keysCount + ) external view returns (uint256) { + return _getCurveValueByKeysCount(keysCount); + } + + function getKeysCountByCurveValue( + uint256 nodeOperatorId, + uint256 amount + ) external view returns (uint256) { + return _getKeysCountByCurveValue(nodeOperatorId, amount); + } + + function getCurveValueByKeysCount( + uint256 nodeOperatorId, + uint256 keysCount + ) external view returns (uint256) { + return _getCurveValueByKeysCount(nodeOperatorId, keysCount); + } +} + +contract CSBondCurveTest is Test { + CSBondCurveTestable public bondCurve; + + function setUp() public { + uint256[] memory _bondCurve = new uint256[](11); + _bondCurve[0] = 2 ether; + _bondCurve[1] = 3.90 ether; // 1.9 + _bondCurve[2] = 5.70 ether; // 1.8 + _bondCurve[3] = 7.40 ether; // 1.7 + _bondCurve[4] = 9.00 ether; // 1.6 + _bondCurve[5] = 10.50 ether; // 1.5 + _bondCurve[6] = 11.90 ether; // 1.4 + _bondCurve[7] = 13.10 ether; // 1.3 + _bondCurve[8] = 14.30 ether; // 1.2 + _bondCurve[9] = 15.40 ether; // 1.1 + _bondCurve[10] = 16.40 ether; // 1.0 + bondCurve = new CSBondCurveTestable(_bondCurve); + } + + function test_setBondCurve_RevertWhen_LessThanMinBondCurveLength() public { + uint256[] memory _bondCurve = new uint256[](1); + _bondCurve[0] = 2 ether; + + vm.expectRevert(CSBondCurve.InvalidBondCurveLength.selector); + + bondCurve.setBondCurve(_bondCurve); + } + + function test_setBondCurve_RevertWhen_MoreThanMaxBondCurveLength() public { + uint256[] memory _bondCurve = new uint256[](21); + _bondCurve = new uint256[](21); + for (uint256 i = 0; i < 21; i++) { + _bondCurve[i] = i; + } + + vm.expectRevert(CSBondCurve.InvalidBondCurveLength.selector); + + bondCurve.setBondCurve(_bondCurve); + } + + function test_setBondMultiplier_RevertWhen_LessThanMin() public { + vm.expectRevert(CSBondCurve.InvalidMultiplier.selector); + + bondCurve.setBondMultiplier(0, 4999); + } + + function test_setBondMultiplier_RevertWhen_MoreThanMax() public { + vm.expectRevert(CSBondCurve.InvalidMultiplier.selector); + + bondCurve.setBondMultiplier(0, 10001); + } + + function test_getKeysCountByCurveValue() public { + assertEq(bondCurve.getKeysCountByCurveValue(0), 0); + assertEq(bondCurve.getKeysCountByCurveValue(2 ether), 1); + assertEq(bondCurve.getKeysCountByCurveValue(3 ether), 1); + assertEq(bondCurve.getKeysCountByCurveValue(3.90 ether), 2); + assertEq(bondCurve.getKeysCountByCurveValue(5.70 ether), 3); + assertEq(bondCurve.getKeysCountByCurveValue(7.40 ether), 4); + assertEq(bondCurve.getKeysCountByCurveValue(9.00 ether), 5); + assertEq(bondCurve.getKeysCountByCurveValue(10.50 ether), 6); + assertEq(bondCurve.getKeysCountByCurveValue(11.90 ether), 7); + assertEq(bondCurve.getKeysCountByCurveValue(13.10 ether), 8); + assertEq(bondCurve.getKeysCountByCurveValue(14.30 ether), 9); + assertEq(bondCurve.getKeysCountByCurveValue(15.40 ether), 10); + assertEq(bondCurve.getKeysCountByCurveValue(16.40 ether), 11); + assertEq(bondCurve.getKeysCountByCurveValue(17.40 ether), 12); + + bondCurve.setBondMultiplier(0, 5000); + + assertEq(bondCurve.getKeysCountByCurveValue(0, 0), 0); + assertEq(bondCurve.getKeysCountByCurveValue(0, 2 ether), 2); + assertEq(bondCurve.getKeysCountByCurveValue(0, 3 ether), 3); + assertEq(bondCurve.getKeysCountByCurveValue(0, 3.90 ether), 4); + assertEq(bondCurve.getKeysCountByCurveValue(0, 5.70 ether), 6); + assertEq(bondCurve.getKeysCountByCurveValue(0, 7.40 ether), 9); + assertEq(bondCurve.getKeysCountByCurveValue(0, 9.00 ether), 12); + assertEq(bondCurve.getKeysCountByCurveValue(0, 10.50 ether), 15); + assertEq(bondCurve.getKeysCountByCurveValue(0, 11.90 ether), 18); + assertEq(bondCurve.getKeysCountByCurveValue(0, 13.10 ether), 20); + } + + function test_getCurveValueByKeysCount() public { + assertEq(bondCurve.getCurveValueByKeysCount(0), 0); + assertEq(bondCurve.getCurveValueByKeysCount(1), 2 ether); + assertEq(bondCurve.getCurveValueByKeysCount(2), 3.90 ether); + assertEq(bondCurve.getCurveValueByKeysCount(3), 5.70 ether); + assertEq(bondCurve.getCurveValueByKeysCount(4), 7.40 ether); + assertEq(bondCurve.getCurveValueByKeysCount(5), 9.00 ether); + assertEq(bondCurve.getCurveValueByKeysCount(6), 10.50 ether); + assertEq(bondCurve.getCurveValueByKeysCount(7), 11.90 ether); + assertEq(bondCurve.getCurveValueByKeysCount(8), 13.10 ether); + assertEq(bondCurve.getCurveValueByKeysCount(9), 14.30 ether); + assertEq(bondCurve.getCurveValueByKeysCount(10), 15.40 ether); + assertEq(bondCurve.getCurveValueByKeysCount(11), 16.40 ether); + assertEq(bondCurve.getCurveValueByKeysCount(12), 17.40 ether); + + bondCurve.setBondMultiplier(0, 5000); + + assertEq(bondCurve.getCurveValueByKeysCount(0, 0), 0); + assertEq(bondCurve.getCurveValueByKeysCount(0, 1), 1 ether); + assertEq(bondCurve.getCurveValueByKeysCount(0, 2), 1.95 ether); + assertEq(bondCurve.getCurveValueByKeysCount(0, 3), 2.85 ether); + assertEq(bondCurve.getCurveValueByKeysCount(0, 4), 3.70 ether); + assertEq(bondCurve.getCurveValueByKeysCount(0, 5), 4.50 ether); + assertEq(bondCurve.getCurveValueByKeysCount(0, 6), 5.25 ether); + assertEq(bondCurve.getCurveValueByKeysCount(0, 7), 5.95 ether); + assertEq(bondCurve.getCurveValueByKeysCount(0, 8), 6.55 ether); + assertEq(bondCurve.getCurveValueByKeysCount(0, 9), 7.15 ether); + assertEq(bondCurve.getCurveValueByKeysCount(0, 10), 7.7 ether); + assertEq(bondCurve.getCurveValueByKeysCount(0, 11), 8.20 ether); + assertEq(bondCurve.getCurveValueByKeysCount(0, 12), 8.70 ether); + } +} diff --git a/test/CSModule.t.sol b/test/CSModule.t.sol index 77107361..56638204 100644 --- a/test/CSModule.t.sol +++ b/test/CSModule.t.sol @@ -57,8 +57,11 @@ contract CSMCommon is Test, Fixtures, Utilities, CSModuleBase { address(accounting) ); csm = new CSModule("community-staking-module", address(locator)); + uint256[] memory curve = new uint256[](2); + curve[0] = 2 ether; + curve[1] = 4 ether; accounting = new CSAccounting( - 2 ether, + curve, admin, address(locator), address(wstETH), diff --git a/test/integration/DepositInTokens.t.sol b/test/integration/DepositInTokens.t.sol index 2b4854f3..f190be8e 100644 --- a/test/integration/DepositInTokens.t.sol +++ b/test/integration/DepositInTokens.t.sol @@ -51,8 +51,11 @@ contract DepositIntegrationTest is strangerPrivateKey = 0x517a4637; stranger = vm.addr(strangerPrivateKey); + uint256[] memory curve = new uint256[](2); + curve[0] = 2 ether; + curve[1] = 4 ether; accounting = new CSAccounting( - 2 ether, + curve, user, address(locator), address(wstETH), diff --git a/test/integration/StakingRouter.t.sol b/test/integration/StakingRouter.t.sol index 990249b0..8a1b0960 100644 --- a/test/integration/StakingRouter.t.sol +++ b/test/integration/StakingRouter.t.sol @@ -40,8 +40,11 @@ contract StakingRouterIntegrationTest is Test, Utilities, IntegrationFixtures { vm.label(address(stakingRouter), "stakingRouter"); csm = new CSModule("community-staking-module", address(locator)); + uint256[] memory curve = new uint256[](2); + curve[0] = 2 ether; + curve[1] = 4 ether; CSAccounting accounting = new CSAccounting( - 2 ether, + curve, address(csm), address(locator), address(wstETH), From 2910a8e0cc9ea3e6fa09d7af31b21095533ede5b Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Tue, 28 Nov 2023 14:05:00 +0400 Subject: [PATCH 02/18] fix: review --- src/CSAccounting.sol | 4 +- src/CSBondCurve.sol | 46 +++++++++++---------- test/CSAccounting.blockedBond.t.sol | 25 ++++++++++-- test/CSAccounting.t.sol | 62 +++++++++++++++++++---------- test/CSBondCurve.t.sol | 3 +- test/helpers/Utilities.sol | 11 +++++ 6 files changed, 102 insertions(+), 49 deletions(-) diff --git a/src/CSAccounting.sol b/src/CSAccounting.sol index ca13957c..d2ecfb11 100644 --- a/src/CSAccounting.sol +++ b/src/CSAccounting.sol @@ -176,9 +176,9 @@ contract CSAccounting is } function setBondCurve( - uint256[] memory bondCurve_ + uint256[] memory bondCurve ) external onlyRole(SET_BOND_CURVE_ROLE) { - _setBondCurve(bondCurve_); + _setBondCurve(bondCurve); } /// @notice Sets basis points of the bond multiplier for the given node operator. diff --git a/src/CSBondCurve.sol b/src/CSBondCurve.sol index 394b563a..66fc777d 100644 --- a/src/CSBondCurve.sol +++ b/src/CSBondCurve.sol @@ -8,11 +8,12 @@ abstract contract CSBondCurve { error InvalidMultiplier(); // @dev Keys count to bond amount mapping - // x - keys count - // y - bond amount for x keys + // i | array index + // i + 1 | keys count for particular bond amount + // bondCurve[i] | bond amount for `i + 1` keys count uint256[] public bondCurve; - uint256 internal constant MIN_CURVE_LENGTH = 2; + uint256 internal constant MIN_CURVE_LENGTH = 1; // todo: might be redefined in the future uint256 internal constant MAX_CURVE_LENGTH = 20; uint256 internal constant BASIS_POINTS = 10000; @@ -23,14 +24,18 @@ abstract contract CSBondCurve { /// By default, all Node Operators have x1 multiplier (10000 basis points). mapping(uint256 => uint256) internal _bondMultiplierBP; + uint256 internal _bondCurveTrend; + constructor(uint256[] memory _bondCurve) { - _checkCurveLength(_bondCurve); - bondCurve = _bondCurve; + _setBondCurve(_bondCurve); } function _setBondCurve(uint256[] memory _bondCurve) internal { _checkCurveLength(_bondCurve); bondCurve = _bondCurve; + _bondCurveTrend = + _bondCurve[_bondCurve.length - 1] - + (_bondCurve.length > 1 ? _bondCurve[_bondCurve.length - 2] : 0); } function _setBondMultiplier( @@ -74,14 +79,12 @@ abstract contract CSBondCurve { uint256 amount ) internal view returns (uint256) { uint256 mult = getBondMultiplier(nodeOperatorId); + if (amount < (bondCurve[0] * mult) / BASIS_POINTS) return 0; uint256 last = (bondCurve[bondCurve.length - 1] * mult) / BASIS_POINTS; if (amount >= last) { return bondCurve.length + - ((amount - last) / - (last - - (bondCurve[bondCurve.length - 2] * mult) / - BASIS_POINTS)); + ((amount - last) / ((_bondCurveTrend * mult) / BASIS_POINTS)); } return _searchKeysByBond(amount, mult); } @@ -94,14 +97,18 @@ abstract contract CSBondCurve { uint256 high = bondCurve.length - 1; while (low <= high) { uint256 mid = (low + high) / 2; - if ( - (bondCurve[mid] * multiplier) / BASIS_POINTS > value && mid != 0 - ) { + uint256 midValue = (bondCurve[mid] * multiplier) / BASIS_POINTS; + if (value == midValue) { + return mid + 1; + } + if (value < midValue) { + // zero mid is avoided above high = mid - 1; - } else if ((bondCurve[mid] * multiplier) / BASIS_POINTS <= value) { + continue; + } + if (value > midValue) { low = mid + 1; - } else { - return mid; + continue; } } return low; @@ -117,18 +124,15 @@ abstract contract CSBondCurve { uint256 nodeOperatorId, uint256 keys ) internal view returns (uint256) { - uint256 mult = getBondMultiplier(nodeOperatorId); if (keys == 0) return 0; + uint256 mult = getBondMultiplier(nodeOperatorId); if (keys <= bondCurve.length) { return (bondCurve[keys - 1] * mult) / BASIS_POINTS; } else { - uint256 last = (bondCurve[bondCurve.length - 1] * mult) / - BASIS_POINTS; return - last + + ((bondCurve[bondCurve.length - 1] * mult) / BASIS_POINTS) + (keys - bondCurve.length) * - (last - - ((bondCurve[bondCurve.length - 2] * mult) / BASIS_POINTS)); + ((_bondCurveTrend * mult) / BASIS_POINTS); } } } diff --git a/test/CSAccounting.blockedBond.t.sol b/test/CSAccounting.blockedBond.t.sol index 7cf569cd..47249e7e 100644 --- a/test/CSAccounting.blockedBond.t.sol +++ b/test/CSAccounting.blockedBond.t.sol @@ -15,6 +15,7 @@ import { CommunityStakingModuleMock } from "./helpers/mocks/CommunityStakingModu import { CommunityStakingFeeDistributorMock } from "./helpers/mocks/CommunityStakingFeeDistributorMock.sol"; import { WithdrawalQueueMockBase, WithdrawalQueueMock } from "./helpers/mocks/WithdrawalQueueMock.sol"; +import { Utilities } from "./helpers/Utilities.sol"; import { Fixtures } from "./helpers/Fixtures.sol"; contract CSAccounting_revealed is CSAccounting { @@ -74,7 +75,12 @@ contract CSAccounting_revealed is CSAccounting { } } -contract CSAccounting_BlockedBondTest is Test, Fixtures, CSAccountingBase { +contract CSAccounting_BlockedBondTest is + Test, + Fixtures, + Utilities, + CSAccountingBase +{ using stdStorage for StdStorage; LidoLocatorMock internal locator; @@ -449,9 +455,15 @@ contract CSAccounting_BlockedBondTest is Test, Fixtures, CSAccountingBase { _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); vm.expectRevert( - "AccessControl: account 0x7fa9385be102ac3eac297483dd6233d62b3e1496 is missing role 0xcc2e7ce7be452f766dd24d55d87a3d42901c31ffa5b600cd1dff475abec91c1f" + bytes( + Utilities.accessErrorString( + address(stranger), + accounting.EL_REWARDS_STEALING_PENALTY_INIT_ROLE() + ) + ) ); + vm.prank(stranger); accounting.initELRewardsStealingPenalty({ nodeOperatorId: 0, blockNumber: 100500, @@ -671,8 +683,15 @@ contract CSAccounting_BlockedBondTest is Test, Fixtures, CSAccountingBase { _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); vm.expectRevert( - "AccessControl: account 0x7fa9385be102ac3eac297483dd6233d62b3e1496 is missing role 0xcc2e7ce7be452f766dd24d55d87a3d42901c31ffa5b600cd1dff475abec91c1f" + bytes( + Utilities.accessErrorString( + address(stranger), + accounting.EL_REWARDS_STEALING_PENALTY_INIT_ROLE() + ) + ) ); + + vm.prank(stranger); accounting.releaseBlockedBondETH(0, 1 ether); } diff --git a/test/CSAccounting.t.sol b/test/CSAccounting.t.sol index 2f1196b8..021fef41 100644 --- a/test/CSAccounting.t.sol +++ b/test/CSAccounting.t.sol @@ -16,6 +16,7 @@ import { CommunityStakingModuleMock } from "./helpers/mocks/CommunityStakingModu import { CommunityStakingFeeDistributorMock } from "./helpers/mocks/CommunityStakingFeeDistributorMock.sol"; import { WithdrawalQueueMockBase, WithdrawalQueueMock } from "./helpers/mocks/WithdrawalQueueMock.sol"; +import { Utilities } from "./helpers/Utilities.sol"; import { Fixtures } from "./helpers/Fixtures.sol"; contract CSAccounting_revealed is CSAccounting { @@ -39,7 +40,7 @@ contract CSAccounting_revealed is CSAccounting { ) {} - function setBondCurve() public { + function setBondCurveForTests() public { uint256[] memory _bondCurve = new uint256[](11); _bondCurve[0] = 2 ether; _bondCurve[1] = 3.90 ether; // 1.9 @@ -52,13 +53,14 @@ contract CSAccounting_revealed is CSAccounting { _bondCurve[8] = 14.30 ether; // 1.2 _bondCurve[9] = 15.40 ether; // 1.1 _bondCurve[10] = 16.40 ether; // 1.0 - bondCurve = _bondCurve; + _setBondCurve(_bondCurve); } } contract CSAccountingTest is Test, Fixtures, + Utilities, PermitTokenBase, CSAccountingBase, WithdrawalQueueMockBase @@ -137,9 +139,15 @@ contract CSAccountingTest is _bondCurve[1] = 4 ether; vm.expectRevert( - "AccessControl: account 0x7fa9385be102ac3eac297483dd6233d62b3e1496 is missing role 0x645c9e6d2a86805cb5a28b1e4751c0dab493df7cf935070ce405489ba1a7bf72" + bytes( + Utilities.accessErrorString( + stranger, + accounting.SET_BOND_CURVE_ROLE() + ) + ) ); + vm.prank(stranger); accounting.setBondCurve(_bondCurve); } @@ -152,9 +160,15 @@ contract CSAccountingTest is function test_setBondMultiplier_RevertWhen_DoesNotHaveRole() public { vm.expectRevert( - "AccessControl: account 0x7fa9385be102ac3eac297483dd6233d62b3e1496 is missing role 0x62131145aee19b18b85aa8ead52ba87f0efb6e61e249155edc68a2c24e8f79b5" + bytes( + Utilities.accessErrorString( + stranger, + accounting.SET_BOND_MULTIPLIER_ROLE() + ) + ) ); + vm.prank(stranger); accounting.setBondMultiplier(0, 9500); // 0.95 } @@ -717,7 +731,7 @@ contract CSAccountingTest is assertEq(totalRewards, ETHAsFee); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); totalRewards = accounting.getTotalRewardsETH( new bytes32[](1), @@ -763,7 +777,7 @@ contract CSAccountingTest is assertEq(totalRewards, stETHAsFee); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); totalRewards = accounting.getTotalRewardsStETH( new bytes32[](1), 0, @@ -810,7 +824,7 @@ contract CSAccountingTest is assertApproxEqAbs(totalRewards, wstETHAsFee, 1); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); totalRewards = accounting.getTotalRewardsWstETH( new bytes32[](1), 0, @@ -850,7 +864,7 @@ contract CSAccountingTest is assertApproxEqAbs(accounting.getExcessBondETH(0), 32 ether, 1); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); assertApproxEqAbs(accounting.getExcessBondETH(0), 42.6 ether, 1); @@ -874,7 +888,7 @@ contract CSAccountingTest is assertApproxEqAbs(accounting.getExcessBondStETH(0), 32 ether, 1); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); assertApproxEqAbs(accounting.getExcessBondStETH(0), 42.6 ether, 1); @@ -902,7 +916,7 @@ contract CSAccountingTest is ); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); assertApproxEqAbs( accounting.getExcessBondWstETH(0), @@ -930,7 +944,7 @@ contract CSAccountingTest is assertApproxEqAbs(accounting.getMissingBondETH(0), 16 ether, 1); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); assertApproxEqAbs(accounting.getMissingBondETH(0), 5.4 ether, 1); @@ -954,7 +968,7 @@ contract CSAccountingTest is assertApproxEqAbs(accounting.getMissingBondStETH(0), 16 ether, 1); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); assertApproxEqAbs(accounting.getMissingBondStETH(0), 5.4 ether, 1); @@ -982,7 +996,7 @@ contract CSAccountingTest is ); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); assertApproxEqAbs( accounting.getMissingBondWstETH(0), @@ -1015,7 +1029,7 @@ contract CSAccountingTest is assertEq(accounting.getUnbondedKeysCount(0), 9); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); assertEq(accounting.getUnbondedKeysCount(0), 7); @@ -1034,7 +1048,7 @@ contract CSAccountingTest is assertEq(accounting.getKeysCountByBondETH(16 ether), 8); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); assertEq(accounting.getKeysCountByBondETH(16 ether), 10); } @@ -1047,7 +1061,7 @@ contract CSAccountingTest is assertEq(accounting.getKeysCountByBondETH(16 ether), 8); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); assertEq(accounting.getKeysCountByBondStETH(16 ether), 10); } @@ -1080,7 +1094,7 @@ contract CSAccountingTest is ); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); assertEq( accounting.getKeysCountByBondWstETH( @@ -1135,7 +1149,7 @@ contract CSAccountingTest is ); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); vm.deal(address(feeDistributor), 0.1 ether); vm.prank(address(feeDistributor)); @@ -1400,7 +1414,7 @@ contract CSAccountingTest is ); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); vm.deal(address(feeDistributor), 0.1 ether); vm.prank(address(feeDistributor)); @@ -1579,7 +1593,7 @@ contract CSAccountingTest is assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); // set sophisticated curve - accounting.setBondCurve(); + accounting.setBondCurveForTests(); vm.deal(address(feeDistributor), 0.1 ether); vm.prank(address(feeDistributor)); @@ -1798,8 +1812,14 @@ contract CSAccountingTest is function test_penalize_RevertWhenCallerHasNoRole() public { vm.expectRevert( - "AccessControl: account 0x0000000000000000000000000000000000000309 is missing role 0x9909cf24c2d3bafa8c229558d86a1b726ba57c3ef6350848dcf434a4181b56c7" + bytes( + Utilities.accessErrorString( + stranger, + accounting.INSTANT_PENALIZE_BOND_ROLE() + ) + ) ); + vm.prank(stranger); accounting.penalize(0, 20); } diff --git a/test/CSBondCurve.t.sol b/test/CSBondCurve.t.sol index af4a5ce0..3b10e218 100644 --- a/test/CSBondCurve.t.sol +++ b/test/CSBondCurve.t.sol @@ -68,8 +68,7 @@ contract CSBondCurveTest is Test { } function test_setBondCurve_RevertWhen_LessThanMinBondCurveLength() public { - uint256[] memory _bondCurve = new uint256[](1); - _bondCurve[0] = 2 ether; + uint256[] memory _bondCurve = new uint256[](0); vm.expectRevert(CSBondCurve.InvalidBondCurveLength.selector); diff --git a/test/helpers/Utilities.sol b/test/helpers/Utilities.sol index 73695604..1babf78c 100644 --- a/test/helpers/Utilities.sol +++ b/test/helpers/Utilities.sol @@ -55,4 +55,15 @@ contract Utilities is CommonBase { revert("wrong chain id"); } } + + function accessErrorString( + address account, + bytes32 role + ) internal pure returns (string memory) { + string memory errorString = "AccessControl: account "; + errorString = string.concat(errorString, vm.toString(account)); + errorString = string.concat(errorString, " is missing role "); + errorString = string.concat(errorString, vm.toString(role)); + return errorString; + } } From d9e9e87b5aa737bead51a7b37d1383d3d1b5191e Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Wed, 29 Nov 2023 10:18:53 +0400 Subject: [PATCH 03/18] Fix variable initialization and simplify code logic --- src/CSBondCurve.sol | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/CSBondCurve.sol b/src/CSBondCurve.sol index 66fc777d..b16c87c9 100644 --- a/src/CSBondCurve.sol +++ b/src/CSBondCurve.sol @@ -93,7 +93,7 @@ abstract contract CSBondCurve { uint256 value, uint256 multiplier ) internal view returns (uint256) { - uint256 low = 0; + uint256 low; uint256 high = bondCurve.length - 1; while (low <= high) { uint256 mid = (low + high) / 2; @@ -104,11 +104,8 @@ abstract contract CSBondCurve { if (value < midValue) { // zero mid is avoided above high = mid - 1; - continue; - } - if (value > midValue) { + } else if (value > midValue) { low = mid + 1; - continue; } } return low; @@ -128,11 +125,10 @@ abstract contract CSBondCurve { uint256 mult = getBondMultiplier(nodeOperatorId); if (keys <= bondCurve.length) { return (bondCurve[keys - 1] * mult) / BASIS_POINTS; - } else { - return - ((bondCurve[bondCurve.length - 1] * mult) / BASIS_POINTS) + - (keys - bondCurve.length) * - ((_bondCurveTrend * mult) / BASIS_POINTS); } + return + ((bondCurve[bondCurve.length - 1] * mult) / BASIS_POINTS) + + (keys - bondCurve.length) * + ((_bondCurveTrend * mult) / BASIS_POINTS); } } From be19ca827dd5dfc56193f6027f7235ffb7b5e7b3 Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Wed, 29 Nov 2023 12:32:57 +0400 Subject: [PATCH 04/18] fix: review (contracts) --- src/CSAccounting.sol | 35 +++++------ src/CSBondCurve.sol | 63 +++++++++---------- test/CSBondCurve.t.sol | 133 ++++++++++++++++++++--------------------- 3 files changed, 117 insertions(+), 114 deletions(-) diff --git a/src/CSAccounting.sol b/src/CSAccounting.sol index d2ecfb11..fd9922df 100644 --- a/src/CSAccounting.sol +++ b/src/CSAccounting.sol @@ -344,10 +344,11 @@ contract CSAccounting is // todo: can be optimized. get active keys once (uint256 current, uint256 required) = _bondETHSummary(nodeOperatorId); uint256 currentKeysCount = _getNodeOperatorActiveKeys(nodeOperatorId); - uint256 requiredForNextKeys = _getCurveValueByKeysCount( - nodeOperatorId, - currentKeysCount + additionalKeysCount - ) - _getCurveValueByKeysCount(nodeOperatorId, currentKeysCount); + uint256 multiplier = getBondMultiplier(nodeOperatorId); + uint256 requiredForNextKeys = _getBondAmountByKeysCount( + currentKeysCount + additionalKeysCount, + multiplier + ) - _getBondAmountByKeysCount(currentKeysCount, multiplier); uint256 missing = required > current ? required - current : 0; if (missing > 0) { @@ -392,7 +393,7 @@ contract CSAccounting is function getRequiredBondETHForKeys( uint256 keysCount ) public view returns (uint256) { - return _getCurveValueByKeysCount(keysCount); + return _getBondAmountByKeysCount(keysCount); } /// @notice Returns the required bond stETH for the given number of keys. @@ -424,14 +425,14 @@ contract CSAccounting is uint256 currentBond = _ethByShares(_bondShares[nodeOperatorId]); uint256 blockedBond = getBlockedBondETH(nodeOperatorId); if (currentBond > blockedBond) { + uint256 multiplier = getBondMultiplier(nodeOperatorId); currentBond -= blockedBond; - uint256 bondedKeys = _getKeysCountByCurveValue( - nodeOperatorId, - currentBond + uint256 bondedKeys = _getKeysCountByBondAmount( + currentBond, + multiplier ); if ( - currentBond > - _getCurveValueByKeysCount(nodeOperatorId, bondedKeys) + currentBond > _getBondAmountByKeysCount(bondedKeys, multiplier) ) { bondedKeys += 1; } @@ -444,7 +445,7 @@ contract CSAccounting is function getKeysCountByBondETH( uint256 ETHAmount ) public view returns (uint256) { - return _getKeysCountByCurveValue(ETHAmount); + return _getKeysCountByBondAmount(ETHAmount); } /// @notice Returns the number of keys by the given bond stETH amount @@ -932,9 +933,9 @@ contract CSAccounting is ) internal view returns (uint256 current, uint256 required) { current = _ethByShares(getBondShares(nodeOperatorId)); required = - _getCurveValueByKeysCount( - nodeOperatorId, - _getNodeOperatorActiveKeys(nodeOperatorId) + _getBondAmountByKeysCount( + _getNodeOperatorActiveKeys(nodeOperatorId), + getBondMultiplier(nodeOperatorId) ) + getBlockedBondETH(nodeOperatorId); } @@ -945,9 +946,9 @@ contract CSAccounting is current = getBondShares(nodeOperatorId); required = _sharesByEth( - _getCurveValueByKeysCount( - nodeOperatorId, - _getNodeOperatorActiveKeys(nodeOperatorId) + _getBondAmountByKeysCount( + _getNodeOperatorActiveKeys(nodeOperatorId), + getBondMultiplier(nodeOperatorId) ) ) + _sharesByEth(getBlockedBondETH(nodeOperatorId)); diff --git a/src/CSBondCurve.sol b/src/CSBondCurve.sol index b16c87c9..16f3688c 100644 --- a/src/CSBondCurve.sol +++ b/src/CSBondCurve.sol @@ -13,12 +13,13 @@ abstract contract CSBondCurve { // bondCurve[i] | bond amount for `i + 1` keys count uint256[] public bondCurve; - uint256 internal constant MIN_CURVE_LENGTH = 1; // todo: might be redefined in the future uint256 internal constant MAX_CURVE_LENGTH = 20; + uint256 internal constant MIN_CURVE_LENGTH = 1; + uint256 internal constant BASIS_POINTS = 10000; + uint256 internal constant MAX_BOND_MULTIPLIER = BASIS_POINTS; // x1 uint256 internal constant MIN_BOND_MULTIPLIER = MAX_BOND_MULTIPLIER / 2; // x0.5 - uint256 internal constant MAX_BOND_MULTIPLIER = 10000; // x1 /// This mapping contains bond multiplier points (in basis points) for Node Operator's bond. /// By default, all Node Operators have x1 multiplier (10000 basis points). @@ -32,6 +33,7 @@ abstract contract CSBondCurve { function _setBondCurve(uint256[] memory _bondCurve) internal { _checkCurveLength(_bondCurve); + // todo: check curve values (not worse than previous and makes sense) bondCurve = _bondCurve; _bondCurveTrend = _bondCurve[_bondCurve.length - 1] - @@ -43,6 +45,7 @@ abstract contract CSBondCurve { uint256 basisPoints ) internal { _checkMultiplier(basisPoints); + // todo: check curve values (not worse than previous) _bondMultiplierBP[nodeOperatorId] = basisPoints; } @@ -66,69 +69,69 @@ abstract contract CSBondCurve { ) revert InvalidMultiplier(); } - /// @notice Returns the amount of keys for the given bond amount. - function _getKeysCountByCurveValue( + /// @notice Returns keys count for the given bond amount. + function _getKeysCountByBondAmount( uint256 amount ) internal view returns (uint256) { - return _getKeysCountByCurveValue(type(uint256).max, amount); + return _getKeysCountByBondAmount(amount, MAX_BOND_MULTIPLIER); } - /// @notice Returns the amount of keys for the given bond amount for particular node operator. - function _getKeysCountByCurveValue( - uint256 nodeOperatorId, - uint256 amount + /// @notice Returns keys count for the given bond amount for particular node operator. + function _getKeysCountByBondAmount( + uint256 amount, + uint256 multiplier ) internal view returns (uint256) { - uint256 mult = getBondMultiplier(nodeOperatorId); - if (amount < (bondCurve[0] * mult) / BASIS_POINTS) return 0; - uint256 last = (bondCurve[bondCurve.length - 1] * mult) / BASIS_POINTS; - if (amount >= last) { + if (amount < (bondCurve[0] * multiplier) / BASIS_POINTS) return 0; + uint256 maxCurveAmount = (bondCurve[bondCurve.length - 1] * + multiplier) / BASIS_POINTS; + if (amount >= maxCurveAmount) { return bondCurve.length + - ((amount - last) / ((_bondCurveTrend * mult) / BASIS_POINTS)); + ((amount - maxCurveAmount) / + ((_bondCurveTrend * multiplier) / BASIS_POINTS)); } - return _searchKeysByBond(amount, mult); + return _searchKeysCount(amount, multiplier); } - function _searchKeysByBond( - uint256 value, + function _searchKeysCount( + uint256 amount, uint256 multiplier ) internal view returns (uint256) { uint256 low; uint256 high = bondCurve.length - 1; while (low <= high) { uint256 mid = (low + high) / 2; - uint256 midValue = (bondCurve[mid] * multiplier) / BASIS_POINTS; - if (value == midValue) { + uint256 midAmount = (bondCurve[mid] * multiplier) / BASIS_POINTS; + if (amount == midAmount) { return mid + 1; } - if (value < midValue) { + if (amount < midAmount) { // zero mid is avoided above high = mid - 1; - } else if (value > midValue) { + } else if (amount > midAmount) { low = mid + 1; } } return low; } - function _getCurveValueByKeysCount( + function _getBondAmountByKeysCount( uint256 keys ) internal view returns (uint256) { - return _getCurveValueByKeysCount(type(uint256).max, keys); + return _getBondAmountByKeysCount(keys, MAX_BOND_MULTIPLIER); } - function _getCurveValueByKeysCount( - uint256 nodeOperatorId, - uint256 keys + function _getBondAmountByKeysCount( + uint256 keys, + uint256 multiplier ) internal view returns (uint256) { if (keys == 0) return 0; - uint256 mult = getBondMultiplier(nodeOperatorId); if (keys <= bondCurve.length) { - return (bondCurve[keys - 1] * mult) / BASIS_POINTS; + return (bondCurve[keys - 1] * multiplier) / BASIS_POINTS; } return - ((bondCurve[bondCurve.length - 1] * mult) / BASIS_POINTS) + + ((bondCurve[bondCurve.length - 1] * multiplier) / BASIS_POINTS) + (keys - bondCurve.length) * - ((_bondCurveTrend * mult) / BASIS_POINTS); + ((_bondCurveTrend * multiplier) / BASIS_POINTS); } } diff --git a/test/CSBondCurve.t.sol b/test/CSBondCurve.t.sol index 3b10e218..d115edf8 100644 --- a/test/CSBondCurve.t.sol +++ b/test/CSBondCurve.t.sol @@ -21,30 +21,30 @@ contract CSBondCurveTestable is CSBondCurve { _setBondMultiplier(nodeOperatorId, basisPoints); } - function getKeysCountByCurveValue( + function getKeysCountByBondAmount( uint256 amount ) external view returns (uint256) { - return _getKeysCountByCurveValue(amount); + return _getKeysCountByBondAmount(amount); } - function getCurveValueByKeysCount( + function getBondAmountByKeysCount( uint256 keysCount ) external view returns (uint256) { - return _getCurveValueByKeysCount(keysCount); + return _getBondAmountByKeysCount(keysCount); } - function getKeysCountByCurveValue( + function getKeysCountByBondAmount( uint256 nodeOperatorId, uint256 amount ) external view returns (uint256) { - return _getKeysCountByCurveValue(nodeOperatorId, amount); + return _getKeysCountByBondAmount(nodeOperatorId, amount); } - function getCurveValueByKeysCount( + function getBondAmountByKeysCount( uint256 nodeOperatorId, uint256 keysCount ) external view returns (uint256) { - return _getCurveValueByKeysCount(nodeOperatorId, keysCount); + return _getBondAmountByKeysCount(nodeOperatorId, keysCount); } } @@ -71,7 +71,6 @@ contract CSBondCurveTest is Test { uint256[] memory _bondCurve = new uint256[](0); vm.expectRevert(CSBondCurve.InvalidBondCurveLength.selector); - bondCurve.setBondCurve(_bondCurve); } @@ -99,65 +98,65 @@ contract CSBondCurveTest is Test { bondCurve.setBondMultiplier(0, 10001); } - function test_getKeysCountByCurveValue() public { - assertEq(bondCurve.getKeysCountByCurveValue(0), 0); - assertEq(bondCurve.getKeysCountByCurveValue(2 ether), 1); - assertEq(bondCurve.getKeysCountByCurveValue(3 ether), 1); - assertEq(bondCurve.getKeysCountByCurveValue(3.90 ether), 2); - assertEq(bondCurve.getKeysCountByCurveValue(5.70 ether), 3); - assertEq(bondCurve.getKeysCountByCurveValue(7.40 ether), 4); - assertEq(bondCurve.getKeysCountByCurveValue(9.00 ether), 5); - assertEq(bondCurve.getKeysCountByCurveValue(10.50 ether), 6); - assertEq(bondCurve.getKeysCountByCurveValue(11.90 ether), 7); - assertEq(bondCurve.getKeysCountByCurveValue(13.10 ether), 8); - assertEq(bondCurve.getKeysCountByCurveValue(14.30 ether), 9); - assertEq(bondCurve.getKeysCountByCurveValue(15.40 ether), 10); - assertEq(bondCurve.getKeysCountByCurveValue(16.40 ether), 11); - assertEq(bondCurve.getKeysCountByCurveValue(17.40 ether), 12); - - bondCurve.setBondMultiplier(0, 5000); - - assertEq(bondCurve.getKeysCountByCurveValue(0, 0), 0); - assertEq(bondCurve.getKeysCountByCurveValue(0, 2 ether), 2); - assertEq(bondCurve.getKeysCountByCurveValue(0, 3 ether), 3); - assertEq(bondCurve.getKeysCountByCurveValue(0, 3.90 ether), 4); - assertEq(bondCurve.getKeysCountByCurveValue(0, 5.70 ether), 6); - assertEq(bondCurve.getKeysCountByCurveValue(0, 7.40 ether), 9); - assertEq(bondCurve.getKeysCountByCurveValue(0, 9.00 ether), 12); - assertEq(bondCurve.getKeysCountByCurveValue(0, 10.50 ether), 15); - assertEq(bondCurve.getKeysCountByCurveValue(0, 11.90 ether), 18); - assertEq(bondCurve.getKeysCountByCurveValue(0, 13.10 ether), 20); + function test_getKeysCountByBondAmount() public { + assertEq(bondCurve.getKeysCountByBondAmount(0), 0); + assertEq(bondCurve.getKeysCountByBondAmount(2 ether), 1); + assertEq(bondCurve.getKeysCountByBondAmount(3 ether), 1); + assertEq(bondCurve.getKeysCountByBondAmount(3.90 ether), 2); + assertEq(bondCurve.getKeysCountByBondAmount(5.70 ether), 3); + assertEq(bondCurve.getKeysCountByBondAmount(7.40 ether), 4); + assertEq(bondCurve.getKeysCountByBondAmount(9.00 ether), 5); + assertEq(bondCurve.getKeysCountByBondAmount(10.50 ether), 6); + assertEq(bondCurve.getKeysCountByBondAmount(11.90 ether), 7); + assertEq(bondCurve.getKeysCountByBondAmount(13.10 ether), 8); + assertEq(bondCurve.getKeysCountByBondAmount(14.30 ether), 9); + assertEq(bondCurve.getKeysCountByBondAmount(15.40 ether), 10); + assertEq(bondCurve.getKeysCountByBondAmount(16.40 ether), 11); + assertEq(bondCurve.getKeysCountByBondAmount(17.40 ether), 12); + } + + function test_getKeysCountByCurveValue_WithMultiplier() public { + assertEq(bondCurve.getKeysCountByBondAmount(0, 5000), 0); + assertEq(bondCurve.getKeysCountByBondAmount(2 ether, 5000), 2); + assertEq(bondCurve.getKeysCountByBondAmount(3 ether, 5000), 3); + assertEq(bondCurve.getKeysCountByBondAmount(3.90 ether, 5000), 4); + assertEq(bondCurve.getKeysCountByBondAmount(5.70 ether, 5000), 6); + assertEq(bondCurve.getKeysCountByBondAmount(7.40 ether, 5000), 9); + assertEq(bondCurve.getKeysCountByBondAmount(9.00 ether, 5000), 12); + assertEq(bondCurve.getKeysCountByBondAmount(10.50 ether, 5000), 15); + assertEq(bondCurve.getKeysCountByBondAmount(11.90 ether, 5000), 18); + assertEq(bondCurve.getKeysCountByBondAmount(13.10 ether, 5000), 20); + } + + function test_getBondAmountByKeysCount() public { + assertEq(bondCurve.getBondAmountByKeysCount(0), 0); + assertEq(bondCurve.getBondAmountByKeysCount(1), 2 ether); + assertEq(bondCurve.getBondAmountByKeysCount(2), 3.90 ether); + assertEq(bondCurve.getBondAmountByKeysCount(3), 5.70 ether); + assertEq(bondCurve.getBondAmountByKeysCount(4), 7.40 ether); + assertEq(bondCurve.getBondAmountByKeysCount(5), 9.00 ether); + assertEq(bondCurve.getBondAmountByKeysCount(6), 10.50 ether); + assertEq(bondCurve.getBondAmountByKeysCount(7), 11.90 ether); + assertEq(bondCurve.getBondAmountByKeysCount(8), 13.10 ether); + assertEq(bondCurve.getBondAmountByKeysCount(9), 14.30 ether); + assertEq(bondCurve.getBondAmountByKeysCount(10), 15.40 ether); + assertEq(bondCurve.getBondAmountByKeysCount(11), 16.40 ether); + assertEq(bondCurve.getBondAmountByKeysCount(12), 17.40 ether); } - function test_getCurveValueByKeysCount() public { - assertEq(bondCurve.getCurveValueByKeysCount(0), 0); - assertEq(bondCurve.getCurveValueByKeysCount(1), 2 ether); - assertEq(bondCurve.getCurveValueByKeysCount(2), 3.90 ether); - assertEq(bondCurve.getCurveValueByKeysCount(3), 5.70 ether); - assertEq(bondCurve.getCurveValueByKeysCount(4), 7.40 ether); - assertEq(bondCurve.getCurveValueByKeysCount(5), 9.00 ether); - assertEq(bondCurve.getCurveValueByKeysCount(6), 10.50 ether); - assertEq(bondCurve.getCurveValueByKeysCount(7), 11.90 ether); - assertEq(bondCurve.getCurveValueByKeysCount(8), 13.10 ether); - assertEq(bondCurve.getCurveValueByKeysCount(9), 14.30 ether); - assertEq(bondCurve.getCurveValueByKeysCount(10), 15.40 ether); - assertEq(bondCurve.getCurveValueByKeysCount(11), 16.40 ether); - assertEq(bondCurve.getCurveValueByKeysCount(12), 17.40 ether); - - bondCurve.setBondMultiplier(0, 5000); - - assertEq(bondCurve.getCurveValueByKeysCount(0, 0), 0); - assertEq(bondCurve.getCurveValueByKeysCount(0, 1), 1 ether); - assertEq(bondCurve.getCurveValueByKeysCount(0, 2), 1.95 ether); - assertEq(bondCurve.getCurveValueByKeysCount(0, 3), 2.85 ether); - assertEq(bondCurve.getCurveValueByKeysCount(0, 4), 3.70 ether); - assertEq(bondCurve.getCurveValueByKeysCount(0, 5), 4.50 ether); - assertEq(bondCurve.getCurveValueByKeysCount(0, 6), 5.25 ether); - assertEq(bondCurve.getCurveValueByKeysCount(0, 7), 5.95 ether); - assertEq(bondCurve.getCurveValueByKeysCount(0, 8), 6.55 ether); - assertEq(bondCurve.getCurveValueByKeysCount(0, 9), 7.15 ether); - assertEq(bondCurve.getCurveValueByKeysCount(0, 10), 7.7 ether); - assertEq(bondCurve.getCurveValueByKeysCount(0, 11), 8.20 ether); - assertEq(bondCurve.getCurveValueByKeysCount(0, 12), 8.70 ether); + function test_getBondAmountByKeysCount_WithMultiplier() public { + assertEq(bondCurve.getBondAmountByKeysCount(0, 5000), 0); + assertEq(bondCurve.getBondAmountByKeysCount(1, 5000), 1 ether); + assertEq(bondCurve.getBondAmountByKeysCount(2, 5000), 1.95 ether); + assertEq(bondCurve.getBondAmountByKeysCount(3, 5000), 2.85 ether); + assertEq(bondCurve.getBondAmountByKeysCount(4, 5000), 3.70 ether); + assertEq(bondCurve.getBondAmountByKeysCount(5, 5000), 4.50 ether); + assertEq(bondCurve.getBondAmountByKeysCount(6, 5000), 5.25 ether); + assertEq(bondCurve.getBondAmountByKeysCount(7, 5000), 5.95 ether); + assertEq(bondCurve.getBondAmountByKeysCount(8, 5000), 6.55 ether); + assertEq(bondCurve.getBondAmountByKeysCount(9, 5000), 7.15 ether); + assertEq(bondCurve.getBondAmountByKeysCount(10, 5000), 7.7 ether); + assertEq(bondCurve.getBondAmountByKeysCount(11, 5000), 8.20 ether); + assertEq(bondCurve.getBondAmountByKeysCount(12, 5000), 8.70 ether); } } From 24b8d521072b822be7f5d53af22be9dadbaeed14 Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Wed, 29 Nov 2023 16:26:22 +0400 Subject: [PATCH 05/18] feat: tests (wip) --- src/CSModule.sol | 4 +- test/CSAccounting.t.sol | 4619 ++++++++++++++++++++++++++++----------- test/CSBondCurve.t.sol | 188 +- test/CSModule.t.sol | 14 +- 4 files changed, 3513 insertions(+), 1312 deletions(-) diff --git a/src/CSModule.sol b/src/CSModule.sol index 30bac7a7..833eae57 100644 --- a/src/CSModule.sol +++ b/src/CSModule.sol @@ -7,7 +7,7 @@ import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; import { ICSAccounting } from "./interfaces/ICSAccounting.sol"; -import { IStakingModule } from "./interfaces/IStakingModule.sol"; +import { ICSModule } from "./interfaces/ICSModule.sol"; import { ILidoLocator } from "./interfaces/ILidoLocator.sol"; import { ILido } from "./interfaces/ILido.sol"; @@ -112,7 +112,7 @@ contract CSModuleBase { error QueueBatchUnvettedKeys(bytes32 batch); } -contract CSModule is IStakingModule, CSModuleBase { +contract CSModule is ICSModule, CSModuleBase { using QueueLib for QueueLib.Queue; // @dev max number of node operators is limited by uint64 due to Batch serialization in 32 bytes diff --git a/test/CSAccounting.t.sol b/test/CSAccounting.t.sol index 021fef41..4f9a4c22 100644 --- a/test/CSAccounting.t.sol +++ b/test/CSAccounting.t.sol @@ -5,6 +5,9 @@ pragma solidity 0.8.21; import "forge-std/Test.sol"; +import { ICSModule } from "../src/interfaces/ICSModule.sol"; +import { IStakingModule } from "../src/interfaces/IStakingModule.sol"; + import { CSAccountingBase, CSAccounting } from "../src/CSAccounting.sol"; import { CSBondCurve } from "../src/CSBondCurve.sol"; import { PermitTokenBase } from "./helpers/Permit.sol"; @@ -12,14 +15,13 @@ import { Stub } from "./helpers/mocks/Stub.sol"; import { LidoMock } from "./helpers/mocks/LidoMock.sol"; import { WstETHMock } from "./helpers/mocks/WstETHMock.sol"; import { LidoLocatorMock } from "./helpers/mocks/LidoLocatorMock.sol"; -import { CommunityStakingModuleMock } from "./helpers/mocks/CommunityStakingModuleMock.sol"; import { CommunityStakingFeeDistributorMock } from "./helpers/mocks/CommunityStakingFeeDistributorMock.sol"; import { WithdrawalQueueMockBase, WithdrawalQueueMock } from "./helpers/mocks/WithdrawalQueueMock.sol"; import { Utilities } from "./helpers/Utilities.sol"; import { Fixtures } from "./helpers/Fixtures.sol"; -contract CSAccounting_revealed is CSAccounting { +contract CSAccountingForTests is CSAccounting { constructor( uint256[] memory bondCurve, address admin, @@ -40,24 +42,27 @@ contract CSAccounting_revealed is CSAccounting { ) {} - function setBondCurveForTests() public { - uint256[] memory _bondCurve = new uint256[](11); - _bondCurve[0] = 2 ether; - _bondCurve[1] = 3.90 ether; // 1.9 - _bondCurve[2] = 5.70 ether; // 1.8 - _bondCurve[3] = 7.40 ether; // 1.7 - _bondCurve[4] = 9.00 ether; // 1.6 - _bondCurve[5] = 10.50 ether; // 1.5 - _bondCurve[6] = 11.90 ether; // 1.4 - _bondCurve[7] = 13.10 ether; // 1.3 - _bondCurve[8] = 14.30 ether; // 1.2 - _bondCurve[9] = 15.40 ether; // 1.1 - _bondCurve[10] = 16.40 ether; // 1.0 - _setBondCurve(_bondCurve); + function setBondCurve_ForTest() public { + uint256[] memory curve = new uint256[](2); + curve[0] = 2 ether; + curve[1] = 3 ether; + setBondCurve_ForTest(curve); + } + + function setBondCurve_ForTest(uint256[] memory curve) public { + _setBondCurve(curve); + } + + function setBondMultiplier_ForTest() public { + _setBondMultiplier(0, 9000); + } + + function setBondMultiplier_ForTest(uint256 id, uint256 multiplier) public { + _setBondMultiplier(id, multiplier); } } -contract CSAccountingTest is +contract CSAccountingBaseTest is Test, Fixtures, Utilities, @@ -72,15 +77,15 @@ contract CSAccountingTest is Stub internal burner; - CSAccounting_revealed public accounting; - CommunityStakingModuleMock public stakingModule; + CSAccountingForTests public accounting; + Stub public stakingModule; CommunityStakingFeeDistributorMock public feeDistributor; address internal admin; address internal user; address internal stranger; - function setUp() public { + function setUp() public virtual { admin = address(1); user = address(2); @@ -88,11 +93,10 @@ contract CSAccountingTest is (locator, wstETH, stETH, burner) = initLido(); - stakingModule = new CommunityStakingModuleMock(); - uint256[] memory curve = new uint256[](2); + stakingModule = new Stub(); + uint256[] memory curve = new uint256[](1); curve[0] = 2 ether; - curve[1] = 4 ether; - accounting = new CSAccounting_revealed( + accounting = new CSAccountingForTests( curve, admin, address(locator), @@ -121,1599 +125,3687 @@ contract CSAccountingTest is vm.stopPrank(); } - function test_setBondCurve() public { - uint256[] memory _bondCurve = new uint256[](2); - _bondCurve[0] = 2 ether; - _bondCurve[1] = 4 ether; + function mock_getNodeOperatorsCount(uint256 returnValue) internal { + vm.mockCall( + address(stakingModule), + abi.encodeWithSelector( + IStakingModule.getNodeOperatorsCount.selector + ), + abi.encode(returnValue) + ); + } - vm.prank(admin); - accounting.setBondCurve(_bondCurve); + function mock_getNodeOperator( + ICSModule.NodeOperatorInfo memory returnValue + ) internal { + vm.mockCall( + address(stakingModule), + abi.encodeWithSelector(ICSModule.getNodeOperator.selector, 0), + abi.encode(returnValue) + ); + } - assertEq(accounting.bondCurve(0), 2 ether); - assertEq(accounting.bondCurve(1), 4 ether); + function mock_getNodeOperator() internal { + ICSModule.NodeOperatorInfo memory n; + n.active = true; + n.managerAddress = address(user); + n.rewardAddress = address(user); + n.totalVettedValidators = 16; + n.totalExitedValidators = 0; + n.totalWithdrawnValidators = 0; + n.totalAddedValidators = 16; + n.totalDepositedValidators = 16; + mock_getNodeOperator(n); } - function test_setBondCurve_RevertWhen_DoesNotHaveRole() public { - uint256[] memory _bondCurve = new uint256[](2); - _bondCurve[0] = 2 ether; - _bondCurve[1] = 4 ether; + function mock_getNodeOperatorsCount() internal { + mock_getNodeOperatorsCount(1); + } +} - vm.expectRevert( - bytes( - Utilities.accessErrorString( - stranger, - accounting.SET_BOND_CURVE_ROLE() - ) - ) - ); +abstract contract BondAmountModifiersTest { + // 1 key -> 2 ether + // 2 keys -> 4 ether + // n keys -> 2 + (n - 1) * 2 ether + function test_default() public virtual; + + // 1 key -> 2 ether + // 2 keys -> 3 ether + // n keys -> 2 + (n - 1) * 1 ether + function test_WithCurve() public virtual; + + // 1 key -> 1.8 ether + // 2 keys -> 3.6 ether + // n keys -> 1.8 + (n - 1) * 1.8 ether + function test_WithMultiplier() public virtual; + + // 1 key -> 2 ether + 1 ether + // 2 keys -> 4 ether + 1 ether + // n keys -> 2 + (n - 1) * 2 ether + 1 ether + function test_WithBlocked() public virtual; + + // 1 key -> 1.8 ether + // 2 keys -> 2.7 ether + // n keys -> 1.8 + (n - 1) * 0.9 ether + function test_WithCurveAndMultiplier() public virtual; + + // 1 key -> 2 ether + 1 ether + // 2 keys -> 3 ether + 1 ether + // n keys -> 2 + (n - 1) * 1 ether + 1 ether + function test_WithCurveAndBlocked() public virtual; + + // 1 key -> 1.8 ether + 1 ether + // 2 keys -> 3.6 ether + 1 ether + // n keys -> 1.8 + (n - 1) * 1.8 ether + 1 ether + function test_WithMultiplierAndBlocked() public virtual; + + // 1 key -> 1.8 ether + 1 ether + // 2 keys -> 2.7 ether + 1 ether + // n keys -> 1.8 + (n - 1) * 0.9 ether + 1 ether + function test_WithCurveAndMultiplierAndBlocked() public virtual; +} - vm.prank(stranger); - accounting.setBondCurve(_bondCurve); +abstract contract CSAccountingBondStateBaseTest is + BondAmountModifiersTest, + CSAccountingBaseTest +{ + function setUp() public virtual override { + super.setUp(); + _operator({ ongoing: 16, withdrawn: 0 }); + } + + function _operator(uint256 ongoing, uint256 withdrawn) internal virtual { + ICSModule.NodeOperatorInfo memory n; + n.active = true; + n.managerAddress = address(user); + n.rewardAddress = address(user); + n.totalVettedValidators = ongoing; + n.totalExitedValidators = 0; + n.totalWithdrawnValidators = withdrawn; + n.totalAddedValidators = ongoing; + n.totalDepositedValidators = ongoing; + mock_getNodeOperator(n); + mock_getNodeOperatorsCount(1); + } + + function _deposit(uint256 bond) internal virtual { + vm.deal(user, bond); + vm.prank(user); + accounting.depositETH{ value: bond }(user, 0); } - function test_setBondMultiplier() public { - vm.prank(admin); - accounting.setBondMultiplier(0, 9500); // 0.95 + function test_WithOneWithdrawnValidator() public virtual; - assertEq(accounting.getBondMultiplier(0), 9500); - } + function test_WithBond() public virtual; - function test_setBondMultiplier_RevertWhen_DoesNotHaveRole() public { - vm.expectRevert( - bytes( - Utilities.accessErrorString( - stranger, - accounting.SET_BOND_MULTIPLIER_ROLE() - ) - ) - ); + function test_WithBondAndOneWithdrawnValidator() public virtual; - vm.prank(stranger); - accounting.setBondMultiplier(0, 9500); // 0.95 + function test_WithExcessBond() public virtual; + + function test_WithExcessBondAndOneWithdrawnValidator() public virtual; + + function test_WithMissingBond() public virtual; + + function test_WithMissingBondAndOneWithdrawnValidator() public virtual; +} + +contract CSAccountingGetExcessBondETHTest is CSAccountingBondStateBaseTest { + function test_default() public override { + _deposit({ bond: 33 ether }); + assertApproxEqAbs(accounting.getExcessBondETH(0), 1 ether, 1 wei); } - function test_totalBondShares() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 32 ether); - vm.startPrank(user); - accounting.depositETH{ value: 32 ether }(user, 0); - uint256 sharesToDeposit = stETH.getSharesByPooledEth(32 ether); - assertEq(accounting.totalBondShares(), sharesToDeposit); + function test_WithCurve() public override { + _deposit({ bond: 32 ether }); + accounting.setBondCurve_ForTest(); + assertApproxEqAbs(accounting.getExcessBondETH(0), 15 ether, 1 wei); } - function test_getRequiredBondETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - assertEq(accounting.getRequiredBondETH(0, 0), 32 ether); + function test_WithMultiplier() public override { + _deposit({ bond: 32 ether }); + accounting.setBondMultiplier_ForTest(); + assertApproxEqAbs(accounting.getExcessBondETH(0), 3.2 ether, 1 wei); } - function test_getRequiredBondStETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - assertEq(accounting.getRequiredBondStETH(0, 0), 32 ether); + function test_WithBlocked() public override { + // todo: implement me } - function test_getRequiredBondWstETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - assertEq( - accounting.getRequiredBondWstETH(0, 0), - stETH.getSharesByPooledEth(32 ether) - ); + function test_WithCurveAndMultiplier() public override { + _deposit({ bond: 32 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + assertApproxEqAbs(accounting.getExcessBondETH(0), 16.7 ether, 1 wei); } - function test_getRequiredBondETH_OneWithdrawnValidator() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 1 }); - assertEq(accounting.getRequiredBondETH(0, 0), 30 ether); + function test_WithCurveAndBlocked() public override { + // todo: implement me } - function test_getRequiredBondStETH_OneWithdrawnValidator() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 1 }); - assertEq(accounting.getRequiredBondStETH(0, 0), 30 ether); + function test_WithMultiplierAndBlocked() public override { + // todo: implement me } - function test_getRequiredBondWstETH_OneWithdrawnValidator() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 1 }); - assertEq( - accounting.getRequiredBondWstETH(0, 0), - stETH.getSharesByPooledEth(30 ether) - ); + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me } - function test_getRequiredBondETH_OneWithdrawnOneAddedValidator() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 1 }); - assertEq(accounting.getRequiredBondETH(0, 1), 32 ether); + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether }); + assertApproxEqAbs(accounting.getExcessBondETH(0), 2 ether, 1 wei); } - function test_getRequiredBondStETH_OneWithdrawnOneAddedValidator() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 1 }); - assertEq(accounting.getRequiredBondStETH(0, 1), 32 ether); + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 32 ether }); + assertEq(accounting.getExcessBondETH(0), 0); } - function test_getRequiredBondWstETH_OneWithdrawnOneAddedValidator() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 1 }); - assertEq( - accounting.getRequiredBondWstETH(0, 1), - stETH.getSharesByPooledEth(32 ether) - ); + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether }); + assertApproxEqAbs(accounting.getExcessBondETH(0), 2 ether, 1 wei); } - function test_getRequiredBondETH_WithExcessBond() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 64 ether); - vm.startPrank(user); - accounting.depositETH{ value: 63 ether }(user, 0); - assertApproxEqAbs( - accounting.getRequiredBondETH(0, 16), - 1 ether, - 1, // max accuracy error - "required ETH should be ~1 ether for the next 16 validators to deposit" - ); + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 64 ether }); + assertApproxEqAbs(accounting.getExcessBondETH(0), 32 ether, 1 wei); } - function test_getRequiredBondStETH_WithExcessBond() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 64 ether); - vm.startPrank(user); - stETH.submit{ value: 64 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 63 ether); - assertApproxEqAbs( - accounting.getRequiredBondStETH(0, 16), - 1 ether, - 1, // max accuracy error - "required stETH should be ~1 ether for the next 16 validators to deposit" - ); + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 64 ether }); + assertApproxEqAbs(accounting.getExcessBondETH(0), 34 ether, 1 wei); } - function test_getRequiredBondWstETH_WithExcessBond() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 64 ether); - vm.startPrank(user); - stETH.submit{ value: 64 ether }({ _referal: address(0) }); - uint256 amount = wstETH.wrap(63 ether); - accounting.depositWstETH(user, 0, amount); - assertApproxEqAbs( - accounting.getRequiredBondWstETH(0, 16), - stETH.getSharesByPooledEth(1 ether), - 2, // max accuracy error - "required wstETH should be ~1 ether for the next 16 validators to deposit" - ); + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 16 ether }); + assertEq(accounting.getExcessBondETH(0), 0); } - function test_getRequiredBondETHForKeys() public { - assertEq(accounting.getRequiredBondETHForKeys(1), 2 ether); + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether }); + assertEq(accounting.getExcessBondETH(0), 0); } +} - function test_getRequiredBondStETHForKeys() public { - assertEq(accounting.getRequiredBondStETHForKeys(1), 2 ether); +contract CSAccountingGetExcessBondStETHTest is CSAccountingBondStateBaseTest { + function test_default() public override { + _deposit({ bond: 33 ether }); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 1 ether, 1 wei); } - function test_getRequiredBondWstETHForKeys() public { - assertEq( - accounting.getRequiredBondWstETHForKeys(1), - stETH.getSharesByPooledEth(2 ether) - ); + function test_WithCurve() public override { + _deposit({ bond: 32 ether }); + accounting.setBondCurve_ForTest(); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 15 ether, 1 wei); } - function test_depositETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 32 ether); - uint256 sharesToDeposit = stETH.getSharesByPooledEth(32 ether); + function test_WithMultiplier() public override { + _deposit({ bond: 32 ether }); + accounting.setBondMultiplier_ForTest(); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 3.2 ether, 1 wei); + } - vm.expectEmit(true, true, true, true, address(accounting)); - emit ETHBondDeposited(0, user, 32 ether); + function test_WithBlocked() public override { + // todo: implement me + } - vm.prank(user); - accounting.depositETH{ value: 32 ether }(user, 0); + function test_WithCurveAndMultiplier() public override { + _deposit({ bond: 32 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 16.7 ether, 1 wei); + } - assertEq( - address(user).balance, - 0, - "user balance should be 0 after deposit" - ); - assertEq( - accounting.getBondShares(0), - sharesToDeposit, - "bond shares should be equal to deposited shares" - ); - assertEq( - stETH.sharesOf(address(accounting)), - sharesToDeposit, - "bond manager shares should be equal to deposited shares" - ); + function test_WithCurveAndBlocked() public override { + // todo: implement me } - function test_depositETH_CoverSeveralValidators() public { - _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); - vm.deal(user, 32 ether); + function test_WithMultiplierAndBlocked() public override { + // todo: implement me + } - uint256 required = accounting.getRequiredBondETHForKeys(1); - vm.startPrank(user); - accounting.depositETH{ value: required }(user, 0); + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me + } - assertApproxEqAbs( - accounting.getRequiredBondETH(0, 0), - 0, - 1, // max accuracy error - "required ETH should be ~0 for 1 deposited validator" - ); + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether }); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 2 ether, 1 wei); + } - required = accounting.getRequiredBondETH(0, 1); - accounting.depositETH{ value: required }(user, 0); - stakingModule.addValidator(0, 1); + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 32 ether }); + assertEq(accounting.getExcessBondStETH(0), 0); + } - assertApproxEqAbs( - accounting.getRequiredBondETH(0, 0), - 0, - 1, // max accuracy error - "required ETH should be ~0 for 2 deposited validators" - ); + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether }); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 2 ether, 1 wei); } - function test_depositStETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 32 ether); - vm.startPrank(user); - uint256 sharesToDeposit = stETH.submit{ value: 32 ether }({ - _referal: address(0) - }); + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 64 ether }); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 32 ether, 1 wei); + } - vm.expectEmit(true, true, true, true, address(accounting)); - emit StETHBondDeposited(0, user, 32 ether); + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 64 ether }); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 34 ether, 1 wei); + } - accounting.depositStETH(user, 0, 32 ether); + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 16 ether }); + assertEq(accounting.getExcessBondStETH(0), 0); + } - assertEq( - stETH.balanceOf(user), - 0, - "user balance should be 0 after deposit" - ); - assertEq( - accounting.getBondShares(0), - sharesToDeposit, - "bond shares should be equal to deposited shares" + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether }); + assertEq(accounting.getExcessBondStETH(0), 0); + } +} + +contract CSAccountingGetExcessBondWstETHTest is CSAccountingBondStateBaseTest { + function test_default() public override { + _deposit({ bond: 33 ether }); + assertApproxEqAbs( + accounting.getExcessBondWstETH(0), + wstETH.getWstETHByStETH(1 ether), + 1 wei ); + } + + function test_WithCurve() public override { + _deposit({ bond: 32 ether }); + accounting.setBondCurve_ForTest(); assertEq( - stETH.sharesOf(address(accounting)), - sharesToDeposit, - "bond manager shares should be equal to deposited shares" + accounting.getExcessBondWstETH(0), + wstETH.getWstETHByStETH(15 ether) ); } - function test_depositStETH_CoverSeveralValidators() public { - _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); - vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - - uint256 required = accounting.getRequiredBondStETHForKeys(1); - accounting.depositStETH(user, 0, required); - + function test_WithMultiplier() public override { + _deposit({ bond: 32 ether }); + accounting.setBondMultiplier_ForTest(); assertApproxEqAbs( - accounting.getRequiredBondStETH(0, 0), - 0, - 1, // max accuracy error - "required stETH should be ~0 for 1 deposited validator" + accounting.getExcessBondWstETH(0), + wstETH.getWstETHByStETH(3.2 ether), + 1 wei ); + } - required = accounting.getRequiredBondStETH(0, 1); - accounting.depositStETH(user, 0, required); - stakingModule.addValidator(0, 1); + function test_WithBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplier() public override { + _deposit({ bond: 32 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); assertApproxEqAbs( - accounting.getRequiredBondStETH(0, 0), - 0, - 1, // max accuracy error - "required stETH should be ~0 for 2 deposited validators" + accounting.getExcessBondWstETH(0), + wstETH.getWstETHByStETH(16.7 ether), + 1 wei ); } - function test_depositWstETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - uint256 wstETHAmount = wstETH.wrap(32 ether); - uint256 sharesToDeposit = stETH.getSharesByPooledEth( - wstETH.getStETHByWstETH(wstETHAmount) - ); + function test_WithCurveAndBlocked() public override { + // todo: implement me + } - vm.expectEmit(true, true, true, true, address(accounting)); - emit WstETHBondDeposited(0, user, wstETHAmount); + function test_WithMultiplierAndBlocked() public override { + // todo: implement me + } - accounting.depositWstETH(user, 0, wstETHAmount); + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me + } - assertEq( - wstETH.balanceOf(user), - 0, - "user balance should be 0 after deposit" - ); - assertEq( - accounting.getBondShares(0), - sharesToDeposit, - "bond shares should be equal to deposited shares" - ); - assertEq( - stETH.sharesOf(address(accounting)), - sharesToDeposit, - "bond manager shares should be equal to deposited shares" + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether }); + assertApproxEqAbs( + accounting.getExcessBondWstETH(0), + wstETH.getWstETHByStETH(2 ether), + 1 wei ); } - function test_depositWstETH_CoverSeveralValidators() public { - _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); - vm.startPrank(user); - vm.deal(user, 32 ether); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - wstETH.wrap(32 ether); - - uint256 required = accounting.getRequiredBondWstETHForKeys(1); - accounting.depositWstETH(user, 0, required); + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 32 ether }); + assertEq(accounting.getExcessBondWstETH(0), 0); + } + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether }); assertApproxEqAbs( - accounting.getRequiredBondWstETH(0, 0), - 0, - 1, // max accuracy error - "required wstETH should be ~0 for 1 deposited validator" + accounting.getExcessBondWstETH(0), + wstETH.getWstETHByStETH(2 ether), + 1 wei ); + } - required = accounting.getRequiredBondStETH(0, 1); - accounting.depositWstETH(user, 0, required); - stakingModule.addValidator(0, 1); + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 64 ether }); + assertApproxEqAbs( + accounting.getExcessBondWstETH(0), + wstETH.getWstETHByStETH(32 ether), + 1 wei + ); + } + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 64 ether }); assertApproxEqAbs( - accounting.getRequiredBondWstETH(0, 0), - 0, - 1, // max accuracy error - "required wstETH should be ~0 for 2 deposited validators" + accounting.getExcessBondWstETH(0), + wstETH.getWstETHByStETH(34 ether), + 1 wei ); } - function test_depositStETHWithPermit() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 32 ether); - vm.prank(user); - uint256 sharesToDeposit = stETH.submit{ value: 32 ether }({ - _referal: address(0) - }); + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 16 ether }); + assertEq(accounting.getExcessBondWstETH(0), 0); + } - vm.expectEmit(true, true, true, true, address(stETH)); - emit Approval(user, address(accounting), 32 ether); - vm.expectEmit(true, true, true, true, address(accounting)); - emit StETHBondDeposited(0, user, 32 ether); + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether }); + assertEq(accounting.getExcessBondWstETH(0), 0); + } +} - vm.prank(user); - accounting.depositStETHWithPermit( - user, +contract CSAccountingGetMissingBondETHTest is CSAccountingBondStateBaseTest { + function test_default() public override { + _deposit({ bond: 16 ether }); + assertApproxEqAbs(accounting.getMissingBondETH(0), 16 ether, 1 wei); + } + + function test_WithCurve() public override { + _deposit({ bond: 16 ether }); + accounting.setBondCurve_ForTest(); + assertApproxEqAbs(accounting.getMissingBondETH(0), 1 ether, 1 wei); + } + + function test_WithMultiplier() public override { + _deposit({ bond: 16 ether }); + accounting.setBondMultiplier_ForTest(); + assertApproxEqAbs(accounting.getMissingBondETH(0), 12.8 ether, 1 wei); + } + + function test_WithBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplier() public override { + _deposit({ bond: 16 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + assertEq(accounting.getMissingBondETH(0), 0); + } + + function test_WithCurveAndBlocked() public override { + // todo: implement me + } + + function test_WithMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether }); + assertApproxEqAbs(accounting.getMissingBondETH(0), 14 ether, 1 wei); + } + + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 32 ether }); + assertApproxEqAbs(accounting.getMissingBondETH(0), 0, 1 wei); + } + + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether }); + assertEq(accounting.getMissingBondETH(0), 0 ether); + } + + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 64 ether }); + assertEq(accounting.getMissingBondETH(0), 0); + } + + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 64 ether }); + assertEq(accounting.getMissingBondETH(0), 0); + } + + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 8 ether }); + assertApproxEqAbs(accounting.getMissingBondETH(0), 24 ether, 2 wei); + } + + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 8 ether }); + assertApproxEqAbs(accounting.getMissingBondETH(0), 22 ether, 2 wei); + } +} + +contract CSAccountingGetMissingBondStETHTest is CSAccountingBondStateBaseTest { + function test_default() public override { + _deposit({ bond: 16 ether }); + assertApproxEqAbs(accounting.getMissingBondStETH(0), 16 ether, 1 wei); + } + + function test_WithCurve() public override { + _deposit({ bond: 16 ether }); + accounting.setBondCurve_ForTest(); + assertApproxEqAbs(accounting.getMissingBondStETH(0), 1 ether, 1 wei); + } + + function test_WithMultiplier() public override { + _deposit({ bond: 16 ether }); + accounting.setBondMultiplier_ForTest(); + assertApproxEqAbs(accounting.getMissingBondStETH(0), 12.8 ether, 1 wei); + } + + function test_WithBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplier() public override { + _deposit({ bond: 16 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + assertEq(accounting.getMissingBondStETH(0), 0); + } + + function test_WithCurveAndBlocked() public override { + // todo: implement me + } + + function test_WithMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether }); + assertApproxEqAbs(accounting.getMissingBondStETH(0), 14 ether, 1 wei); + } + + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 32 ether }); + assertApproxEqAbs(accounting.getMissingBondStETH(0), 0, 1 wei); + } + + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether }); + assertEq(accounting.getMissingBondStETH(0), 0 ether); + } + + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 64 ether }); + assertEq(accounting.getMissingBondStETH(0), 0); + } + + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 64 ether }); + assertEq(accounting.getMissingBondStETH(0), 0); + } + + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 8 ether }); + assertApproxEqAbs(accounting.getMissingBondStETH(0), 24 ether, 2 wei); + } + + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 8 ether }); + assertApproxEqAbs(accounting.getMissingBondStETH(0), 22 ether, 2 wei); + } +} + +contract CSAccountingGetMissingBondWstETHTest is CSAccountingBondStateBaseTest { + function test_default() public override { + _deposit({ bond: 16 ether }); + assertApproxEqAbs( + accounting.getMissingBondWstETH(0), + wstETH.getWstETHByStETH(16 ether), + 1 wei + ); + } + + function test_WithCurve() public override { + _deposit({ bond: 16 ether }); + accounting.setBondCurve_ForTest(); + assertApproxEqAbs( + accounting.getMissingBondWstETH(0), + wstETH.getWstETHByStETH(1 ether), + 1 wei + ); + } + + function test_WithMultiplier() public override { + _deposit({ bond: 16 ether }); + accounting.setBondMultiplier_ForTest(); + assertApproxEqAbs( + accounting.getMissingBondWstETH(0), + wstETH.getWstETHByStETH(12.8 ether), + 1 wei + ); + } + + function test_WithBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplier() public override { + _deposit({ bond: 16 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + assertEq( + accounting.getMissingBondWstETH(0), + wstETH.getWstETHByStETH(0) + ); + } + + function test_WithCurveAndBlocked() public override { + // todo: implement me + } + + function test_WithMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether }); + assertEq( + accounting.getMissingBondWstETH(0), + wstETH.getWstETHByStETH(14 ether) + ); + } + + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 32 ether }); + assertEq(accounting.getMissingBondWstETH(0), 0); + } + + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether }); + assertEq( + accounting.getMissingBondWstETH(0), + wstETH.getWstETHByStETH(0 ether) + ); + } + + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 64 ether }); + assertEq(accounting.getMissingBondWstETH(0), 0); + } + + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 64 ether }); + assertEq(accounting.getMissingBondWstETH(0), 0); + } + + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 8 ether }); + assertApproxEqAbs( + accounting.getMissingBondWstETH(0), + wstETH.getWstETHByStETH(24 ether), + 2 wei + ); + } + + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 8 ether }); + assertApproxEqAbs( + accounting.getMissingBondWstETH(0), + wstETH.getWstETHByStETH(22 ether), + 1 wei + ); + } +} + +contract CSAccountingGetUnbondedKeysCountTest is CSAccountingBondStateBaseTest { + function test_default() public override { + _deposit({ bond: 11.5 ether }); + assertEq(accounting.getUnbondedKeysCount(0), 10); + } + + function test_WithCurve() public override { + _deposit({ bond: 11.5 ether }); + accounting.setBondCurve_ForTest(); + assertEq(accounting.getUnbondedKeysCount(0), 5); + } + + function test_WithMultiplier() public override { + _deposit({ bond: 11.5 ether }); + accounting.setBondMultiplier_ForTest(); + assertEq(accounting.getUnbondedKeysCount(0), 9); + } + + function test_WithBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplier() public override { + _deposit({ bond: 11.5 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + assertEq(accounting.getUnbondedKeysCount(0), 4); + } + + function test_WithCurveAndBlocked() public override { + // todo: implement me + } + + function test_WithMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 11.5 ether }); + assertEq(accounting.getUnbondedKeysCount(0), 9); + } + + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 11.5 ether }); + assertEq(accounting.getUnbondedKeysCount(0), 10); + } + + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 11.5 ether }); + assertEq(accounting.getUnbondedKeysCount(0), 9); + } + + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 33 ether }); + assertEq(accounting.getUnbondedKeysCount(0), 0); + } + + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 33 ether }); + assertEq(accounting.getUnbondedKeysCount(0), 0); + } + + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 5.75 ether }); + assertEq(accounting.getUnbondedKeysCount(0), 13); + } + + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 5.75 ether }); + assertEq(accounting.getUnbondedKeysCount(0), 12); + } +} + +abstract contract CSAccountingGetRequiredBondBaseTest is + CSAccountingBondStateBaseTest +{ + function test_OneWithdrawnOneAddedValidator() public virtual; + + function test_WithBondAndOneWithdrawnAndOneAddedValidator() public virtual; + + function test_WithExcessBondAndOneWithdrawnAndOneAddedValidator() + public + virtual; + + function test_WithMissingBondAndOneWithdrawnAndOneAddedValidator() + public + virtual; +} + +contract CSAccountingGetRequiredETHBondTest is + CSAccountingGetRequiredBondBaseTest +{ + function test_default() public override { + assertEq(accounting.getRequiredBondETH(0, 0), 32 ether); + } + + function test_WithCurve() public override { + accounting.setBondCurve_ForTest(); + assertEq(accounting.getRequiredBondETH(0, 0), 17 ether); + } + + function test_WithMultiplier() public override { + accounting.setBondMultiplier_ForTest(); + assertEq(accounting.getRequiredBondETH(0, 0), 28.8 ether); + } + + function test_WithBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplier() public override { + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + assertEq(accounting.getRequiredBondETH(0, 0), 15.3 ether); + } + + function test_WithCurveAndBlocked() public override { + // todo: implement me + } + + function test_WithMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + assertEq(accounting.getRequiredBondETH(0, 0), 30 ether); + } + + function test_OneWithdrawnOneAddedValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + assertEq(accounting.getRequiredBondETH(0, 1), 32 ether); + } + + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 32 ether }); + assertApproxEqAbs(accounting.getRequiredBondETH(0, 0), 0, 1 wei); + } + + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether }); + assertEq(accounting.getRequiredBondETH(0, 0), 0); + } + + function test_WithBondAndOneWithdrawnAndOneAddedValidator() + public + override + { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether }); + assertApproxEqAbs(accounting.getRequiredBondETH(0, 1), 0, 1); + } + + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 33 ether }); + assertEq(accounting.getRequiredBondETH(0, 0), 0); + } + + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 33 ether }); + assertEq(accounting.getRequiredBondETH(0, 0), 0); + } + + function test_WithExcessBondAndOneWithdrawnAndOneAddedValidator() + public + override + { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 33 ether }); + assertEq(accounting.getRequiredBondETH(0, 1), 0); + } + + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 16 ether }); + assertApproxEqAbs(accounting.getRequiredBondETH(0, 0), 16 ether, 1 wei); + } + + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether }); + assertApproxEqAbs(accounting.getRequiredBondETH(0, 0), 14 ether, 1 wei); + } + + function test_WithMissingBondAndOneWithdrawnAndOneAddedValidator() + public + override + { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether }); + assertApproxEqAbs(accounting.getRequiredBondETH(0, 1), 16 ether, 1 wei); + } +} + +contract CSAccountingGetRequiredStETHBondTest is + CSAccountingGetRequiredBondBaseTest +{ + function test_default() public override { + assertEq(accounting.getRequiredBondStETH(0, 0), 32 ether); + } + + function test_WithCurve() public override { + accounting.setBondCurve_ForTest(); + assertEq(accounting.getRequiredBondStETH(0, 0), 17 ether); + } + + function test_WithMultiplier() public override { + accounting.setBondMultiplier_ForTest(); + assertEq(accounting.getRequiredBondStETH(0, 0), 28.8 ether); + } + + function test_WithBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplier() public override { + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + assertEq(accounting.getRequiredBondStETH(0, 0), 15.3 ether); + } + + function test_WithCurveAndBlocked() public override { + // todo: implement me + } + + function test_WithMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + assertEq(accounting.getRequiredBondStETH(0, 0), 30 ether); + } + + function test_OneWithdrawnOneAddedValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + assertEq(accounting.getRequiredBondStETH(0, 1), 32 ether); + } + + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 32 ether }); + assertApproxEqAbs(accounting.getRequiredBondStETH(0, 0), 0, 1 wei); + } + + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether }); + assertEq(accounting.getRequiredBondStETH(0, 0), 0); + } + + function test_WithBondAndOneWithdrawnAndOneAddedValidator() + public + override + { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether }); + assertApproxEqAbs(accounting.getRequiredBondStETH(0, 1), 0, 1); + } + + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 33 ether }); + assertEq(accounting.getRequiredBondStETH(0, 0), 0); + } + + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 33 ether }); + assertEq(accounting.getRequiredBondStETH(0, 0), 0); + } + + function test_WithExcessBondAndOneWithdrawnAndOneAddedValidator() + public + override + { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 33 ether }); + assertEq(accounting.getRequiredBondStETH(0, 1), 0); + } + + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 16 ether }); + assertApproxEqAbs( + accounting.getRequiredBondStETH(0, 0), + 16 ether, + 1 wei + ); + } + + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether }); + assertApproxEqAbs( + accounting.getRequiredBondStETH(0, 0), + 14 ether, + 1 wei + ); + } + + function test_WithMissingBondAndOneWithdrawnAndOneAddedValidator() + public + override + { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether }); + assertApproxEqAbs( + accounting.getRequiredBondStETH(0, 1), + 16 ether, + 1 wei + ); + } +} + +contract CSAccountingGetRequiredWstETHBondTest is + CSAccountingGetRequiredBondBaseTest +{ + function test_default() public override { + assertEq( + accounting.getRequiredBondWstETH(0, 0), + stETH.getSharesByPooledEth(32 ether) + ); + } + + function test_WithCurve() public override { + accounting.setBondCurve_ForTest(); + assertEq( + accounting.getRequiredBondWstETH(0, 0), + stETH.getSharesByPooledEth(17 ether) + ); + } + + function test_WithMultiplier() public override { + accounting.setBondMultiplier_ForTest(); + assertEq( + accounting.getRequiredBondWstETH(0, 0), + stETH.getSharesByPooledEth(28.8 ether) + ); + } + + function test_WithBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplier() public override { + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + assertEq( + accounting.getRequiredBondWstETH(0, 0), + stETH.getSharesByPooledEth(15.3 ether) + ); + } + + function test_WithCurveAndBlocked() public override { + // todo: implement me + } + + function test_WithMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + assertEq( + accounting.getRequiredBondWstETH(0, 0), + stETH.getSharesByPooledEth(30 ether) + ); + } + + function test_OneWithdrawnOneAddedValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + assertEq( + accounting.getRequiredBondWstETH(0, 1), + stETH.getSharesByPooledEth(32 ether) + ); + } + + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 32 ether }); + assertApproxEqAbs(accounting.getRequiredBondWstETH(0, 0), 0, 1 wei); + } + + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether }); + assertEq(accounting.getRequiredBondWstETH(0, 0), 0); + } + + function test_WithBondAndOneWithdrawnAndOneAddedValidator() + public + override + { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether }); + assertApproxEqAbs(accounting.getRequiredBondWstETH(0, 1), 0, 1); + } + + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 33 ether }); + assertEq(accounting.getRequiredBondWstETH(0, 0), 0); + } + + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 33 ether }); + assertEq(accounting.getRequiredBondWstETH(0, 0), 0); + } + + function test_WithExcessBondAndOneWithdrawnAndOneAddedValidator() + public + override + { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 33 ether }); + assertEq(accounting.getRequiredBondWstETH(0, 1), 0); + } + + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 16 ether }); + assertApproxEqAbs( + accounting.getRequiredBondWstETH(0, 0), + stETH.getSharesByPooledEth(16 ether), + 1 wei + ); + } + + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether }); + assertEq( + accounting.getRequiredBondWstETH(0, 0), + stETH.getSharesByPooledEth(14 ether) + ); + } + + function test_WithMissingBondAndOneWithdrawnAndOneAddedValidator() + public + override + { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether }); + assertApproxEqAbs( + accounting.getRequiredBondWstETH(0, 1), + stETH.getSharesByPooledEth(16 ether), + 1 wei + ); + } +} + +// todo: should be changed ??? +contract CSAccountingGetRequiredBondETHForKeysTest is + BondAmountModifiersTest, + CSAccountingBaseTest +{ + function test_default() public override { + assertEq(accounting.getRequiredBondETHForKeys(0), 0); + assertEq(accounting.getRequiredBondETHForKeys(1), 2 ether); + assertEq(accounting.getRequiredBondETHForKeys(2), 4 ether); + } + + function test_WithCurve() public override { + accounting.setBondCurve_ForTest(); + assertEq(accounting.getRequiredBondETHForKeys(0), 0); + assertEq(accounting.getRequiredBondETHForKeys(1), 2 ether); + assertEq(accounting.getRequiredBondETHForKeys(2), 3 ether); + } + + function test_WithMultiplier() public override {} + + function test_WithBlocked() public override {} + + function test_WithCurveAndMultiplier() public override {} + + function test_WithCurveAndBlocked() public override {} + + function test_WithMultiplierAndBlocked() public override {} + + function test_WithCurveAndMultiplierAndBlocked() public override {} +} + +contract CSAccountingGetRequiredBondStETHForKeysTest is + BondAmountModifiersTest, + CSAccountingBaseTest +{ + function test_default() public override { + assertEq(accounting.getRequiredBondStETHForKeys(0), 0); + assertEq(accounting.getRequiredBondStETHForKeys(1), 2 ether); + assertEq(accounting.getRequiredBondStETHForKeys(2), 4 ether); + } + + function test_WithCurve() public override { + accounting.setBondCurve_ForTest(); + assertEq(accounting.getRequiredBondETHForKeys(0), 0); + assertEq(accounting.getRequiredBondStETHForKeys(1), 2 ether); + assertEq(accounting.getRequiredBondStETHForKeys(2), 3 ether); + } + + function test_WithMultiplier() public override {} + + function test_WithBlocked() public override {} + + function test_WithCurveAndMultiplier() public override {} + + function test_WithCurveAndBlocked() public override {} + + function test_WithMultiplierAndBlocked() public override {} + + function test_WithCurveAndMultiplierAndBlocked() public override {} +} + +contract CSAccountingGetRequiredBondWstETHForKeysTest is + BondAmountModifiersTest, + CSAccountingBaseTest +{ + function test_default() public override { + assertEq(accounting.getRequiredBondWstETHForKeys(0), 0); + assertEq( + accounting.getRequiredBondWstETHForKeys(1), + stETH.getSharesByPooledEth(2 ether) + ); + assertEq( + accounting.getRequiredBondWstETHForKeys(2), + stETH.getSharesByPooledEth(4 ether) + ); + } + + function test_WithCurve() public override { + accounting.setBondCurve_ForTest(); + assertEq(accounting.getRequiredBondWstETHForKeys(0), 0); + assertEq( + accounting.getRequiredBondWstETHForKeys(1), + stETH.getSharesByPooledEth(2 ether) + ); + assertEq( + accounting.getRequiredBondWstETHForKeys(2), + stETH.getSharesByPooledEth(3 ether) + ); + } + + function test_WithMultiplier() public override {} + + function test_WithBlocked() public override {} + + function test_WithCurveAndMultiplier() public override {} + + function test_WithCurveAndBlocked() public override {} + + function test_WithMultiplierAndBlocked() public override {} + + function test_WithCurveAndMultiplierAndBlocked() public override {} +} + +contract CSAccountingGetKeysCountByBondETHTest is + BondAmountModifiersTest, + CSAccountingBaseTest +{ + function test_default() public override { + assertEq(accounting.getKeysCountByBondETH(0), 0); + assertEq(accounting.getKeysCountByBondETH(1.99 ether), 0); + assertEq(accounting.getKeysCountByBondETH(2 ether), 1); + assertEq(accounting.getKeysCountByBondETH(4 ether), 2); + assertEq(accounting.getKeysCountByBondETH(16 ether), 8); + } + + function test_WithCurve() public override { + accounting.setBondCurve_ForTest(); + assertEq(accounting.getKeysCountByBondETH(0), 0); + assertEq(accounting.getKeysCountByBondETH(1.99 ether), 0); + assertEq(accounting.getKeysCountByBondETH(2 ether), 1); + assertEq(accounting.getKeysCountByBondETH(3 ether), 2); + assertEq(accounting.getKeysCountByBondETH(16 ether), 15); + } + + function test_WithMultiplier() public override {} + + function test_WithBlocked() public override {} + + function test_WithCurveAndMultiplier() public override {} + + function test_WithCurveAndBlocked() public override {} + + function test_WithMultiplierAndBlocked() public override {} + + function test_WithCurveAndMultiplierAndBlocked() public override {} +} + +contract CSAccountingGetKeysCountByBondStETHTest is + BondAmountModifiersTest, + CSAccountingBaseTest +{ + function test_default() public override { + assertEq(accounting.getKeysCountByBondStETH(0), 0); + assertEq(accounting.getKeysCountByBondStETH(1.99 ether), 0); + assertEq(accounting.getKeysCountByBondStETH(2 ether), 1); + assertEq(accounting.getKeysCountByBondStETH(4 ether), 2); + assertEq(accounting.getKeysCountByBondETH(16 ether), 8); + } + + function test_WithCurve() public override { + accounting.setBondCurve_ForTest(); + assertEq(accounting.getKeysCountByBondStETH(0), 0); + assertEq(accounting.getKeysCountByBondStETH(1.99 ether), 0); + assertEq(accounting.getKeysCountByBondStETH(2 ether), 1); + assertEq(accounting.getKeysCountByBondStETH(3 ether), 2); + assertEq(accounting.getKeysCountByBondETH(16 ether), 15); + } + + function test_WithMultiplier() public override {} + + function test_WithBlocked() public override {} + + function test_WithCurveAndMultiplier() public override {} + + function test_WithCurveAndBlocked() public override {} + + function test_WithMultiplierAndBlocked() public override {} + + function test_WithCurveAndMultiplierAndBlocked() public override {} +} + +contract CSAccountingGetKeysCountByBondWstETHTest is + BondAmountModifiersTest, + CSAccountingBaseTest +{ + function test_default() public override { + assertEq(accounting.getKeysCountByBondWstETH(0), 0); + assertEq( + accounting.getKeysCountByBondWstETH( + wstETH.getWstETHByStETH(1.99 ether) + ), + 0 + ); + assertEq( + accounting.getKeysCountByBondWstETH( + wstETH.getWstETHByStETH(2 ether + 1 wei) + ), + 1 + ); + assertEq( + accounting.getKeysCountByBondWstETH( + wstETH.getWstETHByStETH(4 ether + 1 wei) + ), + 2 + ); + assertEq( + accounting.getKeysCountByBondWstETH( + wstETH.getWstETHByStETH(16 ether + 1 wei) + ), + 8 + ); + } + + function test_WithCurve() public override { + accounting.setBondCurve_ForTest(); + assertEq(accounting.getKeysCountByBondWstETH(0), 0); + assertEq( + accounting.getKeysCountByBondWstETH( + wstETH.getWstETHByStETH(1.99 ether) + ), + 0 + ); + assertEq( + accounting.getKeysCountByBondWstETH( + wstETH.getWstETHByStETH(2 ether + 1 wei) + ), + 1 + ); + assertEq( + accounting.getKeysCountByBondWstETH( + wstETH.getWstETHByStETH(4 ether) + ), + 2 + ); + assertEq( + accounting.getKeysCountByBondWstETH( + wstETH.getWstETHByStETH(16 ether + 1 wei) + ), + 15 + ); + } + + function test_WithMultiplier() public override {} + + function test_WithBlocked() public override {} + + function test_WithCurveAndMultiplier() public override {} + + function test_WithCurveAndBlocked() public override {} + + function test_WithMultiplierAndBlocked() public override {} + + function test_WithCurveAndMultiplierAndBlocked() public override {} +} + +abstract contract CSAccountingRewardsBaseTest is CSAccountingBondStateBaseTest { + struct RewardsLeaf { + bytes32[] proof; + uint256 nodeOperatorId; + uint256 shares; + } + + RewardsLeaf leaf; + + uint256 sharesAsFee; + uint256 stETHAsFee; + uint256 wstETHAsFee; + uint256 unstETHAsFee; + uint256 unstETHSharesAsFee; + + function setUp() public override { + super.setUp(); + mock_getNodeOperator(); + mock_getNodeOperatorsCount(); + } + + function _deposit(uint256 bond, uint256 fee) internal { + // Deposit bond for node operator + vm.deal(user, bond); + vm.prank(user); + accounting.depositETH{ value: bond }(user, 0); + // Set validator fee rewards + vm.deal(address(feeDistributor), fee); + vm.prank(address(feeDistributor)); + sharesAsFee = stETH.submit{ value: fee }(address(0)); + stETHAsFee = stETH.getPooledEthByShares(sharesAsFee); + wstETHAsFee = wstETH.getWstETHByStETH(stETHAsFee); + unstETHAsFee = stETH.getPooledEthByShares(sharesAsFee); + unstETHSharesAsFee = stETH.getSharesByPooledEth(unstETHAsFee); + leaf = RewardsLeaf({ + proof: new bytes32[](1), + nodeOperatorId: 0, + shares: sharesAsFee + }); + } +} + +contract CSAccountingGetTotalRewardsETHTest is CSAccountingRewardsBaseTest { + function test_default() public override { + _deposit({ bond: 0 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + 0 + ); + } + + function test_WithCurve() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + assertEq( + accounting.getTotalRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 15 ether + ); + } + + function test_WithMultiplier() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondMultiplier_ForTest(); + assertEq( + accounting.getTotalRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 3.2 ether + ); + } + + function test_WithBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplier() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + assertEq( + accounting.getTotalRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 16.7 ether + ); + } + + function test_WithCurveAndBlocked() public override { + // todo: implement me + } + + function test_WithMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 0 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + 0 ether + ); + } + + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 32 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + ); + } + + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 2 ether + ); + } + + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 33 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 1 ether + ); + } + + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 33 ether, fee: 0.1 ether }); + assertApproxEqAbs( + accounting.getTotalRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 3 ether, + 1 wei + ); + } + + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 16 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + 0 + ); + } + + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + 0 + ); + } +} + +contract CSAccountingGetTotalRewardsStETHTest is CSAccountingRewardsBaseTest { + function test_default() public override { + _deposit({ bond: 0 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + 0 + ); + } + + function test_WithCurve() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + assertEq( + accounting.getTotalRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 15 ether + ); + } + + function test_WithMultiplier() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondMultiplier_ForTest(); + assertEq( + accounting.getTotalRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 3.2 ether + ); + } + + function test_WithBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplier() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + assertEq( + accounting.getTotalRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 16.7 ether + ); + } + + function test_WithCurveAndBlocked() public override { + // todo: implement me + } + + function test_WithMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 0 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + 0 ether + ); + } + + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 32 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + ); + } + + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 2 ether + ); + } + + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 33 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 1 ether + ); + } + + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 33 ether, fee: 0.1 ether }); + assertApproxEqAbs( + accounting.getTotalRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 3 ether, + 1 wei + ); + } + + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 16 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + 0 + ); + } + + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + 0 + ); + } +} + +contract CSAccountingGetTotalRewardsWstETHTest is CSAccountingRewardsBaseTest { + function test_default() public override { + _deposit({ bond: 0 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + 0 + ); + } + + function test_WithCurve() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + assertEq( + accounting.getTotalRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + wstETH.getWstETHByStETH(stETHAsFee + 15 ether) + ); + } + + function test_WithMultiplier() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondMultiplier_ForTest(); + assertEq( + accounting.getTotalRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + wstETH.getWstETHByStETH(stETHAsFee + 3.2 ether) + ); + } + + function test_WithBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplier() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + assertEq( + accounting.getTotalRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + wstETH.getWstETHByStETH(stETHAsFee + 16.7 ether) + ); + } + + function test_WithCurveAndBlocked() public override { + // todo: implement me + } + + function test_WithMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 0 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + 0 ether + ); + } + + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 32 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + wstETH.getWstETHByStETH(stETHAsFee) + ); + } + + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + wstETH.getWstETHByStETH(stETHAsFee + 2 ether) + ); + } + + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 33 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + wstETH.getWstETHByStETH(stETHAsFee + 1 ether) + ); + } + + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 33 ether, fee: 0.1 ether }); + assertApproxEqAbs( + accounting.getTotalRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + wstETH.getWstETHByStETH(stETHAsFee + 3 ether), + 1 wei + ); + } + + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 16 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + 0 + ); + } + + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether, fee: 0.1 ether }); + assertEq( + accounting.getTotalRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + 0 + ); + } +} + +abstract contract CSAccountingClaimRewardsBaseTest is + CSAccountingRewardsBaseTest +{ + function test_EventEmitted() public virtual; + + function test_WithDesirableValue() public virtual; + + function test_RevertWhen_NotOwner() public virtual; + + // todo: check total bond shares +} + +contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { + function test_default() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq( + stETH.balanceOf(address(user)), + stETHAsFee, + "user balance should be equal to fee reward" + ); + assertEq( + bondSharesAfter, + bondSharesBefore, + "bond shares after claim should be equal to before" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to before" + ); + } + + function test_WithCurve() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq( + stETH.balanceOf(address(user)), + stETHAsFee + 15 ether, + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(15 ether), + 1 wei, + "bond shares after claim should be equal to before minus excess" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to after" + ); + } + + function test_WithMultiplier() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondMultiplier_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq( + stETH.balanceOf(address(user)), + stETHAsFee + 3.2 ether, + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(3.2 ether), + 1 wei, + "bond shares after claim should be equal to before minus excess" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to after" + ); + } + + function test_WithBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplier() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq( + stETH.balanceOf(address(user)), + stETHAsFee + 16.7 ether, + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(16.7 ether), + 1 wei, + "bond shares after claim should be equal to before minus excess" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to after" + ); + } + + function test_WithCurveAndBlocked() public override { + // todo: implement me + } + + function test_WithMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me + } + + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether, fee: 0.1 ether }); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertApproxEqAbs( + stETH.balanceOf(address(user)), + stETHAsFee + 2 ether, + 1 wei, + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(2 ether), + 1 wei, + "bond shares after claim should be equal to before" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to before" + ); + } + + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 32 ether, fee: 0.1 ether }); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq( + stETH.balanceOf(address(user)), + stETHAsFee, + "user balance should be equal to fee reward" + ); + assertEq( + bondSharesAfter, + bondSharesBefore, + "bond shares after claim should be equal to before minus fee" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to after" + ); + } + + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether, fee: 0.1 ether }); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq( + stETH.balanceOf(address(user)), + stETHAsFee + 2 ether, + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(2 ether), + 1 wei, + "bond shares after claim should be equal to before" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to before" + ); + } + + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 33 ether, fee: 0.1 ether }); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq( + stETH.balanceOf(address(user)), + stETHAsFee + 1 ether, + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(1 ether), + 1 wei, + "bond shares after claim should be equal to before minus fee" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to after" + ); + } + + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 33 ether, fee: 0.1 ether }); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertApproxEqAbs( + stETH.balanceOf(address(user)), + stETHAsFee + 3 ether, + 1 wei, + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(3 ether), + 1 wei, + "bond shares after claim should be equal to before minus fee" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to after" + ); + } + + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 16 ether, fee: 0.1 ether }); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq( + stETH.balanceOf(address(user)), 0, - 32 ether, - CSAccounting.PermitInput({ - value: 32 ether, - deadline: type(uint256).max, - // mock permit signature - v: 0, - r: 0, - s: 0 - }) + "user balance should be equal to fee reward" + ); + assertEq( + bondSharesAfter, + bondSharesBefore + sharesAsFee, + "bond shares after claim should be equal to before" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to after" + ); + } + + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether, fee: 0.1 ether }); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX ); + uint256 bondSharesAfter = accounting.getBondShares(0); assertEq( - stETH.balanceOf(user), + stETH.balanceOf(address(user)), 0, - "user balance should be 0 after deposit" + "user balance should be equal to fee reward" ); assertEq( - accounting.getBondShares(0), - sharesToDeposit, - "bond shares should be equal to deposited shares" + bondSharesAfter, + bondSharesBefore + sharesAsFee, + "bond shares after claim should be equal to before" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to after" + ); + } + + function test_EventEmitted() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + + vm.expectEmit(true, true, true, true, address(accounting)); + emit StETHRewardsClaimed( + leaf.nodeOperatorId, + user, + stETH.getPooledEthByShares(sharesAsFee) + ); + + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + } + + function test_WithDesirableValue() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + + uint256 sharesToClaim = stETH.getSharesByPooledEth(0.05 ether); + uint256 stETHToClaim = stETH.getPooledEthByShares(sharesToClaim); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + 0.05 ether + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq( + stETH.balanceOf(address(user)), + stETHToClaim, + "user balance should be equal to claimed" + ); + assertEq( + bondSharesAfter, + (bondSharesBefore + sharesAsFee) - sharesToClaim, + "bond shares after should be equal to before and fee minus claimed shares" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after should be equal to before and fee minus claimed shares" + ); + } + + function test_RevertWhen_NotOwner() public override { + vm.expectRevert( + abi.encodeWithSelector(NotOwnerToClaim.selector, stranger, user) + ); + vm.prank(stranger); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + } +} + +contract CSAccountingClaimWstETHRewardsTest is + CSAccountingClaimRewardsBaseTest +{ + function test_default() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); + + assertEq( + wstETH.balanceOf(address(user)), + wstETHAsFee, + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore, + 1 wei, + "bond shares after claim should contain wrapped fee accuracy error" + ); + assertEq( + wstETH.balanceOf(address(accounting)), + 0, + "bond manager wstETH balance should be 0" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to bond shares after" + ); + } + + function test_WithCurve() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); + + assertEq( + wstETH.balanceOf(address(user)), + wstETH.getWstETHByStETH(stETHAsFee + 15 ether), + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(15 ether), + 1 wei, + "bond shares after claim should contain wrapped fee accuracy error" + ); + assertEq( + wstETH.balanceOf(address(accounting)), + 0, + "bond manager wstETH balance should be 0" ); assertEq( stETH.sharesOf(address(accounting)), - sharesToDeposit, - "bond manager shares should be equal to deposited shares" + bondSharesAfter, + "bond manager after claim should be equal to bond shares after" ); } - function test_depositStETHWithPermit_alreadyPermitted() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 32 ether); - vm.prank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - - vm.expectEmit(true, true, true, true, address(accounting)); - emit StETHBondDeposited(0, user, 32 ether); + function test_WithMultiplier() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondMultiplier_ForTest(); - vm.mockCall( - address(stETH), - abi.encodeWithSelector( - stETH.allowance.selector, - user, - address(accounting) - ), - abi.encode(32 ether) + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX ); + uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); - vm.recordLogs(); - - vm.prank(user); - accounting.depositStETHWithPermit( - user, + assertEq( + wstETH.balanceOf(address(user)), + wstETH.getWstETHByStETH(stETHAsFee + 3.2 ether), + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(3.2 ether), + 1 wei, + "bond shares after claim should contain wrapped fee accuracy error" + ); + assertEq( + wstETH.balanceOf(address(accounting)), 0, - 32 ether, - CSAccounting.PermitInput({ - value: 32 ether, - deadline: type(uint256).max, - // mock permit signature - v: 0, - r: 0, - s: 0 - }) + "bond manager wstETH balance should be 0" ); - assertEq( - vm.getRecordedLogs().length, - 1, - "should emit only one event about deposit" + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to bond shares after" ); } - function test_depositWstETHWithPermit() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - uint256 wstETHAmount = wstETH.wrap(32 ether); - uint256 sharesToDeposit = stETH.getSharesByPooledEth( - wstETH.getStETHByWstETH(wstETHAmount) - ); - vm.stopPrank(); + function test_WithBlocked() public override { + // todo: implement me + } - vm.expectEmit(true, true, true, true, address(wstETH)); - emit Approval(user, address(accounting), 32 ether); - vm.expectEmit(true, true, true, true, address(accounting)); - emit WstETHBondDeposited(0, user, wstETHAmount); + function test_WithCurveAndMultiplier() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); - accounting.depositWstETHWithPermit( - user, - 0, - wstETHAmount, - CSAccounting.PermitInput({ - value: 32 ether, - deadline: type(uint256).max, - // mock permit signature - v: 0, - r: 0, - s: 0 - }) + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX ); + uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); assertEq( - wstETH.balanceOf(user), - 0, - "user balance should be 0 after deposit" + wstETH.balanceOf(address(user)), + wstETH.getWstETHByStETH(stETHAsFee + 16.7 ether), + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(16.7 ether), + 1 wei, + "bond shares after claim should contain wrapped fee accuracy error" ); assertEq( - accounting.getBondShares(0), - sharesToDeposit, - "bond shares should be equal to deposited shares" + wstETH.balanceOf(address(accounting)), + 0, + "bond manager wstETH balance should be 0" ); assertEq( - accounting.totalBondShares(), - sharesToDeposit, - "bond manager shares should be equal to deposited shares" + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to bond shares after" ); } - function test_depositWstETHWithPermit_alreadyPermitted() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - uint256 wstETHAmount = wstETH.wrap(32 ether); - vm.stopPrank(); + function test_WithCurveAndBlocked() public override { + // todo: implement me + } - vm.expectEmit(true, true, true, true, address(accounting)); - emit WstETHBondDeposited(0, user, wstETHAmount); + function test_WithMultiplierAndBlocked() public override { + // todo: implement me + } - vm.mockCall( - address(wstETH), - abi.encodeWithSelector( - wstETH.allowance.selector, - user, - address(accounting) - ), - abi.encode(32 ether) - ); + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me + } - vm.recordLogs(); + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether, fee: 0.1 ether }); + uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); - accounting.depositWstETHWithPermit( - user, - 0, - wstETHAmount, - CSAccounting.PermitInput({ - value: 32 ether, - deadline: type(uint256).max, - // mock permit signature - v: 0, - r: 0, - s: 0 - }) + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX ); + uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); + assertApproxEqAbs( + wstETH.balanceOf(address(user)), + wstETHAsFee + stETH.getSharesByPooledEth(2 ether), + 1 wei, + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(2 ether), + 1 wei, + "bond shares after claim should contain wrapped fee accuracy error" + ); assertEq( - vm.getRecordedLogs().length, - 1, - "should emit only one event about deposit" + wstETH.balanceOf(address(accounting)), + 0, + "bond manager wstETH balance should be 0" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to bond shares after" ); } - function test_depositETH_RevertIfNotExistedOperator() public { - vm.expectRevert("node operator does not exist"); - vm.prank(user); - accounting.depositETH{ value: 0 }(user, 0); - } + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 32 ether, fee: 0.1 ether }); - function test_depositStETH_RevertIfNotExistedOperator() public { - vm.expectRevert("node operator does not exist"); + uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); - accounting.depositStETH(user, 0, 32 ether); - } - - function test_depositETH_RevertIfInvalidSender() public { - vm.expectRevert(InvalidSender.selector); - vm.prank(stranger); - accounting.depositETH{ value: 0 }(user, 0); - } - - function test_depositStETH_RevertIfInvalidSender() public { - vm.expectRevert(InvalidSender.selector); - vm.prank(stranger); - accounting.depositStETH(user, 0, 32 ether); - } + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); - function test_depositStETHWithPermit_RevertIfInvalidSender() public { - vm.expectRevert(InvalidSender.selector); - vm.prank(stranger); - accounting.depositStETHWithPermit( - user, + assertEq( + wstETH.balanceOf(address(user)), + wstETHAsFee, + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore, + 1 wei, + "bond shares after claim should contain wrapped fee accuracy error" + ); + assertEq( + wstETH.balanceOf(address(accounting)), 0, - 32 ether, - CSAccounting.PermitInput({ - value: 32 ether, - deadline: type(uint256).max, - // mock permit signature - v: 0, - r: 0, - s: 0 - }) + "bond manager wstETH balance should be 0" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to bond shares after" ); } - function test_depositWstETH_RevertIfInvalidSender() public { - vm.expectRevert(InvalidSender.selector); - vm.prank(stranger); - accounting.depositWstETH(user, 0, 32 ether); - } + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether, fee: 0.1 ether }); - function test_depositWstETHWithPermit_RevertIfInvalidSender() public { - vm.expectRevert(InvalidSender.selector); - vm.prank(stranger); - accounting.depositWstETHWithPermit( - user, - 0, - 32 ether, - CSAccounting.PermitInput({ - value: 32 ether, - deadline: type(uint256).max, - // mock permit signature - v: 0, - r: 0, - s: 0 - }) + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); + + assertApproxEqAbs( + wstETH.balanceOf(address(user)), + wstETHAsFee + stETH.getSharesByPooledEth(2 ether), + 1 wei, + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(2 ether), + 1 wei, + "bond shares after claim should contain wrapped fee accuracy error" + ); + assertEq( + wstETH.balanceOf(address(accounting)), + 0, + "bond manager wstETH balance should be 0" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to bond shares after" ); } - function test_getTotalRewardsETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - uint256 sharesAsFee = stETH.submit{ value: 0.1 ether }(address(0)); - uint256 ETHAsFee = stETH.getPooledEthByShares(sharesAsFee); - vm.deal(user, 32 ether); - vm.prank(user); - accounting.depositETH{ value: 32 ether }(user, 0); + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 33 ether, fee: 0.1 ether }); - uint256 totalRewards = accounting.getTotalRewardsETH( - new bytes32[](1), - 0, - sharesAsFee + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX ); + uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); - assertEq(totalRewards, ETHAsFee); - - // set sophisticated curve - accounting.setBondCurveForTests(); - - totalRewards = accounting.getTotalRewardsETH( - new bytes32[](1), + assertApproxEqAbs( + wstETH.balanceOf(address(user)), + wstETHAsFee + stETH.getSharesByPooledEth(1 ether), + 1 wei, + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(1 ether), + 1 wei, + "bond shares after claim should contain wrapped fee accuracy error" + ); + assertEq( + wstETH.balanceOf(address(accounting)), 0, - sharesAsFee + "bond manager wstETH balance should be 0" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to bond shares after" ); + } - // fee + excess after curve - assertEq(totalRewards, ETHAsFee + 10.6 ether); + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 33 ether, fee: 0.1 ether }); - // set multiplier - vm.prank(admin); - accounting.setBondMultiplier(0, 9500); // 0.95 + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); - totalRewards = accounting.getTotalRewardsETH( - new bytes32[](1), + assertApproxEqAbs( + wstETH.balanceOf(address(user)), + wstETHAsFee + stETH.getSharesByPooledEth(3 ether), + 1 wei, + "user balance should be equal to fee reward" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(3 ether), + 1 wei, + "bond shares after claim should contain wrapped fee accuracy error" + ); + assertEq( + wstETH.balanceOf(address(accounting)), 0, - sharesAsFee + "bond manager wstETH balance should be 0" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to bond shares after" ); - - // fee + excess after curve + multiplier - assertEq(totalRewards, ETHAsFee + (32 ether - 21.4 ether * 0.95)); } - function test_getTotalRewardsStETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - uint256 sharesAsFee = stETH.submit{ value: 0.1 ether }(address(0)); - uint256 stETHAsFee = stETH.getPooledEthByShares(sharesAsFee); - vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 32 ether); - vm.stopPrank(); + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 16 ether, fee: 0.1 ether }); - uint256 totalRewards = accounting.getTotalRewardsStETH( - new bytes32[](1), - 0, - sharesAsFee + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX ); + uint256 bondSharesAfter = accounting.getBondShares(0); - assertEq(totalRewards, stETHAsFee); - - // set sophisticated curve - accounting.setBondCurveForTests(); - totalRewards = accounting.getTotalRewardsStETH( - new bytes32[](1), + assertEq( + wstETH.balanceOf(address(user)), 0, - sharesAsFee + "user balance should be equal to fee reward" ); - - // fee + excess after curve - assertEq(totalRewards, stETHAsFee + 10.6 ether); - - // set multiplier - vm.prank(admin); - accounting.setBondMultiplier(0, 9500); // 0.95 - - totalRewards = accounting.getTotalRewardsStETH( - new bytes32[](1), + assertEq( + bondSharesAfter, + bondSharesBefore + sharesAsFee, + "bond shares after claim should contain wrapped fee accuracy error" + ); + assertEq( + wstETH.balanceOf(address(accounting)), 0, - sharesAsFee + "bond manager wstETH balance should be 0" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to bond shares after" ); - - // fee + excess after curve + multiplier - assertEq(totalRewards, stETHAsFee + (32 ether - 21.4 ether * 0.95)); } - function test_getTotalRewardsWstETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - uint256 sharesAsFee = stETH.submit{ value: 0.1 ether }(address(0)); - uint256 wstETHAsFee = wstETH.getWstETHByStETH( - stETH.getPooledEthByShares(sharesAsFee) - ); - vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 32 ether); - vm.stopPrank(); + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether, fee: 0.1 ether }); - uint256 totalRewards = accounting.getTotalRewardsWstETH( - new bytes32[](1), - 0, - sharesAsFee + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX ); + uint256 bondSharesAfter = accounting.getBondShares(0); - assertApproxEqAbs(totalRewards, wstETHAsFee, 1); - - // set sophisticated curve - accounting.setBondCurveForTests(); - totalRewards = accounting.getTotalRewardsWstETH( - new bytes32[](1), + assertEq( + wstETH.balanceOf(address(user)), 0, - sharesAsFee + "user balance should be equal to fee reward" ); - - // fee + excess after curve - assertApproxEqAbs( - totalRewards, - wstETHAsFee + wstETH.getWstETHByStETH(10.6 ether), - 1 + assertEq( + bondSharesAfter, + bondSharesBefore + sharesAsFee, + "bond shares after claim should contain wrapped fee accuracy error" ); - - // set multiplier - vm.prank(admin); - accounting.setBondMultiplier(0, 9500); // 0.95 - - totalRewards = accounting.getTotalRewardsWstETH( - new bytes32[](1), + assertEq( + wstETH.balanceOf(address(accounting)), 0, - sharesAsFee + "bond manager wstETH balance should be 0" ); - - // fee + excess after curve + multiplier assertEq( - totalRewards, - wstETHAsFee + wstETH.getWstETHByStETH(32 ether - 21.4 ether * 0.95) + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to bond shares after" ); } - function test_getExcessBondETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 64 ether); - vm.prank(user); - accounting.depositETH{ value: 64 ether }(user, 0); - - assertApproxEqAbs(accounting.getExcessBondETH(0), 32 ether, 1); - - // set sophisticated curve - accounting.setBondCurveForTests(); + function test_EventEmitted() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); - assertApproxEqAbs(accounting.getExcessBondETH(0), 42.6 ether, 1); - - // set multiplier - vm.prank(admin); - accounting.setBondMultiplier(0, 9500); // 0.95 + vm.expectEmit(true, true, true, true, address(accounting)); + emit WstETHRewardsClaimed(0, user, wstETHAsFee); - assertApproxEqAbs( - accounting.getExcessBondETH(0), - 32 ether + (32 ether - 21.4 ether * 0.95), - 1 + vm.prank(user); + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX ); } - function test_getExcessBondStETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 64 ether); - vm.prank(user); - accounting.depositETH{ value: 64 ether }(user, 0); - - assertApproxEqAbs(accounting.getExcessBondStETH(0), 32 ether, 1); - - // set sophisticated curve - accounting.setBondCurveForTests(); - - assertApproxEqAbs(accounting.getExcessBondStETH(0), 42.6 ether, 1); + function test_WithDesirableValue() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); - // set multiplier - vm.prank(admin); - accounting.setBondMultiplier(0, 9500); // 0.95 - - assertApproxEqAbs( - accounting.getExcessBondStETH(0), - 32 ether + (32 ether - 21.4 ether * 0.95), - 1 + uint256 sharesToClaim = stETH.getSharesByPooledEth(0.05 ether); + uint256 wstETHToClaim = wstETH.getWstETHByStETH( + stETH.getPooledEthByShares(sharesToClaim) ); - } - function test_getExcessBondWstETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 64 ether); + uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); - accounting.depositETH{ value: 64 ether }(user, 0); - - assertApproxEqAbs( - accounting.getExcessBondWstETH(0), - wstETH.getWstETHByStETH(32 ether), - 1 + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + sharesToClaim ); + uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); - // set sophisticated curve - accounting.setBondCurveForTests(); - - assertApproxEqAbs( - accounting.getExcessBondWstETH(0), - wstETH.getWstETHByStETH(42.6 ether), - 1 + assertEq( + wstETH.balanceOf(address(user)), + wstETHToClaim, + "user balance should be equal to claimed" ); - - // set multiplier - vm.prank(admin); - accounting.setBondMultiplier(0, 9500); // 0.95 - assertApproxEqAbs( - accounting.getExcessBondWstETH(0), - wstETH.getWstETHByStETH(32 ether + (32 ether - 21.4 ether * 0.95)), - 1 + bondSharesAfter, + (bondSharesBefore + sharesAsFee) - sharesToClaim, + 1 wei, + "bond shares after should be equal to before and fee minus claimed shares" + ); + assertEq( + wstETH.balanceOf(address(accounting)), + 0, + "bond manager wstETH balance should be 0" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after should be equal to before and fee minus claimed shares" ); } - function test_getMissingBondETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 16 ether); - vm.prank(user); - accounting.depositETH{ value: 16 ether }(user, 0); - - assertApproxEqAbs(accounting.getMissingBondETH(0), 16 ether, 1); - - // set sophisticated curve - accounting.setBondCurveForTests(); - - assertApproxEqAbs(accounting.getMissingBondETH(0), 5.4 ether, 1); - - // set multiplier - vm.prank(admin); - accounting.setBondMultiplier(0, 9500); // 0.95 - - assertApproxEqAbs( - accounting.getMissingBondETH(0), - 16 ether - (32 ether - 21.4 ether * 0.95), - 1 + function test_RevertWhen_NotOwner() public override { + vm.expectRevert( + abi.encodeWithSelector(NotOwnerToClaim.selector, stranger, user) + ); + vm.prank(stranger); + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX ); } +} - function test_getMissingBondStETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 16 ether); - vm.prank(user); - accounting.depositETH{ value: 16 ether }(user, 0); - - assertApproxEqAbs(accounting.getMissingBondStETH(0), 16 ether, 1); - - // set sophisticated curve - accounting.setBondCurveForTests(); - - assertApproxEqAbs(accounting.getMissingBondStETH(0), 5.4 ether, 1); +contract CSAccountingRequestRewardsETHRewardsTest is + CSAccountingClaimRewardsBaseTest +{ + function test_default() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); - // set multiplier - vm.prank(admin); - accounting.setBondMultiplier(0, 9500); // 0.95 + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); - assertApproxEqAbs( - accounting.getMissingBondStETH(0), - 16 ether - (32 ether - 21.4 ether * 0.95), - 1 + assertEq(requestIds.length, 1, "request ids length should be 1"); + assertEq( + bondSharesAfter, + bondSharesBefore, + "bond shares should not change after request" + ); + assertEq( + stETH.sharesOf(address(locator.withdrawalQueue())), + unstETHSharesAsFee, + "shares of withdrawal queue should be equal to requested shares" ); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); } - function test_getMissingBondWstETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 16 ether); - vm.prank(user); - accounting.depositETH{ value: 16 ether }(user, 0); + function test_WithCurve() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); - assertApproxEqAbs( - accounting.getMissingBondWstETH(0), - wstETH.getWstETHByStETH(16 ether), - 1 + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX ); + uint256 bondSharesAfter = accounting.getBondShares(0); - // set sophisticated curve - accounting.setBondCurveForTests(); - + assertEq(requestIds.length, 1, "request ids length should be 1"); assertApproxEqAbs( - accounting.getMissingBondWstETH(0), - wstETH.getWstETHByStETH(5.4 ether), - 1 + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(15 ether), + 1 wei, + "bond shares should not change after request" ); - - // set multiplier - vm.prank(admin); - accounting.setBondMultiplier(0, 9500); // 0.95 - assertApproxEqAbs( - accounting.getMissingBondWstETH(0), - wstETH.getWstETHByStETH(16 ether - (32 ether - 21.4 ether * 0.95)), - 1 + stETH.sharesOf(address(locator.withdrawalQueue())), + unstETHSharesAsFee + stETH.getSharesByPooledEth(15 ether), + 1 wei, + "shares of withdrawal queue should be equal to requested shares" ); - } - - function test_getUnbondedKeysCount() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 32 ether); - vm.prank(user); - accounting.depositETH{ value: 11.57 ether }(user, 0); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + } - assertEq(accounting.getUnbondedKeysCount(0), 10); + function test_WithMultiplier() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondMultiplier_ForTest(); + uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); - accounting.depositETH{ value: 2.43 ether }(user, 0); + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); - assertEq(accounting.getUnbondedKeysCount(0), 9); + assertEq(requestIds.length, 1, "request ids length should be 1"); + assertEq( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(3.2 ether), + "bond shares should be changed after request" + ); + assertEq( + stETH.sharesOf(address(locator.withdrawalQueue())), + unstETHSharesAsFee + stETH.getSharesByPooledEth(3.2 ether), + "shares of withdrawal queue should be equal to requested shares and excess" + ); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + } - // set sophisticated curve - accounting.setBondCurveForTests(); + function test_WithBlocked() public override { + // todo: implement me + } - assertEq(accounting.getUnbondedKeysCount(0), 7); + function test_WithCurveAndMultiplier() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); - // set multiplier - vm.prank(admin); - accounting.setBondMultiplier(0, 9500); // 0.95 + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); - assertEq(accounting.getUnbondedKeysCount(0), 6); + assertEq(requestIds.length, 1, "request ids length should be 1"); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(16.7 ether), + 1 wei, + "bond shares should be changed after request" + ); + assertApproxEqAbs( + stETH.sharesOf(address(locator.withdrawalQueue())), + unstETHSharesAsFee + stETH.getSharesByPooledEth(16.7 ether), + 1 wei, + "shares of withdrawal queue should be equal to requested shares and excess" + ); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); } - function test_getKeysCountByBondETH() public { - assertEq(accounting.getKeysCountByBondETH(0), 0); - assertEq(accounting.getKeysCountByBondETH(1.99 ether), 0); - assertEq(accounting.getKeysCountByBondETH(2 ether), 1); - assertEq(accounting.getKeysCountByBondETH(4 ether), 2); - assertEq(accounting.getKeysCountByBondETH(16 ether), 8); - - // set sophisticated curve - accounting.setBondCurveForTests(); + function test_WithCurveAndBlocked() public override { + // todo: implement me + } - assertEq(accounting.getKeysCountByBondETH(16 ether), 10); + function test_WithMultiplierAndBlocked() public override { + // todo: implement me } - function test_getKeysCountByBondStETH() public { - assertEq(accounting.getKeysCountByBondStETH(0), 0); - assertEq(accounting.getKeysCountByBondStETH(1.99 ether), 0); - assertEq(accounting.getKeysCountByBondStETH(2 ether), 1); - assertEq(accounting.getKeysCountByBondStETH(4 ether), 2); - assertEq(accounting.getKeysCountByBondETH(16 ether), 8); + function test_WithCurveAndMultiplierAndBlocked() public override { + // todo: implement me + } - // set sophisticated curve - accounting.setBondCurveForTests(); + function test_WithOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether, fee: 0.1 ether }); - assertEq(accounting.getKeysCountByBondStETH(16 ether), 10); - } + uint256 bondSharesBefore = accounting.getBondShares(0); - function test_getKeysCountByBondWstETH() public { - assertEq(accounting.getKeysCountByBondWstETH(0), 0); - assertEq( - accounting.getKeysCountByBondWstETH( - wstETH.getWstETHByStETH(1.99 ether) - ), - 0 - ); - assertEq( - accounting.getKeysCountByBondWstETH( - wstETH.getWstETHByStETH(2 ether + 1 wei) - ), - 1 - ); - assertEq( - accounting.getKeysCountByBondWstETH( - wstETH.getWstETHByStETH(4 ether + 1 wei) - ), - 2 - ); - assertEq( - accounting.getKeysCountByBondWstETH( - wstETH.getWstETHByStETH(16 ether + 1 wei) - ), - 8 + vm.prank(user); + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX ); - // set sophisticated curve - accounting.setBondCurveForTests(); + uint256 bondSharesAfter = accounting.getBondShares(0); - assertEq( - accounting.getKeysCountByBondWstETH( - wstETH.getWstETHByStETH(16 ether + 1 wei) - ), - 10 + assertEq(requestIds.length, 1, "request ids length should be 1"); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(2 ether), + 1 wei, + "bond shares should be changed after request" + ); + assertApproxEqAbs( + stETH.sharesOf(address(locator.withdrawalQueue())), + unstETHSharesAsFee + stETH.getSharesByPooledEth(2 ether), + 1 wei, + "shares of withdrawal queue should be equal to requested shares and excess" ); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); } - function test_claimRewardsStETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - uint256 sharesAsFee = stETH.submit{ value: 0.1 ether }(address(0)); - uint256 stETHAsFee = stETH.getPooledEthByShares(sharesAsFee); - vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 32 ether); - - vm.expectEmit(true, true, true, true, address(accounting)); - emit StETHRewardsClaimed( - 0, - user, - stETH.getPooledEthByShares(sharesAsFee) - ); + function test_WithBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 32 ether, fee: 0.1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); - accounting.claimRewardsStETH( - new bytes32[](1), - 0, - sharesAsFee, + + vm.prank(user); + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, UINT256_MAX ); + uint256 bondSharesAfter = accounting.getBondShares(0); - vm.stopPrank(); - assertEq( - stETH.balanceOf(address(user)), - stETHAsFee, - "user balance should be equal to fee reward" - ); + assertEq(requestIds.length, 1, "request ids length should be 1"); assertEq( bondSharesAfter, bondSharesBefore, - "bond shares after claim should be equal to before" + "bond shares should not change after request" ); assertEq( - stETH.sharesOf(address(accounting)), - bondSharesAfter, - "bond manager after claim should be equal to before" + stETH.sharesOf(address(locator.withdrawalQueue())), + unstETHSharesAsFee, + "shares of withdrawal queue should be equal to requested shares" ); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + } - // set sophisticated curve - accounting.setBondCurveForTests(); + function test_WithBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 32 ether, fee: 0.1 ether }); - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - stETH.submit{ value: 0.1 ether }(address(0)); + uint256 bondSharesBefore = accounting.getBondShares(0); - uint256 balanceBefore = stETH.balanceOf(address(user)); vm.prank(user); - accounting.claimRewardsStETH( - new bytes32[](1), - 0, - sharesAsFee, + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, UINT256_MAX ); - // claimed fee before + fee + excess after curve - assertEq( - stETH.balanceOf(address(user)), - balanceBefore + stETHAsFee + 10.6 ether, - "user balance should be equal to fee reward + excess" + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq(requestIds.length, 1, "request ids length should be 1"); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(2 ether), + 1 wei, + "bond shares should be changed after request" + ); + assertApproxEqAbs( + stETH.sharesOf(address(locator.withdrawalQueue())), + unstETHSharesAsFee + stETH.getSharesByPooledEth(2 ether), + 1 wei, + "shares of withdrawal queue should be equal to requested shares and excess" ); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + } - // set multiplier - vm.prank(admin); - accounting.setBondMultiplier(0, 9500); // 0.95 + function test_WithExcessBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 33 ether, fee: 0.1 ether }); - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - stETH.submit{ value: 0.1 ether }(address(0)); + uint256 bondSharesBefore = accounting.getBondShares(0); - balanceBefore = stETH.balanceOf(address(user)); vm.prank(user); - accounting.claimRewardsStETH( - new bytes32[](1), - 0, - sharesAsFee, + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, UINT256_MAX ); - // claimed fee before x2 + fee + excess after multiplier + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq(requestIds.length, 1, "request ids length should be 1"); assertApproxEqAbs( - stETH.balanceOf(address(user)), - balanceBefore + stETHAsFee + (21.4 ether - 21.4 ether * 0.95), - 1, - "user balance should be equal to fee reward + excess" + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(1 ether), + 1 wei, + "bond shares should not change after request" + ); + assertApproxEqAbs( + stETH.sharesOf(address(locator.withdrawalQueue())), + unstETHSharesAsFee + stETH.getSharesByPooledEth(1 ether), + 1 wei, + "shares of withdrawal queue should be equal to requested shares and excess" ); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); } - function test_claimRewardsStETH_WithDesirableValue() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - uint256 sharesAsFee = stETH.submit{ value: 0.1 ether }(address(0)); - uint256 sharesToClaim = stETH.getSharesByPooledEth(0.05 ether); - uint256 stETHToClaim = stETH.getPooledEthByShares(sharesToClaim); - vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 32 ether); - - vm.expectEmit(true, true, true, true, address(accounting)); - emit StETHRewardsClaimed(0, user, stETHToClaim); + function test_WithExcessBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 33 ether, fee: 0.1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); - accounting.claimRewardsStETH( - new bytes32[](1), - 0, - sharesAsFee, - 0.05 ether + vm.prank(user); + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX ); + uint256 bondSharesAfter = accounting.getBondShares(0); - assertEq( - stETH.balanceOf(address(user)), - stETHToClaim, - "user balance should be equal to claimed" - ); - assertEq( + assertEq(requestIds.length, 1, "request ids length should be 1"); + assertApproxEqAbs( bondSharesAfter, - (bondSharesBefore + sharesAsFee) - sharesToClaim, - "bond shares after should be equal to before and fee minus claimed shares" + bondSharesBefore - stETH.getSharesByPooledEth(3 ether), + 1 wei, + "bond shares should be changed after request" ); - assertEq( - stETH.sharesOf(address(accounting)), - bondSharesAfter, - "bond manager after should be equal to before and fee minus claimed shares" + assertApproxEqAbs( + stETH.sharesOf(address(locator.withdrawalQueue())), + unstETHSharesAsFee + stETH.getSharesByPooledEth(3 ether), + 1 wei, + "shares of withdrawal queue should be equal to requested shares and excess" ); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); } - function test_claimRewardsStETH_WhenAmountToClaimIsHigherThanRewards() - public - { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - uint256 sharesAsFee = stETH.submit{ value: 0.1 ether }(address(0)); - uint256 stETHAsFee = stETH.getPooledEthByShares(sharesAsFee); - - vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 32 ether); - - vm.expectEmit(true, true, true, true, address(accounting)); - emit StETHRewardsClaimed(0, user, stETHAsFee); + function test_WithMissingBond() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _deposit({ bond: 16 ether, fee: 0.1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); - accounting.claimRewardsStETH( - new bytes32[](1), - 0, - sharesAsFee, - 100 * 1e18 + + vm.prank(user); + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX ); + uint256 bondSharesAfter = accounting.getBondShares(0); - assertEq( - stETH.balanceOf(address(user)), - stETHAsFee, - "user balance should be equal to fee reward" - ); + assertEq(requestIds.length, 0, "request ids length should be 0"); assertEq( bondSharesAfter, - bondSharesBefore, - "bond shares after should be equal to before" + bondSharesBefore + sharesAsFee, + "bond shares should not change after request" ); assertEq( - stETH.sharesOf(address(accounting)), - bondSharesAfter, - "bond manager after should be equal to before" + stETH.sharesOf(address(locator.withdrawalQueue())), + 0, + "shares of withdrawal queue should be equal to requested shares" ); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); } - function test_claimRewardsStETH_WhenRequiredBondIsEqualActual() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(feeDistributor), 1 ether); - vm.prank(address(feeDistributor)); - uint256 sharesAsFee = stETH.submit{ value: 1 ether }(address(0)); - - vm.deal(user, 31 ether); - vm.startPrank(user); - stETH.submit{ value: 31 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 31 ether); - - vm.expectEmit(true, true, true, true, address(accounting)); - emit StETHRewardsClaimed(0, user, 0); + function test_WithMissingBondAndOneWithdrawnValidator() public override { + _operator({ ongoing: 16, withdrawn: 1 }); + _deposit({ bond: 16 ether, fee: 0.1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); - accounting.claimRewardsStETH( - new bytes32[](1), - 0, - sharesAsFee, + + vm.prank(user); + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, UINT256_MAX ); + uint256 bondSharesAfter = accounting.getBondShares(0); - assertEq(stETH.balanceOf(address(user)), 0, "user balance should be 0"); + assertEq(requestIds.length, 0, "request ids length should be 0"); assertEq( bondSharesAfter, bondSharesBefore + sharesAsFee, - "bond shares should be increased by fee" + "bond shares should not change after request" ); assertEq( - stETH.sharesOf(address(accounting)), - bondSharesAfter, - "bond manager shares should be increased by fee" + stETH.sharesOf(address(locator.withdrawalQueue())), + 0, + "shares of withdrawal queue should be equal to requested shares" + ); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + } + + function test_EventEmitted() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + + vm.expectEmit( + true, + true, + true, + true, + address(locator.withdrawalQueue()) + ); + emit WithdrawalRequested( + 1, + address(accounting), + user, + unstETHAsFee, + unstETHSharesAsFee + ); + vm.expectEmit(true, true, true, true, address(accounting)); + emit ETHRewardsRequested(0, user, unstETHAsFee); + + vm.prank(user); + accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX ); } - function test_claimRewardsStETH_WhenRequiredBondIsHigherActual() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(feeDistributor), 1 ether); - vm.prank(address(feeDistributor)); - uint256 sharesAsFee = stETH.submit{ value: 0.5 ether }(address(0)); - - vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 31 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 31 ether); + function test_WithDesirableValue() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); - vm.expectEmit(true, true, true, true, address(accounting)); - emit StETHRewardsClaimed(0, user, 0); + uint256 sharesToRequest = stETH.getSharesByPooledEth(0.05 ether); + uint256 unstETHToRequest = stETH.getPooledEthByShares(sharesToRequest); + uint256 unstETHSharesToRequest = stETH.getSharesByPooledEth( + unstETHToRequest + ); uint256 bondSharesBefore = accounting.getBondShares(0); - accounting.claimRewardsStETH( - new bytes32[](1), - 0, - sharesAsFee, - UINT256_MAX + vm.prank(user); + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + 0.05 ether ); uint256 bondSharesAfter = accounting.getBondShares(0); - assertEq(stETH.balanceOf(address(user)), 0, "user balance should be 0"); + assertEq(requestIds.length, 1, "request ids length should be 1"); assertEq( bondSharesAfter, - bondSharesBefore + sharesAsFee, - "bond shares should be increased by fee" + bondSharesBefore + sharesAsFee - sharesToRequest, + "bond shares should change after request" ); assertEq( - stETH.sharesOf(address(accounting)), - bondSharesAfter, - "bond manager shares should be increased by fee" + stETH.sharesOf(address(locator.withdrawalQueue())), + unstETHSharesToRequest, + "shares of withdrawal queue should be equal to requested shares" ); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); } - function test_claimRewardsStETH_RevertWhenCallerIsNotRewardAddress() - public - { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - + function test_RevertWhen_NotOwner() public override { vm.expectRevert( abi.encodeWithSelector(NotOwnerToClaim.selector, stranger, user) ); vm.prank(stranger); - accounting.claimRewardsStETH(new bytes32[](1), 0, 1, 1 ether); + accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); } +} - function test_claimRewardsWstETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - uint256 sharesAsFee = stETH.submit{ value: 0.1 ether }(address(0)); - uint256 wstETHAsFee = wstETH.getWstETHByStETH( - stETH.getPooledEthByShares(sharesAsFee) - ); +contract CSAccountingDepositsTest is CSAccountingBaseTest { + function setUp() public override { + super.setUp(); + mock_getNodeOperator(); + mock_getNodeOperatorsCount(); + } + + function test_depositETH() public { vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 32 ether); + uint256 sharesToDeposit = stETH.getSharesByPooledEth(32 ether); vm.expectEmit(true, true, true, true, address(accounting)); - emit WstETHRewardsClaimed(0, user, wstETHAsFee); + emit ETHBondDeposited(0, user, 32 ether); - uint256 bondSharesBefore = accounting.getBondShares(0); - accounting.claimRewardsWstETH( - new bytes32[](1), - 0, - sharesAsFee, - UINT256_MAX - ); - uint256 bondSharesAfter = accounting.getBondShares(0); - vm.stopPrank(); + vm.prank(user); + accounting.depositETH{ value: 32 ether }(user, 0); assertEq( - wstETH.balanceOf(address(user)), - wstETHAsFee, - "user balance should be equal to fee reward" - ); - assertEq( - bondSharesAfter, - bondSharesBefore + 1 wei, - "bond shares after claim should contain wrapped fee accuracy error" + address(user).balance, + 0, + "user balance should be 0 after deposit" ); assertEq( - wstETH.balanceOf(address(accounting)), - 0, - "bond manager wstETH balance should be 0" + accounting.getBondShares(0), + sharesToDeposit, + "bond shares should be equal to deposited shares" ); assertEq( stETH.sharesOf(address(accounting)), - bondSharesBefore + 1 wei, - "bond manager after claim should contain wrapped fee accuracy error" + sharesToDeposit, + "bond manager shares should be equal to deposited shares" ); + } - // set sophisticated curve - accounting.setBondCurveForTests(); + function test_depositStETH() public { + vm.deal(user, 32 ether); + vm.prank(user); + uint256 sharesToDeposit = stETH.submit{ value: 32 ether }({ + _referal: address(0) + }); - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - stETH.submit{ value: 0.1 ether }(address(0)); + vm.expectEmit(true, true, true, true, address(accounting)); + emit StETHBondDeposited(0, user, 32 ether); - uint256 balanceBefore = wstETH.balanceOf(address(user)); vm.prank(user); - accounting.claimRewardsWstETH( - new bytes32[](1), + accounting.depositStETH(user, 0, 32 ether); + + assertEq( + stETH.balanceOf(user), 0, - sharesAsFee, - UINT256_MAX + "user balance should be 0 after deposit" ); - - // claimed fee before + fee + excess after curve - assertApproxEqAbs( - wstETH.balanceOf(address(user)), - balanceBefore + - wstETH.getWstETHByStETH( - stETH.getPooledEthByShares(sharesAsFee) + 10.6 ether - ), - 1, - "user balance should be equal to fee reward + excess" + assertEq( + accounting.getBondShares(0), + sharesToDeposit, + "bond shares should be equal to deposited shares" + ); + assertEq( + stETH.sharesOf(address(accounting)), + sharesToDeposit, + "bond manager shares should be equal to deposited shares" ); + } - // set multiplier - vm.prank(admin); - accounting.setBondMultiplier(0, 9500); // 0.95 + function test_depositWstETH() public { + vm.deal(user, 32 ether); + vm.startPrank(user); + stETH.submit{ value: 32 ether }({ _referal: address(0) }); + uint256 wstETHAmount = wstETH.wrap(32 ether); + uint256 sharesToDeposit = stETH.getSharesByPooledEth( + wstETH.getStETHByWstETH(wstETHAmount) + ); + vm.stopPrank(); - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - stETH.submit{ value: 0.1 ether }(address(0)); + vm.expectEmit(true, true, true, true, address(accounting)); + emit WstETHBondDeposited(0, user, wstETHAmount); - balanceBefore = wstETH.balanceOf(address(user)); vm.prank(user); - accounting.claimRewardsWstETH( - new bytes32[](1), + accounting.depositWstETH(user, 0, wstETHAmount); + + assertEq( + wstETH.balanceOf(user), 0, - sharesAsFee, - UINT256_MAX + "user balance should be 0 after deposit" ); - - // claimed fee before x2 + fee + excess after multiplier - assertApproxEqAbs( - wstETH.balanceOf(address(user)), - balanceBefore + - wstETH.getWstETHByStETH( - stETH.getPooledEthByShares(sharesAsFee) + - 21.4 ether - - 21.4 ether * - 0.95 - ), - 1, - "user balance should be equal to fee reward + excess" + assertEq( + accounting.getBondShares(0), + sharesToDeposit, + "bond shares should be equal to deposited shares" + ); + assertEq( + stETH.sharesOf(address(accounting)), + sharesToDeposit, + "bond manager shares should be equal to deposited shares" ); } - function test_claimRewardsWstETH_WithDesirableValue() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - uint256 sharesAsFee = stETH.submit{ value: 0.1 ether }(address(0)); - uint256 sharesToClaim = stETH.getSharesByPooledEth(0.05 ether); - uint256 wstETHToClaim = wstETH.getWstETHByStETH( - stETH.getPooledEthByShares(sharesToClaim) - ); + function test_depositStETHWithPermit() public { vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 32 ether); + vm.prank(user); + uint256 sharesToDeposit = stETH.submit{ value: 32 ether }({ + _referal: address(0) + }); + vm.expectEmit(true, true, true, true, address(stETH)); + emit Approval(user, address(accounting), 32 ether); vm.expectEmit(true, true, true, true, address(accounting)); - emit WstETHRewardsClaimed(0, user, wstETHToClaim); + emit StETHBondDeposited(0, user, 32 ether); - uint256 bondSharesBefore = accounting.getBondShares(0); - accounting.claimRewardsWstETH( - new bytes32[](1), + vm.prank(user); + accounting.depositStETHWithPermit( + user, 0, - sharesAsFee, - stETH.getSharesByPooledEth(0.05 ether) + 32 ether, + CSAccounting.PermitInput({ + value: 32 ether, + deadline: type(uint256).max, + // mock permit signature + v: 0, + r: 0, + s: 0 + }) ); - uint256 bondSharesAfter = accounting.getBondShares(0); assertEq( - wstETH.balanceOf(address(user)), - wstETHToClaim, - "user balance should be equal to fee reward" - ); - assertEq( - bondSharesAfter, - (bondSharesBefore + sharesAsFee) - wstETHToClaim, - "bond shares after should be equal to before and fee minus claimed shares" + stETH.balanceOf(user), + 0, + "user balance should be 0 after deposit" ); assertEq( - wstETH.balanceOf(address(accounting)), - 0, - "bond manager wstETH balance should be 0" + accounting.getBondShares(0), + sharesToDeposit, + "bond shares should be equal to deposited shares" ); assertEq( stETH.sharesOf(address(accounting)), - (bondSharesBefore + sharesAsFee) - wstETHToClaim, - "bond shares after should be equal to before and fee minus claimed shares" + sharesToDeposit, + "bond manager shares should be equal to deposited shares" ); } - function test_claimRewardsWstETH_RevertWhenCallerIsNotRewardAddress() - public - { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); + function test_depositStETHWithPermit_AlreadyPermitted() public { + vm.deal(user, 32 ether); + vm.prank(user); + stETH.submit{ value: 32 ether }({ _referal: address(0) }); - vm.expectRevert( - abi.encodeWithSelector(NotOwnerToClaim.selector, stranger, user) + vm.expectEmit(true, true, true, true, address(accounting)); + emit StETHBondDeposited(0, user, 32 ether); + + vm.mockCall( + address(stETH), + abi.encodeWithSelector( + stETH.allowance.selector, + user, + address(accounting) + ), + abi.encode(32 ether) ); - vm.prank(stranger); - accounting.claimRewardsWstETH(new bytes32[](1), 0, 1, 1 ether); - } - function test_requestRewardsETH() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - uint256 sharesAsFee = stETH.submit{ value: 0.1 ether }(address(0)); + vm.recordLogs(); + + vm.prank(user); + accounting.depositStETHWithPermit( + user, + 0, + 32 ether, + CSAccounting.PermitInput({ + value: 32 ether, + deadline: type(uint256).max, + // mock permit signature + v: 0, + r: 0, + s: 0 + }) + ); + + assertEq( + vm.getRecordedLogs().length, + 1, + "should emit only one event about deposit" + ); + } + function test_depositWstETHWithPermit() public { vm.deal(user, 32 ether); vm.startPrank(user); stETH.submit{ value: 32 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 32 ether); - - uint256 requestedAsUnstETH = stETH.getPooledEthByShares(sharesAsFee); - uint256 requestedAsUnstETHAsShares = stETH.getSharesByPooledEth( - requestedAsUnstETH + uint256 wstETHAmount = wstETH.wrap(32 ether); + uint256 sharesToDeposit = stETH.getSharesByPooledEth( + wstETH.getStETHByWstETH(wstETHAmount) ); + vm.stopPrank(); - vm.expectEmit( - true, - true, - true, - true, - address(locator.withdrawalQueue()) - ); - emit WithdrawalRequested( - 1, - address(accounting), - user, - requestedAsUnstETH, - requestedAsUnstETHAsShares - ); + vm.expectEmit(true, true, true, true, address(wstETH)); + emit Approval(user, address(accounting), 32 ether); vm.expectEmit(true, true, true, true, address(accounting)); - emit ETHRewardsRequested( - 0, + emit WstETHBondDeposited(0, user, wstETHAmount); + + vm.prank(user); + accounting.depositWstETHWithPermit( user, - stETH.getPooledEthByShares(sharesAsFee) + 0, + wstETHAmount, + CSAccounting.PermitInput({ + value: 32 ether, + deadline: type(uint256).max, + // mock permit signature + v: 0, + r: 0, + s: 0 + }) ); - uint256 bondSharesBefore = accounting.getBondShares(0); - uint256[] memory requestIds = accounting.requestRewardsETH( - new bytes32[](1), + assertEq( + wstETH.balanceOf(user), 0, - sharesAsFee, - UINT256_MAX + "user balance should be 0 after deposit" ); - uint256 bondSharesAfter = accounting.getBondShares(0); - vm.stopPrank(); - - assertEq(requestIds.length, 1, "request ids length should be 1"); assertEq( - bondSharesAfter, - bondSharesBefore, - "bond shares should not change after request" + accounting.getBondShares(0), + sharesToDeposit, + "bond shares should be equal to deposited shares" ); assertEq( - stETH.sharesOf(address(locator.withdrawalQueue())), - requestedAsUnstETHAsShares, - "shares of withdrawal queue should be equal to requested shares" + accounting.totalBondShares(), + sharesToDeposit, + "bond manager shares should be equal to deposited shares" ); - assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + } - // set sophisticated curve - accounting.setBondCurveForTests(); + function test_depositWstETHWithPermit_AlreadyPermitted() public { + vm.deal(user, 32 ether); + vm.startPrank(user); + stETH.submit{ value: 32 ether }({ _referal: address(0) }); + uint256 wstETHAmount = wstETH.wrap(32 ether); + vm.stopPrank(); - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - stETH.submit{ value: 0.1 ether }(address(0)); + vm.expectEmit(true, true, true, true, address(accounting)); + emit WstETHBondDeposited(0, user, wstETHAmount); - uint256 balanceBefore = stETH.sharesOf( - address(locator.withdrawalQueue()) + vm.mockCall( + address(wstETH), + abi.encodeWithSelector( + wstETH.allowance.selector, + user, + address(accounting) + ), + abi.encode(32 ether) ); + + vm.recordLogs(); + vm.prank(user); - accounting.requestRewardsETH( - new bytes32[](1), + accounting.depositWstETHWithPermit( + user, 0, - sharesAsFee, - UINT256_MAX + wstETHAmount, + CSAccounting.PermitInput({ + value: 32 ether, + deadline: type(uint256).max, + // mock permit signature + v: 0, + r: 0, + s: 0 + }) ); - // requested fee before + fee + excess after curve assertEq( - stETH.sharesOf(address(locator.withdrawalQueue())), - balanceBefore + - requestedAsUnstETHAsShares + - stETH.getSharesByPooledEth(10.6 ether), - "shares of withdrawal queue should be equal to requested shares + excess" + vm.getRecordedLogs().length, + 1, + "should emit only one event about deposit" ); + } - // set multiplier - vm.prank(admin); - accounting.setBondMultiplier(0, 9500); // 0.95 - - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - stETH.submit{ value: 0.1 ether }(address(0)); + function test_depositETH_RevertIfNotExistedOperator() public { + vm.expectRevert("node operator does not exist"); + vm.prank(user); + accounting.depositETH{ value: 0 }(user, 1); + } - balanceBefore = stETH.sharesOf(address(locator.withdrawalQueue())); + function test_depositStETH_RevertIfNotExistedOperator() public { + vm.expectRevert("node operator does not exist"); vm.prank(user); - accounting.requestRewardsETH( - new bytes32[](1), - 0, - sharesAsFee, - UINT256_MAX - ); + accounting.depositStETH(user, 1, 0 ether); + } - // claimed fee before x2 + fee + excess after multiplier - assertApproxEqAbs( - stETH.sharesOf(address(locator.withdrawalQueue())), - balanceBefore + - requestedAsUnstETHAsShares + - stETH.getSharesByPooledEth(21.4 ether - 21.4 ether * 0.95), - 1, - "shares of withdrawal queue should be equal to requested shares + excess" - ); + function test_depositWstETH_RevertIfNotExistedOperator() public { + vm.expectRevert("node operator does not exist"); + vm.prank(user); + accounting.depositWstETH(user, 1, 0 ether); } - function test_requestRewardsETH_WithDesirableValue() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(feeDistributor), 0.1 ether); - vm.prank(address(feeDistributor)); - uint256 sharesAsFee = stETH.submit{ value: 0.1 ether }(address(0)); + function test_depositETH_RevertIfInvalidSender() public { + vm.expectRevert(InvalidSender.selector); + vm.prank(stranger); + accounting.depositETH{ value: 0 }(user, 0); + } - vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); + function test_depositStETH_RevertIfInvalidSender() public { + vm.expectRevert(InvalidSender.selector); + vm.prank(stranger); accounting.depositStETH(user, 0, 32 ether); + } - uint256 requestedAsShares = stETH.getSharesByPooledEth(0.05 ether); - uint256 requestedAsUnstETH = stETH.getPooledEthByShares( - requestedAsShares - ); - uint256 requestedAsUnstETHAsShares = stETH.getSharesByPooledEth( - requestedAsUnstETH - ); - - vm.expectEmit( - true, - true, - true, - true, - address(locator.withdrawalQueue()) - ); - emit WithdrawalRequested( - 1, - address(accounting), + function test_depositStETHWithPermit_RevertIfInvalidSender() public { + vm.expectRevert(InvalidSender.selector); + vm.prank(stranger); + accounting.depositStETHWithPermit( user, - requestedAsUnstETH, - requestedAsUnstETHAsShares - ); - vm.expectEmit(true, true, true, true, address(accounting)); - emit ETHRewardsRequested(0, user, requestedAsUnstETH); - - uint256 bondSharesBefore = accounting.getBondShares(0); - uint256[] memory requestIds = accounting.requestRewardsETH( - new bytes32[](1), 0, - sharesAsFee, - 0.05 ether + 32 ether, + CSAccounting.PermitInput({ + value: 32 ether, + deadline: type(uint256).max, + // mock permit signature + v: 0, + r: 0, + s: 0 + }) ); - uint256 bondSharesAfter = accounting.getBondShares(0); + } - assertEq(requestIds.length, 1, "request ids length should be 1"); - assertEq( - bondSharesAfter, - (bondSharesBefore + sharesAsFee) - requestedAsShares, - "bond shares after should be equal to before and fee minus requested shares" - ); - assertEq( - stETH.sharesOf(address(locator.withdrawalQueue())), - requestedAsUnstETHAsShares, - "shares of withdrawal queue should be equal to requested shares" + function test_depositWstETH_RevertIfInvalidSender() public { + vm.expectRevert(InvalidSender.selector); + vm.prank(stranger); + accounting.depositWstETH(user, 0, 32 ether); + } + + function test_depositWstETHWithPermit_RevertIfInvalidSender() public { + vm.expectRevert(InvalidSender.selector); + vm.prank(stranger); + accounting.depositWstETHWithPermit( + user, + 0, + 32 ether, + CSAccounting.PermitInput({ + value: 32 ether, + deadline: type(uint256).max, + // mock permit signature + v: 0, + r: 0, + s: 0 + }) ); - assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); } +} - function test_penalize_LessThanDeposit() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); +contract CSAccountingPenalizeTest is CSAccountingBaseTest { + function setUp() public override { + super.setUp(); + mock_getNodeOperator(); + mock_getNodeOperatorsCount(); vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 32 ether); - vm.stopPrank(); + vm.prank(user); + accounting.depositETH{ value: 32 ether }(user, 0); + } + function test_penalize_LessThanDeposit() public { uint256 shares = stETH.getSharesByPooledEth(1 ether); uint256 penalized = stETH.getPooledEthByShares(shares); vm.expectEmit(true, true, true, true, address(accounting)); @@ -1741,13 +3833,6 @@ contract CSAccountingTest is } function test_penalize_MoreThanDeposit() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 32 ether); - vm.stopPrank(); - uint256 bondSharesBefore = accounting.getBondShares(0); uint256 penaltyShares = stETH.getSharesByPooledEth(33 ether); vm.expectEmit(true, true, true, true, address(accounting)); @@ -1778,13 +3863,6 @@ contract CSAccountingTest is } function test_penalize_EqualToDeposit() public { - _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(user, 32 ether); - vm.startPrank(user); - stETH.submit{ value: 32 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 32 ether); - vm.stopPrank(); - uint256 shares = stETH.getSharesByPooledEth(32 ether); uint256 penalized = stETH.getPooledEthByShares(shares); vm.expectEmit(true, true, true, true, address(accounting)); @@ -1823,20 +3901,89 @@ contract CSAccountingTest is vm.prank(stranger); accounting.penalize(0, 20); } +} - function _createNodeOperator( - uint64 ongoingVals, - uint64 withdrawnVals - ) internal { - stakingModule.setNodeOperator({ - _nodeOperatorId: 0, - _active: true, - _rewardAddress: user, - _totalVettedValidators: ongoingVals, - _totalExitedValidators: 0, - _totalWithdrawnValidators: withdrawnVals, - _totalAddedValidators: ongoingVals, - _totalDepositedValidators: ongoingVals - }); +contract CSAccountingMiscTest is CSAccountingBaseTest { + function test_totalBondShares() public { + mock_getNodeOperatorsCount(2); + vm.deal(user, 64 ether); + vm.startPrank(user); + accounting.depositETH{ value: 32 ether }(user, 0); + accounting.depositETH{ value: 32 ether }(user, 1); + vm.stopPrank(); + uint256 totalDepositedShares = stETH.getSharesByPooledEth(32 ether) + + stETH.getSharesByPooledEth(32 ether); + assertEq(accounting.totalBondShares(), totalDepositedShares); + } + + function test_setFeeDistributor() public { + vm.prank(admin); + accounting.setFeeDistributor(address(1337)); + assertEq(accounting.FEE_DISTRIBUTOR(), address(1337)); + } + + function test_setFeeDistributor_RevertWhen_DoesNotHaveRole() public { + vm.expectRevert( + bytes( + Utilities.accessErrorString( + stranger, + accounting.DEFAULT_ADMIN_ROLE() + ) + ) + ); + + vm.prank(stranger); + accounting.setFeeDistributor(address(1337)); + } + + function test_setBondCurve() public { + uint256[] memory _bondCurve = new uint256[](2); + _bondCurve[0] = 2 ether; + _bondCurve[1] = 4 ether; + + vm.prank(admin); + accounting.setBondCurve(_bondCurve); + + assertEq(accounting.bondCurve(0), 2 ether); + assertEq(accounting.bondCurve(1), 4 ether); + } + + function test_setBondCurve_RevertWhen_DoesNotHaveRole() public { + uint256[] memory _bondCurve = new uint256[](2); + _bondCurve[0] = 2 ether; + _bondCurve[1] = 4 ether; + + vm.expectRevert( + bytes( + Utilities.accessErrorString( + stranger, + accounting.SET_BOND_CURVE_ROLE() + ) + ) + ); + + vm.prank(stranger); + accounting.setBondCurve(_bondCurve); + } + + function test_setBondMultiplier() public { + vm.prank(admin); + accounting.setBondMultiplier(0, 9500); + + assertEq(accounting.getBondMultiplier(0), 9500); + } + + function test_setBondMultiplier_RevertWhen_DoesNotHaveRole() public { + vm.expectRevert( + bytes( + Utilities.accessErrorString( + stranger, + accounting.SET_BOND_MULTIPLIER_ROLE() + ) + ) + ); + + vm.prank(stranger); + accounting.setBondMultiplier(0, 9500); } } diff --git a/test/CSBondCurve.t.sol b/test/CSBondCurve.t.sol index d115edf8..db5ff36f 100644 --- a/test/CSBondCurve.t.sol +++ b/test/CSBondCurve.t.sol @@ -8,10 +8,10 @@ import "forge-std/Test.sol"; import { CSBondCurve } from "../src/CSBondCurve.sol"; contract CSBondCurveTestable is CSBondCurve { - constructor(uint256[] memory _bondCurve) CSBondCurve(_bondCurve) {} + constructor(uint256[] memory bondCurve) CSBondCurve(bondCurve) {} - function setBondCurve(uint256[] memory _bondCurve) external { - _setBondCurve(_bondCurve); + function setBondCurve(uint256[] memory bondCurve) external { + _setBondCurve(bondCurve); } function setBondMultiplier( @@ -28,23 +28,23 @@ contract CSBondCurveTestable is CSBondCurve { } function getBondAmountByKeysCount( - uint256 keysCount + uint256 keys ) external view returns (uint256) { - return _getBondAmountByKeysCount(keysCount); + return _getBondAmountByKeysCount(keys); } function getKeysCountByBondAmount( - uint256 nodeOperatorId, - uint256 amount + uint256 amount, + uint256 multiplier ) external view returns (uint256) { - return _getKeysCountByBondAmount(nodeOperatorId, amount); + return _getKeysCountByBondAmount(amount, multiplier); } function getBondAmountByKeysCount( - uint256 nodeOperatorId, - uint256 keysCount + uint256 keys, + uint256 multiplier ) external view returns (uint256) { - return _getBondAmountByKeysCount(nodeOperatorId, keysCount); + return _getBondAmountByKeysCount(keys, multiplier); } } @@ -67,6 +67,17 @@ contract CSBondCurveTest is Test { bondCurve = new CSBondCurveTestable(_bondCurve); } + function test_setBondCurve() public { + uint256[] memory _bondCurve = new uint256[](11); + _bondCurve[0] = 16 ether; + _bondCurve[1] = 32 ether; + + bondCurve.setBondCurve(_bondCurve); + + assertEq(bondCurve.bondCurve(0), 16 ether); + assertEq(bondCurve.bondCurve(1), 32 ether); + } + function test_setBondCurve_RevertWhen_LessThanMinBondCurveLength() public { uint256[] memory _bondCurve = new uint256[](0); @@ -86,77 +97,120 @@ contract CSBondCurveTest is Test { bondCurve.setBondCurve(_bondCurve); } - function test_setBondMultiplier_RevertWhen_LessThanMin() public { - vm.expectRevert(CSBondCurve.InvalidMultiplier.selector); - - bondCurve.setBondMultiplier(0, 4999); - } - - function test_setBondMultiplier_RevertWhen_MoreThanMax() public { - vm.expectRevert(CSBondCurve.InvalidMultiplier.selector); - - bondCurve.setBondMultiplier(0, 10001); - } - function test_getKeysCountByBondAmount() public { assertEq(bondCurve.getKeysCountByBondAmount(0), 0); assertEq(bondCurve.getKeysCountByBondAmount(2 ether), 1); assertEq(bondCurve.getKeysCountByBondAmount(3 ether), 1); assertEq(bondCurve.getKeysCountByBondAmount(3.90 ether), 2); - assertEq(bondCurve.getKeysCountByBondAmount(5.70 ether), 3); - assertEq(bondCurve.getKeysCountByBondAmount(7.40 ether), 4); - assertEq(bondCurve.getKeysCountByBondAmount(9.00 ether), 5); - assertEq(bondCurve.getKeysCountByBondAmount(10.50 ether), 6); - assertEq(bondCurve.getKeysCountByBondAmount(11.90 ether), 7); - assertEq(bondCurve.getKeysCountByBondAmount(13.10 ether), 8); - assertEq(bondCurve.getKeysCountByBondAmount(14.30 ether), 9); - assertEq(bondCurve.getKeysCountByBondAmount(15.40 ether), 10); - assertEq(bondCurve.getKeysCountByBondAmount(16.40 ether), 11); + assertEq(bondCurve.getKeysCountByBondAmount(17 ether), 11); assertEq(bondCurve.getKeysCountByBondAmount(17.40 ether), 12); } - function test_getKeysCountByCurveValue_WithMultiplier() public { - assertEq(bondCurve.getKeysCountByBondAmount(0, 5000), 0); - assertEq(bondCurve.getKeysCountByBondAmount(2 ether, 5000), 2); - assertEq(bondCurve.getKeysCountByBondAmount(3 ether, 5000), 3); - assertEq(bondCurve.getKeysCountByBondAmount(3.90 ether, 5000), 4); - assertEq(bondCurve.getKeysCountByBondAmount(5.70 ether, 5000), 6); - assertEq(bondCurve.getKeysCountByBondAmount(7.40 ether, 5000), 9); - assertEq(bondCurve.getKeysCountByBondAmount(9.00 ether, 5000), 12); - assertEq(bondCurve.getKeysCountByBondAmount(10.50 ether, 5000), 15); - assertEq(bondCurve.getKeysCountByBondAmount(11.90 ether, 5000), 18); - assertEq(bondCurve.getKeysCountByBondAmount(13.10 ether, 5000), 20); - } - function test_getBondAmountByKeysCount() public { assertEq(bondCurve.getBondAmountByKeysCount(0), 0); assertEq(bondCurve.getBondAmountByKeysCount(1), 2 ether); assertEq(bondCurve.getBondAmountByKeysCount(2), 3.90 ether); - assertEq(bondCurve.getBondAmountByKeysCount(3), 5.70 ether); - assertEq(bondCurve.getBondAmountByKeysCount(4), 7.40 ether); - assertEq(bondCurve.getBondAmountByKeysCount(5), 9.00 ether); - assertEq(bondCurve.getBondAmountByKeysCount(6), 10.50 ether); - assertEq(bondCurve.getBondAmountByKeysCount(7), 11.90 ether); - assertEq(bondCurve.getBondAmountByKeysCount(8), 13.10 ether); - assertEq(bondCurve.getBondAmountByKeysCount(9), 14.30 ether); - assertEq(bondCurve.getBondAmountByKeysCount(10), 15.40 ether); assertEq(bondCurve.getBondAmountByKeysCount(11), 16.40 ether); assertEq(bondCurve.getBondAmountByKeysCount(12), 17.40 ether); } +} + +contract CSBondCurveWithMultiplierTest is Test { + CSBondCurveTestable public bondCurve; + + function setUp() public { + uint256[] memory simple = new uint256[](1); + simple[0] = 2 ether; + bondCurve = new CSBondCurveTestable(simple); + } + + function test_setBondMultiplier() public { + assertEq(bondCurve.getBondMultiplier(0), 10000); + + bondCurve.setBondMultiplier(0, 5000); + assertEq(bondCurve.getBondMultiplier(0), 5000); + + bondCurve.setBondMultiplier(0, 10000); + assertEq(bondCurve.getBondMultiplier(0), 10000); + } + + function test_setBondMultiplier_RevertWhen_LessThanMin() public { + vm.expectRevert(CSBondCurve.InvalidMultiplier.selector); + + bondCurve.setBondMultiplier(0, 4999); + } + + function test_setBondMultiplier_RevertWhen_MoreThanMax() public { + vm.expectRevert(CSBondCurve.InvalidMultiplier.selector); + + bondCurve.setBondMultiplier(0, 10001); + } - function test_getBondAmountByKeysCount_WithMultiplier() public { - assertEq(bondCurve.getBondAmountByKeysCount(0, 5000), 0); - assertEq(bondCurve.getBondAmountByKeysCount(1, 5000), 1 ether); - assertEq(bondCurve.getBondAmountByKeysCount(2, 5000), 1.95 ether); - assertEq(bondCurve.getBondAmountByKeysCount(3, 5000), 2.85 ether); - assertEq(bondCurve.getBondAmountByKeysCount(4, 5000), 3.70 ether); - assertEq(bondCurve.getBondAmountByKeysCount(5, 5000), 4.50 ether); - assertEq(bondCurve.getBondAmountByKeysCount(6, 5000), 5.25 ether); - assertEq(bondCurve.getBondAmountByKeysCount(7, 5000), 5.95 ether); - assertEq(bondCurve.getBondAmountByKeysCount(8, 5000), 6.55 ether); - assertEq(bondCurve.getBondAmountByKeysCount(9, 5000), 7.15 ether); - assertEq(bondCurve.getBondAmountByKeysCount(10, 5000), 7.7 ether); - assertEq(bondCurve.getBondAmountByKeysCount(11, 5000), 8.20 ether); - assertEq(bondCurve.getBondAmountByKeysCount(12, 5000), 8.70 ether); + function test_getKeysCountByCurveValue() public { + assertEq( + bondCurve.getKeysCountByBondAmount({ amount: 0, multiplier: 5000 }), + 0 + ); + assertEq( + bondCurve.getKeysCountByBondAmount({ + amount: 1 ether, + multiplier: 5000 + }), + 1 + ); + assertEq( + bondCurve.getKeysCountByBondAmount({ + amount: 2.99 ether, + multiplier: 5000 + }), + 2 + ); + + assertEq( + bondCurve.getKeysCountByBondAmount({ amount: 0, multiplier: 9000 }), + 0 + ); + assertEq( + bondCurve.getKeysCountByBondAmount({ + amount: 1.8 ether, + multiplier: 9000 + }), + 1 + ); + assertEq( + bondCurve.getKeysCountByBondAmount({ + amount: 5.39 ether, + multiplier: 9000 + }), + 2 + ); + } + + function test_getBondAmountByKeysCount() public { + assertEq( + bondCurve.getBondAmountByKeysCount({ keys: 0, multiplier: 5000 }), + 0 + ); + assertEq( + bondCurve.getBondAmountByKeysCount({ keys: 1, multiplier: 5000 }), + 1 ether + ); + assertEq( + bondCurve.getBondAmountByKeysCount({ keys: 2, multiplier: 5000 }), + 2 ether + ); + + assertEq( + bondCurve.getBondAmountByKeysCount({ keys: 0, multiplier: 9000 }), + 0 + ); + assertEq( + bondCurve.getBondAmountByKeysCount({ keys: 1, multiplier: 9000 }), + 1.8 ether + ); + assertEq( + bondCurve.getBondAmountByKeysCount({ keys: 2, multiplier: 9000 }), + 3.6 ether + ); } } diff --git a/test/CSModule.t.sol b/test/CSModule.t.sol index 56638204..206446c7 100644 --- a/test/CSModule.t.sol +++ b/test/CSModule.t.sol @@ -455,7 +455,7 @@ contract CSMObtainDepositData is CSMCommon { contract CsmProposeNodeOperatorManagerAddressChange is CSMCommon { function test_proposeNodeOperatorManagerAddressChange() public { uint256 noId = createNodeOperator(); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.managerAddress, nodeOperator); assertEq(no.rewardAddress, nodeOperator); @@ -507,7 +507,7 @@ contract CsmProposeNodeOperatorManagerAddressChange is CSMCommon { contract CsmConfirmNodeOperatorManagerAddressChange is CSMCommon { function test_confirmNodeOperatorManagerAddressChange() public { uint256 noId = createNodeOperator(); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.managerAddress, nodeOperator); assertEq(no.rewardAddress, nodeOperator); @@ -556,7 +556,7 @@ contract CsmConfirmNodeOperatorManagerAddressChange is CSMCommon { contract CsmProposeNodeOperatorRewardAddressChange is CSMCommon { function test_proposeNodeOperatorRewardAddressChange() public { uint256 noId = createNodeOperator(); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.managerAddress, nodeOperator); assertEq(no.rewardAddress, nodeOperator); @@ -608,7 +608,7 @@ contract CsmProposeNodeOperatorRewardAddressChange is CSMCommon { contract CsmConfirmNodeOperatorRewardAddressChange is CSMCommon { function test_confirmNodeOperatorRewardAddressChange() public { uint256 noId = createNodeOperator(); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.managerAddress, nodeOperator); assertEq(no.rewardAddress, nodeOperator); @@ -668,7 +668,7 @@ contract CsmResetNodeOperatorManagerAddress is CSMCommon { vm.prank(stranger); csm.resetNodeOperatorManagerAddress(noId); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.managerAddress, stranger); assertEq(no.rewardAddress, stranger); } @@ -707,7 +707,7 @@ contract CsmVetKeys is CSMCommon { emit VettedSigningKeysCountChanged(noId, 1); csm.vetKeys(noId, 1); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.totalVettedValidators, 1); BatchInfo[] memory exp = new BatchInfo[](1); @@ -730,7 +730,7 @@ contract CsmVetKeys is CSMCommon { emit VettedSigningKeysCountChanged(noId, 2); csm.vetKeys(noId, 2); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.totalVettedValidators, 2); BatchInfo[] memory exp = new BatchInfo[](2); From 7803f8f1897e77befbb0547b1d63c7e816f6a9eb Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Fri, 1 Dec 2023 09:12:39 +0400 Subject: [PATCH 06/18] feat: tests (check total bond shares) --- test/CSAccounting.t.sol | 51 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/test/CSAccounting.t.sol b/test/CSAccounting.t.sol index 4f9a4c22..301cc057 100644 --- a/test/CSAccounting.t.sol +++ b/test/CSAccounting.t.sol @@ -2074,8 +2074,6 @@ abstract contract CSAccountingClaimRewardsBaseTest is function test_WithDesirableValue() public virtual; function test_RevertWhen_NotOwner() public virtual; - - // todo: check total bond shares } contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { @@ -2107,6 +2105,7 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { bondSharesAfter, "bond manager after claim should be equal to before" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithCurve() public override { @@ -2139,6 +2138,7 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { bondSharesAfter, "bond manager after claim should be equal to after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithMultiplier() public override { @@ -2171,6 +2171,7 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { bondSharesAfter, "bond manager after claim should be equal to after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithBlocked() public override { @@ -2208,6 +2209,7 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { bondSharesAfter, "bond manager after claim should be equal to after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithCurveAndBlocked() public override { @@ -2253,6 +2255,7 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { bondSharesAfter, "bond manager after claim should be equal to before" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithBond() public override { @@ -2284,6 +2287,7 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { bondSharesAfter, "bond manager after claim should be equal to after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithBondAndOneWithdrawnValidator() public override { @@ -2316,6 +2320,7 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { bondSharesAfter, "bond manager after claim should be equal to before" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithExcessBond() public override { @@ -2348,6 +2353,7 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { bondSharesAfter, "bond manager after claim should be equal to after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithExcessBondAndOneWithdrawnValidator() public override { @@ -2381,6 +2387,7 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { bondSharesAfter, "bond manager after claim should be equal to after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithMissingBond() public override { @@ -2412,6 +2419,7 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { bondSharesAfter, "bond manager after claim should be equal to after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithMissingBondAndOneWithdrawnValidator() public override { @@ -2443,6 +2451,7 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { bondSharesAfter, "bond manager after claim should be equal to after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_EventEmitted() public override { @@ -2495,6 +2504,7 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { bondSharesAfter, "bond manager after should be equal to before and fee minus claimed shares" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_RevertWhen_NotOwner() public override { @@ -2549,6 +2559,7 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithCurve() public override { @@ -2587,6 +2598,7 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithMultiplier() public override { @@ -2625,6 +2637,7 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithBlocked() public override { @@ -2668,6 +2681,7 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithCurveAndBlocked() public override { @@ -2719,6 +2733,7 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithBond() public override { @@ -2757,6 +2772,7 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithBondAndOneWithdrawnValidator() public override { @@ -2796,6 +2812,7 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithExcessBond() public override { @@ -2835,6 +2852,7 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithExcessBondAndOneWithdrawnValidator() public override { @@ -2874,6 +2892,7 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithMissingBond() public override { @@ -2910,6 +2929,7 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithMissingBondAndOneWithdrawnValidator() public override { @@ -2946,6 +2966,7 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_EventEmitted() public override { @@ -3003,6 +3024,7 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after should be equal to before and fee minus claimed shares" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_RevertWhen_NotOwner() public override { @@ -3047,6 +3069,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is "shares of withdrawal queue should be equal to requested shares" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithCurve() public override { @@ -3077,6 +3100,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is "shares of withdrawal queue should be equal to requested shares" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithMultiplier() public override { @@ -3105,6 +3129,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is "shares of withdrawal queue should be equal to requested shares and excess" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithBlocked() public override { @@ -3140,6 +3165,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is "shares of withdrawal queue should be equal to requested shares and excess" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithCurveAndBlocked() public override { @@ -3184,6 +3210,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is "shares of withdrawal queue should be equal to requested shares and excess" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithBond() public override { @@ -3214,6 +3241,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is "shares of withdrawal queue should be equal to requested shares" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithBondAndOneWithdrawnValidator() public override { @@ -3246,6 +3274,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is "shares of withdrawal queue should be equal to requested shares and excess" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithExcessBond() public override { @@ -3278,6 +3307,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is "shares of withdrawal queue should be equal to requested shares and excess" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithExcessBondAndOneWithdrawnValidator() public override { @@ -3310,6 +3340,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is "shares of withdrawal queue should be equal to requested shares and excess" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithMissingBond() public override { @@ -3340,6 +3371,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is "shares of withdrawal queue should be equal to requested shares" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithMissingBondAndOneWithdrawnValidator() public override { @@ -3370,6 +3402,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is "shares of withdrawal queue should be equal to requested shares" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_EventEmitted() public override { @@ -3432,6 +3465,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is "shares of withdrawal queue should be equal to requested shares" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_RevertWhen_NotOwner() public override { @@ -3480,6 +3514,7 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { sharesToDeposit, "bond manager shares should be equal to deposited shares" ); + assertEq(accounting.totalBondShares(), sharesToDeposit); } function test_depositStETH() public { @@ -3510,6 +3545,7 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { sharesToDeposit, "bond manager shares should be equal to deposited shares" ); + assertEq(accounting.totalBondShares(), sharesToDeposit); } function test_depositWstETH() public { @@ -3543,6 +3579,7 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { sharesToDeposit, "bond manager shares should be equal to deposited shares" ); + assertEq(accounting.totalBondShares(), sharesToDeposit); } function test_depositStETHWithPermit() public { @@ -3587,6 +3624,7 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { sharesToDeposit, "bond manager shares should be equal to deposited shares" ); + assertEq(accounting.totalBondShares(), sharesToDeposit); } function test_depositStETHWithPermit_AlreadyPermitted() public { @@ -3676,6 +3714,7 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { sharesToDeposit, "bond manager shares should be equal to deposited shares" ); + assertEq(accounting.totalBondShares(), sharesToDeposit); } function test_depositWstETHWithPermit_AlreadyPermitted() public { @@ -3814,15 +3853,16 @@ contract CSAccountingPenalizeTest is CSAccountingBaseTest { uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(admin); accounting.penalize(0, 1 ether); + uint256 bondSharesAfter = accounting.getBondShares(0); assertEq( - accounting.getBondShares(0), + bondSharesAfter, bondSharesBefore - shares, "bond shares should be decreased by penalty" ); assertEq( stETH.sharesOf(address(accounting)), - bondSharesBefore - shares, + bondSharesAfter, "bond manager shares should be decreased by penalty" ); assertEq( @@ -3830,6 +3870,7 @@ contract CSAccountingPenalizeTest is CSAccountingBaseTest { shares, "burner shares should be equal to penalty" ); + assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_penalize_MoreThanDeposit() public { @@ -3860,6 +3901,7 @@ contract CSAccountingPenalizeTest is CSAccountingBaseTest { bondSharesBefore, "burner shares should be equal to bond shares" ); + assertEq(accounting.totalBondShares(), 0); } function test_penalize_EqualToDeposit() public { @@ -3886,6 +3928,7 @@ contract CSAccountingPenalizeTest is CSAccountingBaseTest { shares, "burner shares should be equal to penalty" ); + assertEq(accounting.totalBondShares(), 0); } function test_penalize_RevertWhenCallerHasNoRole() public { From ecfe2c8bb01fa2718b6dec957bd159c8efca4c86 Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Fri, 1 Dec 2023 10:30:08 +0400 Subject: [PATCH 07/18] refactor: `BlockedBond` -> abstract `BondLock` --- script/DeployBase.s.sol | 4 +- src/CSAccounting.sol | 173 +++--------------- src/CSBondLock.sol | 173 ++++++++++++++++++ ...ing.blockedBond.t.sol => CSBondLock.t.sol} | 134 +++++++------- 4 files changed, 272 insertions(+), 212 deletions(-) create mode 100644 src/CSBondLock.sol rename test/{CSAccounting.blockedBond.t.sol => CSBondLock.t.sol} (87%) diff --git a/script/DeployBase.s.sol b/script/DeployBase.s.sol index 37f703fe..ea80429e 100644 --- a/script/DeployBase.s.sol +++ b/script/DeployBase.s.sol @@ -84,8 +84,8 @@ abstract contract DeployBase is Script { communityStakingModule: address(csm), wstETH: address(wstETH), // todo: arguable. should be discussed - _blockedBondRetentionPeriod: 8 weeks, - _blockedBondManagementPeriod: 1 weeks + bondLockRetentionPeriod: 8 weeks, + bondLockManagementPeriod: 1 weeks }); CSFeeOracle oracleImpl = new CSFeeOracle({ diff --git a/src/CSAccounting.sol b/src/CSAccounting.sol index fd9922df..bde7e84d 100644 --- a/src/CSAccounting.sol +++ b/src/CSAccounting.sol @@ -6,6 +6,7 @@ pragma solidity 0.8.21; import { AccessControlEnumerable } from "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; import { CSBondCurve } from "./CSBondCurve.sol"; +import { CSBondLock } from "./CSBondLock.sol"; import { ILidoLocator } from "./interfaces/ILidoLocator.sol"; import { ICSModule } from "./interfaces/ICSModule.sol"; @@ -45,39 +46,25 @@ contract CSAccountingBase { address to, uint256 amount ); - event ELRewardsStealingPenaltyInitiated( - uint256 indexed nodeOperatorId, - uint256 proposedBlockNumber, - uint256 stolenAmount - ); - event BlockedBondChanged( - uint256 indexed nodeOperatorId, - uint256 newAmountETH, - uint256 retentionUntil - ); - event BlockedBondCompensated( - uint256 indexed nodeOperatorId, - uint256 amountETH - ); - event BlockedBondReleased( - uint256 indexed nodeOperatorId, - uint256 amountETH - ); event BondPenalized( uint256 indexed nodeOperatorId, uint256 penaltyETH, uint256 coveringETH ); + event ELRewardsStealingPenaltyInitiated( + uint256 indexed nodeOperatorId, + uint256 proposedBlockNumber, + uint256 stolenAmount + ); error NotOwnerToClaim(address msgSender, address owner); - error InvalidBlockedBondRetentionPeriod(); - error InvalidStolenAmount(); error InvalidSender(); } contract CSAccounting is CSAccountingBase, CSBondCurve, + CSBondLock, AccessControlEnumerable { struct PermitInput { @@ -87,10 +74,6 @@ contract CSAccounting is bytes32 r; bytes32 s; } - struct BlockedBond { - uint256 ETHAmount; - uint256 retentionUntil; - } bytes32 public constant INSTANT_PENALIZE_BOND_ROLE = keccak256("INSTANT_PENALIZE_BOND_ROLE"); // 0x9909cf24c2d3bafa8c229558d86a1b726ba57c3ef6350848dcf434a4181b56c7 @@ -103,12 +86,6 @@ contract CSAccounting is bytes32 public constant SET_BOND_MULTIPLIER_ROLE = keccak256("SET_BOND_MULTIPLIER_ROLE"); // 0x62131145aee19b18b85aa8ead52ba87f0efb6e61e249155edc68a2c24e8f79b5 - // todo: should be reconsidered - uint256 public constant MIN_BLOCKED_BOND_RETENTION_PERIOD = 4 weeks; - uint256 public constant MAX_BLOCKED_BOND_RETENTION_PERIOD = 365 days; - uint256 public constant MIN_BLOCKED_BOND_MANAGEMENT_PERIOD = 1 days; - uint256 public constant MAX_BLOCKED_BOND_MANAGEMENT_PERIOD = 7 days; - ILidoLocator private immutable LIDO_LOCATOR; ICSModule private immutable CSM; IWstETH private immutable WSTETH; @@ -116,28 +93,27 @@ contract CSAccounting is address public FEE_DISTRIBUTOR; uint256 public totalBondShares; - uint256 public blockedBondRetentionPeriod; - uint256 public blockedBondManagementPeriod; - mapping(uint256 => uint256) internal _bondShares; - mapping(uint256 => BlockedBond) internal _blockedBondEther; /// @param bondCurve initial bond curve /// @param admin admin role member address /// @param lidoLocator lido locator contract address /// @param wstETH wstETH contract address /// @param communityStakingModule community staking module contract address - /// @param _blockedBondRetentionPeriod retention period for blocked bond in seconds - /// @param _blockedBondManagementPeriod management period for blocked bond in seconds + /// @param bondLockRetentionPeriod retention period for blocked bond in seconds + /// @param bondLockManagementPeriod management period for blocked bond in seconds constructor( uint256[] memory bondCurve, address admin, address lidoLocator, address wstETH, address communityStakingModule, - uint256 _blockedBondRetentionPeriod, - uint256 _blockedBondManagementPeriod - ) CSBondCurve(bondCurve) { + uint256 bondLockRetentionPeriod, + uint256 bondLockManagementPeriod + ) + CSBondCurve(bondCurve) + CSBondLock(bondLockRetentionPeriod, bondLockManagementPeriod) + { // check zero addresses require(admin != address(0), "admin is zero address"); require(lidoLocator != address(0), "lido locator is zero address"); @@ -146,18 +122,11 @@ contract CSAccounting is "community staking module is zero address" ); require(wstETH != address(0), "wstETH is zero address"); - _validateBlockedBondPeriods( - _blockedBondRetentionPeriod, - _blockedBondManagementPeriod - ); _setupRole(DEFAULT_ADMIN_ROLE, admin); LIDO_LOCATOR = ILidoLocator(lidoLocator); CSM = ICSModule(communityStakingModule); WSTETH = IWstETH(wstETH); - - blockedBondRetentionPeriod = _blockedBondRetentionPeriod; - blockedBondManagementPeriod = _blockedBondManagementPeriod; } function setFeeDistributor( @@ -170,9 +139,7 @@ contract CSAccounting is uint256 retention, uint256 management ) external onlyRole(DEFAULT_ADMIN_ROLE) { - _validateBlockedBondPeriods(retention, management); - blockedBondRetentionPeriod = retention; - blockedBondManagementPeriod = management; + _setBondLockPeriods(retention, management); } function setBondCurve( @@ -189,20 +156,6 @@ contract CSAccounting is _setBondMultiplier(nodeOperatorId, basisPoints); } - function _validateBlockedBondPeriods( - uint256 retention, - uint256 management - ) internal pure { - if ( - retention < MIN_BLOCKED_BOND_RETENTION_PERIOD || - retention > MAX_BLOCKED_BOND_RETENTION_PERIOD || - management < MIN_BLOCKED_BOND_MANAGEMENT_PERIOD || - management > MAX_BLOCKED_BOND_MANAGEMENT_PERIOD - ) { - revert InvalidBlockedBondRetentionPeriod(); - } - } - /// @notice Returns the bond shares for the given node operator. /// @param nodeOperatorId id of the node operator to get bond for. /// @return bond shares. @@ -326,12 +279,7 @@ contract CSAccounting is function getBlockedBondETH( uint256 nodeOperatorId ) public view returns (uint256) { - if ( - _blockedBondEther[nodeOperatorId].retentionUntil >= block.timestamp - ) { - return _blockedBondEther[nodeOperatorId].ETHAmount; - } - return 0; + return CSBondLock._get(nodeOperatorId); } /// @notice Returns the required bond ETH (inc. missed and excess) for the given node operator to upload new keys. @@ -734,19 +682,12 @@ contract CSAccounting is onlyRole(EL_REWARDS_STEALING_PENALTY_INIT_ROLE) onlyExistingNodeOperator(nodeOperatorId) { - if (amount == 0) { - revert InvalidStolenAmount(); - } emit ELRewardsStealingPenaltyInitiated( nodeOperatorId, blockNumber, amount ); - _changeBlockedBondState({ - nodeOperatorId: nodeOperatorId, - ETHAmount: _blockedBondEther[nodeOperatorId].ETHAmount + amount, - retentionUntil: block.timestamp + blockedBondRetentionPeriod - }); + CSBondLock._lock(nodeOperatorId, amount); } /// @notice Releases blocked bond ETH for the given node operator. @@ -760,8 +701,7 @@ contract CSAccounting is onlyRole(EL_REWARDS_STEALING_PENALTY_INIT_ROLE) onlyExistingNodeOperator(nodeOperatorId) { - emit BlockedBondReleased(nodeOperatorId, amount); - _reduceBlockedBondETH(nodeOperatorId, amount); + CSBondLock._release(nodeOperatorId, amount); } /// @notice Compensates blocked bond ETH for the given node operator. @@ -769,27 +709,8 @@ contract CSAccounting is function compensateBlockedBondETH( uint256 nodeOperatorId ) external payable onlyExistingNodeOperator(nodeOperatorId) { - require(msg.value > 0, "value should be greater than zero"); + CSBondLock._compensate(nodeOperatorId, msg.value); payable(LIDO_LOCATOR.elRewardsVault()).transfer(msg.value); - emit BlockedBondCompensated(nodeOperatorId, msg.value); - _reduceBlockedBondETH(nodeOperatorId, msg.value); - } - - function _reduceBlockedBondETH( - uint256 nodeOperatorId, - uint256 amount - ) internal { - uint256 blocked = getBlockedBondETH(nodeOperatorId); - require(blocked > 0, "no blocked bond to release"); - require( - _blockedBondEther[nodeOperatorId].ETHAmount >= amount, - "blocked bond is less than amount to release" - ); - _changeBlockedBondState( - nodeOperatorId, - _blockedBondEther[nodeOperatorId].ETHAmount - amount, - _blockedBondEther[nodeOperatorId].retentionUntil - ); } /// @dev Should be called by the committee. Doesn't settle blocked bond if it is in the safe frame (1 day) @@ -798,48 +719,7 @@ contract CSAccounting is function settleBlockedBondETH( uint256[] memory nodeOperatorIds ) external onlyRole(EL_REWARDS_STEALING_PENALTY_SETTLE_ROLE) { - for (uint256 i; i < nodeOperatorIds.length; ++i) { - uint256 nodeOperatorId = nodeOperatorIds[i]; - BlockedBond storage blockedBond = _blockedBondEther[nodeOperatorId]; - if ( - block.timestamp + - blockedBondRetentionPeriod - - blockedBond.retentionUntil < - blockedBondManagementPeriod - ) { - // blocked bond in safe frame to manage it by committee or node operator - continue; - } - uint256 uncovered; - if ( - blockedBond.ETHAmount > 0 && - blockedBond.retentionUntil >= block.timestamp - ) { - uncovered = _penalize(nodeOperatorId, blockedBond.ETHAmount); - } - _changeBlockedBondState({ - nodeOperatorId: nodeOperatorId, - ETHAmount: uncovered, - retentionUntil: blockedBond.retentionUntil - }); - } - } - - function _changeBlockedBondState( - uint256 nodeOperatorId, - uint256 ETHAmount, - uint256 retentionUntil - ) internal { - if (ETHAmount == 0) { - delete _blockedBondEther[nodeOperatorId]; - emit BlockedBondChanged(nodeOperatorId, 0, 0); - return; - } - _blockedBondEther[nodeOperatorId] = BlockedBond({ - ETHAmount: ETHAmount, - retentionUntil: retentionUntil - }); - emit BlockedBondChanged(nodeOperatorId, ETHAmount, retentionUntil); + CSBondLock._settle(nodeOperatorIds); } /// @notice Penalize bond by burning shares of the given node operator. @@ -852,9 +732,14 @@ contract CSAccounting is function _penalize( uint256 nodeOperatorId, - uint256 ETHAmount - ) internal onlyExistingNodeOperator(nodeOperatorId) returns (uint256) { - uint256 penaltyShares = _sharesByEth(ETHAmount); + uint256 amount + ) + internal + override + onlyExistingNodeOperator(nodeOperatorId) + returns (uint256) + { + uint256 penaltyShares = _sharesByEth(amount); uint256 currentShares = getBondShares(nodeOperatorId); uint256 sharesToBurn = penaltyShares < currentShares ? penaltyShares diff --git a/src/CSBondLock.sol b/src/CSBondLock.sol new file mode 100644 index 00000000..42deac65 --- /dev/null +++ b/src/CSBondLock.sol @@ -0,0 +1,173 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.21; + +abstract contract CSBondLockBase { + event BondLockChanged( + uint256 indexed nodeOperatorId, + uint256 newAmount, + uint256 retentionUntil + ); + event BondLockCompensated(uint256 indexed nodeOperatorId, uint256 amount); + event BondLockReleased(uint256 indexed nodeOperatorId, uint256 amount); + + error InvalidBondLockRetentionPeriod(); + error InvalidBondLockAmount(); +} + +abstract contract CSBondLock is CSBondLockBase { + struct BondLock { + uint256 amount; + uint256 retentionUntil; + } + + // todo: should be reconsidered + uint256 public constant MIN_BOND_LOCK_RETENTION_PERIOD = 4 weeks; + uint256 public constant MAX_BOND_LOCK_RETENTION_PERIOD = 365 days; + uint256 public constant MIN_BOND_LOCK_MANAGEMENT_PERIOD = 1 days; + uint256 public constant MAX_BOND_LOCK_MANAGEMENT_PERIOD = 7 days; + + uint256 public bondLockRetentionPeriod; + uint256 public bondLockManagementPeriod; + + mapping(uint256 => BondLock) internal _bondLock; + + constructor(uint256 retentionPeriod, uint256 managementPeriod) { + _setBondLockPeriods(retentionPeriod, managementPeriod); + } + + function _setBondLockPeriods( + uint256 retention, + uint256 management + ) internal { + _validateBondLockPeriods(retention, management); + bondLockRetentionPeriod = retention; + bondLockManagementPeriod = management; + } + + function _validateBondLockPeriods( + uint256 retention, + uint256 management + ) internal pure { + if ( + retention < MIN_BOND_LOCK_RETENTION_PERIOD || + retention > MAX_BOND_LOCK_RETENTION_PERIOD || + management < MIN_BOND_LOCK_MANAGEMENT_PERIOD || + management > MAX_BOND_LOCK_MANAGEMENT_PERIOD + ) { + revert InvalidBondLockRetentionPeriod(); + } + } + + /// @notice Returns the amount of locked bond by the given node operator. + function _get(uint256 nodeOperatorId) internal view returns (uint256) { + if (_bondLock[nodeOperatorId].retentionUntil >= block.timestamp) { + return _bondLock[nodeOperatorId].amount; + } + return 0; + } + + /// @notice Reports EL rewards stealing for the given node operator. + /// @param nodeOperatorId id of the node operator to lock bond for. + /// @param amount amount to lock. + function _lock(uint256 nodeOperatorId, uint256 amount) internal { + if (amount == 0) { + revert InvalidBondLockAmount(); + } + _changeBondLock({ + nodeOperatorId: nodeOperatorId, + amount: _bondLock[nodeOperatorId].amount + amount, + retentionUntil: block.timestamp + bondLockRetentionPeriod + }); + } + + /// @dev Should be called by the committee. Doesn't settle blocked bond if it is in the safe frame (1 day) + /// @notice Settles blocked bond for the given node operators. + /// @param nodeOperatorIds ids of the node operators to settle blocked bond for. + function _settle(uint256[] memory nodeOperatorIds) internal { + for (uint256 i; i < nodeOperatorIds.length; ++i) { + uint256 nodeOperatorId = nodeOperatorIds[i]; + BondLock storage bondLock = _bondLock[nodeOperatorId]; + if ( + block.timestamp + + bondLockRetentionPeriod - + bondLock.retentionUntil < + bondLockManagementPeriod + ) { + // blocked bond in safe frame to manage it by committee or node operator + continue; + } + uint256 uncovered; + if ( + bondLock.amount > 0 && + bondLock.retentionUntil >= block.timestamp + ) { + uncovered = _penalize(nodeOperatorId, bondLock.amount); + } + _changeBondLock({ + nodeOperatorId: nodeOperatorId, + amount: uncovered, + retentionUntil: bondLock.retentionUntil + }); + } + } + + /// @notice Releases blocked bond ETH for the given node operator. + /// @param nodeOperatorId id of the node operator to release blocked bond for. + /// @param amount amount of ETH to release. + function _release(uint256 nodeOperatorId, uint256 amount) internal { + if (amount == 0) { + revert InvalidBondLockAmount(); + } + emit BondLockReleased(nodeOperatorId, amount); + _reduceAmount(nodeOperatorId, amount); + } + + /// @notice Compensates blocked bond ETH for the given node operator. + /// @param nodeOperatorId id of the node operator to compensate blocked bond for. + function _compensate(uint256 nodeOperatorId, uint256 amount) internal { + if (amount == 0) { + revert InvalidBondLockAmount(); + } + emit BondLockCompensated(nodeOperatorId, amount); + _reduceAmount(nodeOperatorId, amount); + } + + function _reduceAmount(uint256 nodeOperatorId, uint256 amount) internal { + uint256 blocked = _get(nodeOperatorId); + if (amount == 0) { + revert InvalidBondLockAmount(); + } + if (blocked < amount) { + revert InvalidBondLockAmount(); + } + _changeBondLock( + nodeOperatorId, + _bondLock[nodeOperatorId].amount - amount, + _bondLock[nodeOperatorId].retentionUntil + ); + } + + function _changeBondLock( + uint256 nodeOperatorId, + uint256 amount, + uint256 retentionUntil + ) internal { + if (amount == 0) { + delete _bondLock[nodeOperatorId]; + emit BondLockChanged(nodeOperatorId, 0, 0); + return; + } + _bondLock[nodeOperatorId] = BondLock({ + amount: amount, + retentionUntil: retentionUntil + }); + emit BondLockChanged(nodeOperatorId, amount, retentionUntil); + } + + function _penalize( + uint256 nodeOperatorId, + uint256 amount + ) internal virtual returns (uint256); +} diff --git a/test/CSAccounting.blockedBond.t.sol b/test/CSBondLock.t.sol similarity index 87% rename from test/CSAccounting.blockedBond.t.sol rename to test/CSBondLock.t.sol index 47249e7e..d4d4b521 100644 --- a/test/CSAccounting.blockedBond.t.sol +++ b/test/CSBondLock.t.sol @@ -6,6 +6,7 @@ pragma solidity 0.8.21; import "forge-std/Test.sol"; import { CSAccountingBase, CSAccounting } from "../src/CSAccounting.sol"; +import { CSBondLockBase, CSBondLock } from "../src/CSBondLock.sol"; import { PermitTokenBase } from "./helpers/Permit.sol"; import { Stub } from "./helpers/mocks/Stub.sol"; import { LidoMock } from "./helpers/mocks/LidoMock.sol"; @@ -48,15 +49,15 @@ contract CSAccounting_revealed is CSAccounting { function _blockedBondEther_get_value( uint256 nodeOperatorId - ) public view returns (BlockedBond memory) { - return _blockedBondEther[nodeOperatorId]; + ) public view returns (BondLock memory) { + return _bondLock[nodeOperatorId]; } function _blockedBondEther_set_value( uint256 nodeOperatorId, - BlockedBond memory value + BondLock memory value ) public { - _blockedBondEther[nodeOperatorId] = value; + _bondLock[nodeOperatorId] = value; } function _changeBlockedBondState_revealed( @@ -64,14 +65,14 @@ contract CSAccounting_revealed is CSAccounting { uint256 ETHAmount, uint256 retentionUntil ) public { - _changeBlockedBondState(nodeOperatorId, ETHAmount, retentionUntil); + _changeBondLock(nodeOperatorId, ETHAmount, retentionUntil); } function _reduceBlockedBondETH_revealed( uint256 nodeOperatorId, uint256 ETHAmount ) public { - _reduceBlockedBondETH(nodeOperatorId, ETHAmount); + _reduceAmount(nodeOperatorId, ETHAmount); } } @@ -79,6 +80,7 @@ contract CSAccounting_BlockedBondTest is Test, Fixtures, Utilities, + CSBondLockBase, CSAccountingBase { using stdStorage for StdStorage; @@ -143,8 +145,8 @@ contract CSAccounting_BlockedBondTest is accounting._blockedBondEther_set_value( noId, - CSAccounting.BlockedBond({ - ETHAmount: amount, + CSBondLock.BondLock({ + amount: amount, retentionUntil: retentionUntil }) ); @@ -167,8 +169,8 @@ contract CSAccounting_BlockedBondTest is accounting._blockedBondEther_set_value( noId, - CSAccounting.BlockedBond({ - ETHAmount: amount, + CSBondLock.BondLock({ + amount: amount, retentionUntil: retentionUntil }) ); @@ -195,8 +197,8 @@ contract CSAccounting_BlockedBondTest is accounting._blockedBondEther_set_value( noId, - CSAccounting.BlockedBond({ - ETHAmount: amount, + CSBondLock.BondLock({ + amount: amount, retentionUntil: retentionUntil }) ); @@ -223,8 +225,8 @@ contract CSAccounting_BlockedBondTest is accounting._blockedBondEther_set_value( noId, - CSAccounting.BlockedBond({ - ETHAmount: amount, + CSBondLock.BondLock({ + amount: amount, retentionUntil: retentionUntil }) ); @@ -237,8 +239,8 @@ contract CSAccounting_BlockedBondTest is accounting._blockedBondEther_set_value( noId, - CSAccounting.BlockedBond({ - ETHAmount: 1 ether, + CSBondLock.BondLock({ + amount: 1 ether, retentionUntil: retentionUntil }) ); @@ -265,8 +267,8 @@ contract CSAccounting_BlockedBondTest is accounting._blockedBondEther_set_value( noId, - CSAccounting.BlockedBond({ - ETHAmount: amount, + CSBondLock.BondLock({ + amount: amount, retentionUntil: retentionUntil }) ); @@ -279,8 +281,8 @@ contract CSAccounting_BlockedBondTest is accounting._blockedBondEther_set_value( noId, - CSAccounting.BlockedBond({ - ETHAmount: 1 ether, + CSBondLock.BondLock({ + amount: 1 ether, retentionUntil: retentionUntil }) ); @@ -306,8 +308,8 @@ contract CSAccounting_BlockedBondTest is accounting._bondShares_set_value(0, 100 ether); accounting._blockedBondEther_set_value( noId, - CSAccounting.BlockedBond({ - ETHAmount: amount, + CSBondLock.BondLock({ + amount: amount, retentionUntil: retentionUntil }) ); @@ -325,21 +327,21 @@ contract CSAccounting_BlockedBondTest is uint256 retentionUntil = block.timestamp + 1 weeks; vm.expectEmit(true, true, true, true, address(accounting)); - emit BlockedBondChanged(noId, amount, retentionUntil); + emit BondLockChanged(noId, amount, retentionUntil); accounting._changeBlockedBondState_revealed({ nodeOperatorId: noId, ETHAmount: amount, retentionUntil: retentionUntil }); - CSAccounting.BlockedBond memory value = accounting + CSBondLock.BondLock memory value = accounting ._blockedBondEther_get_value(noId); - assertEq(value.ETHAmount, amount); + assertEq(value.amount, amount); assertEq(value.retentionUntil, retentionUntil); vm.expectEmit(true, true, true, true, address(accounting)); - emit BlockedBondChanged(noId, 0, 0); + emit BondLockChanged(noId, 0, 0); accounting._changeBlockedBondState_revealed({ nodeOperatorId: noId, @@ -349,7 +351,7 @@ contract CSAccounting_BlockedBondTest is value = accounting._blockedBondEther_get_value(noId); - assertEq(value.ETHAmount, 0); + assertEq(value.amount, 0); assertEq(value.retentionUntil, 0); } @@ -367,7 +369,7 @@ contract CSAccounting_BlockedBondTest is firstStolenAmount ); vm.expectEmit(true, true, true, true, address(accounting)); - emit BlockedBondChanged( + emit BondLockChanged( noId, firstStolenAmount, block.timestamp + 8 weeks @@ -381,7 +383,7 @@ contract CSAccounting_BlockedBondTest is }); assertEq( - accounting._blockedBondEther_get_value(noId).ETHAmount, + accounting._blockedBondEther_get_value(noId).amount, firstStolenAmount ); assertEq( @@ -402,7 +404,7 @@ contract CSAccounting_BlockedBondTest is secondStolenAmount ); vm.expectEmit(true, true, true, true, address(accounting)); - emit BlockedBondChanged( + emit BondLockChanged( noId, firstStolenAmount + secondStolenAmount, block.timestamp + 8 weeks @@ -416,7 +418,7 @@ contract CSAccounting_BlockedBondTest is }); assertEq( - accounting._blockedBondEther_get_value(noId).ETHAmount, + accounting._blockedBondEther_get_value(noId).amount, firstStolenAmount + secondStolenAmount ); assertEq( @@ -441,7 +443,7 @@ contract CSAccounting_BlockedBondTest is function test_initELRewardsStealingPenalty_revertWhenZero() public { _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); - vm.expectRevert(InvalidStolenAmount.selector); + vm.expectRevert(InvalidBondLockAmount.selector); vm.prank(admin); accounting.initELRewardsStealingPenalty({ @@ -489,8 +491,8 @@ contract CSAccounting_BlockedBondTest is accounting._blockedBondEther_set_value( 0, - CSAccounting.BlockedBond({ - ETHAmount: 1 ether, + CSBondLock.BondLock({ + amount: 1 ether, retentionUntil: retentionUntil }) ); @@ -501,10 +503,10 @@ contract CSAccounting_BlockedBondTest is vm.prank(admin); accounting.settleBlockedBondETH(nosToPenalize); - CSAccounting.BlockedBond memory value = accounting + CSBondLock.BondLock memory value = accounting ._blockedBondEther_get_value(0); - assertEq(value.ETHAmount, 1 ether); + assertEq(value.amount, 1 ether); assertEq(value.retentionUntil, retentionUntil); // penalty amount is less than the bond @@ -519,20 +521,20 @@ contract CSAccounting_BlockedBondTest is emit BondPenalized(0, penalty, covering); vm.expectEmit(true, true, true, true, address(accounting)); - emit BlockedBondChanged(0, 0, 0); + emit BondLockChanged(0, 0, 0); vm.prank(admin); accounting.settleBlockedBondETH(nosToPenalize); value = accounting._blockedBondEther_get_value(0); - assertEq(value.ETHAmount, 0); + assertEq(value.amount, 0); assertEq(value.retentionUntil, 0); // penalty amount is greater than the bond accounting._blockedBondEther_set_value( 0, - CSAccounting.BlockedBond({ - ETHAmount: 100 ether, + CSBondLock.BondLock({ + amount: 100 ether, retentionUntil: retentionUntil }) ); @@ -547,33 +549,33 @@ contract CSAccounting_BlockedBondTest is emit BondPenalized(0, penalty, covering); vm.expectEmit(true, true, true, true, address(accounting)); - emit BlockedBondChanged(0, uncovered, retentionUntil); + emit BondLockChanged(0, uncovered, retentionUntil); vm.prank(admin); accounting.settleBlockedBondETH(nosToPenalize); value = accounting._blockedBondEther_get_value(0); - assertEq(value.ETHAmount, uncovered); + assertEq(value.amount, uncovered); assertEq(value.retentionUntil, retentionUntil); // retention period expired accounting._blockedBondEther_set_value( 0, - CSAccounting.BlockedBond({ - ETHAmount: 100 ether, + CSBondLock.BondLock({ + amount: 100 ether, retentionUntil: retentionUntil }) ); vm.warp(retentionUntil + 12); vm.expectEmit(true, true, true, true, address(accounting)); - emit BlockedBondChanged(0, 0, 0); + emit BondLockChanged(0, 0, 0); vm.prank(admin); accounting.settleBlockedBondETH(nosToPenalize); value = accounting._blockedBondEther_get_value(0); - assertEq(value.ETHAmount, 0); + assertEq(value.amount, 0); assertEq(value.retentionUntil, 0); } @@ -584,8 +586,8 @@ contract CSAccounting_BlockedBondTest is accounting._blockedBondEther_set_value( noId, - CSAccounting.BlockedBond({ - ETHAmount: amount, + CSBondLock.BondLock({ + amount: amount, retentionUntil: retentionUntil }) ); @@ -595,14 +597,14 @@ contract CSAccounting_BlockedBondTest is uint256 rest = amount - toReduce; vm.expectEmit(true, true, true, true, address(accounting)); - emit BlockedBondChanged(noId, rest, retentionUntil); + emit BondLockChanged(noId, rest, retentionUntil); accounting._reduceBlockedBondETH_revealed(noId, toReduce); - CSAccounting.BlockedBond memory value = accounting + CSBondLock.BondLock memory value = accounting ._blockedBondEther_get_value(noId); - assertEq(value.ETHAmount, rest); + assertEq(value.amount, rest); assertEq(value.retentionUntil, retentionUntil); // all blocked bond is released @@ -611,18 +613,18 @@ contract CSAccounting_BlockedBondTest is retentionUntil = 0; vm.expectEmit(true, true, true, true, address(accounting)); - emit BlockedBondChanged(noId, rest, retentionUntil); + emit BondLockChanged(noId, rest, retentionUntil); accounting._reduceBlockedBondETH_revealed(noId, toReduce); value = accounting._blockedBondEther_get_value(noId); - assertEq(value.ETHAmount, rest); + assertEq(value.amount, rest); assertEq(value.retentionUntil, retentionUntil); } function test_private_reduceBlockedBondETH_revertWhenNoBlocked() public { - vm.expectRevert("no blocked bond to release"); + vm.expectRevert(InvalidBondLockAmount.selector); accounting._reduceBlockedBondETH_revealed(0, 1 ether); } @@ -635,13 +637,13 @@ contract CSAccounting_BlockedBondTest is accounting._blockedBondEther_set_value( noId, - CSAccounting.BlockedBond({ - ETHAmount: amount, + CSBondLock.BondLock({ + amount: amount, retentionUntil: retentionUntil }) ); - vm.expectRevert("blocked bond is less than amount to release"); + vm.expectRevert(InvalidBondLockAmount.selector); accounting._reduceBlockedBondETH_revealed(0, 101 ether); } @@ -654,8 +656,8 @@ contract CSAccounting_BlockedBondTest is accounting._blockedBondEther_set_value( noId, - CSAccounting.BlockedBond({ - ETHAmount: amount, + CSBondLock.BondLock({ + amount: amount, retentionUntil: retentionUntil }) ); @@ -664,9 +666,9 @@ contract CSAccounting_BlockedBondTest is uint256 rest = amount - toRelease; vm.expectEmit(true, true, true, true, address(accounting)); - emit BlockedBondReleased(noId, toRelease); + emit BondLockReleased(noId, toRelease); vm.expectEmit(true, true, true, true, address(accounting)); - emit BlockedBondChanged(noId, rest, retentionUntil); + emit BondLockChanged(noId, rest, retentionUntil); vm.prank(admin); accounting.releaseBlockedBondETH(noId, toRelease); @@ -704,8 +706,8 @@ contract CSAccounting_BlockedBondTest is accounting._blockedBondEther_set_value( noId, - CSAccounting.BlockedBond({ - ETHAmount: amount, + CSBondLock.BondLock({ + amount: amount, retentionUntil: retentionUntil }) ); @@ -714,9 +716,9 @@ contract CSAccounting_BlockedBondTest is uint256 rest = amount - toCompensate; vm.expectEmit(true, true, true, true, address(accounting)); - emit BlockedBondCompensated(noId, toCompensate); + emit BondLockCompensated(noId, toCompensate); vm.expectEmit(true, true, true, true, address(accounting)); - emit BlockedBondChanged(noId, rest, retentionUntil); + emit BondLockChanged(noId, rest, retentionUntil); vm.deal(user, toCompensate); vm.prank(user); @@ -728,7 +730,7 @@ contract CSAccounting_BlockedBondTest is function test_compensateBlockedBondETH_revertWhenZero() public { _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); - vm.expectRevert("value should be greater than zero"); + vm.expectRevert(InvalidBondLockAmount.selector); accounting.compensateBlockedBondETH{ value: 0 }(0); } From 0b59cc1b9168a9cb606aba4c262601c34ef1b568 Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Fri, 1 Dec 2023 12:01:14 +0400 Subject: [PATCH 08/18] chore: add comments --- src/CSBondCurve.sol | 72 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/src/CSBondCurve.sol b/src/CSBondCurve.sol index 16f3688c..8b478112 100644 --- a/src/CSBondCurve.sol +++ b/src/CSBondCurve.sol @@ -7,12 +7,67 @@ abstract contract CSBondCurve { error InvalidBondCurveLength(); error InvalidMultiplier(); - // @dev Keys count to bond amount mapping - // i | array index - // i + 1 | keys count for particular bond amount - // bondCurve[i] | bond amount for `i + 1` keys count + /// @dev Array of bond amounts for particular keys count. + /// + /// For example: + /// Array Index |> 0 1 2 i + /// Bond Amount |> [ 2 ETH ] [ 3.9 ETH ] [ 5.7 ETH ] [ ... ] + /// Keys Count |> 1 2 3 i + 1 + /// + /// Bond Amount (ETH) + /// ^ + /// | + /// 6 - + /// | ------------------- 5.9 ETH -->.. + /// 5.5 - . ^ + /// | . | + /// 5 - . | + /// | . | + /// 4.5 - . | + /// | . | + /// 4 - .. | + /// | -------- 3.9 ETH -->.. | + /// 3.5 - .^ | + /// | .. | | + /// 3 - .. | | + /// | . | | + /// 2.5 - . | | + /// | .. | | + /// 2 - -------->.. | | + /// | ^ | | + /// |----------|----------|----------|----------|----> Keys Count + /// | 1 2 3 i + /// uint256[] public bondCurve; + /// @dev This mapping contains bond multiplier points (in basis points) for Node Operator's bond. + /// By default, all Node Operators have x1 multiplier (10000 basis points). + /// + /// For example: + /// Some Node Operator's bond multiplier is x0.90 (9500 basis points). + /// Bond Curve for this Node Operator will be: + /// + /// Bond Amount (ETH) + /// ^ + /// | + /// 4 - + /// | ------------------- 3.6 ETH -->. + /// 3.5 - .. ^ + /// | .. | + /// 3 - .. | + /// | -------- 2.7 ETH -->... | + /// 2.5 - .. | | + /// | .. | | + /// 2 - .. | | + /// | 1.8 ETH->... | | + /// 1.5 - ^ | | + /// | | | | + /// 1 - | | | + /// |----------|----------|----------|----------|----> Keys Count + /// | 1 2 3 i + /// + mapping(uint256 => uint256) public bondMultiplierBP; + // todo: might be redefined in the future uint256 internal constant MAX_CURVE_LENGTH = 20; uint256 internal constant MIN_CURVE_LENGTH = 1; @@ -21,10 +76,6 @@ abstract contract CSBondCurve { uint256 internal constant MAX_BOND_MULTIPLIER = BASIS_POINTS; // x1 uint256 internal constant MIN_BOND_MULTIPLIER = MAX_BOND_MULTIPLIER / 2; // x0.5 - /// This mapping contains bond multiplier points (in basis points) for Node Operator's bond. - /// By default, all Node Operators have x1 multiplier (10000 basis points). - mapping(uint256 => uint256) internal _bondMultiplierBP; - uint256 internal _bondCurveTrend; constructor(uint256[] memory _bondCurve) { @@ -37,6 +88,7 @@ abstract contract CSBondCurve { bondCurve = _bondCurve; _bondCurveTrend = _bondCurve[_bondCurve.length - 1] - + // if curve length is 1, then to calculate trend we use 0 as previous value (_bondCurve.length > 1 ? _bondCurve[_bondCurve.length - 2] : 0); } @@ -46,7 +98,7 @@ abstract contract CSBondCurve { ) internal { _checkMultiplier(basisPoints); // todo: check curve values (not worse than previous) - _bondMultiplierBP[nodeOperatorId] = basisPoints; + bondMultiplierBP[nodeOperatorId] = basisPoints; } /// @notice Returns basis points of the bond multiplier for the given node operator. @@ -54,7 +106,7 @@ abstract contract CSBondCurve { function getBondMultiplier( uint256 nodeOperatorId ) public view returns (uint256) { - uint256 basisPoints = _bondMultiplierBP[nodeOperatorId]; + uint256 basisPoints = bondMultiplierBP[nodeOperatorId]; return basisPoints > 0 ? basisPoints : MAX_BOND_MULTIPLIER; } From 21b677a2d6b2889c48f92a0e0f20973feb3ca6ad Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Fri, 1 Dec 2023 12:22:04 +0400 Subject: [PATCH 09/18] chore: add comments --- src/CSAccounting.sol | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/CSAccounting.sol b/src/CSAccounting.sol index bde7e84d..d61a9568 100644 --- a/src/CSAccounting.sol +++ b/src/CSAccounting.sol @@ -336,6 +336,8 @@ contract CSAccounting is } /// @notice Returns the required bond ETH for the given number of keys. + /// @dev To calculate the amount for the new keys 2 calls are required: + /// getRequiredBondETHForKeys(newTotal) - getRequiredBondETHForKeys(currentTotal) /// @param keysCount number of keys to get required bond for. /// @return required ETH. function getRequiredBondETHForKeys( @@ -345,6 +347,8 @@ contract CSAccounting is } /// @notice Returns the required bond stETH for the given number of keys. + /// @dev To calculate the amount for the new keys 2 calls are required: + /// getRequiredBondStETHForKeys(newTotal) - getRequiredBondStETHForKeys(currentTotal) /// @param keysCount number of keys to get required bond for. /// @return required stETH. function getRequiredBondStETHForKeys( @@ -354,6 +358,8 @@ contract CSAccounting is } /// @notice Returns the required bond wstETH for the given number of keys. + /// @dev To calculate the amount for the new keys 2 calls are required: + /// getRequiredBondWstETHForKeys(newTotal) - getRequiredBondWstETHForKeys(currentTotal) /// @param keysCount number of keys to get required bond for. /// @return required wstETH. function getRequiredBondWstETHForKeys( @@ -389,7 +395,7 @@ contract CSAccounting is return activeKeys; } - /// @notice Returns the number of keys by the given bond stETH amount + /// @notice Returns the number of keys by the given bond ETH amount function getKeysCountByBondETH( uint256 ETHAmount ) public view returns (uint256) { From cabe9328c7b97975649feee17949e41b600450c6 Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Fri, 1 Dec 2023 16:12:17 +0400 Subject: [PATCH 10/18] feat: bond lock for accounting tests --- test/CSAccounting.t.sol | 1274 ++++++++++++++++++++++++++++++++------- test/CSBondLock.t.sol | 183 ------ 2 files changed, 1066 insertions(+), 391 deletions(-) diff --git a/test/CSAccounting.t.sol b/test/CSAccounting.t.sol index 301cc057..32fc46a1 100644 --- a/test/CSAccounting.t.sol +++ b/test/CSAccounting.t.sol @@ -9,6 +9,7 @@ import { ICSModule } from "../src/interfaces/ICSModule.sol"; import { IStakingModule } from "../src/interfaces/IStakingModule.sol"; import { CSAccountingBase, CSAccounting } from "../src/CSAccounting.sol"; +import { CSBondLock } from "../src/CSBondLock.sol"; import { CSBondCurve } from "../src/CSBondCurve.sol"; import { PermitTokenBase } from "./helpers/Permit.sol"; import { Stub } from "./helpers/mocks/Stub.sol"; @@ -60,6 +61,14 @@ contract CSAccountingForTests is CSAccounting { function setBondMultiplier_ForTest(uint256 id, uint256 multiplier) public { _setBondMultiplier(id, multiplier); } + + function setBondLock_ForTest() public { + CSBondLock._lock(0, 1 ether); + } + + function setBondLock_ForTest(uint256 id, uint256 amount) public { + CSBondLock._lock(id, amount); + } } contract CSAccountingBaseTest is @@ -256,38 +265,50 @@ contract CSAccountingGetExcessBondETHTest is CSAccountingBondStateBaseTest { } function test_WithCurve() public override { - _deposit({ bond: 32 ether }); + _deposit({ bond: 33 ether }); accounting.setBondCurve_ForTest(); - assertApproxEqAbs(accounting.getExcessBondETH(0), 15 ether, 1 wei); + assertApproxEqAbs(accounting.getExcessBondETH(0), 16 ether, 1 wei); } function test_WithMultiplier() public override { - _deposit({ bond: 32 ether }); + _deposit({ bond: 33 ether }); accounting.setBondMultiplier_ForTest(); - assertApproxEqAbs(accounting.getExcessBondETH(0), 3.2 ether, 1 wei); + assertApproxEqAbs(accounting.getExcessBondETH(0), 4.2 ether, 1 wei); } function test_WithBlocked() public override { - // todo: implement me + _deposit({ bond: 33 ether }); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getExcessBondETH(0), 0 ether, 1 wei); } function test_WithCurveAndMultiplier() public override { - _deposit({ bond: 32 ether }); + _deposit({ bond: 33 ether }); accounting.setBondCurve_ForTest(); accounting.setBondMultiplier_ForTest(); - assertApproxEqAbs(accounting.getExcessBondETH(0), 16.7 ether, 1 wei); + assertApproxEqAbs(accounting.getExcessBondETH(0), 17.7 ether, 1 wei); } function test_WithCurveAndBlocked() public override { - // todo: implement me + _deposit({ bond: 33 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getExcessBondETH(0), 15 ether, 1 wei); } function test_WithMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 33 ether }); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getExcessBondETH(0), 3.2 ether, 1 wei); } function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 33 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getExcessBondETH(0), 16.7 ether, 1 wei); } function test_WithOneWithdrawnValidator() public override { @@ -340,38 +361,50 @@ contract CSAccountingGetExcessBondStETHTest is CSAccountingBondStateBaseTest { } function test_WithCurve() public override { - _deposit({ bond: 32 ether }); + _deposit({ bond: 33 ether }); accounting.setBondCurve_ForTest(); - assertApproxEqAbs(accounting.getExcessBondStETH(0), 15 ether, 1 wei); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 16 ether, 1 wei); } function test_WithMultiplier() public override { - _deposit({ bond: 32 ether }); + _deposit({ bond: 33 ether }); accounting.setBondMultiplier_ForTest(); - assertApproxEqAbs(accounting.getExcessBondStETH(0), 3.2 ether, 1 wei); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 4.2 ether, 1 wei); } function test_WithBlocked() public override { - // todo: implement me + _deposit({ bond: 33 ether }); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 0 ether, 1 wei); } function test_WithCurveAndMultiplier() public override { - _deposit({ bond: 32 ether }); + _deposit({ bond: 33 ether }); accounting.setBondCurve_ForTest(); accounting.setBondMultiplier_ForTest(); - assertApproxEqAbs(accounting.getExcessBondStETH(0), 16.7 ether, 1 wei); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 17.7 ether, 1 wei); } function test_WithCurveAndBlocked() public override { - // todo: implement me + _deposit({ bond: 33 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 15 ether, 1 wei); } function test_WithMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 33 ether }); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 3.2 ether, 1 wei); } function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 33 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 16.7 ether, 1 wei); } function test_WithOneWithdrawnValidator() public override { @@ -428,49 +461,78 @@ contract CSAccountingGetExcessBondWstETHTest is CSAccountingBondStateBaseTest { } function test_WithCurve() public override { - _deposit({ bond: 32 ether }); + _deposit({ bond: 33 ether }); accounting.setBondCurve_ForTest(); - assertEq( + assertApproxEqAbs( accounting.getExcessBondWstETH(0), - wstETH.getWstETHByStETH(15 ether) + wstETH.getWstETHByStETH(16 ether), + 1 wei ); } function test_WithMultiplier() public override { - _deposit({ bond: 32 ether }); + _deposit({ bond: 33 ether }); accounting.setBondMultiplier_ForTest(); assertApproxEqAbs( accounting.getExcessBondWstETH(0), - wstETH.getWstETHByStETH(3.2 ether), + wstETH.getWstETHByStETH(4.2 ether), 1 wei ); } function test_WithBlocked() public override { - // todo: implement me + _deposit({ bond: 33 ether }); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getExcessBondWstETH(0), + wstETH.getWstETHByStETH(0 ether), + 1 wei + ); } function test_WithCurveAndMultiplier() public override { - _deposit({ bond: 32 ether }); + _deposit({ bond: 33 ether }); accounting.setBondCurve_ForTest(); accounting.setBondMultiplier_ForTest(); assertApproxEqAbs( accounting.getExcessBondWstETH(0), - wstETH.getWstETHByStETH(16.7 ether), + wstETH.getWstETHByStETH(17.7 ether), 1 wei ); } function test_WithCurveAndBlocked() public override { - // todo: implement me + _deposit({ bond: 33 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getExcessBondWstETH(0), + wstETH.getWstETHByStETH(15 ether), + 1 wei + ); } function test_WithMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 33 ether }); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getExcessBondWstETH(0), + wstETH.getWstETHByStETH(3.2 ether), + 1 wei + ); } function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 33 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getExcessBondWstETH(0), + wstETH.getWstETHByStETH(16.7 ether), + 1 wei + ); } function test_WithOneWithdrawnValidator() public override { @@ -551,7 +613,9 @@ contract CSAccountingGetMissingBondETHTest is CSAccountingBondStateBaseTest { } function test_WithBlocked() public override { - // todo: implement me + _deposit({ bond: 16 ether }); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getMissingBondETH(0), 17 ether, 1 wei); } function test_WithCurveAndMultiplier() public override { @@ -562,15 +626,25 @@ contract CSAccountingGetMissingBondETHTest is CSAccountingBondStateBaseTest { } function test_WithCurveAndBlocked() public override { - // todo: implement me + _deposit({ bond: 16 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getMissingBondETH(0), 2 ether, 1 wei); } function test_WithMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 16 ether }); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getMissingBondETH(0), 13.8 ether, 1 wei); } function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 16 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getMissingBondETH(0), 0.3 ether, 1 wei); } function test_WithOneWithdrawnValidator() public override { @@ -635,7 +709,9 @@ contract CSAccountingGetMissingBondStETHTest is CSAccountingBondStateBaseTest { } function test_WithBlocked() public override { - // todo: implement me + _deposit({ bond: 16 ether }); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getMissingBondStETH(0), 17 ether, 1 wei); } function test_WithCurveAndMultiplier() public override { @@ -646,15 +722,25 @@ contract CSAccountingGetMissingBondStETHTest is CSAccountingBondStateBaseTest { } function test_WithCurveAndBlocked() public override { - // todo: implement me + _deposit({ bond: 16 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getMissingBondStETH(0), 2 ether, 1 wei); } function test_WithMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 16 ether }); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getMissingBondStETH(0), 13.8 ether, 1 wei); } function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 16 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs(accounting.getMissingBondStETH(0), 0.3 ether, 1 wei); } function test_WithOneWithdrawnValidator() public override { @@ -731,7 +817,13 @@ contract CSAccountingGetMissingBondWstETHTest is CSAccountingBondStateBaseTest { } function test_WithBlocked() public override { - // todo: implement me + _deposit({ bond: 16 ether }); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getMissingBondWstETH(0), + wstETH.getWstETHByStETH(17 ether), + 1 wei + ); } function test_WithCurveAndMultiplier() public override { @@ -745,15 +837,37 @@ contract CSAccountingGetMissingBondWstETHTest is CSAccountingBondStateBaseTest { } function test_WithCurveAndBlocked() public override { - // todo: implement me + _deposit({ bond: 16 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getMissingBondWstETH(0), + wstETH.getWstETHByStETH(2 ether), + 1 wei + ); } function test_WithMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 16 ether }); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getMissingBondWstETH(0), + wstETH.getWstETHByStETH(13.8 ether), + 1 wei + ); } function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 16 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getMissingBondWstETH(0), + wstETH.getWstETHByStETH(0.3 ether), + 1 wei + ); } function test_WithOneWithdrawnValidator() public override { @@ -832,7 +946,9 @@ contract CSAccountingGetUnbondedKeysCountTest is CSAccountingBondStateBaseTest { } function test_WithBlocked() public override { - // todo: implement me + _deposit({ bond: 11.5 ether }); + accounting.setBondLock_ForTest(); + assertEq(accounting.getUnbondedKeysCount(0), 10); } function test_WithCurveAndMultiplier() public override { @@ -843,15 +959,25 @@ contract CSAccountingGetUnbondedKeysCountTest is CSAccountingBondStateBaseTest { } function test_WithCurveAndBlocked() public override { - // todo: implement me + _deposit({ bond: 11.5 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + assertEq(accounting.getUnbondedKeysCount(0), 6); } function test_WithMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 11.5 ether }); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertEq(accounting.getUnbondedKeysCount(0), 10); } function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 11.5 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertEq(accounting.getUnbondedKeysCount(0), 5); } function test_WithOneWithdrawnValidator() public override { @@ -931,7 +1057,8 @@ contract CSAccountingGetRequiredETHBondTest is } function test_WithBlocked() public override { - // todo: implement me + accounting.setBondLock_ForTest(); + assertEq(accounting.getRequiredBondETH(0, 0), 33 ether); } function test_WithCurveAndMultiplier() public override { @@ -941,15 +1068,22 @@ contract CSAccountingGetRequiredETHBondTest is } function test_WithCurveAndBlocked() public override { - // todo: implement me + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + assertEq(accounting.getRequiredBondETH(0, 0), 18 ether); } function test_WithMultiplierAndBlocked() public override { - // todo: implement me + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertEq(accounting.getRequiredBondETH(0, 0), 29.8 ether); } function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertEq(accounting.getRequiredBondETH(0, 0), 16.3 ether); } function test_WithOneWithdrawnValidator() public override { @@ -1044,7 +1178,8 @@ contract CSAccountingGetRequiredStETHBondTest is } function test_WithBlocked() public override { - // todo: implement me + accounting.setBondLock_ForTest(); + assertEq(accounting.getRequiredBondStETH(0, 0), 33 ether); } function test_WithCurveAndMultiplier() public override { @@ -1054,15 +1189,22 @@ contract CSAccountingGetRequiredStETHBondTest is } function test_WithCurveAndBlocked() public override { - // todo: implement me + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + assertEq(accounting.getRequiredBondStETH(0, 0), 18 ether); } function test_WithMultiplierAndBlocked() public override { - // todo: implement me + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertEq(accounting.getRequiredBondStETH(0, 0), 29.8 ether); } function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertEq(accounting.getRequiredBondStETH(0, 0), 16.3 ether); } function test_WithOneWithdrawnValidator() public override { @@ -1178,7 +1320,11 @@ contract CSAccountingGetRequiredWstETHBondTest is } function test_WithBlocked() public override { - // todo: implement me + accounting.setBondLock_ForTest(); + assertEq( + accounting.getRequiredBondWstETH(0, 0), + stETH.getSharesByPooledEth(33 ether) + ); } function test_WithCurveAndMultiplier() public override { @@ -1191,15 +1337,31 @@ contract CSAccountingGetRequiredWstETHBondTest is } function test_WithCurveAndBlocked() public override { - // todo: implement me + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + assertEq( + accounting.getRequiredBondWstETH(0, 0), + stETH.getSharesByPooledEth(18 ether) + ); } function test_WithMultiplierAndBlocked() public override { - // todo: implement me + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertEq( + accounting.getRequiredBondWstETH(0, 0), + stETH.getSharesByPooledEth(29.8 ether) + ); } function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertEq( + accounting.getRequiredBondWstETH(0, 0), + stETH.getSharesByPooledEth(16.3 ether) + ); } function test_WithOneWithdrawnValidator() public override { @@ -1293,7 +1455,6 @@ contract CSAccountingGetRequiredWstETHBondTest is } } -// todo: should be changed ??? contract CSAccountingGetRequiredBondETHForKeysTest is BondAmountModifiersTest, CSAccountingBaseTest @@ -1620,7 +1781,16 @@ contract CSAccountingGetTotalRewardsETHTest is CSAccountingRewardsBaseTest { } function test_WithBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondLock_ForTest(); + assertEq( + accounting.getTotalRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + 0 + ); } function test_WithCurveAndMultiplier() public override { @@ -1638,15 +1808,49 @@ contract CSAccountingGetTotalRewardsETHTest is CSAccountingRewardsBaseTest { } function test_WithCurveAndBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getTotalRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 14 ether, + 1 wei + ); } function test_WithMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getTotalRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 2.2 ether, + 1 wei + ); } function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getTotalRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 15.7 ether, + 1 wei + ); } function test_WithOneWithdrawnValidator() public override { @@ -1782,7 +1986,16 @@ contract CSAccountingGetTotalRewardsStETHTest is CSAccountingRewardsBaseTest { } function test_WithBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondLock_ForTest(); + assertEq( + accounting.getTotalRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + 0 + ); } function test_WithCurveAndMultiplier() public override { @@ -1800,15 +2013,49 @@ contract CSAccountingGetTotalRewardsStETHTest is CSAccountingRewardsBaseTest { } function test_WithCurveAndBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getTotalRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 14 ether, + 1 wei + ); } function test_WithMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getTotalRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 2.2 ether, + 1 wei + ); } function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getTotalRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + stETHAsFee + 15.7 ether, + 1 wei + ); } function test_WithOneWithdrawnValidator() public override { @@ -1944,7 +2191,16 @@ contract CSAccountingGetTotalRewardsWstETHTest is CSAccountingRewardsBaseTest { } function test_WithBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondLock_ForTest(); + assertEq( + accounting.getTotalRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + 0 + ); } function test_WithCurveAndMultiplier() public override { @@ -1962,18 +2218,52 @@ contract CSAccountingGetTotalRewardsWstETHTest is CSAccountingRewardsBaseTest { } function test_WithCurveAndBlocked() public override { - // todo: implement me - } - - function test_WithMultiplierAndBlocked() public override { - // todo: implement me - } - - function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me - } - - function test_WithOneWithdrawnValidator() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getTotalRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + wstETH.getWstETHByStETH(stETHAsFee + 14 ether), + 1 wei + ); + } + + function test_WithMultiplierAndBlocked() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getTotalRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + wstETH.getWstETHByStETH(stETHAsFee + 2.2 ether), + 1 wei + ); + } + + function test_WithCurveAndMultiplierAndBlocked() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + assertApproxEqAbs( + accounting.getTotalRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares + ), + wstETH.getWstETHByStETH(stETHAsFee + 15.7 ether), + 1 wei + ); + } + + function test_WithOneWithdrawnValidator() public override { _operator({ ongoing: 16, withdrawn: 1 }); _deposit({ bond: 0 ether, fee: 0.1 ether }); assertEq( @@ -2105,7 +2395,11 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { bondSharesAfter, "bond manager after claim should be equal to before" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to before" + ); } function test_WithCurve() public override { @@ -2125,20 +2419,24 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { assertEq( stETH.balanceOf(address(user)), stETHAsFee + 15 ether, - "user balance should be equal to fee reward" + "user balance should be equal to fee reward plus excess bond after curve" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(15 ether), 1 wei, - "bond shares after claim should be equal to before minus excess" + "bond shares after claim should be equal to before minus excess bond after curve" ); assertEq( stETH.sharesOf(address(accounting)), bondSharesAfter, "bond manager after claim should be equal to after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to after" + ); } function test_WithMultiplier() public override { @@ -2158,24 +2456,61 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { assertEq( stETH.balanceOf(address(user)), stETHAsFee + 3.2 ether, - "user balance should be equal to fee reward" + "user balance should be equal to fee reward plus excess bond after multiplier" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(3.2 ether), 1 wei, - "bond shares after claim should be equal to before minus excess" + "bond shares after claim should be equal to before minus excess bond after multiplier" ); assertEq( stETH.sharesOf(address(accounting)), bondSharesAfter, "bond manager after claim should be equal to after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to after" + ); } function test_WithBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondLock_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq( + stETH.balanceOf(address(user)), + 0, + "user balance should be equal to zero" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore + sharesAsFee, + 1 wei, + "bond shares after claim should be equal to before plus fee shares" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to after" + ); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to after" + ); } function test_WithCurveAndMultiplier() public override { @@ -2196,32 +2531,142 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { assertEq( stETH.balanceOf(address(user)), stETHAsFee + 16.7 ether, - "user balance should be equal to fee reward" + "user balance should be equal to fee reward plus excess bond after curve and multiplier" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(16.7 ether), 1 wei, - "bond shares after claim should be equal to before minus excess" + "bond shares after claim should be equal to before minus excess bond after curve and multiplier" ); assertEq( stETH.sharesOf(address(accounting)), bondSharesAfter, "bond manager after claim should be equal to after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to after" + ); } function test_WithCurveAndBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertApproxEqAbs( + stETH.balanceOf(address(user)), + stETHAsFee + 14 ether, + 1 wei, + "user balance should be equal to fee reward plus excess bond after curve minus blocked" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(14 ether), + 1 wei, + "bond shares after claim should be equal to before minus excess bond after curve minus blocked" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to after" + ); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to after" + ); } function test_WithMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertApproxEqAbs( + stETH.balanceOf(address(user)), + stETHAsFee + 2.2 ether, + 1 wei, + "user balance should be equal to fee reward plus excess bond after multiplier minus blocked" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(2.2 ether), + 1 wei, + "bond shares after claim should be equal to before minus excess bond after multiplier minus blocked" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to after" + ); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to after" + ); } function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsStETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertApproxEqAbs( + stETH.balanceOf(address(user)), + stETHAsFee + 15.7 ether, + 1 wei, + "user balance should be equal to fee reward plus excess bond after curve and multiplier minus blocked" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(15.7 ether), + 2 wei, + "bond shares after claim should be equal to before minus excess bond after curve and multiplier minus blocked" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to after" + ); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to after" + ); } function test_WithOneWithdrawnValidator() public override { @@ -2242,20 +2687,24 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { stETH.balanceOf(address(user)), stETHAsFee + 2 ether, 1 wei, - "user balance should be equal to fee reward" + "user balance should be equal to fee reward plus excess bond after one validator withdrawn" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(2 ether), 1 wei, - "bond shares after claim should be equal to before" + "bond shares after claim should be equal to before minus excess bond after one validator withdrawn" ); assertEq( stETH.sharesOf(address(accounting)), bondSharesAfter, - "bond manager after claim should be equal to before" + "bond manager after claim should be equal to after" + ); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithBond() public override { @@ -2280,14 +2729,18 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { assertEq( bondSharesAfter, bondSharesBefore, - "bond shares after claim should be equal to before minus fee" + "bond shares after claim should be equal to before" ); assertEq( stETH.sharesOf(address(accounting)), bondSharesAfter, "bond manager after claim should be equal to after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to after" + ); } function test_WithBondAndOneWithdrawnValidator() public override { @@ -2307,20 +2760,24 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { assertEq( stETH.balanceOf(address(user)), stETHAsFee + 2 ether, - "user balance should be equal to fee reward" + "user balance should be equal to fee reward plus excess bond after one validator withdrawn" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(2 ether), 1 wei, - "bond shares after claim should be equal to before" + "bond shares after claim should be equal to before minus excess bond after one validator withdrawn" ); assertEq( stETH.sharesOf(address(accounting)), bondSharesAfter, - "bond manager after claim should be equal to before" + "bond manager after claim should be equal to before minus excess bond after one validator withdrawn" + ); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithExcessBond() public override { @@ -2340,20 +2797,24 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { assertEq( stETH.balanceOf(address(user)), stETHAsFee + 1 ether, - "user balance should be equal to fee reward" + "user balance should be equal to fee reward plus excess bond" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(1 ether), 1 wei, - "bond shares after claim should be equal to before minus fee" + "bond shares after claim should be equal to before minus excess bond" ); assertEq( stETH.sharesOf(address(accounting)), bondSharesAfter, "bond manager after claim should be equal to after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to after" + ); } function test_WithExcessBondAndOneWithdrawnValidator() public override { @@ -2374,20 +2835,24 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { stETH.balanceOf(address(user)), stETHAsFee + 3 ether, 1 wei, - "user balance should be equal to fee reward" + "user balance should be equal to fee reward plus excess bond after one validator withdrawn" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(3 ether), 1 wei, - "bond shares after claim should be equal to before minus fee" + "bond shares after claim should be equal to before minus excess bond after one validator withdrawn" ); assertEq( stETH.sharesOf(address(accounting)), bondSharesAfter, "bond manager after claim should be equal to after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to after" + ); } function test_WithMissingBond() public override { @@ -2407,19 +2872,23 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { assertEq( stETH.balanceOf(address(user)), 0, - "user balance should be equal to fee reward" + "user balance should be equal to zero" ); assertEq( bondSharesAfter, bondSharesBefore + sharesAsFee, - "bond shares after claim should be equal to before" + "bond shares after claim should be equal to before plus fee shares" ); assertEq( stETH.sharesOf(address(accounting)), bondSharesAfter, "bond manager after claim should be equal to after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to after" + ); } function test_WithMissingBondAndOneWithdrawnValidator() public override { @@ -2439,19 +2908,23 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { assertEq( stETH.balanceOf(address(user)), 0, - "user balance should be equal to fee reward" + "user balance should be equal to zero" ); assertEq( bondSharesAfter, bondSharesBefore + sharesAsFee, - "bond shares after claim should be equal to before" + "bond shares after claim should be equal to before plus fee shares" ); assertEq( stETH.sharesOf(address(accounting)), bondSharesAfter, "bond manager after claim should be equal to after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to after" + ); } function test_EventEmitted() public override { @@ -2547,7 +3020,7 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, bondSharesBefore, 1 wei, - "bond shares after claim should contain wrapped fee accuracy error" + "bond shares after claim should be equal to before" ); assertEq( wstETH.balanceOf(address(accounting)), @@ -2557,9 +3030,13 @@ contract CSAccountingClaimWstETHRewardsTest is assertEq( stETH.sharesOf(address(accounting)), bondSharesAfter, - "bond manager after claim should be equal to bond shares after" + "bond manager after claim should be equal to after" + ); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); } function test_WithCurve() public override { @@ -2580,13 +3057,13 @@ contract CSAccountingClaimWstETHRewardsTest is assertEq( wstETH.balanceOf(address(user)), wstETH.getWstETHByStETH(stETHAsFee + 15 ether), - "user balance should be equal to fee reward" + "user balance should be equal to fee reward plus excess bond after curve" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(15 ether), 1 wei, - "bond shares after claim should contain wrapped fee accuracy error" + "bond shares after claim should be equal to before minus excess bond after curve" ); assertEq( wstETH.balanceOf(address(accounting)), @@ -2598,7 +3075,11 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to bond shares after" + ); } function test_WithMultiplier() public override { @@ -2616,16 +3097,193 @@ contract CSAccountingClaimWstETHRewardsTest is uint256 bondSharesAfter = accounting.getBondShares(0); vm.stopPrank(); - assertEq( + assertEq( + wstETH.balanceOf(address(user)), + wstETH.getWstETHByStETH(stETHAsFee + 3.2 ether), + "user balance should be equal to fee reward plus excess bond after multiplier" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(3.2 ether), + 1 wei, + "bond shares after claim should be equal to before minus excess bond after multiplier" + ); + assertEq( + wstETH.balanceOf(address(accounting)), + 0, + "bond manager wstETH balance should be 0" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to bond shares after" + ); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to bond shares after" + ); + } + + function test_WithBlocked() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondLock_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); + + assertEq( + wstETH.balanceOf(address(user)), + 0, + "user balance should be equal to zero" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore + sharesAsFee, + 1 wei, + "bond shares after claim should be equal to before plus fee shares" + ); + assertEq( + wstETH.balanceOf(address(accounting)), + 0, + "bond manager wstETH balance should be 0" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to bond shares after" + ); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to bond shares after" + ); + } + + function test_WithCurveAndMultiplier() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); + + assertEq( + wstETH.balanceOf(address(user)), + wstETH.getWstETHByStETH(stETHAsFee + 16.7 ether), + "user balance should be equal to fee reward plus excess bond after curve and multiplier" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(16.7 ether), + 1 wei, + "bond shares after claim should be equal to before minus excess bond after curve and multiplier" + ); + assertEq( + wstETH.balanceOf(address(accounting)), + 0, + "bond manager wstETH balance should be 0" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to bond shares after" + ); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to bond shares after" + ); + } + + function test_WithCurveAndBlocked() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); + + assertApproxEqAbs( + wstETH.balanceOf(address(user)), + wstETH.getWstETHByStETH(stETHAsFee + 14 ether), + 1 wei, + "user balance should be equal to fee reward plus excess bond after curve minus blocked" + ); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(14 ether), + 1 wei, + "bond shares after claim should be equal to before minus excess bond after curve minus blocked" + ); + assertEq( + wstETH.balanceOf(address(accounting)), + 0, + "bond manager wstETH balance should be 0" + ); + assertEq( + stETH.sharesOf(address(accounting)), + bondSharesAfter, + "bond manager after claim should be equal to bond shares after" + ); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to bond shares after" + ); + } + + function test_WithMultiplierAndBlocked() public override { + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + accounting.claimRewardsWstETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + vm.stopPrank(); + + assertApproxEqAbs( wstETH.balanceOf(address(user)), - wstETH.getWstETHByStETH(stETHAsFee + 3.2 ether), - "user balance should be equal to fee reward" + wstETH.getWstETHByStETH(stETHAsFee + 2.2 ether), + 1 wei, + "user balance should be equal to fee reward plus excess bond after multiplier minus blocked" ); assertApproxEqAbs( bondSharesAfter, - bondSharesBefore - stETH.getSharesByPooledEth(3.2 ether), + bondSharesBefore - stETH.getSharesByPooledEth(2.2 ether), 1 wei, - "bond shares after claim should contain wrapped fee accuracy error" + "bond shares after claim should be equal to before minus excess bond after multiplier minus blocked" ); assertEq( wstETH.balanceOf(address(accounting)), @@ -2637,17 +3295,18 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); - } - - function test_WithBlocked() public override { - // todo: implement me + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to bond shares after" + ); } - function test_WithCurveAndMultiplier() public override { + function test_WithCurveAndMultiplierAndBlocked() public override { _deposit({ bond: 32 ether, fee: 0.1 ether }); accounting.setBondCurve_ForTest(); accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -2660,16 +3319,17 @@ contract CSAccountingClaimWstETHRewardsTest is uint256 bondSharesAfter = accounting.getBondShares(0); vm.stopPrank(); - assertEq( + assertApproxEqAbs( wstETH.balanceOf(address(user)), - wstETH.getWstETHByStETH(stETHAsFee + 16.7 ether), - "user balance should be equal to fee reward" + wstETH.getWstETHByStETH(stETHAsFee + 15.7 ether), + 1 wei, + "user balance should be equal to fee reward plus excess bond after curve and multiplier minus blocked" ); assertApproxEqAbs( bondSharesAfter, - bondSharesBefore - stETH.getSharesByPooledEth(16.7 ether), - 1 wei, - "bond shares after claim should contain wrapped fee accuracy error" + bondSharesBefore - stETH.getSharesByPooledEth(15.7 ether), + 2 wei, + "bond shares after claim should be equal to before minus excess bond after curve and multiplier minus blocked" ); assertEq( wstETH.balanceOf(address(accounting)), @@ -2681,19 +3341,11 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); - } - - function test_WithCurveAndBlocked() public override { - // todo: implement me - } - - function test_WithMultiplierAndBlocked() public override { - // todo: implement me - } - - function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to bond shares after" + ); } function test_WithOneWithdrawnValidator() public override { @@ -2715,13 +3367,13 @@ contract CSAccountingClaimWstETHRewardsTest is wstETH.balanceOf(address(user)), wstETHAsFee + stETH.getSharesByPooledEth(2 ether), 1 wei, - "user balance should be equal to fee reward" + "user balance should be equal to fee reward plus excess bond after one validator withdrawn" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(2 ether), 1 wei, - "bond shares after claim should contain wrapped fee accuracy error" + "bond shares after claim should be equal to before minus excess bond after one validator withdrawn" ); assertEq( wstETH.balanceOf(address(accounting)), @@ -2733,7 +3385,11 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to bond shares after" + ); } function test_WithBond() public override { @@ -2760,7 +3416,7 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, bondSharesBefore, 1 wei, - "bond shares after claim should contain wrapped fee accuracy error" + "bond shares after claim should be equal to before" ); assertEq( wstETH.balanceOf(address(accounting)), @@ -2772,7 +3428,11 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to bond shares after" + ); } function test_WithBondAndOneWithdrawnValidator() public override { @@ -2800,7 +3460,7 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(2 ether), 1 wei, - "bond shares after claim should contain wrapped fee accuracy error" + "bond shares after claim should be equal to before minus excess bond after one validator withdrawn" ); assertEq( wstETH.balanceOf(address(accounting)), @@ -2812,7 +3472,11 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to bond shares after" + ); } function test_WithExcessBond() public override { @@ -2834,13 +3498,13 @@ contract CSAccountingClaimWstETHRewardsTest is wstETH.balanceOf(address(user)), wstETHAsFee + stETH.getSharesByPooledEth(1 ether), 1 wei, - "user balance should be equal to fee reward" + "user balance should be equal to fee reward plus excess bond" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(1 ether), 1 wei, - "bond shares after claim should contain wrapped fee accuracy error" + "bond shares after claim should be equal to before minus excess bond" ); assertEq( wstETH.balanceOf(address(accounting)), @@ -2852,7 +3516,11 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to bond shares after" + ); } function test_WithExcessBondAndOneWithdrawnValidator() public override { @@ -2874,13 +3542,13 @@ contract CSAccountingClaimWstETHRewardsTest is wstETH.balanceOf(address(user)), wstETHAsFee + stETH.getSharesByPooledEth(3 ether), 1 wei, - "user balance should be equal to fee reward" + "user balance should be equal to fee reward plus excess bond after one validator withdrawn" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(3 ether), 1 wei, - "bond shares after claim should contain wrapped fee accuracy error" + "bond shares after claim should be equal to before minus excess bond after one validator withdrawn" ); assertEq( wstETH.balanceOf(address(accounting)), @@ -2892,7 +3560,11 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to bond shares after" + ); } function test_WithMissingBond() public override { @@ -2912,12 +3584,12 @@ contract CSAccountingClaimWstETHRewardsTest is assertEq( wstETH.balanceOf(address(user)), 0, - "user balance should be equal to fee reward" + "user balance should be equal to zero" ); assertEq( bondSharesAfter, bondSharesBefore + sharesAsFee, - "bond shares after claim should contain wrapped fee accuracy error" + "bond shares after claim should be equal to before plus fee shares" ); assertEq( wstETH.balanceOf(address(accounting)), @@ -2929,7 +3601,11 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to bond shares after" + ); } function test_WithMissingBondAndOneWithdrawnValidator() public override { @@ -2949,12 +3625,12 @@ contract CSAccountingClaimWstETHRewardsTest is assertEq( wstETH.balanceOf(address(user)), 0, - "user balance should be equal to fee reward" + "user balance should be equal to zero" ); assertEq( bondSharesAfter, bondSharesBefore + sharesAsFee, - "bond shares after claim should contain wrapped fee accuracy error" + "bond shares after claim should be equal to before plus fee shares" ); assertEq( wstETH.balanceOf(address(accounting)), @@ -2966,7 +3642,11 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after claim should be equal to bond shares after" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after claim should be equal to bond shares after" + ); } function test_EventEmitted() public override { @@ -3024,7 +3704,11 @@ contract CSAccountingClaimWstETHRewardsTest is bondSharesAfter, "bond manager after should be equal to before and fee minus claimed shares" ); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares after should be equal to before and fee minus claimed shares" + ); } function test_RevertWhen_NotOwner() public override { @@ -3069,7 +3753,11 @@ contract CSAccountingRequestRewardsETHRewardsTest is "shares of withdrawal queue should be equal to requested shares" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should not change" + ); } function test_WithCurve() public override { @@ -3091,16 +3779,20 @@ contract CSAccountingRequestRewardsETHRewardsTest is bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(15 ether), 1 wei, - "bond shares should not change after request" + "bond shares should be changed after request minus excess bond after curve" ); assertApproxEqAbs( stETH.sharesOf(address(locator.withdrawalQueue())), unstETHSharesAsFee + stETH.getSharesByPooledEth(15 ether), 1 wei, - "shares of withdrawal queue should be equal to requested shares" + "shares of withdrawal queue should be equal to requested shares and excess bond after curve" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should be equal to after" + ); } function test_WithMultiplier() public override { @@ -3121,19 +3813,52 @@ contract CSAccountingRequestRewardsETHRewardsTest is assertEq( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(3.2 ether), - "bond shares should be changed after request" + "bond shares should be changed after request minus excess bond after multiplier" ); assertEq( stETH.sharesOf(address(locator.withdrawalQueue())), unstETHSharesAsFee + stETH.getSharesByPooledEth(3.2 ether), - "shares of withdrawal queue should be equal to requested shares and excess" + "shares of withdrawal queue should be equal to requested shares and excess bond after multiplier" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should be equal to after" + ); } function test_WithBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondLock_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq(requestIds.length, 0, "request ids length should be 1"); + assertEq( + bondSharesAfter, + bondSharesBefore + sharesAsFee, + "bond shares should be equal to before plus fee shares" + ); + assertEq( + stETH.sharesOf(address(locator.withdrawalQueue())), + 0, + "shares of withdrawal queue should be equal to zero" + ); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should be equal to after" + ); } function test_WithCurveAndMultiplier() public override { @@ -3156,28 +3881,129 @@ contract CSAccountingRequestRewardsETHRewardsTest is bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(16.7 ether), 1 wei, - "bond shares should be changed after request" + "bond shares should be equal to before minus excess bond after curve and multiplier" ); assertApproxEqAbs( stETH.sharesOf(address(locator.withdrawalQueue())), unstETHSharesAsFee + stETH.getSharesByPooledEth(16.7 ether), 1 wei, - "shares of withdrawal queue should be equal to requested shares and excess" + "shares of withdrawal queue should be equal to requested shares and excess bond after curve and multiplier" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should be equal to after" + ); } function test_WithCurveAndBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondLock_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq(requestIds.length, 1, "request ids length should be 1"); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(14 ether), + 1 wei, + "bond shares should be equal to before minus excess bond after curve and blocked" + ); + assertApproxEqAbs( + stETH.sharesOf(address(locator.withdrawalQueue())), + unstETHSharesAsFee + stETH.getSharesByPooledEth(14 ether), + 1 wei, + "shares of withdrawal queue should be equal to requested shares" + ); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should be equal to after" + ); } function test_WithMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq(requestIds.length, 1, "request ids length should be 1"); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(2.2 ether), + 1 wei, + "bond shares should be equal to before minus excess bond after multiplier and blocked" + ); + assertApproxEqAbs( + stETH.sharesOf(address(locator.withdrawalQueue())), + unstETHSharesAsFee + stETH.getSharesByPooledEth(2.2 ether), + 1 wei, + "shares of withdrawal queue should be equal to requested shares and excess bond after multiplier and blocked" + ); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should be equal to after" + ); } function test_WithCurveAndMultiplierAndBlocked() public override { - // todo: implement me + _deposit({ bond: 32 ether, fee: 0.1 ether }); + accounting.setBondCurve_ForTest(); + accounting.setBondMultiplier_ForTest(); + accounting.setBondLock_ForTest(); + + uint256 bondSharesBefore = accounting.getBondShares(0); + vm.prank(user); + uint256[] memory requestIds = accounting.requestRewardsETH( + leaf.proof, + leaf.nodeOperatorId, + leaf.shares, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); + + assertEq(requestIds.length, 1, "request ids length should be 1"); + assertApproxEqAbs( + bondSharesAfter, + bondSharesBefore - stETH.getSharesByPooledEth(15.7 ether), + 2 wei, + "bond shares should be equal to before minus excess bond after curve and multiplier and blocked" + ); + assertApproxEqAbs( + stETH.sharesOf(address(locator.withdrawalQueue())), + unstETHSharesAsFee + stETH.getSharesByPooledEth(15.7 ether), + 2 wei, + "shares of withdrawal queue should be equal to requested shares and excess bond after curve and multiplier and blocked" + ); + assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should be equal to after" + ); } function test_WithOneWithdrawnValidator() public override { @@ -3201,16 +4027,20 @@ contract CSAccountingRequestRewardsETHRewardsTest is bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(2 ether), 1 wei, - "bond shares should be changed after request" + "bond shares should be equal to before minus excess bond after one validator withdrawn" ); assertApproxEqAbs( stETH.sharesOf(address(locator.withdrawalQueue())), unstETHSharesAsFee + stETH.getSharesByPooledEth(2 ether), 1 wei, - "shares of withdrawal queue should be equal to requested shares and excess" + "shares of withdrawal queue should be equal to requested shares and excess bond after one validator withdrawn" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should be equal to after" + ); } function test_WithBond() public override { @@ -3241,7 +4071,11 @@ contract CSAccountingRequestRewardsETHRewardsTest is "shares of withdrawal queue should be equal to requested shares" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should be equal to after" + ); } function test_WithBondAndOneWithdrawnValidator() public override { @@ -3265,16 +4099,20 @@ contract CSAccountingRequestRewardsETHRewardsTest is bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(2 ether), 1 wei, - "bond shares should be changed after request" + "bond shares should be equal to before minus excess bond after one validator withdrawn" ); assertApproxEqAbs( stETH.sharesOf(address(locator.withdrawalQueue())), unstETHSharesAsFee + stETH.getSharesByPooledEth(2 ether), 1 wei, - "shares of withdrawal queue should be equal to requested shares and excess" + "shares of withdrawal queue should be equal to requested shares and excess bond after one validator withdrawn" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should be equal to after" + ); } function test_WithExcessBond() public override { @@ -3298,16 +4136,20 @@ contract CSAccountingRequestRewardsETHRewardsTest is bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(1 ether), 1 wei, - "bond shares should not change after request" + "bond shares should be equal to before minus excess bond" ); assertApproxEqAbs( stETH.sharesOf(address(locator.withdrawalQueue())), unstETHSharesAsFee + stETH.getSharesByPooledEth(1 ether), 1 wei, - "shares of withdrawal queue should be equal to requested shares and excess" + "shares of withdrawal queue should be equal to requested shares and excess bond" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should be equal to after" + ); } function test_WithExcessBondAndOneWithdrawnValidator() public override { @@ -3331,16 +4173,20 @@ contract CSAccountingRequestRewardsETHRewardsTest is bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(3 ether), 1 wei, - "bond shares should be changed after request" + "bond shares should be equal to before minus excess bond after one validator withdrawn" ); assertApproxEqAbs( stETH.sharesOf(address(locator.withdrawalQueue())), unstETHSharesAsFee + stETH.getSharesByPooledEth(3 ether), 1 wei, - "shares of withdrawal queue should be equal to requested shares and excess" + "shares of withdrawal queue should be equal to requested shares and excess bond after one validator withdrawn" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should be equal to after" + ); } function test_WithMissingBond() public override { @@ -3363,15 +4209,19 @@ contract CSAccountingRequestRewardsETHRewardsTest is assertEq( bondSharesAfter, bondSharesBefore + sharesAsFee, - "bond shares should not change after request" + "bond shares should be equal to before plus fee shares" ); assertEq( stETH.sharesOf(address(locator.withdrawalQueue())), 0, - "shares of withdrawal queue should be equal to requested shares" + "shares of withdrawal queue should be equal to zero" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should be equal to after" + ); } function test_WithMissingBondAndOneWithdrawnValidator() public override { @@ -3394,15 +4244,19 @@ contract CSAccountingRequestRewardsETHRewardsTest is assertEq( bondSharesAfter, bondSharesBefore + sharesAsFee, - "bond shares should not change after request" + "bond shares should be equal to before plus fee shares" ); assertEq( stETH.sharesOf(address(locator.withdrawalQueue())), 0, - "shares of withdrawal queue should be equal to requested shares" + "shares of withdrawal queue should be equal to zero" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should be equal to after" + ); } function test_EventEmitted() public override { @@ -3457,7 +4311,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is assertEq( bondSharesAfter, bondSharesBefore + sharesAsFee - sharesToRequest, - "bond shares should change after request" + "bond shares should be equal to before plus fee shares minus requested shares" ); assertEq( stETH.sharesOf(address(locator.withdrawalQueue())), @@ -3465,7 +4319,11 @@ contract CSAccountingRequestRewardsETHRewardsTest is "shares of withdrawal queue should be equal to requested shares" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); - assertEq(accounting.totalBondShares(), bondSharesAfter); + assertEq( + accounting.totalBondShares(), + bondSharesAfter, + "total bond shares should be equal to after" + ); } function test_RevertWhen_NotOwner() public override { diff --git a/test/CSBondLock.t.sol b/test/CSBondLock.t.sol index d4d4b521..6213ea92 100644 --- a/test/CSBondLock.t.sol +++ b/test/CSBondLock.t.sol @@ -138,189 +138,6 @@ contract CSAccounting_BlockedBondTest is vm.stopPrank(); } - function test_getBlockedBondETH() public { - uint256 noId = 0; - uint256 amount = 1 ether; - uint256 retentionUntil = block.timestamp + 1 weeks; - - accounting._blockedBondEther_set_value( - noId, - CSBondLock.BondLock({ - amount: amount, - retentionUntil: retentionUntil - }) - ); - - assertEq(accounting.getBlockedBondETH(noId), amount); - - // retentionUntil is not passed yet - vm.warp(retentionUntil); - assertEq(accounting.getBlockedBondETH(noId), amount); - - // the next block after retentionUntil - vm.warp(retentionUntil + 12); - assertEq(accounting.getBlockedBondETH(noId), 0); - } - - function test_getRequiredBondETH_withBlockedBond() public { - uint256 noId = 0; - uint256 amount = 100500 ether; - uint256 retentionUntil = block.timestamp + 1 weeks; - - accounting._blockedBondEther_set_value( - noId, - CSBondLock.BondLock({ - amount: amount, - retentionUntil: retentionUntil - }) - ); - - assertEq(accounting.getRequiredBondETH(noId, 0), amount); - - // the next block after retentionUntil - vm.warp(retentionUntil + 12); - assertEq(accounting.getRequiredBondETH(noId, 0), 0); - } - - function test_getExcessBondETH_withBlockedBond() public { - _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); - - uint256 noId = 0; - uint256 amount = 100500 ether; - uint256 retentionUntil = block.timestamp + 1 weeks; - - vm.deal(user, 12 ether); - vm.startPrank(user); - stETH.submit{ value: 12 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 12 ether); - vm.stopPrank(); - - accounting._blockedBondEther_set_value( - noId, - CSBondLock.BondLock({ - amount: amount, - retentionUntil: retentionUntil - }) - ); - - assertEq(accounting.getExcessBondETH(noId), 0); - - // the next block after retentionUntil - vm.warp(retentionUntil + 12); - assertApproxEqAbs(accounting.getExcessBondETH(0), 10 ether, 1); - } - - function test_claimRewardStETH_withBlockedBond() public { - _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); - - uint256 noId = 0; - uint256 amount = 100500 ether; - uint256 retentionUntil = block.timestamp + 1 weeks; - - vm.deal(user, 12 ether); - vm.startPrank(user); - stETH.submit{ value: 12 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 12 ether); - vm.stopPrank(); - - accounting._blockedBondEther_set_value( - noId, - CSBondLock.BondLock({ - amount: amount, - retentionUntil: retentionUntil - }) - ); - - vm.expectEmit(true, true, true, true, address(accounting)); - emit StETHRewardsClaimed(0, user, 0); - - vm.prank(user); - accounting.claimRewardsStETH(new bytes32[](0), noId, 0, UINT256_MAX); - - accounting._blockedBondEther_set_value( - noId, - CSBondLock.BondLock({ - amount: 1 ether, - retentionUntil: retentionUntil - }) - ); - - vm.expectEmit(true, true, true, true, address(accounting)); - emit StETHRewardsClaimed(0, user, 9 ether + 1 wei); - - vm.prank(user); - accounting.claimRewardsStETH(new bytes32[](0), noId, 0, UINT256_MAX); - } - - function test_claimRewardsWstETH_withBlockedBond() public { - _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); - - uint256 noId = 0; - uint256 amount = 100500 ether; - uint256 retentionUntil = block.timestamp + 1 weeks; - - vm.deal(user, 12 ether); - vm.startPrank(user); - stETH.submit{ value: 12 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 12 ether); - vm.stopPrank(); - - accounting._blockedBondEther_set_value( - noId, - CSBondLock.BondLock({ - amount: amount, - retentionUntil: retentionUntil - }) - ); - - vm.expectEmit(true, true, true, true, address(accounting)); - emit WstETHRewardsClaimed(0, user, 0); - - vm.prank(user); - accounting.claimRewardsWstETH(new bytes32[](0), noId, 0, UINT256_MAX); - - accounting._blockedBondEther_set_value( - noId, - CSBondLock.BondLock({ - amount: 1 ether, - retentionUntil: retentionUntil - }) - ); - - vm.expectEmit(true, true, true, true, address(accounting)); - emit WstETHRewardsClaimed( - 0, - user, - stETH.getSharesByPooledEth(9 ether + 1 wei) - ); - - vm.prank(user); - accounting.claimRewardsWstETH(new bytes32[](0), noId, 0, UINT256_MAX); - } - - function test_requestRewardsETH_withBlockedBond() public { - _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); - - uint256 noId = 0; - uint256 amount = 100500 ether; - uint256 retentionUntil = block.timestamp + 1 weeks; - - accounting._bondShares_set_value(0, 100 ether); - accounting._blockedBondEther_set_value( - noId, - CSBondLock.BondLock({ - amount: amount, - retentionUntil: retentionUntil - }) - ); - - vm.expectEmit(true, true, true, true, address(accounting)); - emit ETHRewardsRequested(0, user, 0); - - vm.prank(user); - accounting.requestRewardsETH(new bytes32[](0), noId, 0, UINT256_MAX); - } - function test_private_changeBlockedBondState() public { uint256 noId = 0; uint256 amount = 1 ether; From 0fab4ea611fa4f98d7c1e7ac105d1485ef46357d Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Fri, 1 Dec 2023 19:06:04 +0400 Subject: [PATCH 11/18] feat: CSBondLock tests --- src/CSBondLock.sol | 32 +- test/CSAccounting.t.sol | 4 + test/CSBondLock.t.sol | 706 ++++++++++++++++------------------------ 3 files changed, 293 insertions(+), 449 deletions(-) diff --git a/src/CSBondLock.sol b/src/CSBondLock.sol index 42deac65..038f59ea 100644 --- a/src/CSBondLock.sol +++ b/src/CSBondLock.sol @@ -28,8 +28,8 @@ abstract contract CSBondLock is CSBondLockBase { uint256 public constant MIN_BOND_LOCK_MANAGEMENT_PERIOD = 1 days; uint256 public constant MAX_BOND_LOCK_MANAGEMENT_PERIOD = 7 days; - uint256 public bondLockRetentionPeriod; - uint256 public bondLockManagementPeriod; + uint256 internal _bondLockRetentionPeriod; + uint256 internal _bondLockManagementPeriod; mapping(uint256 => BondLock) internal _bondLock; @@ -42,8 +42,16 @@ abstract contract CSBondLock is CSBondLockBase { uint256 management ) internal { _validateBondLockPeriods(retention, management); - bondLockRetentionPeriod = retention; - bondLockManagementPeriod = management; + _bondLockRetentionPeriod = retention; + _bondLockManagementPeriod = management; + } + + function getBondLockPeriods() + external + view + returns (uint256 retention, uint256 management) + { + return (_bondLockRetentionPeriod, _bondLockManagementPeriod); } function _validateBondLockPeriods( @@ -78,7 +86,7 @@ abstract contract CSBondLock is CSBondLockBase { _changeBondLock({ nodeOperatorId: nodeOperatorId, amount: _bondLock[nodeOperatorId].amount + amount, - retentionUntil: block.timestamp + bondLockRetentionPeriod + retentionUntil: block.timestamp + _bondLockRetentionPeriod }); } @@ -91,9 +99,9 @@ abstract contract CSBondLock is CSBondLockBase { BondLock storage bondLock = _bondLock[nodeOperatorId]; if ( block.timestamp + - bondLockRetentionPeriod - + _bondLockRetentionPeriod - bondLock.retentionUntil < - bondLockManagementPeriod + _bondLockManagementPeriod ) { // blocked bond in safe frame to manage it by committee or node operator continue; @@ -117,9 +125,6 @@ abstract contract CSBondLock is CSBondLockBase { /// @param nodeOperatorId id of the node operator to release blocked bond for. /// @param amount amount of ETH to release. function _release(uint256 nodeOperatorId, uint256 amount) internal { - if (amount == 0) { - revert InvalidBondLockAmount(); - } emit BondLockReleased(nodeOperatorId, amount); _reduceAmount(nodeOperatorId, amount); } @@ -127,14 +132,11 @@ abstract contract CSBondLock is CSBondLockBase { /// @notice Compensates blocked bond ETH for the given node operator. /// @param nodeOperatorId id of the node operator to compensate blocked bond for. function _compensate(uint256 nodeOperatorId, uint256 amount) internal { - if (amount == 0) { - revert InvalidBondLockAmount(); - } emit BondLockCompensated(nodeOperatorId, amount); _reduceAmount(nodeOperatorId, amount); } - function _reduceAmount(uint256 nodeOperatorId, uint256 amount) internal { + function _reduceAmount(uint256 nodeOperatorId, uint256 amount) private { uint256 blocked = _get(nodeOperatorId); if (amount == 0) { revert InvalidBondLockAmount(); @@ -153,7 +155,7 @@ abstract contract CSBondLock is CSBondLockBase { uint256 nodeOperatorId, uint256 amount, uint256 retentionUntil - ) internal { + ) private { if (amount == 0) { delete _bondLock[nodeOperatorId]; emit BondLockChanged(nodeOperatorId, 0, 0); diff --git a/test/CSAccounting.t.sol b/test/CSAccounting.t.sol index 32fc46a1..6620b20b 100644 --- a/test/CSAccounting.t.sol +++ b/test/CSAccounting.t.sol @@ -22,6 +22,10 @@ import { WithdrawalQueueMockBase, WithdrawalQueueMock } from "./helpers/mocks/Wi import { Utilities } from "./helpers/Utilities.sol"; import { Fixtures } from "./helpers/Fixtures.sol"; +// todo: non-existing node operator tests +// todo: bond lock permission tests +// todo: bond lock emit event tests + contract CSAccountingForTests is CSAccounting { constructor( uint256[] memory bondCurve, diff --git a/test/CSBondLock.t.sol b/test/CSBondLock.t.sol index 6213ea92..fe77009c 100644 --- a/test/CSBondLock.t.sol +++ b/test/CSBondLock.t.sol @@ -5,7 +5,6 @@ pragma solidity 0.8.21; import "forge-std/Test.sol"; -import { CSAccountingBase, CSAccounting } from "../src/CSAccounting.sol"; import { CSBondLockBase, CSBondLock } from "../src/CSBondLock.sol"; import { PermitTokenBase } from "./helpers/Permit.sol"; import { Stub } from "./helpers/mocks/Stub.sol"; @@ -19,558 +18,397 @@ import { WithdrawalQueueMockBase, WithdrawalQueueMock } from "./helpers/mocks/Wi import { Utilities } from "./helpers/Utilities.sol"; import { Fixtures } from "./helpers/Fixtures.sol"; -contract CSAccounting_revealed is CSAccounting { - constructor( - uint256[] memory bondCurve, - address admin, - address lidoLocator, - address wstETH, - address communityStakingModule, - uint256 blockedBondRetentionPeriod, - uint256 blockedBondManagementPeriod - ) - CSAccounting( - bondCurve, - admin, - lidoLocator, - wstETH, - communityStakingModule, - blockedBondRetentionPeriod, - blockedBondManagementPeriod - ) - {} - - function _bondShares_set_value( +abstract contract CSBondLockTestableBase { + event BondPenalized( uint256 nodeOperatorId, - uint256 value - ) public { - _bondShares[nodeOperatorId] = value; + uint256 penaltyEth, + uint256 coveringEth + ); +} + +contract CSBondLockTestable is CSBondLockTestableBase, CSBondLock { + constructor( + uint256 retentionPeriod, + uint256 managementPeriod + ) CSBondLock(retentionPeriod, managementPeriod) {} + + function setBondLockPeriods( + uint256 retention, + uint256 management + ) external { + _setBondLockPeriods(retention, management); } - function _blockedBondEther_get_value( - uint256 nodeOperatorId - ) public view returns (BondLock memory) { - return _bondLock[nodeOperatorId]; + function get(uint256 nodeOperatorId) external view returns (uint256) { + return _get(nodeOperatorId); } - function _blockedBondEther_set_value( - uint256 nodeOperatorId, - BondLock memory value - ) public { - _bondLock[nodeOperatorId] = value; + function lock(uint256 nodeOperatorId, uint256 amount) external { + _lock(nodeOperatorId, amount); } - function _changeBlockedBondState_revealed( - uint256 nodeOperatorId, - uint256 ETHAmount, - uint256 retentionUntil - ) public { - _changeBondLock(nodeOperatorId, ETHAmount, retentionUntil); + function settle(uint256[] memory nodeOperatorIds) external { + _settle(nodeOperatorIds); } - function _reduceBlockedBondETH_revealed( - uint256 nodeOperatorId, - uint256 ETHAmount - ) public { - _reduceAmount(nodeOperatorId, ETHAmount); + function release(uint256 nodeOperatorId, uint256 amount) external { + _release(nodeOperatorId, amount); } -} -contract CSAccounting_BlockedBondTest is - Test, - Fixtures, - Utilities, - CSBondLockBase, - CSAccountingBase -{ - using stdStorage for StdStorage; + function compensate(uint256 nodeOperatorId, uint256 amount) external { + _compensate(nodeOperatorId, amount); + } - LidoLocatorMock internal locator; - WstETHMock internal wstETH; - LidoMock internal stETH; + uint256 internal _mockedBondAmountForPenalize; - Stub internal burner; + function _penalize( + uint256 nodeOperatorId, + uint256 amount + ) internal override returns (uint256) { + uint256 toPenalize = amount < _mockedBondAmountForPenalize + ? amount + : _mockedBondAmountForPenalize; + emit BondPenalized(nodeOperatorId, amount, toPenalize); + return amount - toPenalize; + } - CSAccounting_revealed public accounting; - CommunityStakingModuleMock public stakingModule; - CommunityStakingFeeDistributorMock public feeDistributor; + function mock_bondAmount(uint256 amount) external { + _mockedBondAmountForPenalize = amount; + } +} - address internal admin; - address internal user; - address internal stranger; +contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { + CSBondLockTestable public bondLock; function setUp() public { - admin = address(1); - - user = address(2); - stranger = address(777); - - (locator, wstETH, stETH, burner) = initLido(); - - stakingModule = new CommunityStakingModuleMock(); - uint256[] memory curve = new uint256[](2); - curve[0] = 2 ether; - curve[1] = 4 ether; - accounting = new CSAccounting_revealed( - curve, - admin, - address(locator), - address(wstETH), - address(stakingModule), - 8 weeks, - 1 days - ); - feeDistributor = new CommunityStakingFeeDistributorMock( - address(locator), - address(accounting) - ); - vm.startPrank(admin); - accounting.setFeeDistributor(address(feeDistributor)); - accounting.grantRole(accounting.INSTANT_PENALIZE_BOND_ROLE(), admin); - accounting.grantRole( - accounting.EL_REWARDS_STEALING_PENALTY_INIT_ROLE(), - admin - ); - accounting.grantRole( - accounting.EL_REWARDS_STEALING_PENALTY_SETTLE_ROLE(), - admin - ); - vm.stopPrank(); + bondLock = new CSBondLockTestable(8 weeks, 1 days); } - function test_private_changeBlockedBondState() public { - uint256 noId = 0; - uint256 amount = 1 ether; - uint256 retentionUntil = block.timestamp + 1 weeks; + function test_setBondLockPeriods() public { + uint256 retention = 4 weeks; + uint256 management = 2 days; - vm.expectEmit(true, true, true, true, address(accounting)); - emit BondLockChanged(noId, amount, retentionUntil); - accounting._changeBlockedBondState_revealed({ - nodeOperatorId: noId, - ETHAmount: amount, - retentionUntil: retentionUntil - }); + bondLock.setBondLockPeriods(retention, management); - CSBondLock.BondLock memory value = accounting - ._blockedBondEther_get_value(noId); + (uint256 _retention, uint256 _management) = bondLock + .getBondLockPeriods(); + assertEq(_retention, retention); + assertEq(_management, management); + } - assertEq(value.amount, amount); - assertEq(value.retentionUntil, retentionUntil); + function test_setBondLockPeriods_RevertWhen_RetentionLessThanMin() public { + vm.expectRevert(InvalidBondLockRetentionPeriod.selector); + bondLock.setBondLockPeriods(3 weeks, 1 days); + } - vm.expectEmit(true, true, true, true, address(accounting)); - emit BondLockChanged(noId, 0, 0); + function test_setBondLockPeriods_RevertWhen_RetentionGreaterThanMax() + public + { + vm.expectRevert(InvalidBondLockRetentionPeriod.selector); + bondLock.setBondLockPeriods(366 days, 1 days); + } - accounting._changeBlockedBondState_revealed({ - nodeOperatorId: noId, - ETHAmount: 0, - retentionUntil: 0 - }); + function test_setBondLockPeriods_RevertWhen_ManagementLessThanMin() public { + vm.expectRevert(InvalidBondLockRetentionPeriod.selector); + bondLock.setBondLockPeriods(8 weeks, 23 hours); + } - value = accounting._blockedBondEther_get_value(noId); + function test_setBondLockPeriods_RevertWhen_ManagementGreaterThanMax() + public + { + vm.expectRevert(InvalidBondLockRetentionPeriod.selector); + bondLock.setBondLockPeriods(8 weeks, 8 days); + } + + function test_get() public { + uint256 noId = 0; + uint256 amount = 1 ether; + bondLock.lock(noId, amount); - assertEq(value.amount, 0); - assertEq(value.retentionUntil, 0); + uint256 value = bondLock.get(noId); + assertEq(value, amount); } - function test_initELRewardsStealingPenalty() public { - _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); + function test_get_WhenRetentionPeriodIsPassed() public { + uint256 noId = 0; + uint256 amount = 1 ether; + bondLock.lock(noId, amount); + + vm.warp(block.timestamp + 8 weeks + 1 seconds); + + uint256 value = bondLock.get(noId); + assertEq(value, 0); + } + function test_lock() public { uint256 noId = 0; - uint256 proposedBlockNumber = 100500; - uint256 firstStolenAmount = 1 ether; + uint256 amount = 1 ether; - vm.expectEmit(true, true, true, true, address(accounting)); - emit ELRewardsStealingPenaltyInitiated( - noId, - proposedBlockNumber, - firstStolenAmount - ); - vm.expectEmit(true, true, true, true, address(accounting)); - emit BondLockChanged( - noId, - firstStolenAmount, - block.timestamp + 8 weeks - ); + vm.expectEmit(true, true, true, true, address(bondLock)); + emit BondLockChanged(noId, amount, block.timestamp + 8 weeks); - vm.prank(admin); - accounting.initELRewardsStealingPenalty({ - nodeOperatorId: noId, - blockNumber: proposedBlockNumber, - amount: firstStolenAmount - }); + bondLock.lock(noId, amount); - assertEq( - accounting._blockedBondEther_get_value(noId).amount, - firstStolenAmount - ); - assertEq( - accounting._blockedBondEther_get_value(noId).retentionUntil, - block.timestamp + 8 weeks - ); + uint256 value = bondLock.get(noId); + assertEq(value, amount); + } - // new block and new stealing - vm.warp(block.timestamp + 12 seconds); + function test_lock_WhenSecondTime() public { + uint256 noId = 0; + uint256 amount = 1 ether; + bondLock.lock(noId, amount); - uint256 secondStolenAmount = 2 ether; - proposedBlockNumber = 100501; + uint256 newBlockTimestamp = block.timestamp + 1 seconds; + vm.warp(newBlockTimestamp); - vm.expectEmit(true, true, true, true, address(accounting)); - emit ELRewardsStealingPenaltyInitiated( - noId, - proposedBlockNumber, - secondStolenAmount - ); - vm.expectEmit(true, true, true, true, address(accounting)); + vm.expectEmit(true, true, true, true, address(bondLock)); emit BondLockChanged( noId, - firstStolenAmount + secondStolenAmount, - block.timestamp + 8 weeks + amount + 1.5 ether, + newBlockTimestamp + 8 weeks ); + bondLock.lock(noId, 1.5 ether); - vm.prank(admin); - accounting.initELRewardsStealingPenalty({ - nodeOperatorId: noId, - blockNumber: proposedBlockNumber, - amount: secondStolenAmount - }); - - assertEq( - accounting._blockedBondEther_get_value(noId).amount, - firstStolenAmount + secondStolenAmount - ); - assertEq( - accounting._blockedBondEther_get_value(noId).retentionUntil, - block.timestamp + 8 weeks - ); + uint256 value = bondLock.get(noId); + assertEq(value, amount + 1.5 ether); } - function test_initELRewardsStealingPenalty_revertWhenNonExistingOperator() - public - { - vm.expectRevert("node operator does not exist"); - - vm.prank(admin); - accounting.initELRewardsStealingPenalty({ - nodeOperatorId: 0, - blockNumber: 100500, - amount: 100 ether - }); - } - - function test_initELRewardsStealingPenalty_revertWhenZero() public { - _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); - + function test_lock_RevertWhen_ZeroAmount() public { vm.expectRevert(InvalidBondLockAmount.selector); - - vm.prank(admin); - accounting.initELRewardsStealingPenalty({ - nodeOperatorId: 0, - blockNumber: 100500, - amount: 0 - }); + bondLock.lock(0, 0); } - function test_initELRewardsStealingPenalty_revertWhenNoRole() public { - _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); - - vm.expectRevert( - bytes( - Utilities.accessErrorString( - address(stranger), - accounting.EL_REWARDS_STEALING_PENALTY_INIT_ROLE() - ) - ) - ); + function test_settle() public { + uint256[] memory idsToSettle = new uint256[](1); + idsToSettle[0] = 0; + bondLock.lock(0, 1 ether); + bondLock.mock_bondAmount(1 ether); - vm.prank(stranger); - accounting.initELRewardsStealingPenalty({ - nodeOperatorId: 0, - blockNumber: 100500, - amount: 100 ether - }); - } + // more than 1 day (management period) after penalty init + // eligible to settle + vm.warp(block.timestamp + 1 days + 1 seconds); - function test_settleBlockedBondETH() public { - _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); + vm.expectEmit(true, true, true, true, address(bondLock)); + emit BondPenalized(0, 1 ether, 1 ether); - vm.deal(user, 12 ether); - vm.startPrank(user); - stETH.submit{ value: 12 ether }({ _referal: address(0) }); - accounting.depositStETH(user, 0, 12 ether); - vm.stopPrank(); + vm.expectEmit(true, true, true, true, address(bondLock)); + emit BondLockChanged(0, 0, 0); - uint256[] memory nosToPenalize = new uint256[](2); - nosToPenalize[0] = 0; - // non-existing node operator should be skipped in the loop - nosToPenalize[1] = 100500; + bondLock.settle(idsToSettle); - uint256 retentionUntil = block.timestamp + 8 weeks; + uint256 value = bondLock.get(0); + assertEq(value, 0 ether); + } - accounting._blockedBondEther_set_value( - 0, - CSBondLock.BondLock({ - amount: 1 ether, - retentionUntil: retentionUntil - }) - ); + function test_settle_WhenLockIsLessThanBond() public { + uint256[] memory idsToSettle = new uint256[](1); + idsToSettle[0] = 0; + bondLock.lock(0, 0.7 ether); + bondLock.mock_bondAmount(1 ether); - // less than 1 day after penalty init - vm.warp(block.timestamp + 20 hours); + // more than 1 day (management period) after penalty init + // eligible to settle + vm.warp(block.timestamp + 1 days + 1 seconds); - vm.prank(admin); - accounting.settleBlockedBondETH(nosToPenalize); + vm.expectEmit(true, true, true, true, address(bondLock)); + emit BondPenalized(0, 0.7 ether, 0.7 ether); - CSBondLock.BondLock memory value = accounting - ._blockedBondEther_get_value(0); + vm.expectEmit(true, true, true, true, address(bondLock)); + emit BondLockChanged(0, 0, 0); - assertEq(value.amount, 1 ether); - assertEq(value.retentionUntil, retentionUntil); + bondLock.settle(idsToSettle); - // penalty amount is less than the bond - vm.warp(block.timestamp + 2 days); + uint256 value = bondLock.get(0); + assertEq(value, 0); + } - uint256 penalty = stETH.getPooledEthByShares( - stETH.getSharesByPooledEth(1 ether) - ); - uint256 covering = penalty; + function test_settle_WhenLockIsGreaterThanBond() public { + uint256[] memory idsToSettle = new uint256[](1); + idsToSettle[0] = 0; + bondLock.lock(0, 1 ether); + uint256 retentionPeriodWhenLock = block.timestamp + 8 weeks; + bondLock.mock_bondAmount(0.7 ether); - vm.expectEmit(true, true, true, true, address(accounting)); - emit BondPenalized(0, penalty, covering); + // more than 1 day (management period) after penalty init + // eligible to settle + vm.warp(block.timestamp + 1 days + 1 seconds); - vm.expectEmit(true, true, true, true, address(accounting)); - emit BondLockChanged(0, 0, 0); + vm.expectEmit(true, true, true, true, address(bondLock)); + emit BondPenalized(0, 1 ether, 0.7 ether); - vm.prank(admin); - accounting.settleBlockedBondETH(nosToPenalize); + vm.expectEmit(true, true, true, true, address(bondLock)); + emit BondLockChanged(0, 0.3 ether, retentionPeriodWhenLock); - value = accounting._blockedBondEther_get_value(0); - assertEq(value.amount, 0); - assertEq(value.retentionUntil, 0); + bondLock.settle(idsToSettle); - // penalty amount is greater than the bond - accounting._blockedBondEther_set_value( - 0, - CSBondLock.BondLock({ - amount: 100 ether, - retentionUntil: retentionUntil - }) - ); + uint256 value = bondLock.get(0); + assertEq(value, 0.3 ether); + } - penalty = stETH.getPooledEthByShares( - stETH.getSharesByPooledEth(100 ether) - ); - covering = 11 ether; - uint256 uncovered = penalty - covering; + function test_settle_WhenRetentionPeriodIsExpired() public { + uint256[] memory idsToSettle = new uint256[](1); + idsToSettle[0] = 0; + bondLock.lock(0, 1 ether); - vm.expectEmit(true, true, true, true, address(accounting)); - emit BondPenalized(0, penalty, covering); + // more than 8 weeks (retention period) after penalty init + // not eligible already + vm.warp(block.timestamp + 8 weeks + 1 seconds); - vm.expectEmit(true, true, true, true, address(accounting)); - emit BondLockChanged(0, uncovered, retentionUntil); + vm.recordLogs(); - vm.prank(admin); - accounting.settleBlockedBondETH(nosToPenalize); + bondLock.settle(idsToSettle); - value = accounting._blockedBondEther_get_value(0); - assertEq(value.amount, uncovered); - assertEq(value.retentionUntil, retentionUntil); + uint256 value = bondLock.get(0); + assertEq(value, 0 ether); - // retention period expired - accounting._blockedBondEther_set_value( - 0, - CSBondLock.BondLock({ - amount: 100 ether, - retentionUntil: retentionUntil - }) + assertEq( + vm.getRecordedLogs().length, + 1, + "should not emit BondPenalized event" ); - vm.warp(retentionUntil + 12); - - vm.expectEmit(true, true, true, true, address(accounting)); - emit BondLockChanged(0, 0, 0); - - vm.prank(admin); - accounting.settleBlockedBondETH(nosToPenalize); - - value = accounting._blockedBondEther_get_value(0); - assertEq(value.amount, 0); - assertEq(value.retentionUntil, 0); } - function test_private_reduceBlockedBondETH() public { - uint256 noId = 0; - uint256 amount = 100 ether; - uint256 retentionUntil = block.timestamp + 1 weeks; + function test_settle_WhenInManagementPeriod() public { + uint256[] memory idsToSettle = new uint256[](1); + idsToSettle[0] = 0; + bondLock.lock(0, 1 ether); - accounting._blockedBondEther_set_value( - noId, - CSBondLock.BondLock({ - amount: amount, - retentionUntil: retentionUntil - }) - ); + // less than 1 day (management period) after penalty init + // not eligible to settle yet + vm.warp(block.timestamp + 20 hours); - // part of blocked bond is released - uint256 toReduce = 10 ether; - uint256 rest = amount - toReduce; + vm.recordLogs(); - vm.expectEmit(true, true, true, true, address(accounting)); - emit BondLockChanged(noId, rest, retentionUntil); + bondLock.settle(idsToSettle); - accounting._reduceBlockedBondETH_revealed(noId, toReduce); + uint256 value = bondLock.get(0); + assertEq(value, 1 ether); - CSBondLock.BondLock memory value = accounting - ._blockedBondEther_get_value(noId); + assertEq(vm.getRecordedLogs().length, 0, "should not emit any events"); + } - assertEq(value.amount, rest); - assertEq(value.retentionUntil, retentionUntil); + function test_settle_WhenDifferentStates() public { + // one eligible, one expired, one in management period + uint256[] memory idsToSettle = new uint256[](3); + idsToSettle[0] = 0; + idsToSettle[1] = 1; + idsToSettle[2] = 2; + + // more than 8 weeks (retention period) after penalty init + // not eligible already + bondLock.lock(0, 1 ether); + vm.warp(block.timestamp + 8 weeks + 1 seconds); + + // more than 1 day (management period) after penalty init + // eligible to settle + bondLock.lock(1, 1 ether); + bondLock.mock_bondAmount(1 ether); + vm.warp(block.timestamp + 1 days + 1 seconds); + + // less than 1 day (management period) after penalty init + // not eligible to settle yet + bondLock.lock(2, 1 ether); + + vm.expectEmit(true, true, true, true, address(bondLock)); + emit BondLockChanged(0, 0, 0); - // all blocked bond is released - toReduce = rest; - rest = 0; - retentionUntil = 0; + vm.expectEmit(true, true, true, true, address(bondLock)); + emit BondPenalized(1, 1 ether, 1 ether); - vm.expectEmit(true, true, true, true, address(accounting)); - emit BondLockChanged(noId, rest, retentionUntil); + vm.expectEmit(true, true, true, true, address(bondLock)); + emit BondLockChanged(1, 0, 0); - accounting._reduceBlockedBondETH_revealed(noId, toReduce); + vm.recordLogs(); - value = accounting._blockedBondEther_get_value(noId); + bondLock.settle(idsToSettle); - assertEq(value.amount, rest); - assertEq(value.retentionUntil, retentionUntil); - } + uint256 value = bondLock.get(0); + assertEq(value, 0 ether); - function test_private_reduceBlockedBondETH_revertWhenNoBlocked() public { - vm.expectRevert(InvalidBondLockAmount.selector); - accounting._reduceBlockedBondETH_revealed(0, 1 ether); - } + value = bondLock.get(1); + assertEq(value, 0 ether); - function test_private_reduceBlockedBondETH_revertWhenAmountGreaterThanBlocked() - public - { - uint256 noId = 0; - uint256 amount = 100 ether; - uint256 retentionUntil = block.timestamp + 1 weeks; - - accounting._blockedBondEther_set_value( - noId, - CSBondLock.BondLock({ - amount: amount, - retentionUntil: retentionUntil - }) - ); + value = bondLock.get(2); + assertEq(value, 1 ether); - vm.expectRevert(InvalidBondLockAmount.selector); - accounting._reduceBlockedBondETH_revealed(0, 101 ether); + assertEq(vm.getRecordedLogs().length, 3, "should emit 3 events"); } - function test_releaseBlockedBondETH() public { - _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); - + function test_release() public { uint256 noId = 0; uint256 amount = 100 ether; - uint256 retentionUntil = block.timestamp + 1 weeks; - accounting._blockedBondEther_set_value( - noId, - CSBondLock.BondLock({ - amount: amount, - retentionUntil: retentionUntil - }) - ); + bondLock.lock(noId, amount); uint256 toRelease = 10 ether; uint256 rest = amount - toRelease; - vm.expectEmit(true, true, true, true, address(accounting)); + vm.expectEmit(true, true, true, true, address(bondLock)); emit BondLockReleased(noId, toRelease); - vm.expectEmit(true, true, true, true, address(accounting)); - emit BondLockChanged(noId, rest, retentionUntil); + vm.expectEmit(true, true, true, true, address(bondLock)); + emit BondLockChanged(noId, rest, block.timestamp + 8 weeks); - vm.prank(admin); - accounting.releaseBlockedBondETH(noId, toRelease); - } + bondLock.release(noId, toRelease); - function test_releaseBlockedBondETH_revertWhenNonExistingOperator() public { - vm.expectRevert("node operator does not exist"); + uint256 value = bondLock.get(noId); + assertEq(value, rest); - vm.prank(admin); - accounting.releaseBlockedBondETH(0, 1 ether); + vm.warp(block.timestamp + 8 weeks + 1 seconds); + value = bondLock.get(noId); + assertEq(value, 0); } - function test_releaseBlockedBondETH_revertWhenNoRole() public { - _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); + function test_release_RevertWhen_ZeroAmount() public { + vm.expectRevert(InvalidBondLockAmount.selector); + bondLock.release(0, 0); + } - vm.expectRevert( - bytes( - Utilities.accessErrorString( - address(stranger), - accounting.EL_REWARDS_STEALING_PENALTY_INIT_ROLE() - ) - ) - ); + function test_release_RevertWhen_GreaterThanLock() public { + uint256 noId = 0; + uint256 amount = 100 ether; - vm.prank(stranger); - accounting.releaseBlockedBondETH(0, 1 ether); - } + bondLock.lock(noId, amount); - function test_compensateBlockedBondETH() public { - _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); + vm.expectRevert(InvalidBondLockAmount.selector); + bondLock.release(noId, amount + 1 ether); + } + function test_compensate() public { uint256 noId = 0; uint256 amount = 100 ether; - uint256 retentionUntil = block.timestamp + 1 weeks; - accounting._blockedBondEther_set_value( - noId, - CSBondLock.BondLock({ - amount: amount, - retentionUntil: retentionUntil - }) - ); + bondLock.lock(noId, amount); uint256 toCompensate = 10 ether; uint256 rest = amount - toCompensate; - vm.expectEmit(true, true, true, true, address(accounting)); + vm.expectEmit(true, true, true, true, address(bondLock)); emit BondLockCompensated(noId, toCompensate); - vm.expectEmit(true, true, true, true, address(accounting)); - emit BondLockChanged(noId, rest, retentionUntil); + vm.expectEmit(true, true, true, true, address(bondLock)); + emit BondLockChanged(noId, rest, block.timestamp + 8 weeks); - vm.deal(user, toCompensate); - vm.prank(user); - accounting.compensateBlockedBondETH{ value: toCompensate }(noId); + bondLock.compensate(noId, toCompensate); - assertEq(address(locator.elRewardsVault()).balance, toCompensate); - } + uint256 value = bondLock.get(noId); + assertEq(value, rest); - function test_compensateBlockedBondETH_revertWhenZero() public { - _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); + vm.warp(block.timestamp + 8 weeks + 1 seconds); + value = bondLock.get(noId); + assertEq(value, 0); + } + function test_compensate_RevertWhen_ZeroAmount() public { vm.expectRevert(InvalidBondLockAmount.selector); - accounting.compensateBlockedBondETH{ value: 0 }(0); + bondLock.compensate(0, 0); } - function test_compensateBlockedBondETH_revertWhenNonExistingOperator() - public - { - vm.expectRevert("node operator does not exist"); - accounting.compensateBlockedBondETH{ value: 1 ether }(0); - } + function test_compensate_RevertWhen_GreaterThanLock() public { + uint256 noId = 0; + uint256 amount = 100 ether; + + bondLock.lock(noId, amount); - function _createNodeOperator( - uint64 ongoingVals, - uint64 withdrawnVals - ) internal { - stakingModule.setNodeOperator({ - _nodeOperatorId: 0, - _active: true, - _rewardAddress: user, - _totalVettedValidators: ongoingVals, - _totalExitedValidators: 0, - _totalWithdrawnValidators: withdrawnVals, - _totalAddedValidators: ongoingVals, - _totalDepositedValidators: ongoingVals - }); + vm.expectRevert(InvalidBondLockAmount.selector); + bondLock.compensate(noId, amount + 1 ether); } } From 911cd31a5877559f5dd52e54e9389cf65a1be424 Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Mon, 4 Dec 2023 19:42:01 +0400 Subject: [PATCH 12/18] fix: review --- src/CSBondCurve.sol | 19 ++++++++++++++----- test/CSAccounting.t.sol | 4 ++-- test/CSBondCurve.t.sol | 20 ++++++++++---------- test/CSBondLock.t.sol | 5 ++++- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/CSBondCurve.sol b/src/CSBondCurve.sol index 8b478112..280f2c2c 100644 --- a/src/CSBondCurve.sol +++ b/src/CSBondCurve.sol @@ -4,9 +4,6 @@ pragma solidity 0.8.21; abstract contract CSBondCurve { - error InvalidBondCurveLength(); - error InvalidMultiplier(); - /// @dev Array of bond amounts for particular keys count. /// /// For example: @@ -44,7 +41,7 @@ abstract contract CSBondCurve { /// By default, all Node Operators have x1 multiplier (10000 basis points). /// /// For example: - /// Some Node Operator's bond multiplier is x0.90 (9500 basis points). + /// Some Node Operator's bond multiplier is x0.90 (9000 basis points). /// Bond Curve for this Node Operator will be: /// /// Bond Amount (ETH) @@ -84,7 +81,7 @@ abstract contract CSBondCurve { function _setBondCurve(uint256[] memory _bondCurve) internal { _checkCurveLength(_bondCurve); - // todo: check curve values (not worse than previous and makes sense) + _checkCurveValues(_bondCurve); bondCurve = _bondCurve; _bondCurveTrend = _bondCurve[_bondCurve.length - 1] - @@ -115,6 +112,14 @@ abstract contract CSBondCurve { revert InvalidBondCurveLength(); } + function _checkCurveValues(uint256[] memory xy) internal pure { + // todo: check curve values (not worse than previous and makes sense) + if (xy[0] == 0) revert InvalidBondCurveValues(); + for (uint256 i = 1; i < xy.length; i++) { + if (xy[i] <= xy[i - 1]) revert InvalidBondCurveValues(); + } + } + function _checkMultiplier(uint256 multiplier) internal pure { if ( multiplier < MIN_BOND_MULTIPLIER || multiplier > MAX_BOND_MULTIPLIER @@ -186,4 +191,8 @@ abstract contract CSBondCurve { (keys - bondCurve.length) * ((_bondCurveTrend * multiplier) / BASIS_POINTS); } + + error InvalidBondCurveLength(); + error InvalidBondCurveValues(); + error InvalidMultiplier(); } diff --git a/test/CSAccounting.t.sol b/test/CSAccounting.t.sol index 6620b20b..ebd553de 100644 --- a/test/CSAccounting.t.sol +++ b/test/CSAccounting.t.sol @@ -2396,12 +2396,12 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { ); assertEq( stETH.sharesOf(address(accounting)), - bondSharesAfter, + bondSharesBefore, "bond manager after claim should be equal to before" ); assertEq( accounting.totalBondShares(), - bondSharesAfter, + bondSharesBefore, "total bond shares after claim should be equal to before" ); } diff --git a/test/CSBondCurve.t.sol b/test/CSBondCurve.t.sol index db5ff36f..17b3ff63 100644 --- a/test/CSBondCurve.t.sol +++ b/test/CSBondCurve.t.sol @@ -68,7 +68,7 @@ contract CSBondCurveTest is Test { } function test_setBondCurve() public { - uint256[] memory _bondCurve = new uint256[](11); + uint256[] memory _bondCurve = new uint256[](2); _bondCurve[0] = 16 ether; _bondCurve[1] = 32 ether; @@ -79,20 +79,20 @@ contract CSBondCurveTest is Test { } function test_setBondCurve_RevertWhen_LessThanMinBondCurveLength() public { - uint256[] memory _bondCurve = new uint256[](0); - vm.expectRevert(CSBondCurve.InvalidBondCurveLength.selector); - bondCurve.setBondCurve(_bondCurve); + bondCurve.setBondCurve(new uint256[](0)); } function test_setBondCurve_RevertWhen_MoreThanMaxBondCurveLength() public { - uint256[] memory _bondCurve = new uint256[](21); - _bondCurve = new uint256[](21); - for (uint256 i = 0; i < 21; i++) { - _bondCurve[i] = i; - } - vm.expectRevert(CSBondCurve.InvalidBondCurveLength.selector); + bondCurve.setBondCurve(new uint256[](21)); + } + + function test_setBondCurve_RevertWhen_ZeroValue() public { + vm.expectRevert(CSBondCurve.InvalidBondCurveValues.selector); + + uint256[] memory _bondCurve = new uint256[](1); + _bondCurve[0] = 0 ether; bondCurve.setBondCurve(_bondCurve); } diff --git a/test/CSBondLock.t.sol b/test/CSBondLock.t.sol index fe77009c..383dd60c 100644 --- a/test/CSBondLock.t.sol +++ b/test/CSBondLock.t.sol @@ -254,6 +254,9 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { // not eligible already vm.warp(block.timestamp + 8 weeks + 1 seconds); + vm.expectEmit(true, true, true, true, address(bondLock)); + emit BondLockChanged(0, 0, 0); + vm.recordLogs(); bondLock.settle(idsToSettle); @@ -264,7 +267,7 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { assertEq( vm.getRecordedLogs().length, 1, - "should not emit BondPenalized event" + "should NOT emit BondPenalized event" ); } From 87c4741d745cf2a9ff7a82bdd8378024c6a293ab Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Mon, 4 Dec 2023 19:54:25 +0400 Subject: [PATCH 13/18] fix: tests --- src/CSModule.sol | 11 ----------- test/CSModule.t.sol | 22 +++++++++++----------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/CSModule.sol b/src/CSModule.sol index d86c5fbe..99c962a6 100644 --- a/src/CSModule.sol +++ b/src/CSModule.sol @@ -36,17 +36,6 @@ struct NodeOperator { uint256 queueNonce; } -struct NodeOperatorInfo { - bool active; - address managerAddress; - address rewardAddress; - uint256 totalVettedValidators; - uint256 totalExitedValidators; - uint256 totalWithdrawnValidators; - uint256 totalAddedValidators; - uint256 totalDepositedValidators; -} - contract CSModuleBase { event NodeOperatorAdded(uint256 indexed nodeOperatorId, address from); event NodeOperatorManagerAddressChangeProposed( diff --git a/test/CSModule.t.sol b/test/CSModule.t.sol index ca0d55ff..9f270088 100644 --- a/test/CSModule.t.sol +++ b/test/CSModule.t.sol @@ -1101,7 +1101,7 @@ contract CsmRemoveKeys is CSMCommon { }); assertEq(obtainedKeys, bytes.concat(key4, key3), "unexpected keys"); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.totalAddedValidators, 2); } @@ -1139,7 +1139,7 @@ contract CsmRemoveKeys is CSMCommon { "unexpected keys" ); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.totalAddedValidators, 3); } @@ -1177,7 +1177,7 @@ contract CsmRemoveKeys is CSMCommon { "unexpected keys" ); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.totalAddedValidators, 3); } @@ -1215,7 +1215,7 @@ contract CsmRemoveKeys is CSMCommon { "unexpected keys" ); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.totalAddedValidators, 3); } @@ -1234,7 +1234,7 @@ contract CsmRemoveKeys is CSMCommon { csm.removeKeys({ nodeOperatorId: noId, startIndex: 0, keysCount: 5 }); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.totalAddedValidators, 0); } @@ -1263,7 +1263,7 @@ contract CsmRemoveKeys is CSMCommon { } csm.removeKeys({ nodeOperatorId: noId, startIndex: 1, keysCount: 2 }); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.totalVettedValidators, 1); } @@ -1285,7 +1285,7 @@ contract CsmRemoveKeys is CSMCommon { */ csm.removeKeys({ nodeOperatorId: noId, startIndex: 3, keysCount: 2 }); - NodeOperatorInfo memory no = csm.getNodeOperator(0); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(0); assertEq(no.totalVettedValidators, 3); } @@ -1416,7 +1416,7 @@ contract CsmGetNodeOperatorSummary is CSMCommon { assertTrue(summary.isTargetLimitActive); assertEq(summary.targetValidatorsCount, 2); // should be unvetted - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.totalVettedValidators, 0); } @@ -1474,7 +1474,7 @@ contract CsmUpdateTargetValidatorsLimits is CSMCommon { csm.vetKeys(noId, 1); csm.updateTargetValidatorsLimits(noId, true, 1); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.totalVettedValidators, 0); } @@ -1487,7 +1487,7 @@ contract CsmUpdateTargetValidatorsLimits is CSMCommon { csm.vetKeys(noId, 1); csm.updateTargetValidatorsLimits(noId, true, 2); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.totalVettedValidators, 1); } @@ -1500,7 +1500,7 @@ contract CsmUpdateTargetValidatorsLimits is CSMCommon { csm.vetKeys(noId, 1); csm.updateTargetValidatorsLimits(noId, false, 1); - NodeOperatorInfo memory no = csm.getNodeOperator(noId); + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.totalVettedValidators, 1); } From c77fac2d918c644421e91160eab825725d01d9d4 Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Tue, 5 Dec 2023 12:10:42 +0400 Subject: [PATCH 14/18] fix: review (part 1) --- src/CSAccounting.sol | 65 ++++++++----- src/CSBondLock.sol | 41 ++++---- test/CSBondLock.t.sol | 213 ++++++++++++++++++------------------------ 3 files changed, 153 insertions(+), 166 deletions(-) diff --git a/src/CSAccounting.sol b/src/CSAccounting.sol index d61a9568..cec775e4 100644 --- a/src/CSAccounting.sol +++ b/src/CSAccounting.sol @@ -56,6 +56,8 @@ contract CSAccountingBase { uint256 proposedBlockNumber, uint256 stolenAmount ); + event BondLockCompensated(uint256 indexed nodeOperatorId, uint256 amount); + event BondLockReleased(uint256 indexed nodeOperatorId, uint256 amount); error NotOwnerToClaim(address msgSender, address owner); error InvalidSender(); @@ -79,6 +81,8 @@ contract CSAccounting is keccak256("INSTANT_PENALIZE_BOND_ROLE"); // 0x9909cf24c2d3bafa8c229558d86a1b726ba57c3ef6350848dcf434a4181b56c7 bytes32 public constant EL_REWARDS_STEALING_PENALTY_INIT_ROLE = keccak256("EL_REWARDS_STEALING_PENALTY_INIT_ROLE"); // 0xcc2e7ce7be452f766dd24d55d87a3d42901c31ffa5b600cd1dff475abec91c1f + bytes32 public constant EL_REWARDS_STEALING_PENALTY_RELEASE_ROLE = + keccak256("EL_REWARDS_STEALING_PENALTY_RELEASE_ROLE"); // 0x8d78671045c549f09e0cf6e7e9856c36698f72f93962abf8e1955dc595a592ee bytes32 public constant EL_REWARDS_STEALING_PENALTY_SETTLE_ROLE = keccak256("EL_REWARDS_STEALING_PENALTY_SETTLE_ROLE"); // 0xdf6226649a1ca132f86d419e46892001284368a8f7445b5eb0d3fadf91329fe6 bytes32 public constant SET_BOND_CURVE_ROLE = @@ -100,8 +104,8 @@ contract CSAccounting is /// @param lidoLocator lido locator contract address /// @param wstETH wstETH contract address /// @param communityStakingModule community staking module contract address - /// @param bondLockRetentionPeriod retention period for blocked bond in seconds - /// @param bondLockManagementPeriod management period for blocked bond in seconds + /// @param bondLockRetentionPeriod retention period for locked bond in seconds + /// @param bondLockManagementPeriod management period for locked bond in seconds constructor( uint256[] memory bondCurve, address admin, @@ -135,7 +139,7 @@ contract CSAccounting is FEE_DISTRIBUTOR = fdAddress; } - function setBlockedBondPeriods( + function setLockedBondPeriods( uint256 retention, uint256 management ) external onlyRole(DEFAULT_ADMIN_ROLE) { @@ -275,13 +279,24 @@ contract CSAccounting is return WSTETH.getWstETHByStETH(getMissingBondStETH(nodeOperatorId)); } - /// @notice Returns the amount of ETH blocked by the given node operator. - function getBlockedBondETH( + /// @notice Returns information about the locked bond for the given node operator. + /// @param nodeOperatorId id of the node operator to get locked bond info for. + /// @return locked bond info. + function getLockedBondInfo( uint256 nodeOperatorId - ) public view returns (uint256) { + ) public view returns (CSBondLock.BondLock memory) { return CSBondLock._get(nodeOperatorId); } + /// @notice Returns the amount of locked bond in ETH by the given node operator. + /// @param nodeOperatorId id of the node operator to get locked bond amount. + /// @return amount of locked bond in ETH. + function getActualLockedBondETH( + uint256 nodeOperatorId + ) public view returns (uint256) { + return CSBondLock._getActualAmount(nodeOperatorId); + } + /// @notice Returns the required bond ETH (inc. missed and excess) for the given node operator to upload new keys. /// @param nodeOperatorId id of the node operator to get required bond for. /// @return required bond ETH. @@ -377,10 +392,10 @@ contract CSAccounting is ) public view returns (uint256) { uint256 activeKeys = _getNodeOperatorActiveKeys(nodeOperatorId); uint256 currentBond = _ethByShares(_bondShares[nodeOperatorId]); - uint256 blockedBond = getBlockedBondETH(nodeOperatorId); - if (currentBond > blockedBond) { + uint256 lockedBond = getActualLockedBondETH(nodeOperatorId); + if (currentBond > lockedBond) { uint256 multiplier = getBondMultiplier(nodeOperatorId); - currentBond -= blockedBond; + currentBond -= lockedBond; uint256 bondedKeys = _getKeysCountByBondAmount( currentBond, multiplier @@ -696,33 +711,35 @@ contract CSAccounting is CSBondLock._lock(nodeOperatorId, amount); } - /// @notice Releases blocked bond ETH for the given node operator. - /// @param nodeOperatorId id of the node operator to release blocked bond for. + /// @notice Releases locked bond ETH for the given node operator. + /// @param nodeOperatorId id of the node operator to release locked bond for. /// @param amount amount of ETH to release. - function releaseBlockedBondETH( + function releaseLockedBondETH( uint256 nodeOperatorId, uint256 amount ) external - onlyRole(EL_REWARDS_STEALING_PENALTY_INIT_ROLE) + onlyRole(EL_REWARDS_STEALING_PENALTY_RELEASE_ROLE) onlyExistingNodeOperator(nodeOperatorId) { - CSBondLock._release(nodeOperatorId, amount); + CSBondLock._reduceAmount(nodeOperatorId, amount); + emit BondLockReleased(nodeOperatorId, amount); } - /// @notice Compensates blocked bond ETH for the given node operator. - /// @param nodeOperatorId id of the node operator to compensate blocked bond for. - function compensateBlockedBondETH( + /// @notice Compensates locked bond ETH for the given node operator. + /// @param nodeOperatorId id of the node operator to compensate locked bond for. + function compensateLockedBondETH( uint256 nodeOperatorId ) external payable onlyExistingNodeOperator(nodeOperatorId) { - CSBondLock._compensate(nodeOperatorId, msg.value); payable(LIDO_LOCATOR.elRewardsVault()).transfer(msg.value); + CSBondLock._reduceAmount(nodeOperatorId, msg.value); + emit BondLockCompensated(nodeOperatorId, msg.value); } - /// @dev Should be called by the committee. Doesn't settle blocked bond if it is in the safe frame (1 day) - /// @notice Settles blocked bond for the given node operators. - /// @param nodeOperatorIds ids of the node operators to settle blocked bond for. - function settleBlockedBondETH( + /// @dev Should be called by the committee. Doesn't settle locked bond if it is in the safe frame (1 day) + /// @notice Settles locked bond for the given node operators. + /// @param nodeOperatorIds ids of the node operators to settle locked bond for. + function settleLockedBondETH( uint256[] memory nodeOperatorIds ) external onlyRole(EL_REWARDS_STEALING_PENALTY_SETTLE_ROLE) { CSBondLock._settle(nodeOperatorIds); @@ -828,7 +845,7 @@ contract CSAccounting is _getNodeOperatorActiveKeys(nodeOperatorId), getBondMultiplier(nodeOperatorId) ) + - getBlockedBondETH(nodeOperatorId); + getActualLockedBondETH(nodeOperatorId); } function _bondSharesSummary( @@ -842,7 +859,7 @@ contract CSAccounting is getBondMultiplier(nodeOperatorId) ) ) + - _sharesByEth(getBlockedBondETH(nodeOperatorId)); + _sharesByEth(getActualLockedBondETH(nodeOperatorId)); } function _sharesByEth(uint256 ethAmount) internal view returns (uint256) { diff --git a/src/CSBondLock.sol b/src/CSBondLock.sol index 038f59ea..14d5076a 100644 --- a/src/CSBondLock.sol +++ b/src/CSBondLock.sol @@ -9,10 +9,12 @@ abstract contract CSBondLockBase { uint256 newAmount, uint256 retentionUntil ); - event BondLockCompensated(uint256 indexed nodeOperatorId, uint256 amount); - event BondLockReleased(uint256 indexed nodeOperatorId, uint256 amount); + event BondLockPeriodsChanged( + uint256 retentionPeriod, + uint256 managementPeriod + ); - error InvalidBondLockRetentionPeriod(); + error InvalidBondLockPeriods(); error InvalidBondLockAmount(); } @@ -44,6 +46,7 @@ abstract contract CSBondLock is CSBondLockBase { _validateBondLockPeriods(retention, management); _bondLockRetentionPeriod = retention; _bondLockManagementPeriod = management; + emit BondLockPeriodsChanged(retention, management); } function getBondLockPeriods() @@ -64,12 +67,21 @@ abstract contract CSBondLock is CSBondLockBase { management < MIN_BOND_LOCK_MANAGEMENT_PERIOD || management > MAX_BOND_LOCK_MANAGEMENT_PERIOD ) { - revert InvalidBondLockRetentionPeriod(); + revert InvalidBondLockPeriods(); } } + /// @notice Returns the amount and retention time of locked bond by the given node operator. + function _get( + uint256 nodeOperatorId + ) internal view returns (BondLock memory) { + return _bondLock[nodeOperatorId]; + } + /// @notice Returns the amount of locked bond by the given node operator. - function _get(uint256 nodeOperatorId) internal view returns (uint256) { + function _getActualAmount( + uint256 nodeOperatorId + ) internal view returns (uint256) { if (_bondLock[nodeOperatorId].retentionUntil >= block.timestamp) { return _bondLock[nodeOperatorId].amount; } @@ -121,23 +133,8 @@ abstract contract CSBondLock is CSBondLockBase { } } - /// @notice Releases blocked bond ETH for the given node operator. - /// @param nodeOperatorId id of the node operator to release blocked bond for. - /// @param amount amount of ETH to release. - function _release(uint256 nodeOperatorId, uint256 amount) internal { - emit BondLockReleased(nodeOperatorId, amount); - _reduceAmount(nodeOperatorId, amount); - } - - /// @notice Compensates blocked bond ETH for the given node operator. - /// @param nodeOperatorId id of the node operator to compensate blocked bond for. - function _compensate(uint256 nodeOperatorId, uint256 amount) internal { - emit BondLockCompensated(nodeOperatorId, amount); - _reduceAmount(nodeOperatorId, amount); - } - - function _reduceAmount(uint256 nodeOperatorId, uint256 amount) private { - uint256 blocked = _get(nodeOperatorId); + function _reduceAmount(uint256 nodeOperatorId, uint256 amount) internal { + uint256 blocked = _getActualAmount(nodeOperatorId); if (amount == 0) { revert InvalidBondLockAmount(); } diff --git a/test/CSBondLock.t.sol b/test/CSBondLock.t.sol index 383dd60c..aa88f4f5 100644 --- a/test/CSBondLock.t.sol +++ b/test/CSBondLock.t.sol @@ -39,10 +39,16 @@ contract CSBondLockTestable is CSBondLockTestableBase, CSBondLock { _setBondLockPeriods(retention, management); } - function get(uint256 nodeOperatorId) external view returns (uint256) { + function get( + uint256 nodeOperatorId + ) external view returns (CSBondLock.BondLock memory) { return _get(nodeOperatorId); } + function getActualAmount(uint256 amount) external view returns (uint256) { + return _getActualAmount(amount); + } + function lock(uint256 nodeOperatorId, uint256 amount) external { _lock(nodeOperatorId, amount); } @@ -51,29 +57,26 @@ contract CSBondLockTestable is CSBondLockTestableBase, CSBondLock { _settle(nodeOperatorIds); } - function release(uint256 nodeOperatorId, uint256 amount) external { - _release(nodeOperatorId, amount); - } - - function compensate(uint256 nodeOperatorId, uint256 amount) external { - _compensate(nodeOperatorId, amount); + function reduceAmount(uint256 nodeOperatorId, uint256 amount) external { + _reduceAmount(nodeOperatorId, amount); } - uint256 internal _mockedBondAmountForPenalize; + uint256 internal _mockedUncoveredPenalty; function _penalize( uint256 nodeOperatorId, uint256 amount ) internal override returns (uint256) { - uint256 toPenalize = amount < _mockedBondAmountForPenalize - ? amount - : _mockedBondAmountForPenalize; - emit BondPenalized(nodeOperatorId, amount, toPenalize); - return amount - toPenalize; + emit BondPenalized( + nodeOperatorId, + amount, + amount - _mockedUncoveredPenalty + ); + return _mockedUncoveredPenalty; } - function mock_bondAmount(uint256 amount) external { - _mockedBondAmountForPenalize = amount; + function mock_uncoveredPenalty(uint256 amount) external { + _mockedUncoveredPenalty = amount; } } @@ -88,6 +91,9 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { uint256 retention = 4 weeks; uint256 management = 2 days; + vm.expectEmit(true, true, true, true, address(bondLock)); + emit BondLockPeriodsChanged(retention, management); + bondLock.setBondLockPeriods(retention, management); (uint256 _retention, uint256 _management) = bondLock @@ -97,60 +103,62 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { } function test_setBondLockPeriods_RevertWhen_RetentionLessThanMin() public { - vm.expectRevert(InvalidBondLockRetentionPeriod.selector); + vm.expectRevert(InvalidBondLockPeriods.selector); bondLock.setBondLockPeriods(3 weeks, 1 days); } function test_setBondLockPeriods_RevertWhen_RetentionGreaterThanMax() public { - vm.expectRevert(InvalidBondLockRetentionPeriod.selector); + vm.expectRevert(InvalidBondLockPeriods.selector); bondLock.setBondLockPeriods(366 days, 1 days); } function test_setBondLockPeriods_RevertWhen_ManagementLessThanMin() public { - vm.expectRevert(InvalidBondLockRetentionPeriod.selector); + vm.expectRevert(InvalidBondLockPeriods.selector); bondLock.setBondLockPeriods(8 weeks, 23 hours); } function test_setBondLockPeriods_RevertWhen_ManagementGreaterThanMax() public { - vm.expectRevert(InvalidBondLockRetentionPeriod.selector); + vm.expectRevert(InvalidBondLockPeriods.selector); bondLock.setBondLockPeriods(8 weeks, 8 days); } - function test_get() public { + function test_getActualAmount() public { uint256 noId = 0; uint256 amount = 1 ether; bondLock.lock(noId, amount); - uint256 value = bondLock.get(noId); + uint256 value = bondLock.getActualAmount(noId); assertEq(value, amount); } - function test_get_WhenRetentionPeriodIsPassed() public { + function test_getActualAmount_WhenRetentionPeriodIsPassed() public { uint256 noId = 0; uint256 amount = 1 ether; bondLock.lock(noId, amount); vm.warp(block.timestamp + 8 weeks + 1 seconds); - uint256 value = bondLock.get(noId); + uint256 value = bondLock.getActualAmount(noId); assertEq(value, 0); } function test_lock() public { uint256 noId = 0; uint256 amount = 1 ether; + uint256 retentionUntil = block.timestamp + 8 weeks; vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondLockChanged(noId, amount, block.timestamp + 8 weeks); + emit BondLockChanged(noId, amount, retentionUntil); bondLock.lock(noId, amount); - uint256 value = bondLock.get(noId); - assertEq(value, amount); + CSBondLock.BondLock memory lock = bondLock.get(noId); + assertEq(lock.amount, amount); + assertEq(lock.retentionUntil, retentionUntil); } function test_lock_WhenSecondTime() public { @@ -169,8 +177,9 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { ); bondLock.lock(noId, 1.5 ether); - uint256 value = bondLock.get(noId); - assertEq(value, amount + 1.5 ether); + CSBondLock.BondLock memory lock = bondLock.get(noId); + assertEq(lock.amount, amount + 1.5 ether); + assertEq(lock.retentionUntil, newBlockTimestamp + 8 weeks); } function test_lock_RevertWhen_ZeroAmount() public { @@ -179,73 +188,56 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { } function test_settle() public { + uint256 noId = 0; uint256[] memory idsToSettle = new uint256[](1); - idsToSettle[0] = 0; + idsToSettle[0] = noId; bondLock.lock(0, 1 ether); - bondLock.mock_bondAmount(1 ether); + bondLock.mock_uncoveredPenalty(0 ether); // more than 1 day (management period) after penalty init // eligible to settle vm.warp(block.timestamp + 1 days + 1 seconds); vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondPenalized(0, 1 ether, 1 ether); + emit BondPenalized(noId, 1 ether, 1 ether); vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondLockChanged(0, 0, 0); + emit BondLockChanged(noId, 0, 0); bondLock.settle(idsToSettle); - uint256 value = bondLock.get(0); - assertEq(value, 0 ether); + CSBondLock.BondLock memory lock = bondLock.get(noId); + assertEq(lock.amount, 0 ether); + assertEq(lock.retentionUntil, 0); } - function test_settle_WhenLockIsLessThanBond() public { - uint256[] memory idsToSettle = new uint256[](1); - idsToSettle[0] = 0; - bondLock.lock(0, 0.7 ether); - bondLock.mock_bondAmount(1 ether); - - // more than 1 day (management period) after penalty init - // eligible to settle - vm.warp(block.timestamp + 1 days + 1 seconds); - - vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondPenalized(0, 0.7 ether, 0.7 ether); - - vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondLockChanged(0, 0, 0); - - bondLock.settle(idsToSettle); - - uint256 value = bondLock.get(0); - assertEq(value, 0); - } - - function test_settle_WhenLockIsGreaterThanBond() public { + function test_settle_WhenUncovered() public { + uint256 noId = 0; uint256[] memory idsToSettle = new uint256[](1); idsToSettle[0] = 0; bondLock.lock(0, 1 ether); uint256 retentionPeriodWhenLock = block.timestamp + 8 weeks; - bondLock.mock_bondAmount(0.7 ether); + bondLock.mock_uncoveredPenalty(0.3 ether); // more than 1 day (management period) after penalty init // eligible to settle vm.warp(block.timestamp + 1 days + 1 seconds); vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondPenalized(0, 1 ether, 0.7 ether); + emit BondPenalized(noId, 1 ether, 0.7 ether); vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondLockChanged(0, 0.3 ether, retentionPeriodWhenLock); + emit BondLockChanged(noId, 0.3 ether, retentionPeriodWhenLock); bondLock.settle(idsToSettle); - uint256 value = bondLock.get(0); - assertEq(value, 0.3 ether); + CSBondLock.BondLock memory lock = bondLock.get(noId); + assertEq(lock.amount, 0.3 ether); + assertEq(lock.retentionUntil, retentionPeriodWhenLock); } function test_settle_WhenRetentionPeriodIsExpired() public { + uint256 noId = 0; uint256[] memory idsToSettle = new uint256[](1); idsToSettle[0] = 0; bondLock.lock(0, 1 ether); @@ -255,14 +247,15 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { vm.warp(block.timestamp + 8 weeks + 1 seconds); vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondLockChanged(0, 0, 0); + emit BondLockChanged(noId, 0, 0); vm.recordLogs(); bondLock.settle(idsToSettle); - uint256 value = bondLock.get(0); - assertEq(value, 0 ether); + CSBondLock.BondLock memory lock = bondLock.get(noId); + assertEq(lock.amount, 0 ether); + assertEq(lock.retentionUntil, 0); assertEq( vm.getRecordedLogs().length, @@ -272,9 +265,11 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { } function test_settle_WhenInManagementPeriod() public { + uint256 noId = 0; uint256[] memory idsToSettle = new uint256[](1); idsToSettle[0] = 0; bondLock.lock(0, 1 ether); + uint256 retentionPeriodWhenLock = block.timestamp + 8 weeks; // less than 1 day (management period) after penalty init // not eligible to settle yet @@ -284,8 +279,9 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { bondLock.settle(idsToSettle); - uint256 value = bondLock.get(0); - assertEq(value, 1 ether); + CSBondLock.BondLock memory lock = bondLock.get(noId); + assertEq(lock.amount, 1 ether); + assertEq(lock.retentionUntil, retentionPeriodWhenLock); assertEq(vm.getRecordedLogs().length, 0, "should not emit any events"); } @@ -305,12 +301,13 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { // more than 1 day (management period) after penalty init // eligible to settle bondLock.lock(1, 1 ether); - bondLock.mock_bondAmount(1 ether); + bondLock.mock_uncoveredPenalty(0 ether); vm.warp(block.timestamp + 1 days + 1 seconds); // less than 1 day (management period) after penalty init // not eligible to settle yet bondLock.lock(2, 1 ether); + uint256 retentionPeriodWhenLockTheLast = block.timestamp + 8 weeks; vm.expectEmit(true, true, true, true, address(bondLock)); emit BondLockChanged(0, 0, 0); @@ -325,93 +322,69 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { bondLock.settle(idsToSettle); - uint256 value = bondLock.get(0); - assertEq(value, 0 ether); + CSBondLock.BondLock memory lock = bondLock.get(0); + assertEq(lock.amount, 0 ether); + assertEq(lock.retentionUntil, 0); - value = bondLock.get(1); - assertEq(value, 0 ether); + lock = bondLock.get(1); + assertEq(lock.amount, 0 ether); + assertEq(lock.retentionUntil, 0); - value = bondLock.get(2); - assertEq(value, 1 ether); + lock = bondLock.get(2); + assertEq(lock.amount, 1 ether); + assertEq(lock.retentionUntil, retentionPeriodWhenLockTheLast); assertEq(vm.getRecordedLogs().length, 3, "should emit 3 events"); } - function test_release() public { + function test_reduceAmount_WhenFull() public { uint256 noId = 0; uint256 amount = 100 ether; bondLock.lock(noId, amount); - uint256 toRelease = 10 ether; - uint256 rest = amount - toRelease; - - vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondLockReleased(noId, toRelease); vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondLockChanged(noId, rest, block.timestamp + 8 weeks); + emit BondLockChanged(noId, 0, 0); - bondLock.release(noId, toRelease); + bondLock.reduceAmount(noId, amount); - uint256 value = bondLock.get(noId); - assertEq(value, rest); - - vm.warp(block.timestamp + 8 weeks + 1 seconds); - value = bondLock.get(noId); - assertEq(value, 0); + CSBondLock.BondLock memory lock = bondLock.get(0); + assertEq(lock.amount, 0); + assertEq(lock.retentionUntil, 0); } - function test_release_RevertWhen_ZeroAmount() public { - vm.expectRevert(InvalidBondLockAmount.selector); - bondLock.release(0, 0); - } - - function test_release_RevertWhen_GreaterThanLock() public { - uint256 noId = 0; - uint256 amount = 100 ether; - - bondLock.lock(noId, amount); - - vm.expectRevert(InvalidBondLockAmount.selector); - bondLock.release(noId, amount + 1 ether); - } - - function test_compensate() public { + function test_reduceAmount_WhenPartial() public { uint256 noId = 0; uint256 amount = 100 ether; bondLock.lock(noId, amount); + uint256 retentionPeriodWhenLock = block.timestamp + 8 weeks; - uint256 toCompensate = 10 ether; - uint256 rest = amount - toCompensate; + uint256 toRelease = 10 ether; + uint256 rest = amount - toRelease; vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondLockCompensated(noId, toCompensate); - vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondLockChanged(noId, rest, block.timestamp + 8 weeks); + emit BondLockChanged(noId, rest, retentionPeriodWhenLock); - bondLock.compensate(noId, toCompensate); + bondLock.reduceAmount(noId, toRelease); - uint256 value = bondLock.get(noId); - assertEq(value, rest); - - vm.warp(block.timestamp + 8 weeks + 1 seconds); - value = bondLock.get(noId); - assertEq(value, 0); + CSBondLock.BondLock memory lock = bondLock.get(0); + assertEq(lock.amount, rest); + assertEq(lock.retentionUntil, retentionPeriodWhenLock); } - function test_compensate_RevertWhen_ZeroAmount() public { + function test_reduceAmount_RevertWhen_ZeroAmount() public { vm.expectRevert(InvalidBondLockAmount.selector); - bondLock.compensate(0, 0); + bondLock.reduceAmount(0, 0); } - function test_compensate_RevertWhen_GreaterThanLock() public { + function test_reduceAmount_RevertWhen_GreaterThanLock() public { uint256 noId = 0; uint256 amount = 100 ether; bondLock.lock(noId, amount); vm.expectRevert(InvalidBondLockAmount.selector); - bondLock.compensate(noId, amount + 1 ether); + bondLock.reduceAmount(noId, amount + 1 ether); } } From 99541defc13e3caf4c0ef9c425f084119601b6e2 Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Tue, 5 Dec 2023 13:08:44 +0400 Subject: [PATCH 15/18] fix: review (part 2) --- test/CSBondLock.t.sol | 83 ++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 32 deletions(-) diff --git a/test/CSBondLock.t.sol b/test/CSBondLock.t.sol index aa88f4f5..49865870 100644 --- a/test/CSBondLock.t.sol +++ b/test/CSBondLock.t.sol @@ -103,27 +103,35 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { } function test_setBondLockPeriods_RevertWhen_RetentionLessThanMin() public { + uint256 minRetention = bondLock.MIN_BOND_LOCK_RETENTION_PERIOD(); + uint256 minManagement = bondLock.MIN_BOND_LOCK_MANAGEMENT_PERIOD(); vm.expectRevert(InvalidBondLockPeriods.selector); - bondLock.setBondLockPeriods(3 weeks, 1 days); + bondLock.setBondLockPeriods(minRetention - 1 seconds, minManagement); } function test_setBondLockPeriods_RevertWhen_RetentionGreaterThanMax() public { + uint256 maxRetention = bondLock.MAX_BOND_LOCK_RETENTION_PERIOD(); + uint256 minManagement = bondLock.MIN_BOND_LOCK_MANAGEMENT_PERIOD(); vm.expectRevert(InvalidBondLockPeriods.selector); - bondLock.setBondLockPeriods(366 days, 1 days); + bondLock.setBondLockPeriods(maxRetention + 1 seconds, minManagement); } function test_setBondLockPeriods_RevertWhen_ManagementLessThanMin() public { + uint256 minRetention = bondLock.MIN_BOND_LOCK_RETENTION_PERIOD(); + uint256 minManagement = bondLock.MIN_BOND_LOCK_MANAGEMENT_PERIOD(); vm.expectRevert(InvalidBondLockPeriods.selector); - bondLock.setBondLockPeriods(8 weeks, 23 hours); + bondLock.setBondLockPeriods(minRetention, minManagement - 1 seconds); } function test_setBondLockPeriods_RevertWhen_ManagementGreaterThanMax() public { + uint256 minRetention = bondLock.MIN_BOND_LOCK_RETENTION_PERIOD(); + uint256 maxManagement = bondLock.MAX_BOND_LOCK_MANAGEMENT_PERIOD(); vm.expectRevert(InvalidBondLockPeriods.selector); - bondLock.setBondLockPeriods(8 weeks, 8 days); + bondLock.setBondLockPeriods(minRetention, maxManagement + 1 seconds); } function test_getActualAmount() public { @@ -136,20 +144,22 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { } function test_getActualAmount_WhenRetentionPeriodIsPassed() public { + (uint256 retentionPeriod, ) = bondLock.getBondLockPeriods(); uint256 noId = 0; uint256 amount = 1 ether; bondLock.lock(noId, amount); - vm.warp(block.timestamp + 8 weeks + 1 seconds); + vm.warp(block.timestamp + retentionPeriod + 1 seconds); uint256 value = bondLock.getActualAmount(noId); assertEq(value, 0); } function test_lock() public { + (uint256 retentionPeriod, ) = bondLock.getBondLockPeriods(); uint256 noId = 0; uint256 amount = 1 ether; - uint256 retentionUntil = block.timestamp + 8 weeks; + uint256 retentionUntil = block.timestamp + retentionPeriod; vm.expectEmit(true, true, true, true, address(bondLock)); emit BondLockChanged(noId, amount, retentionUntil); @@ -162,24 +172,22 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { } function test_lock_WhenSecondTime() public { + (uint256 retentionPeriod, ) = bondLock.getBondLockPeriods(); uint256 noId = 0; uint256 amount = 1 ether; bondLock.lock(noId, amount); uint256 newBlockTimestamp = block.timestamp + 1 seconds; vm.warp(newBlockTimestamp); + uint256 newRetentionUntil = newBlockTimestamp + retentionPeriod; vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondLockChanged( - noId, - amount + 1.5 ether, - newBlockTimestamp + 8 weeks - ); + emit BondLockChanged(noId, amount + 1.5 ether, newRetentionUntil); bondLock.lock(noId, 1.5 ether); CSBondLock.BondLock memory lock = bondLock.get(noId); assertEq(lock.amount, amount + 1.5 ether); - assertEq(lock.retentionUntil, newBlockTimestamp + 8 weeks); + assertEq(lock.retentionUntil, newRetentionUntil); } function test_lock_RevertWhen_ZeroAmount() public { @@ -188,15 +196,16 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { } function test_settle() public { + (, uint256 managementPeriod) = bondLock.getBondLockPeriods(); uint256 noId = 0; uint256[] memory idsToSettle = new uint256[](1); idsToSettle[0] = noId; bondLock.lock(0, 1 ether); bondLock.mock_uncoveredPenalty(0 ether); - // more than 1 day (management period) after penalty init + // more than management period after penalty init // eligible to settle - vm.warp(block.timestamp + 1 days + 1 seconds); + vm.warp(block.timestamp + managementPeriod + 1 seconds); vm.expectEmit(true, true, true, true, address(bondLock)); emit BondPenalized(noId, 1 ether, 1 ether); @@ -212,16 +221,17 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { } function test_settle_WhenUncovered() public { + (, uint256 managementPeriod) = bondLock.getBondLockPeriods(); uint256 noId = 0; uint256[] memory idsToSettle = new uint256[](1); - idsToSettle[0] = 0; - bondLock.lock(0, 1 ether); + idsToSettle[0] = noId; + bondLock.lock(noId, 1 ether); uint256 retentionPeriodWhenLock = block.timestamp + 8 weeks; bondLock.mock_uncoveredPenalty(0.3 ether); - // more than 1 day (management period) after penalty init + // more than management period after penalty init // eligible to settle - vm.warp(block.timestamp + 1 days + 1 seconds); + vm.warp(block.timestamp + managementPeriod + 1 seconds); vm.expectEmit(true, true, true, true, address(bondLock)); emit BondPenalized(noId, 1 ether, 0.7 ether); @@ -237,14 +247,15 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { } function test_settle_WhenRetentionPeriodIsExpired() public { + (uint256 retentionPeriod, ) = bondLock.getBondLockPeriods(); uint256 noId = 0; uint256[] memory idsToSettle = new uint256[](1); idsToSettle[0] = 0; bondLock.lock(0, 1 ether); - // more than 8 weeks (retention period) after penalty init + // more than retention period after penalty init // not eligible already - vm.warp(block.timestamp + 8 weeks + 1 seconds); + vm.warp(block.timestamp + retentionPeriod + 1 seconds); vm.expectEmit(true, true, true, true, address(bondLock)); emit BondLockChanged(noId, 0, 0); @@ -265,15 +276,17 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { } function test_settle_WhenInManagementPeriod() public { + (uint256 retentionPeriod, uint256 managementPeriod) = bondLock + .getBondLockPeriods(); uint256 noId = 0; uint256[] memory idsToSettle = new uint256[](1); - idsToSettle[0] = 0; - bondLock.lock(0, 1 ether); - uint256 retentionPeriodWhenLock = block.timestamp + 8 weeks; + idsToSettle[0] = noId; + bondLock.lock(noId, 1 ether); + uint256 retentionPeriodWhenLock = block.timestamp + retentionPeriod; - // less than 1 day (management period) after penalty init + // less than management period after penalty init // not eligible to settle yet - vm.warp(block.timestamp + 20 hours); + vm.warp(block.timestamp + managementPeriod - 1 hours); vm.recordLogs(); @@ -287,27 +300,30 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { } function test_settle_WhenDifferentStates() public { + (uint256 retentionPeriod, uint256 managementPeriod) = bondLock + .getBondLockPeriods(); // one eligible, one expired, one in management period uint256[] memory idsToSettle = new uint256[](3); idsToSettle[0] = 0; idsToSettle[1] = 1; idsToSettle[2] = 2; - // more than 8 weeks (retention period) after penalty init + // more than retention period after penalty init // not eligible already bondLock.lock(0, 1 ether); - vm.warp(block.timestamp + 8 weeks + 1 seconds); + vm.warp(block.timestamp + retentionPeriod + 1 seconds); - // more than 1 day (management period) after penalty init + // more than management period after penalty init // eligible to settle bondLock.lock(1, 1 ether); bondLock.mock_uncoveredPenalty(0 ether); - vm.warp(block.timestamp + 1 days + 1 seconds); + vm.warp(block.timestamp + managementPeriod + 1 seconds); - // less than 1 day (management period) after penalty init + // less than management period after penalty init // not eligible to settle yet bondLock.lock(2, 1 ether); - uint256 retentionPeriodWhenLockTheLast = block.timestamp + 8 weeks; + uint256 retentionPeriodWhenLockTheLast = block.timestamp + + retentionPeriod; vm.expectEmit(true, true, true, true, address(bondLock)); emit BondLockChanged(0, 0, 0); @@ -354,15 +370,18 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { } function test_reduceAmount_WhenPartial() public { + (uint256 retentionPeriod, ) = bondLock.getBondLockPeriods(); uint256 noId = 0; uint256 amount = 100 ether; bondLock.lock(noId, amount); - uint256 retentionPeriodWhenLock = block.timestamp + 8 weeks; + uint256 retentionPeriodWhenLock = block.timestamp + retentionPeriod; uint256 toRelease = 10 ether; uint256 rest = amount - toRelease; + vm.warp(block.timestamp + 1 seconds); + vm.expectEmit(true, true, true, true, address(bondLock)); emit BondLockChanged(noId, rest, retentionPeriodWhenLock); From a11151cbc027f1aadc169ad5f6f2943d29a125d3 Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Tue, 5 Dec 2023 13:20:24 +0400 Subject: [PATCH 16/18] fix: review (penalty) --- test/CSBondLock.t.sol | 43 +++++++------------------------------------ 1 file changed, 7 insertions(+), 36 deletions(-) diff --git a/test/CSBondLock.t.sol b/test/CSBondLock.t.sol index 49865870..8fe2e8b7 100644 --- a/test/CSBondLock.t.sol +++ b/test/CSBondLock.t.sol @@ -18,15 +18,7 @@ import { WithdrawalQueueMockBase, WithdrawalQueueMock } from "./helpers/mocks/Wi import { Utilities } from "./helpers/Utilities.sol"; import { Fixtures } from "./helpers/Fixtures.sol"; -abstract contract CSBondLockTestableBase { - event BondPenalized( - uint256 nodeOperatorId, - uint256 penaltyEth, - uint256 coveringEth - ); -} - -contract CSBondLockTestable is CSBondLockTestableBase, CSBondLock { +contract CSBondLockTestable is CSBondLock { constructor( uint256 retentionPeriod, uint256 managementPeriod @@ -62,16 +54,13 @@ contract CSBondLockTestable is CSBondLockTestableBase, CSBondLock { } uint256 internal _mockedUncoveredPenalty; + mapping(uint256 => bool) public penalized; function _penalize( uint256 nodeOperatorId, uint256 amount ) internal override returns (uint256) { - emit BondPenalized( - nodeOperatorId, - amount, - amount - _mockedUncoveredPenalty - ); + penalized[nodeOperatorId] = true; return _mockedUncoveredPenalty; } @@ -80,7 +69,7 @@ contract CSBondLockTestable is CSBondLockTestableBase, CSBondLock { } } -contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { +contract CSBondLockTest is Test, CSBondLockBase { CSBondLockTestable public bondLock; function setUp() public { @@ -207,9 +196,6 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { // eligible to settle vm.warp(block.timestamp + managementPeriod + 1 seconds); - vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondPenalized(noId, 1 ether, 1 ether); - vm.expectEmit(true, true, true, true, address(bondLock)); emit BondLockChanged(noId, 0, 0); @@ -218,6 +204,7 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { CSBondLock.BondLock memory lock = bondLock.get(noId); assertEq(lock.amount, 0 ether); assertEq(lock.retentionUntil, 0); + assertEq(bondLock.penalized(noId), true); } function test_settle_WhenUncovered() public { @@ -233,9 +220,6 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { // eligible to settle vm.warp(block.timestamp + managementPeriod + 1 seconds); - vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondPenalized(noId, 1 ether, 0.7 ether); - vm.expectEmit(true, true, true, true, address(bondLock)); emit BondLockChanged(noId, 0.3 ether, retentionPeriodWhenLock); @@ -244,6 +228,7 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { CSBondLock.BondLock memory lock = bondLock.get(noId); assertEq(lock.amount, 0.3 ether); assertEq(lock.retentionUntil, retentionPeriodWhenLock); + assertEq(bondLock.penalized(noId), true); } function test_settle_WhenRetentionPeriodIsExpired() public { @@ -260,19 +245,11 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { vm.expectEmit(true, true, true, true, address(bondLock)); emit BondLockChanged(noId, 0, 0); - vm.recordLogs(); - bondLock.settle(idsToSettle); CSBondLock.BondLock memory lock = bondLock.get(noId); assertEq(lock.amount, 0 ether); assertEq(lock.retentionUntil, 0); - - assertEq( - vm.getRecordedLogs().length, - 1, - "should NOT emit BondPenalized event" - ); } function test_settle_WhenInManagementPeriod() public { @@ -328,14 +305,9 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { vm.expectEmit(true, true, true, true, address(bondLock)); emit BondLockChanged(0, 0, 0); - vm.expectEmit(true, true, true, true, address(bondLock)); - emit BondPenalized(1, 1 ether, 1 ether); - vm.expectEmit(true, true, true, true, address(bondLock)); emit BondLockChanged(1, 0, 0); - vm.recordLogs(); - bondLock.settle(idsToSettle); CSBondLock.BondLock memory lock = bondLock.get(0); @@ -345,12 +317,11 @@ contract CSBondLockTest is Test, CSBondLockBase, CSBondLockTestableBase { lock = bondLock.get(1); assertEq(lock.amount, 0 ether); assertEq(lock.retentionUntil, 0); + assertEq(bondLock.penalized(1), true); lock = bondLock.get(2); assertEq(lock.amount, 1 ether); assertEq(lock.retentionUntil, retentionPeriodWhenLockTheLast); - - assertEq(vm.getRecordedLogs().length, 3, "should emit 3 events"); } function test_reduceAmount_WhenFull() public { From fd829de077322392673d9074366bad8b3e1218c9 Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Wed, 6 Dec 2023 10:42:26 +0400 Subject: [PATCH 17/18] fix: review (part 3) --- src/CSAccounting.sol | 1 + src/CSBondCurve.sol | 55 +-- test/CSAccounting.t.sol | 838 +++++++++++++++++++++------------------- test/CSBondCurve.t.sol | 96 ++--- 4 files changed, 512 insertions(+), 478 deletions(-) diff --git a/src/CSAccounting.sol b/src/CSAccounting.sol index cec775e4..afdb965b 100644 --- a/src/CSAccounting.sol +++ b/src/CSAccounting.sol @@ -143,6 +143,7 @@ contract CSAccounting is uint256 retention, uint256 management ) external onlyRole(DEFAULT_ADMIN_ROLE) { + // todo: is it admin role? _setBondLockPeriods(retention, management); } diff --git a/src/CSBondCurve.sol b/src/CSBondCurve.sol index 280f2c2c..8d89b97a 100644 --- a/src/CSBondCurve.sol +++ b/src/CSBondCurve.sol @@ -3,7 +3,15 @@ pragma solidity 0.8.21; -abstract contract CSBondCurve { +abstract contract CSBondCurveBase { + event BondCurveChanged(uint256[] bondCurve); + event BondMultiplierChanged( + uint256 indexed nodeOperatorId, + uint256 basisPoints + ); +} + +abstract contract CSBondCurve is CSBondCurveBase { /// @dev Array of bond amounts for particular keys count. /// /// For example: @@ -41,8 +49,9 @@ abstract contract CSBondCurve { /// By default, all Node Operators have x1 multiplier (10000 basis points). /// /// For example: - /// Some Node Operator's bond multiplier is x0.90 (9000 basis points). - /// Bond Curve for this Node Operator will be: + /// There is a bond curve as above ^ + /// Some Node Operator has x0.90 bond multiplier (9000 basis points) + /// Bond Curve with multiplier for this Node Operator will be: /// /// Bond Amount (ETH) /// ^ @@ -80,22 +89,35 @@ abstract contract CSBondCurve { } function _setBondCurve(uint256[] memory _bondCurve) internal { - _checkCurveLength(_bondCurve); - _checkCurveValues(_bondCurve); + if ( + _bondCurve.length < MIN_CURVE_LENGTH || + _bondCurve.length > MAX_CURVE_LENGTH + ) revert InvalidBondCurveLength(); + // todo: check curve values (not worse than previous and makes sense) + if (_bondCurve[0] == 0) revert InvalidBondCurveValues(); + for (uint256 i = 1; i < _bondCurve.length; i++) { + if (_bondCurve[i] <= _bondCurve[i - 1]) + revert InvalidBondCurveValues(); + } bondCurve = _bondCurve; _bondCurveTrend = _bondCurve[_bondCurve.length - 1] - - // if curve length is 1, then to calculate trend we use 0 as previous value + // if the curve length is 1, then 0 is used as the previous value to calculate the trend (_bondCurve.length > 1 ? _bondCurve[_bondCurve.length - 2] : 0); + emit BondCurveChanged(_bondCurve); } function _setBondMultiplier( uint256 nodeOperatorId, uint256 basisPoints ) internal { - _checkMultiplier(basisPoints); + if ( + basisPoints < MIN_BOND_MULTIPLIER || + basisPoints > MAX_BOND_MULTIPLIER + ) revert InvalidMultiplier(); // todo: check curve values (not worse than previous) bondMultiplierBP[nodeOperatorId] = basisPoints; + emit BondMultiplierChanged(nodeOperatorId, basisPoints); } /// @notice Returns basis points of the bond multiplier for the given node operator. @@ -107,25 +129,6 @@ abstract contract CSBondCurve { return basisPoints > 0 ? basisPoints : MAX_BOND_MULTIPLIER; } - function _checkCurveLength(uint256[] memory xy) internal pure { - if (xy.length < MIN_CURVE_LENGTH || xy.length > MAX_CURVE_LENGTH) - revert InvalidBondCurveLength(); - } - - function _checkCurveValues(uint256[] memory xy) internal pure { - // todo: check curve values (not worse than previous and makes sense) - if (xy[0] == 0) revert InvalidBondCurveValues(); - for (uint256 i = 1; i < xy.length; i++) { - if (xy[i] <= xy[i - 1]) revert InvalidBondCurveValues(); - } - } - - function _checkMultiplier(uint256 multiplier) internal pure { - if ( - multiplier < MIN_BOND_MULTIPLIER || multiplier > MAX_BOND_MULTIPLIER - ) revert InvalidMultiplier(); - } - /// @notice Returns keys count for the given bond amount. function _getKeysCountByBondAmount( uint256 amount diff --git a/test/CSAccounting.t.sol b/test/CSAccounting.t.sol index ebd553de..06189268 100644 --- a/test/CSAccounting.t.sol +++ b/test/CSAccounting.t.sol @@ -33,8 +33,8 @@ contract CSAccountingForTests is CSAccounting { address lidoLocator, address wstETH, address communityStakingModule, - uint256 blockedBondRetentionPeriod, - uint256 blockedBondManagementPeriod + uint256 lockedBondRetentionPeriod, + uint256 lockedBondManagementPeriod ) CSAccounting( bondCurve, @@ -42,34 +42,19 @@ contract CSAccountingForTests is CSAccounting { lidoLocator, wstETH, communityStakingModule, - blockedBondRetentionPeriod, - blockedBondManagementPeriod + lockedBondRetentionPeriod, + lockedBondManagementPeriod ) {} - function setBondCurve_ForTest() public { - uint256[] memory curve = new uint256[](2); - curve[0] = 2 ether; - curve[1] = 3 ether; - setBondCurve_ForTest(curve); - } - function setBondCurve_ForTest(uint256[] memory curve) public { _setBondCurve(curve); } - function setBondMultiplier_ForTest() public { - _setBondMultiplier(0, 9000); - } - function setBondMultiplier_ForTest(uint256 id, uint256 multiplier) public { _setBondMultiplier(id, multiplier); } - function setBondLock_ForTest() public { - CSBondLock._lock(0, 1 ether); - } - function setBondLock_ForTest(uint256 id, uint256 amount) public { CSBondLock._lock(id, amount); } @@ -195,7 +180,7 @@ abstract contract BondAmountModifiersTest { // 1 key -> 2 ether + 1 ether // 2 keys -> 4 ether + 1 ether // n keys -> 2 + (n - 1) * 2 ether + 1 ether - function test_WithBlocked() public virtual; + function test_WithLocked() public virtual; // 1 key -> 1.8 ether // 2 keys -> 2.7 ether @@ -205,28 +190,23 @@ abstract contract BondAmountModifiersTest { // 1 key -> 2 ether + 1 ether // 2 keys -> 3 ether + 1 ether // n keys -> 2 + (n - 1) * 1 ether + 1 ether - function test_WithCurveAndBlocked() public virtual; + function test_WithCurveAndLocked() public virtual; // 1 key -> 1.8 ether + 1 ether // 2 keys -> 3.6 ether + 1 ether // n keys -> 1.8 + (n - 1) * 1.8 ether + 1 ether - function test_WithMultiplierAndBlocked() public virtual; + function test_WithMultiplierAndLocked() public virtual; // 1 key -> 1.8 ether + 1 ether // 2 keys -> 2.7 ether + 1 ether // n keys -> 1.8 + (n - 1) * 0.9 ether + 1 ether - function test_WithCurveAndMultiplierAndBlocked() public virtual; + function test_WithCurveAndMultiplierAndLocked() public virtual; } abstract contract CSAccountingBondStateBaseTest is BondAmountModifiersTest, CSAccountingBaseTest { - function setUp() public virtual override { - super.setUp(); - _operator({ ongoing: 16, withdrawn: 0 }); - } - function _operator(uint256 ongoing, uint256 withdrawn) internal virtual { ICSModule.NodeOperatorInfo memory n; n.active = true; @@ -247,6 +227,20 @@ abstract contract CSAccountingBondStateBaseTest is accounting.depositETH{ value: bond }(user, 0); } + uint256[] public defaultCurve = [2 ether, 3 ether]; + + function _curve(uint256[] memory curve) internal virtual { + accounting.setBondCurve_ForTest(curve); + } + + function _multiplier(uint256 id, uint256 multiplier) internal virtual { + accounting.setBondMultiplier_ForTest(id, multiplier); + } + + function _lock(uint256 id, uint256 amount) internal virtual { + accounting.setBondLock_ForTest(id, amount); + } + function test_WithOneWithdrawnValidator() public virtual; function test_WithBond() public virtual; @@ -264,54 +258,62 @@ abstract contract CSAccountingBondStateBaseTest is contract CSAccountingGetExcessBondETHTest is CSAccountingBondStateBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); assertApproxEqAbs(accounting.getExcessBondETH(0), 1 ether, 1 wei); } function test_WithCurve() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertApproxEqAbs(accounting.getExcessBondETH(0), 16 ether, 1 wei); } function test_WithMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondMultiplier_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); assertApproxEqAbs(accounting.getExcessBondETH(0), 4.2 ether, 1 wei); } - function test_WithBlocked() public override { + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondLock_ForTest(); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getExcessBondETH(0), 0 ether, 1 wei); } function test_WithCurveAndMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); assertApproxEqAbs(accounting.getExcessBondETH(0), 17.7 ether, 1 wei); } - function test_WithCurveAndBlocked() public override { + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getExcessBondETH(0), 15 ether, 1 wei); } - function test_WithMultiplierAndBlocked() public override { + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getExcessBondETH(0), 3.2 ether, 1 wei); } - function test_WithCurveAndMultiplierAndBlocked() public override { + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getExcessBondETH(0), 16.7 ether, 1 wei); } @@ -360,54 +362,62 @@ contract CSAccountingGetExcessBondETHTest is CSAccountingBondStateBaseTest { contract CSAccountingGetExcessBondStETHTest is CSAccountingBondStateBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); assertApproxEqAbs(accounting.getExcessBondStETH(0), 1 ether, 1 wei); } function test_WithCurve() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertApproxEqAbs(accounting.getExcessBondStETH(0), 16 ether, 1 wei); } function test_WithMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondMultiplier_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); assertApproxEqAbs(accounting.getExcessBondStETH(0), 4.2 ether, 1 wei); } - function test_WithBlocked() public override { + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondLock_ForTest(); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getExcessBondStETH(0), 0 ether, 1 wei); } function test_WithCurveAndMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); assertApproxEqAbs(accounting.getExcessBondStETH(0), 17.7 ether, 1 wei); } - function test_WithCurveAndBlocked() public override { + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getExcessBondStETH(0), 15 ether, 1 wei); } - function test_WithMultiplierAndBlocked() public override { + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getExcessBondStETH(0), 3.2 ether, 1 wei); } - function test_WithCurveAndMultiplierAndBlocked() public override { + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getExcessBondStETH(0), 16.7 ether, 1 wei); } @@ -456,6 +466,7 @@ contract CSAccountingGetExcessBondStETHTest is CSAccountingBondStateBaseTest { contract CSAccountingGetExcessBondWstETHTest is CSAccountingBondStateBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); assertApproxEqAbs( accounting.getExcessBondWstETH(0), @@ -465,8 +476,9 @@ contract CSAccountingGetExcessBondWstETHTest is CSAccountingBondStateBaseTest { } function test_WithCurve() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertApproxEqAbs( accounting.getExcessBondWstETH(0), wstETH.getWstETHByStETH(16 ether), @@ -475,8 +487,9 @@ contract CSAccountingGetExcessBondWstETHTest is CSAccountingBondStateBaseTest { } function test_WithMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondMultiplier_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); assertApproxEqAbs( accounting.getExcessBondWstETH(0), wstETH.getWstETHByStETH(4.2 ether), @@ -484,9 +497,10 @@ contract CSAccountingGetExcessBondWstETHTest is CSAccountingBondStateBaseTest { ); } - function test_WithBlocked() public override { + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondLock_ForTest(); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getExcessBondWstETH(0), wstETH.getWstETHByStETH(0 ether), @@ -495,9 +509,10 @@ contract CSAccountingGetExcessBondWstETHTest is CSAccountingBondStateBaseTest { } function test_WithCurveAndMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); assertApproxEqAbs( accounting.getExcessBondWstETH(0), wstETH.getWstETHByStETH(17.7 ether), @@ -505,10 +520,11 @@ contract CSAccountingGetExcessBondWstETHTest is CSAccountingBondStateBaseTest { ); } - function test_WithCurveAndBlocked() public override { + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getExcessBondWstETH(0), wstETH.getWstETHByStETH(15 ether), @@ -516,10 +532,11 @@ contract CSAccountingGetExcessBondWstETHTest is CSAccountingBondStateBaseTest { ); } - function test_WithMultiplierAndBlocked() public override { + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getExcessBondWstETH(0), wstETH.getWstETHByStETH(3.2 ether), @@ -527,11 +544,12 @@ contract CSAccountingGetExcessBondWstETHTest is CSAccountingBondStateBaseTest { ); } - function test_WithCurveAndMultiplierAndBlocked() public override { + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 33 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getExcessBondWstETH(0), wstETH.getWstETHByStETH(16.7 ether), @@ -600,54 +618,62 @@ contract CSAccountingGetExcessBondWstETHTest is CSAccountingBondStateBaseTest { contract CSAccountingGetMissingBondETHTest is CSAccountingBondStateBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); assertApproxEqAbs(accounting.getMissingBondETH(0), 16 ether, 1 wei); } function test_WithCurve() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertApproxEqAbs(accounting.getMissingBondETH(0), 1 ether, 1 wei); } function test_WithMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondMultiplier_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); assertApproxEqAbs(accounting.getMissingBondETH(0), 12.8 ether, 1 wei); } - function test_WithBlocked() public override { + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondLock_ForTest(); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getMissingBondETH(0), 17 ether, 1 wei); } function test_WithCurveAndMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); assertEq(accounting.getMissingBondETH(0), 0); } - function test_WithCurveAndBlocked() public override { + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getMissingBondETH(0), 2 ether, 1 wei); } - function test_WithMultiplierAndBlocked() public override { + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getMissingBondETH(0), 13.8 ether, 1 wei); } - function test_WithCurveAndMultiplierAndBlocked() public override { + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getMissingBondETH(0), 0.3 ether, 1 wei); } @@ -696,54 +722,62 @@ contract CSAccountingGetMissingBondETHTest is CSAccountingBondStateBaseTest { contract CSAccountingGetMissingBondStETHTest is CSAccountingBondStateBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); assertApproxEqAbs(accounting.getMissingBondStETH(0), 16 ether, 1 wei); } function test_WithCurve() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertApproxEqAbs(accounting.getMissingBondStETH(0), 1 ether, 1 wei); } function test_WithMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondMultiplier_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); assertApproxEqAbs(accounting.getMissingBondStETH(0), 12.8 ether, 1 wei); } - function test_WithBlocked() public override { + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondLock_ForTest(); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getMissingBondStETH(0), 17 ether, 1 wei); } function test_WithCurveAndMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); assertEq(accounting.getMissingBondStETH(0), 0); } - function test_WithCurveAndBlocked() public override { + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getMissingBondStETH(0), 2 ether, 1 wei); } - function test_WithMultiplierAndBlocked() public override { + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getMissingBondStETH(0), 13.8 ether, 1 wei); } - function test_WithCurveAndMultiplierAndBlocked() public override { + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs(accounting.getMissingBondStETH(0), 0.3 ether, 1 wei); } @@ -792,6 +826,7 @@ contract CSAccountingGetMissingBondStETHTest is CSAccountingBondStateBaseTest { contract CSAccountingGetMissingBondWstETHTest is CSAccountingBondStateBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); assertApproxEqAbs( accounting.getMissingBondWstETH(0), @@ -801,8 +836,9 @@ contract CSAccountingGetMissingBondWstETHTest is CSAccountingBondStateBaseTest { } function test_WithCurve() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertApproxEqAbs( accounting.getMissingBondWstETH(0), wstETH.getWstETHByStETH(1 ether), @@ -811,8 +847,9 @@ contract CSAccountingGetMissingBondWstETHTest is CSAccountingBondStateBaseTest { } function test_WithMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondMultiplier_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); assertApproxEqAbs( accounting.getMissingBondWstETH(0), wstETH.getWstETHByStETH(12.8 ether), @@ -820,9 +857,10 @@ contract CSAccountingGetMissingBondWstETHTest is CSAccountingBondStateBaseTest { ); } - function test_WithBlocked() public override { + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondLock_ForTest(); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getMissingBondWstETH(0), wstETH.getWstETHByStETH(17 ether), @@ -831,19 +869,21 @@ contract CSAccountingGetMissingBondWstETHTest is CSAccountingBondStateBaseTest { } function test_WithCurveAndMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); assertEq( accounting.getMissingBondWstETH(0), wstETH.getWstETHByStETH(0) ); } - function test_WithCurveAndBlocked() public override { + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getMissingBondWstETH(0), wstETH.getWstETHByStETH(2 ether), @@ -851,10 +891,11 @@ contract CSAccountingGetMissingBondWstETHTest is CSAccountingBondStateBaseTest { ); } - function test_WithMultiplierAndBlocked() public override { + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getMissingBondWstETH(0), wstETH.getWstETHByStETH(13.8 ether), @@ -862,11 +903,12 @@ contract CSAccountingGetMissingBondWstETHTest is CSAccountingBondStateBaseTest { ); } - function test_WithCurveAndMultiplierAndBlocked() public override { + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 16 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getMissingBondWstETH(0), wstETH.getWstETHByStETH(0.3 ether), @@ -933,54 +975,62 @@ contract CSAccountingGetMissingBondWstETHTest is CSAccountingBondStateBaseTest { contract CSAccountingGetUnbondedKeysCountTest is CSAccountingBondStateBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 11.5 ether }); assertEq(accounting.getUnbondedKeysCount(0), 10); } function test_WithCurve() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 11.5 ether }); - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertEq(accounting.getUnbondedKeysCount(0), 5); } function test_WithMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 11.5 ether }); - accounting.setBondMultiplier_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); assertEq(accounting.getUnbondedKeysCount(0), 9); } - function test_WithBlocked() public override { + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 11.5 ether }); - accounting.setBondLock_ForTest(); + _lock({ id: 0, amount: 1 ether }); assertEq(accounting.getUnbondedKeysCount(0), 10); } function test_WithCurveAndMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 11.5 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); assertEq(accounting.getUnbondedKeysCount(0), 4); } - function test_WithCurveAndBlocked() public override { + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 11.5 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); assertEq(accounting.getUnbondedKeysCount(0), 6); } - function test_WithMultiplierAndBlocked() public override { + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 11.5 ether }); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertEq(accounting.getUnbondedKeysCount(0), 10); } - function test_WithCurveAndMultiplierAndBlocked() public override { + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 11.5 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertEq(accounting.getUnbondedKeysCount(0), 5); } @@ -1047,46 +1097,54 @@ contract CSAccountingGetRequiredETHBondTest is CSAccountingGetRequiredBondBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); assertEq(accounting.getRequiredBondETH(0, 0), 32 ether); } function test_WithCurve() public override { - accounting.setBondCurve_ForTest(); + _operator({ ongoing: 16, withdrawn: 0 }); + _curve(defaultCurve); assertEq(accounting.getRequiredBondETH(0, 0), 17 ether); } function test_WithMultiplier() public override { - accounting.setBondMultiplier_ForTest(); + _operator({ ongoing: 16, withdrawn: 0 }); + _multiplier({ id: 0, multiplier: 9000 }); assertEq(accounting.getRequiredBondETH(0, 0), 28.8 ether); } - function test_WithBlocked() public override { - accounting.setBondLock_ForTest(); + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _lock({ id: 0, amount: 1 ether }); assertEq(accounting.getRequiredBondETH(0, 0), 33 ether); } function test_WithCurveAndMultiplier() public override { - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _operator({ ongoing: 16, withdrawn: 0 }); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); assertEq(accounting.getRequiredBondETH(0, 0), 15.3 ether); } - function test_WithCurveAndBlocked() public override { - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); assertEq(accounting.getRequiredBondETH(0, 0), 18 ether); } - function test_WithMultiplierAndBlocked() public override { - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertEq(accounting.getRequiredBondETH(0, 0), 29.8 ether); } - function test_WithCurveAndMultiplierAndBlocked() public override { - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertEq(accounting.getRequiredBondETH(0, 0), 16.3 ether); } @@ -1168,46 +1226,54 @@ contract CSAccountingGetRequiredStETHBondTest is CSAccountingGetRequiredBondBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); assertEq(accounting.getRequiredBondStETH(0, 0), 32 ether); } function test_WithCurve() public override { - accounting.setBondCurve_ForTest(); + _operator({ ongoing: 16, withdrawn: 0 }); + _curve(defaultCurve); assertEq(accounting.getRequiredBondStETH(0, 0), 17 ether); } function test_WithMultiplier() public override { - accounting.setBondMultiplier_ForTest(); + _operator({ ongoing: 16, withdrawn: 0 }); + _multiplier({ id: 0, multiplier: 9000 }); assertEq(accounting.getRequiredBondStETH(0, 0), 28.8 ether); } - function test_WithBlocked() public override { - accounting.setBondLock_ForTest(); + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _lock({ id: 0, amount: 1 ether }); assertEq(accounting.getRequiredBondStETH(0, 0), 33 ether); } function test_WithCurveAndMultiplier() public override { - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _operator({ ongoing: 16, withdrawn: 0 }); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); assertEq(accounting.getRequiredBondStETH(0, 0), 15.3 ether); } - function test_WithCurveAndBlocked() public override { - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); assertEq(accounting.getRequiredBondStETH(0, 0), 18 ether); } - function test_WithMultiplierAndBlocked() public override { - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertEq(accounting.getRequiredBondStETH(0, 0), 29.8 ether); } - function test_WithCurveAndMultiplierAndBlocked() public override { - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertEq(accounting.getRequiredBondStETH(0, 0), 16.3 ether); } @@ -1301,6 +1367,7 @@ contract CSAccountingGetRequiredWstETHBondTest is CSAccountingGetRequiredBondBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); assertEq( accounting.getRequiredBondWstETH(0, 0), stETH.getSharesByPooledEth(32 ether) @@ -1308,7 +1375,8 @@ contract CSAccountingGetRequiredWstETHBondTest is } function test_WithCurve() public override { - accounting.setBondCurve_ForTest(); + _operator({ ongoing: 16, withdrawn: 0 }); + _curve(defaultCurve); assertEq( accounting.getRequiredBondWstETH(0, 0), stETH.getSharesByPooledEth(17 ether) @@ -1316,15 +1384,17 @@ contract CSAccountingGetRequiredWstETHBondTest is } function test_WithMultiplier() public override { - accounting.setBondMultiplier_ForTest(); + _operator({ ongoing: 16, withdrawn: 0 }); + _multiplier({ id: 0, multiplier: 9000 }); assertEq( accounting.getRequiredBondWstETH(0, 0), stETH.getSharesByPooledEth(28.8 ether) ); } - function test_WithBlocked() public override { - accounting.setBondLock_ForTest(); + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _lock({ id: 0, amount: 1 ether }); assertEq( accounting.getRequiredBondWstETH(0, 0), stETH.getSharesByPooledEth(33 ether) @@ -1332,36 +1402,40 @@ contract CSAccountingGetRequiredWstETHBondTest is } function test_WithCurveAndMultiplier() public override { - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _operator({ ongoing: 16, withdrawn: 0 }); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); assertEq( accounting.getRequiredBondWstETH(0, 0), stETH.getSharesByPooledEth(15.3 ether) ); } - function test_WithCurveAndBlocked() public override { - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); assertEq( accounting.getRequiredBondWstETH(0, 0), stETH.getSharesByPooledEth(18 ether) ); } - function test_WithMultiplierAndBlocked() public override { - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertEq( accounting.getRequiredBondWstETH(0, 0), stETH.getSharesByPooledEth(29.8 ether) ); } - function test_WithCurveAndMultiplierAndBlocked() public override { - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertEq( accounting.getRequiredBondWstETH(0, 0), stETH.getSharesByPooledEth(16.3 ether) @@ -1459,9 +1533,22 @@ contract CSAccountingGetRequiredWstETHBondTest is } } -contract CSAccountingGetRequiredBondETHForKeysTest is - BondAmountModifiersTest, +abstract contract CSAccountingGetRequiredBondForKeysBaseTest is CSAccountingBaseTest +{ + uint256[] public defaultCurve = [2 ether, 3 ether]; + + function _curve(uint256[] memory curve) internal virtual { + accounting.setBondCurve_ForTest(curve); + } + + function test_default() public virtual; + + function test_WithCurve() public virtual; +} + +contract CSAccountingGetRequiredBondETHForKeysTest is + CSAccountingGetRequiredBondForKeysBaseTest { function test_default() public override { assertEq(accounting.getRequiredBondETHForKeys(0), 0); @@ -1470,28 +1557,15 @@ contract CSAccountingGetRequiredBondETHForKeysTest is } function test_WithCurve() public override { - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertEq(accounting.getRequiredBondETHForKeys(0), 0); assertEq(accounting.getRequiredBondETHForKeys(1), 2 ether); assertEq(accounting.getRequiredBondETHForKeys(2), 3 ether); } - - function test_WithMultiplier() public override {} - - function test_WithBlocked() public override {} - - function test_WithCurveAndMultiplier() public override {} - - function test_WithCurveAndBlocked() public override {} - - function test_WithMultiplierAndBlocked() public override {} - - function test_WithCurveAndMultiplierAndBlocked() public override {} } contract CSAccountingGetRequiredBondStETHForKeysTest is - BondAmountModifiersTest, - CSAccountingBaseTest + CSAccountingGetRequiredBondForKeysBaseTest { function test_default() public override { assertEq(accounting.getRequiredBondStETHForKeys(0), 0); @@ -1500,28 +1574,15 @@ contract CSAccountingGetRequiredBondStETHForKeysTest is } function test_WithCurve() public override { - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertEq(accounting.getRequiredBondETHForKeys(0), 0); assertEq(accounting.getRequiredBondStETHForKeys(1), 2 ether); assertEq(accounting.getRequiredBondStETHForKeys(2), 3 ether); } - - function test_WithMultiplier() public override {} - - function test_WithBlocked() public override {} - - function test_WithCurveAndMultiplier() public override {} - - function test_WithCurveAndBlocked() public override {} - - function test_WithMultiplierAndBlocked() public override {} - - function test_WithCurveAndMultiplierAndBlocked() public override {} } contract CSAccountingGetRequiredBondWstETHForKeysTest is - BondAmountModifiersTest, - CSAccountingBaseTest + CSAccountingGetRequiredBondForKeysBaseTest { function test_default() public override { assertEq(accounting.getRequiredBondWstETHForKeys(0), 0); @@ -1536,7 +1597,7 @@ contract CSAccountingGetRequiredBondWstETHForKeysTest is } function test_WithCurve() public override { - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertEq(accounting.getRequiredBondWstETHForKeys(0), 0); assertEq( accounting.getRequiredBondWstETHForKeys(1), @@ -1547,23 +1608,10 @@ contract CSAccountingGetRequiredBondWstETHForKeysTest is stETH.getSharesByPooledEth(3 ether) ); } - - function test_WithMultiplier() public override {} - - function test_WithBlocked() public override {} - - function test_WithCurveAndMultiplier() public override {} - - function test_WithCurveAndBlocked() public override {} - - function test_WithMultiplierAndBlocked() public override {} - - function test_WithCurveAndMultiplierAndBlocked() public override {} } contract CSAccountingGetKeysCountByBondETHTest is - BondAmountModifiersTest, - CSAccountingBaseTest + CSAccountingGetRequiredBondForKeysBaseTest { function test_default() public override { assertEq(accounting.getKeysCountByBondETH(0), 0); @@ -1574,30 +1622,17 @@ contract CSAccountingGetKeysCountByBondETHTest is } function test_WithCurve() public override { - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertEq(accounting.getKeysCountByBondETH(0), 0); assertEq(accounting.getKeysCountByBondETH(1.99 ether), 0); assertEq(accounting.getKeysCountByBondETH(2 ether), 1); assertEq(accounting.getKeysCountByBondETH(3 ether), 2); assertEq(accounting.getKeysCountByBondETH(16 ether), 15); } - - function test_WithMultiplier() public override {} - - function test_WithBlocked() public override {} - - function test_WithCurveAndMultiplier() public override {} - - function test_WithCurveAndBlocked() public override {} - - function test_WithMultiplierAndBlocked() public override {} - - function test_WithCurveAndMultiplierAndBlocked() public override {} } contract CSAccountingGetKeysCountByBondStETHTest is - BondAmountModifiersTest, - CSAccountingBaseTest + CSAccountingGetRequiredBondForKeysBaseTest { function test_default() public override { assertEq(accounting.getKeysCountByBondStETH(0), 0); @@ -1608,30 +1643,17 @@ contract CSAccountingGetKeysCountByBondStETHTest is } function test_WithCurve() public override { - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertEq(accounting.getKeysCountByBondStETH(0), 0); assertEq(accounting.getKeysCountByBondStETH(1.99 ether), 0); assertEq(accounting.getKeysCountByBondStETH(2 ether), 1); assertEq(accounting.getKeysCountByBondStETH(3 ether), 2); assertEq(accounting.getKeysCountByBondETH(16 ether), 15); } - - function test_WithMultiplier() public override {} - - function test_WithBlocked() public override {} - - function test_WithCurveAndMultiplier() public override {} - - function test_WithCurveAndBlocked() public override {} - - function test_WithMultiplierAndBlocked() public override {} - - function test_WithCurveAndMultiplierAndBlocked() public override {} } contract CSAccountingGetKeysCountByBondWstETHTest is - BondAmountModifiersTest, - CSAccountingBaseTest + CSAccountingGetRequiredBondForKeysBaseTest { function test_default() public override { assertEq(accounting.getKeysCountByBondWstETH(0), 0); @@ -1662,7 +1684,7 @@ contract CSAccountingGetKeysCountByBondWstETHTest is } function test_WithCurve() public override { - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertEq(accounting.getKeysCountByBondWstETH(0), 0); assertEq( accounting.getKeysCountByBondWstETH( @@ -1689,18 +1711,6 @@ contract CSAccountingGetKeysCountByBondWstETHTest is 15 ); } - - function test_WithMultiplier() public override {} - - function test_WithBlocked() public override {} - - function test_WithCurveAndMultiplier() public override {} - - function test_WithCurveAndBlocked() public override {} - - function test_WithMultiplierAndBlocked() public override {} - - function test_WithCurveAndMultiplierAndBlocked() public override {} } abstract contract CSAccountingRewardsBaseTest is CSAccountingBondStateBaseTest { @@ -1747,6 +1757,7 @@ abstract contract CSAccountingRewardsBaseTest is CSAccountingBondStateBaseTest { contract CSAccountingGetTotalRewardsETHTest is CSAccountingRewardsBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 0 ether, fee: 0.1 ether }); assertEq( accounting.getTotalRewardsETH( @@ -1759,8 +1770,9 @@ contract CSAccountingGetTotalRewardsETHTest is CSAccountingRewardsBaseTest { } function test_WithCurve() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertEq( accounting.getTotalRewardsETH( leaf.proof, @@ -1772,8 +1784,9 @@ contract CSAccountingGetTotalRewardsETHTest is CSAccountingRewardsBaseTest { } function test_WithMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondMultiplier_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); assertEq( accounting.getTotalRewardsETH( leaf.proof, @@ -1784,9 +1797,10 @@ contract CSAccountingGetTotalRewardsETHTest is CSAccountingRewardsBaseTest { ); } - function test_WithBlocked() public override { + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondLock_ForTest(); + _lock({ id: 0, amount: 1 ether }); assertEq( accounting.getTotalRewardsETH( leaf.proof, @@ -1798,9 +1812,10 @@ contract CSAccountingGetTotalRewardsETHTest is CSAccountingRewardsBaseTest { } function test_WithCurveAndMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); assertEq( accounting.getTotalRewardsETH( leaf.proof, @@ -1811,10 +1826,11 @@ contract CSAccountingGetTotalRewardsETHTest is CSAccountingRewardsBaseTest { ); } - function test_WithCurveAndBlocked() public override { + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getTotalRewardsETH( leaf.proof, @@ -1826,10 +1842,11 @@ contract CSAccountingGetTotalRewardsETHTest is CSAccountingRewardsBaseTest { ); } - function test_WithMultiplierAndBlocked() public override { + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getTotalRewardsETH( leaf.proof, @@ -1841,11 +1858,12 @@ contract CSAccountingGetTotalRewardsETHTest is CSAccountingRewardsBaseTest { ); } - function test_WithCurveAndMultiplierAndBlocked() public override { + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getTotalRewardsETH( leaf.proof, @@ -1952,6 +1970,7 @@ contract CSAccountingGetTotalRewardsETHTest is CSAccountingRewardsBaseTest { contract CSAccountingGetTotalRewardsStETHTest is CSAccountingRewardsBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 0 ether, fee: 0.1 ether }); assertEq( accounting.getTotalRewardsStETH( @@ -1964,8 +1983,9 @@ contract CSAccountingGetTotalRewardsStETHTest is CSAccountingRewardsBaseTest { } function test_WithCurve() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertEq( accounting.getTotalRewardsStETH( leaf.proof, @@ -1977,8 +1997,9 @@ contract CSAccountingGetTotalRewardsStETHTest is CSAccountingRewardsBaseTest { } function test_WithMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondMultiplier_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); assertEq( accounting.getTotalRewardsStETH( leaf.proof, @@ -1989,9 +2010,10 @@ contract CSAccountingGetTotalRewardsStETHTest is CSAccountingRewardsBaseTest { ); } - function test_WithBlocked() public override { + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondLock_ForTest(); + _lock({ id: 0, amount: 1 ether }); assertEq( accounting.getTotalRewardsStETH( leaf.proof, @@ -2003,9 +2025,10 @@ contract CSAccountingGetTotalRewardsStETHTest is CSAccountingRewardsBaseTest { } function test_WithCurveAndMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); assertEq( accounting.getTotalRewardsStETH( leaf.proof, @@ -2016,10 +2039,11 @@ contract CSAccountingGetTotalRewardsStETHTest is CSAccountingRewardsBaseTest { ); } - function test_WithCurveAndBlocked() public override { + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getTotalRewardsStETH( leaf.proof, @@ -2031,10 +2055,11 @@ contract CSAccountingGetTotalRewardsStETHTest is CSAccountingRewardsBaseTest { ); } - function test_WithMultiplierAndBlocked() public override { + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getTotalRewardsStETH( leaf.proof, @@ -2046,11 +2071,12 @@ contract CSAccountingGetTotalRewardsStETHTest is CSAccountingRewardsBaseTest { ); } - function test_WithCurveAndMultiplierAndBlocked() public override { + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getTotalRewardsStETH( leaf.proof, @@ -2157,6 +2183,7 @@ contract CSAccountingGetTotalRewardsStETHTest is CSAccountingRewardsBaseTest { contract CSAccountingGetTotalRewardsWstETHTest is CSAccountingRewardsBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 0 ether, fee: 0.1 ether }); assertEq( accounting.getTotalRewardsWstETH( @@ -2169,8 +2196,9 @@ contract CSAccountingGetTotalRewardsWstETHTest is CSAccountingRewardsBaseTest { } function test_WithCurve() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); assertEq( accounting.getTotalRewardsWstETH( leaf.proof, @@ -2182,8 +2210,9 @@ contract CSAccountingGetTotalRewardsWstETHTest is CSAccountingRewardsBaseTest { } function test_WithMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondMultiplier_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); assertEq( accounting.getTotalRewardsWstETH( leaf.proof, @@ -2194,9 +2223,10 @@ contract CSAccountingGetTotalRewardsWstETHTest is CSAccountingRewardsBaseTest { ); } - function test_WithBlocked() public override { + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondLock_ForTest(); + _lock({ id: 0, amount: 1 ether }); assertEq( accounting.getTotalRewardsWstETH( leaf.proof, @@ -2208,9 +2238,10 @@ contract CSAccountingGetTotalRewardsWstETHTest is CSAccountingRewardsBaseTest { } function test_WithCurveAndMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); assertEq( accounting.getTotalRewardsWstETH( leaf.proof, @@ -2221,10 +2252,11 @@ contract CSAccountingGetTotalRewardsWstETHTest is CSAccountingRewardsBaseTest { ); } - function test_WithCurveAndBlocked() public override { + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getTotalRewardsWstETH( leaf.proof, @@ -2236,10 +2268,11 @@ contract CSAccountingGetTotalRewardsWstETHTest is CSAccountingRewardsBaseTest { ); } - function test_WithMultiplierAndBlocked() public override { + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getTotalRewardsWstETH( leaf.proof, @@ -2251,11 +2284,12 @@ contract CSAccountingGetTotalRewardsWstETHTest is CSAccountingRewardsBaseTest { ); } - function test_WithCurveAndMultiplierAndBlocked() public override { + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); assertApproxEqAbs( accounting.getTotalRewardsWstETH( leaf.proof, @@ -2372,6 +2406,7 @@ abstract contract CSAccountingClaimRewardsBaseTest is contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); @@ -2407,8 +2442,9 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { } function test_WithCurve() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -2444,8 +2480,9 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { } function test_WithMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondMultiplier_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -2480,9 +2517,10 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { ); } - function test_WithBlocked() public override { + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondLock_ForTest(); + _lock({ id: 0, amount: 1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -2518,9 +2556,10 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { } function test_WithCurveAndMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -2555,10 +2594,11 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { ); } - function test_WithCurveAndBlocked() public override { + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -2574,13 +2614,13 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { stETH.balanceOf(address(user)), stETHAsFee + 14 ether, 1 wei, - "user balance should be equal to fee reward plus excess bond after curve minus blocked" + "user balance should be equal to fee reward plus excess bond after curve minus locked" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(14 ether), 1 wei, - "bond shares after claim should be equal to before minus excess bond after curve minus blocked" + "bond shares after claim should be equal to before minus excess bond after curve minus locked" ); assertEq( stETH.sharesOf(address(accounting)), @@ -2594,10 +2634,11 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { ); } - function test_WithMultiplierAndBlocked() public override { + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -2613,13 +2654,13 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { stETH.balanceOf(address(user)), stETHAsFee + 2.2 ether, 1 wei, - "user balance should be equal to fee reward plus excess bond after multiplier minus blocked" + "user balance should be equal to fee reward plus excess bond after multiplier minus locked" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(2.2 ether), 1 wei, - "bond shares after claim should be equal to before minus excess bond after multiplier minus blocked" + "bond shares after claim should be equal to before minus excess bond after multiplier minus locked" ); assertEq( stETH.sharesOf(address(accounting)), @@ -2633,11 +2674,12 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { ); } - function test_WithCurveAndMultiplierAndBlocked() public override { + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -2653,13 +2695,13 @@ contract CSAccountingClaimStETHRewardsTest is CSAccountingClaimRewardsBaseTest { stETH.balanceOf(address(user)), stETHAsFee + 15.7 ether, 1 wei, - "user balance should be equal to fee reward plus excess bond after curve and multiplier minus blocked" + "user balance should be equal to fee reward plus excess bond after curve and multiplier minus locked" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(15.7 ether), 2 wei, - "bond shares after claim should be equal to before minus excess bond after curve and multiplier minus blocked" + "bond shares after claim should be equal to before minus excess bond after curve and multiplier minus locked" ); assertEq( stETH.sharesOf(address(accounting)), @@ -3002,6 +3044,7 @@ contract CSAccountingClaimWstETHRewardsTest is CSAccountingClaimRewardsBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); @@ -3045,7 +3088,7 @@ contract CSAccountingClaimWstETHRewardsTest is function test_WithCurve() public override { _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -3087,8 +3130,9 @@ contract CSAccountingClaimWstETHRewardsTest is } function test_WithMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondMultiplier_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -3129,9 +3173,10 @@ contract CSAccountingClaimWstETHRewardsTest is ); } - function test_WithBlocked() public override { + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondLock_ForTest(); + _lock({ id: 0, amount: 1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -3173,9 +3218,10 @@ contract CSAccountingClaimWstETHRewardsTest is } function test_WithCurveAndMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -3216,10 +3262,11 @@ contract CSAccountingClaimWstETHRewardsTest is ); } - function test_WithCurveAndBlocked() public override { + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -3236,13 +3283,13 @@ contract CSAccountingClaimWstETHRewardsTest is wstETH.balanceOf(address(user)), wstETH.getWstETHByStETH(stETHAsFee + 14 ether), 1 wei, - "user balance should be equal to fee reward plus excess bond after curve minus blocked" + "user balance should be equal to fee reward plus excess bond after curve minus locked" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(14 ether), 1 wei, - "bond shares after claim should be equal to before minus excess bond after curve minus blocked" + "bond shares after claim should be equal to before minus excess bond after curve minus locked" ); assertEq( wstETH.balanceOf(address(accounting)), @@ -3261,10 +3308,11 @@ contract CSAccountingClaimWstETHRewardsTest is ); } - function test_WithMultiplierAndBlocked() public override { + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -3281,13 +3329,13 @@ contract CSAccountingClaimWstETHRewardsTest is wstETH.balanceOf(address(user)), wstETH.getWstETHByStETH(stETHAsFee + 2.2 ether), 1 wei, - "user balance should be equal to fee reward plus excess bond after multiplier minus blocked" + "user balance should be equal to fee reward plus excess bond after multiplier minus locked" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(2.2 ether), 1 wei, - "bond shares after claim should be equal to before minus excess bond after multiplier minus blocked" + "bond shares after claim should be equal to before minus excess bond after multiplier minus locked" ); assertEq( wstETH.balanceOf(address(accounting)), @@ -3306,11 +3354,12 @@ contract CSAccountingClaimWstETHRewardsTest is ); } - function test_WithCurveAndMultiplierAndBlocked() public override { + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -3327,13 +3376,13 @@ contract CSAccountingClaimWstETHRewardsTest is wstETH.balanceOf(address(user)), wstETH.getWstETHByStETH(stETHAsFee + 15.7 ether), 1 wei, - "user balance should be equal to fee reward plus excess bond after curve and multiplier minus blocked" + "user balance should be equal to fee reward plus excess bond after curve and multiplier minus locked" ); assertApproxEqAbs( bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(15.7 ether), 2 wei, - "bond shares after claim should be equal to before minus excess bond after curve and multiplier minus blocked" + "bond shares after claim should be equal to before minus excess bond after curve and multiplier minus locked" ); assertEq( wstETH.balanceOf(address(accounting)), @@ -3733,6 +3782,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is CSAccountingClaimRewardsBaseTest { function test_default() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); @@ -3766,7 +3816,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is function test_WithCurve() public override { _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); + _curve(defaultCurve); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -3800,8 +3850,9 @@ contract CSAccountingRequestRewardsETHRewardsTest is } function test_WithMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondMultiplier_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -3832,9 +3883,10 @@ contract CSAccountingRequestRewardsETHRewardsTest is ); } - function test_WithBlocked() public override { + function test_WithLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondLock_ForTest(); + _lock({ id: 0, amount: 1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -3866,9 +3918,10 @@ contract CSAccountingRequestRewardsETHRewardsTest is } function test_WithCurveAndMultiplier() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -3901,10 +3954,11 @@ contract CSAccountingRequestRewardsETHRewardsTest is ); } - function test_WithCurveAndBlocked() public override { + function test_WithCurveAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _lock({ id: 0, amount: 1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -3921,7 +3975,7 @@ contract CSAccountingRequestRewardsETHRewardsTest is bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(14 ether), 1 wei, - "bond shares should be equal to before minus excess bond after curve and blocked" + "bond shares should be equal to before minus excess bond after curve and locked" ); assertApproxEqAbs( stETH.sharesOf(address(locator.withdrawalQueue())), @@ -3937,10 +3991,11 @@ contract CSAccountingRequestRewardsETHRewardsTest is ); } - function test_WithMultiplierAndBlocked() public override { + function test_WithMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -3957,13 +4012,13 @@ contract CSAccountingRequestRewardsETHRewardsTest is bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(2.2 ether), 1 wei, - "bond shares should be equal to before minus excess bond after multiplier and blocked" + "bond shares should be equal to before minus excess bond after multiplier and locked" ); assertApproxEqAbs( stETH.sharesOf(address(locator.withdrawalQueue())), unstETHSharesAsFee + stETH.getSharesByPooledEth(2.2 ether), 1 wei, - "shares of withdrawal queue should be equal to requested shares and excess bond after multiplier and blocked" + "shares of withdrawal queue should be equal to requested shares and excess bond after multiplier and locked" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); assertEq( @@ -3973,11 +4028,12 @@ contract CSAccountingRequestRewardsETHRewardsTest is ); } - function test_WithCurveAndMultiplierAndBlocked() public override { + function test_WithCurveAndMultiplierAndLocked() public override { + _operator({ ongoing: 16, withdrawn: 0 }); _deposit({ bond: 32 ether, fee: 0.1 ether }); - accounting.setBondCurve_ForTest(); - accounting.setBondMultiplier_ForTest(); - accounting.setBondLock_ForTest(); + _curve(defaultCurve); + _multiplier({ id: 0, multiplier: 9000 }); + _lock({ id: 0, amount: 1 ether }); uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(user); @@ -3994,13 +4050,13 @@ contract CSAccountingRequestRewardsETHRewardsTest is bondSharesAfter, bondSharesBefore - stETH.getSharesByPooledEth(15.7 ether), 2 wei, - "bond shares should be equal to before minus excess bond after curve and multiplier and blocked" + "bond shares should be equal to before minus excess bond after curve and multiplier and locked" ); assertApproxEqAbs( stETH.sharesOf(address(locator.withdrawalQueue())), unstETHSharesAsFee + stETH.getSharesByPooledEth(15.7 ether), 2 wei, - "shares of withdrawal queue should be equal to requested shares and excess bond after curve and multiplier and blocked" + "shares of withdrawal queue should be equal to requested shares and excess bond after curve and multiplier and locked" ); assertEq(stETH.sharesOf(address(user)), 0, "user shares should be 0"); assertEq( diff --git a/test/CSBondCurve.t.sol b/test/CSBondCurve.t.sol index 17b3ff63..7ddf3b7d 100644 --- a/test/CSBondCurve.t.sol +++ b/test/CSBondCurve.t.sol @@ -5,11 +5,15 @@ pragma solidity 0.8.21; import "forge-std/Test.sol"; -import { CSBondCurve } from "../src/CSBondCurve.sol"; +import { CSBondCurve, CSBondCurveBase } from "../src/CSBondCurve.sol"; contract CSBondCurveTestable is CSBondCurve { constructor(uint256[] memory bondCurve) CSBondCurve(bondCurve) {} + function getBondCurveTrend() external view returns (uint256) { + return _bondCurveTrend; + } + function setBondCurve(uint256[] memory bondCurve) external { _setBondCurve(bondCurve); } @@ -48,22 +52,16 @@ contract CSBondCurveTestable is CSBondCurve { } } -contract CSBondCurveTest is Test { +contract CSBondCurveTest is Test, CSBondCurveBase { + // todo: add gas-cost test for _searchKeysCount + CSBondCurveTestable public bondCurve; function setUp() public { - uint256[] memory _bondCurve = new uint256[](11); + uint256[] memory _bondCurve = new uint256[](3); _bondCurve[0] = 2 ether; - _bondCurve[1] = 3.90 ether; // 1.9 - _bondCurve[2] = 5.70 ether; // 1.8 - _bondCurve[3] = 7.40 ether; // 1.7 - _bondCurve[4] = 9.00 ether; // 1.6 - _bondCurve[5] = 10.50 ether; // 1.5 - _bondCurve[6] = 11.90 ether; // 1.4 - _bondCurve[7] = 13.10 ether; // 1.3 - _bondCurve[8] = 14.30 ether; // 1.2 - _bondCurve[9] = 15.40 ether; // 1.1 - _bondCurve[10] = 16.40 ether; // 1.0 + _bondCurve[1] = 4 ether; + _bondCurve[2] = 5 ether; bondCurve = new CSBondCurveTestable(_bondCurve); } @@ -72,10 +70,14 @@ contract CSBondCurveTest is Test { _bondCurve[0] = 16 ether; _bondCurve[1] = 32 ether; + vm.expectEmit(true, true, true, true, address(bondCurve)); + emit BondCurveChanged(_bondCurve); + bondCurve.setBondCurve(_bondCurve); assertEq(bondCurve.bondCurve(0), 16 ether); assertEq(bondCurve.bondCurve(1), 32 ether); + assertEq(bondCurve.getBondCurveTrend(), 16 ether); } function test_setBondCurve_RevertWhen_LessThanMinBondCurveLength() public { @@ -89,33 +91,34 @@ contract CSBondCurveTest is Test { } function test_setBondCurve_RevertWhen_ZeroValue() public { - vm.expectRevert(CSBondCurve.InvalidBondCurveValues.selector); - uint256[] memory _bondCurve = new uint256[](1); _bondCurve[0] = 0 ether; + vm.expectRevert(CSBondCurve.InvalidBondCurveValues.selector); bondCurve.setBondCurve(_bondCurve); } function test_getKeysCountByBondAmount() public { assertEq(bondCurve.getKeysCountByBondAmount(0), 0); + assertEq(bondCurve.getKeysCountByBondAmount(1.9 ether), 0); assertEq(bondCurve.getKeysCountByBondAmount(2 ether), 1); - assertEq(bondCurve.getKeysCountByBondAmount(3 ether), 1); - assertEq(bondCurve.getKeysCountByBondAmount(3.90 ether), 2); - assertEq(bondCurve.getKeysCountByBondAmount(17 ether), 11); - assertEq(bondCurve.getKeysCountByBondAmount(17.40 ether), 12); + assertEq(bondCurve.getKeysCountByBondAmount(2.1 ether), 1); + assertEq(bondCurve.getKeysCountByBondAmount(4 ether), 2); + assertEq(bondCurve.getKeysCountByBondAmount(5 ether), 3); + assertEq(bondCurve.getKeysCountByBondAmount(5.1 ether), 3); + assertEq(bondCurve.getKeysCountByBondAmount(6 ether), 4); } function test_getBondAmountByKeysCount() public { assertEq(bondCurve.getBondAmountByKeysCount(0), 0); assertEq(bondCurve.getBondAmountByKeysCount(1), 2 ether); - assertEq(bondCurve.getBondAmountByKeysCount(2), 3.90 ether); - assertEq(bondCurve.getBondAmountByKeysCount(11), 16.40 ether); - assertEq(bondCurve.getBondAmountByKeysCount(12), 17.40 ether); + assertEq(bondCurve.getBondAmountByKeysCount(2), 4 ether); + assertEq(bondCurve.getBondAmountByKeysCount(3), 5 ether); + assertEq(bondCurve.getBondAmountByKeysCount(4), 6 ether); } } -contract CSBondCurveWithMultiplierTest is Test { +contract CSBondCurveWithMultiplierTest is Test, CSBondCurveBase { CSBondCurveTestable public bondCurve; function setUp() public { @@ -127,6 +130,9 @@ contract CSBondCurveWithMultiplierTest is Test { function test_setBondMultiplier() public { assertEq(bondCurve.getBondMultiplier(0), 10000); + vm.expectEmit(true, true, true, true, address(bondCurve)); + emit BondMultiplierChanged(0, 5000); + bondCurve.setBondMultiplier(0, 5000); assertEq(bondCurve.getBondMultiplier(0), 5000); @@ -136,54 +142,22 @@ contract CSBondCurveWithMultiplierTest is Test { function test_setBondMultiplier_RevertWhen_LessThanMin() public { vm.expectRevert(CSBondCurve.InvalidMultiplier.selector); - bondCurve.setBondMultiplier(0, 4999); } function test_setBondMultiplier_RevertWhen_MoreThanMax() public { vm.expectRevert(CSBondCurve.InvalidMultiplier.selector); - bondCurve.setBondMultiplier(0, 10001); } function test_getKeysCountByCurveValue() public { - assertEq( - bondCurve.getKeysCountByBondAmount({ amount: 0, multiplier: 5000 }), - 0 - ); - assertEq( - bondCurve.getKeysCountByBondAmount({ - amount: 1 ether, - multiplier: 5000 - }), - 1 - ); - assertEq( - bondCurve.getKeysCountByBondAmount({ - amount: 2.99 ether, - multiplier: 5000 - }), - 2 - ); + assertEq(bondCurve.getKeysCountByBondAmount(0 ether, 5000), 0); + assertEq(bondCurve.getKeysCountByBondAmount(1 ether, 5000), 1); + assertEq(bondCurve.getKeysCountByBondAmount(2 ether, 5000), 2); - assertEq( - bondCurve.getKeysCountByBondAmount({ amount: 0, multiplier: 9000 }), - 0 - ); - assertEq( - bondCurve.getKeysCountByBondAmount({ - amount: 1.8 ether, - multiplier: 9000 - }), - 1 - ); - assertEq( - bondCurve.getKeysCountByBondAmount({ - amount: 5.39 ether, - multiplier: 9000 - }), - 2 - ); + assertEq(bondCurve.getKeysCountByBondAmount(0 ether, 9000), 0); + assertEq(bondCurve.getKeysCountByBondAmount(1.8 ether, 9000), 1); + assertEq(bondCurve.getKeysCountByBondAmount(5.39 ether, 9000), 2); } function test_getBondAmountByKeysCount() public { From db68e6cc83b3a89ce9d54a1044c4ac8394348f4c Mon Sep 17 00:00:00 2001 From: vgorkavenko Date: Wed, 6 Dec 2023 13:30:18 +0400 Subject: [PATCH 18/18] fix: review (one more) --- test/CSBondCurve.t.sol | 32 +++++++------------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/test/CSBondCurve.t.sol b/test/CSBondCurve.t.sol index 7ddf3b7d..a5fe66f6 100644 --- a/test/CSBondCurve.t.sol +++ b/test/CSBondCurve.t.sol @@ -161,30 +161,12 @@ contract CSBondCurveWithMultiplierTest is Test, CSBondCurveBase { } function test_getBondAmountByKeysCount() public { - assertEq( - bondCurve.getBondAmountByKeysCount({ keys: 0, multiplier: 5000 }), - 0 - ); - assertEq( - bondCurve.getBondAmountByKeysCount({ keys: 1, multiplier: 5000 }), - 1 ether - ); - assertEq( - bondCurve.getBondAmountByKeysCount({ keys: 2, multiplier: 5000 }), - 2 ether - ); - - assertEq( - bondCurve.getBondAmountByKeysCount({ keys: 0, multiplier: 9000 }), - 0 - ); - assertEq( - bondCurve.getBondAmountByKeysCount({ keys: 1, multiplier: 9000 }), - 1.8 ether - ); - assertEq( - bondCurve.getBondAmountByKeysCount({ keys: 2, multiplier: 9000 }), - 3.6 ether - ); + assertEq(bondCurve.getBondAmountByKeysCount(0, 5000), 0); + assertEq(bondCurve.getBondAmountByKeysCount(1, 5000), 1 ether); + assertEq(bondCurve.getBondAmountByKeysCount(2, 5000), 2 ether); + + assertEq(bondCurve.getBondAmountByKeysCount(0, 9000), 0); + assertEq(bondCurve.getBondAmountByKeysCount(1, 9000), 1.8 ether); + assertEq(bondCurve.getBondAmountByKeysCount(2, 9000), 3.6 ether); } }