diff --git a/GAS.md b/GAS.md index f9b57666..972c8396 100644 --- a/GAS.md +++ b/GAS.md @@ -1,23 +1,25 @@ | src/CSAccounting.sol:CSAccounting contract | | | | | | |--------------------------------------------|-----------------|--------|--------|--------|---------| | Function Name | min | avg | median | max | # calls | -| ADD_BOND_CURVE_ROLE | 252 | 252 | 252 | 252 | 173 | -| INITIALIZE_ROLE | 296 | 296 | 296 | 296 | 173 | -| RESET_BOND_CURVE_ROLE | 296 | 296 | 296 | 296 | 213 | -| SET_BOND_CURVE_ROLE | 252 | 252 | 252 | 252 | 213 | -| addBondCurve | 121324 | 121324 | 121324 | 121324 | 5 | +| ADD_BOND_CURVE_ROLE | 252 | 252 | 252 | 252 | 193 | +| INITIALIZE_ROLE | 296 | 296 | 296 | 296 | 193 | +| RESET_BOND_CURVE_ROLE | 296 | 296 | 296 | 296 | 233 | +| SET_BOND_CURVE_ROLE | 252 | 252 | 252 | 252 | 233 | +| addBondCurve | 121336 | 127946 | 121336 | 144474 | 7 | | feeDistributor | 2428 | 2428 | 2428 | 2428 | 2 | | getActualLockedBond | 581 | 687 | 741 | 741 | 3 | -| getBondAmountByKeysCount | 1347 | 1439 | 1347 | 1610 | 153 | +| getBondAmountByKeysCount | 1347 | 1433 | 1347 | 1610 | 173 | | getBondAmountByKeysCountWstETH | 14217 | 14217 | 14217 | 14217 | 2 | -| getBondCurve | 2182 | 15981 | 16278 | 16278 | 157 | +| getBondCurve | 2182 | 15861 | 16278 | 16278 | 179 | | getBondLockRetentionPeriod | 2370 | 2370 | 2370 | 2370 | 2 | -| getLockedBondInfo | 815 | 815 | 815 | 815 | 7 | -| getRequiredBondForNextKeys | 10108 | 33316 | 52608 | 53134 | 13 | -| getRequiredBondForNextKeysWstETH | 58979 | 58979 | 58979 | 58979 | 2 | -| getUnbondedKeysCount | 7730 | 24242 | 15730 | 46230 | 250 | -| grantRole | 118386 | 118391 | 118386 | 118398 | 772 | -| setFeeDistributor | 47567 | 47567 | 47567 | 47567 | 173 | +| getBondShares | 563 | 563 | 563 | 563 | 10 | +| getLockedBondInfo | 815 | 815 | 815 | 815 | 8 | +| getRequiredBondForNextKeys | 10003 | 31805 | 50503 | 51178 | 17 | +| getRequiredBondForNextKeysWstETH | 57023 | 57023 | 57023 | 57023 | 2 | +| getUnbondedKeysCount | 7774 | 24581 | 15774 | 46274 | 306 | +| grantRole | 118386 | 118391 | 118386 | 118398 | 852 | +| setBondCurve | 49830 | 49830 | 49830 | 49830 | 2 | +| setFeeDistributor | 47567 | 47567 | 47567 | 47567 | 193 | | src/CSEarlyAdoption.sol:CSEarlyAdoption contract | | | | | | @@ -64,80 +66,82 @@ | src/CSModule.sol:CSModule contract | | | | | | |-----------------------------------------|-----------------|--------|--------|---------|---------| | Function Name | min | avg | median | max | # calls | -| DEFAULT_ADMIN_ROLE | 327 | 327 | 327 | 327 | 1 | -| DEPOSIT_SIZE | 307 | 307 | 307 | 307 | 10 | -| EL_REWARDS_STEALING_FINE | 328 | 328 | 328 | 328 | 7 | -| INITIALIZE_ROLE | 286 | 286 | 286 | 286 | 220 | -| INITIAL_SLASHING_PENALTY | 352 | 352 | 352 | 352 | 3 | +| DEFAULT_ADMIN_ROLE | 283 | 283 | 283 | 283 | 1 | +| DEPOSIT_SIZE | 307 | 307 | 307 | 307 | 12 | +| EL_REWARDS_STEALING_FINE | 328 | 328 | 328 | 328 | 8 | +| INITIALIZE_ROLE | 308 | 308 | 308 | 308 | 240 | +| INITIAL_SLASHING_PENALTY | 352 | 352 | 352 | 352 | 4 | | MAX_SIGNING_KEYS_BEFORE_PUBLIC_RELEASE | 315 | 315 | 315 | 315 | 1 | -| MODULE_MANAGER_ROLE | 328 | 328 | 328 | 328 | 217 | -| PAUSE_ROLE | 285 | 285 | 285 | 285 | 174 | -| PENALIZE_ROLE | 284 | 284 | 284 | 284 | 173 | -| RECOVERER_ROLE | 305 | 305 | 305 | 305 | 4 | -| REPORT_EL_REWARDS_STEALING_PENALTY_ROLE | 308 | 308 | 308 | 308 | 175 | -| RESUME_ROLE | 329 | 329 | 329 | 329 | 174 | -| SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE | 284 | 284 | 284 | 284 | 175 | -| STAKING_ROUTER_ROLE | 315 | 315 | 315 | 315 | 197 | -| VERIFIER_ROLE | 327 | 327 | 327 | 327 | 177 | -| accounting | 470 | 470 | 470 | 470 | 1 | -| activatePublicRelease | 23726 | 46554 | 46677 | 46677 | 187 | -| addNodeOperatorETH | 26187 | 601087 | 547220 | 1072382 | 153 | -| addNodeOperatorStETH | 26973 | 367345 | 535274 | 539789 | 3 | -| addNodeOperatorWstETH | 26952 | 379386 | 553064 | 558143 | 3 | -| addValidatorKeysETH | 25615 | 229082 | 258998 | 313415 | 6 | -| addValidatorKeysStETH | 26373 | 171253 | 241430 | 245957 | 3 | -| addValidatorKeysWstETH | 26373 | 183308 | 259515 | 264036 | 3 | -| cancelELRewardsStealingPenalty | 26253 | 89593 | 99047 | 134024 | 4 | -| claimRewardsStETH | 25070 | 45579 | 27287 | 84381 | 3 | -| claimRewardsWstETH | 25028 | 45167 | 27245 | 83230 | 3 | -| cleanDepositQueue | 26304 | 36076 | 33834 | 53080 | 12 | -| compensateELRewardsStealingPenalty | 23644 | 109979 | 153147 | 153147 | 3 | -| confirmNodeOperatorManagerAddressChange | 23712 | 29037 | 29097 | 34186 | 5 | -| confirmNodeOperatorRewardAddressChange | 23691 | 33081 | 33970 | 38918 | 6 | -| decreaseOperatorVettedKeys | 24834 | 91390 | 107427 | 155121 | 15 | -| depositQueueItem | 624 | 1290 | 624 | 2624 | 12 | +| MODULE_MANAGER_ROLE | 328 | 328 | 328 | 328 | 237 | +| PAUSE_ROLE | 285 | 285 | 285 | 285 | 194 | +| RECOVERER_ROLE | 327 | 327 | 327 | 327 | 4 | +| REPORT_EL_REWARDS_STEALING_PENALTY_ROLE | 307 | 307 | 307 | 307 | 195 | +| RESUME_ROLE | 329 | 329 | 329 | 329 | 194 | +| SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE | 284 | 284 | 284 | 284 | 195 | +| STAKING_ROUTER_ROLE | 359 | 359 | 359 | 359 | 217 | +| VERIFIER_ROLE | 327 | 327 | 327 | 327 | 197 | +| accounting | 427 | 427 | 427 | 427 | 1 | +| activatePublicRelease | 23726 | 46566 | 46677 | 46677 | 207 | +| addNodeOperatorETH | 26187 | 594315 | 545701 | 1070863 | 173 | +| addNodeOperatorStETH | 26973 | 366332 | 533755 | 538270 | 3 | +| addNodeOperatorWstETH | 26952 | 378373 | 551545 | 556624 | 3 | +| addValidatorKeysETH | 25615 | 235433 | 257622 | 312302 | 8 | +| addValidatorKeysStETH | 26395 | 170537 | 240345 | 244872 | 3 | +| addValidatorKeysWstETH | 26395 | 182592 | 258430 | 262951 | 3 | +| cancelELRewardsStealingPenalty | 26275 | 92404 | 101872 | 139597 | 4 | +| claimRewardsStETH | 25051 | 50627 | 27268 | 99562 | 3 | +| claimRewardsWstETH | 25050 | 50256 | 27267 | 98452 | 3 | +| cleanDepositQueue | 26281 | 36053 | 33811 | 53057 | 12 | +| compensateELRewardsStealingPenalty | 23688 | 114405 | 137770 | 158395 | 4 | +| confirmNodeOperatorManagerAddressChange | 23690 | 29015 | 29075 | 34164 | 5 | +| confirmNodeOperatorRewardAddressChange | 23713 | 33103 | 33992 | 38940 | 6 | +| decreaseOperatorVettedKeys | 24834 | 91474 | 107524 | 155315 | 15 | +| depositETH | 23656 | 118654 | 125062 | 175097 | 8 | +| depositQueueItem | 623 | 1289 | 623 | 2623 | 12 | +| depositStETH | 24649 | 102467 | 108282 | 158317 | 5 | +| depositWstETH | 24696 | 115487 | 124549 | 174584 | 5 | | earlyAdoption | 427 | 427 | 427 | 427 | 1 | -| getNodeOperator | 2214 | 9973 | 8214 | 20214 | 291 | +| getNodeOperator | 2258 | 10121 | 8258 | 18258 | 352 | | getNodeOperatorSigningKeys | 819 | 2877 | 3594 | 3594 | 7 | -| getNodeOperatorSummary | 1515 | 5426 | 7515 | 7515 | 45 | -| getNodeOperatorsCount | 425 | 488 | 425 | 2425 | 314 | -| getNonce | 380 | 680 | 380 | 2380 | 40 | -| getStakingModuleSummary | 618 | 2751 | 2618 | 4618 | 15 | +| getNodeOperatorSummary | 1492 | 5403 | 7492 | 7492 | 45 | +| getNodeOperatorsCount | 380 | 391 | 380 | 2380 | 171 | +| getNonce | 380 | 533 | 380 | 2380 | 78 | +| getStakingModuleSummary | 640 | 2773 | 2640 | 4640 | 15 | | getType | 427 | 427 | 427 | 427 | 1 | -| grantRole | 26943 | 51436 | 51451 | 51451 | 1664 | -| hasRole | 725 | 725 | 725 | 725 | 2 | -| isPaused | 419 | 819 | 419 | 2419 | 5 | -| normalizeQueue | 30255 | 54734 | 54734 | 79213 | 2 | -| obtainDepositData | 24453 | 106299 | 96745 | 158540 | 47 | +| grantRole | 26943 | 51436 | 51451 | 51451 | 1651 | +| hasRole | 747 | 747 | 747 | 747 | 2 | +| isPaused | 441 | 841 | 441 | 2441 | 5 | +| normalizeQueue | 30213 | 54692 | 54692 | 79171 | 2 | +| obtainDepositData | 24453 | 106774 | 101269 | 158540 | 49 | | onExitedAndStuckValidatorsCountsUpdated | 23670 | 23703 | 23703 | 23736 | 2 | -| onRewardsMinted | 23964 | 46966 | 47099 | 69835 | 3 | -| onWithdrawalCredentialsChanged | 23782 | 25268 | 25011 | 27011 | 3 | +| onRewardsMinted | 23986 | 46988 | 47121 | 69857 | 3 | +| onWithdrawalCredentialsChanged | 23737 | 25223 | 24966 | 26966 | 3 | | pauseFor | 23988 | 45359 | 47497 | 47497 | 11 | -| proposeNodeOperatorManagerAddressChange | 24143 | 42592 | 53582 | 53582 | 9 | +| proposeNodeOperatorManagerAddressChange | 24165 | 42614 | 53604 | 53604 | 9 | | proposeNodeOperatorRewardAddressChange | 24166 | 33434 | 36483 | 36483 | 10 | | publicRelease | 409 | 409 | 409 | 409 | 1 | -| queue | 475 | 808 | 475 | 2475 | 6 | -| recoverERC20 | 58414 | 58414 | 58414 | 58414 | 1 | -| recoverEther | 23703 | 25991 | 25991 | 28280 | 2 | -| recoverStETHShares | 62866 | 62866 | 62866 | 62866 | 1 | +| queue | 520 | 853 | 520 | 2520 | 6 | +| recoverERC20 | 58436 | 58436 | 58436 | 58436 | 1 | +| recoverEther | 23725 | 26013 | 26013 | 28302 | 2 | +| recoverStETHShares | 62845 | 62845 | 62845 | 62845 | 1 | | removalCharge | 386 | 1386 | 1386 | 2386 | 2 | -| removeKeys | 24026 | 145216 | 172224 | 240206 | 15 | -| reportELRewardsStealingPenalty | 24306 | 122478 | 136018 | 146035 | 16 | -| requestRewardsETH | 25051 | 45546 | 27268 | 84320 | 3 | -| resetNodeOperatorManagerAddress | 23690 | 31835 | 31312 | 38442 | 5 | +| removeKeys | 23982 | 145240 | 172266 | 240248 | 15 | +| reportELRewardsStealingPenalty | 24306 | 130691 | 141222 | 151239 | 23 | +| requestRewardsETH | 25051 | 50613 | 27268 | 99520 | 3 | +| resetNodeOperatorManagerAddress | 23668 | 31813 | 31290 | 38420 | 5 | | resume | 23730 | 26642 | 26642 | 29555 | 2 | | revokeRole | 29529 | 29529 | 29529 | 29529 | 1 | -| setAccounting | 24268 | 46394 | 46497 | 46497 | 216 | +| setAccounting | 24245 | 46379 | 46474 | 46474 | 236 | | setEarlyAdoption | 24006 | 38634 | 46475 | 46475 | 8 | -| setRemovalCharge | 24026 | 47013 | 47146 | 47158 | 175 | -| settleELRewardsStealingPenalty | 24690 | 67441 | 38740 | 112093 | 7 | -| submitInitialSlashing | 24058 | 97644 | 134090 | 134990 | 12 | -| submitWithdrawal | 24327 | 121313 | 140970 | 234701 | 14 | -| unsafeUpdateValidatorsCount | 24263 | 61236 | 35929 | 159791 | 10 | -| updateExitedValidatorsCount | 24811 | 58512 | 47489 | 110217 | 11 | -| updateRefundedValidatorsCount | 24092 | 27679 | 27679 | 31266 | 2 | -| updateStuckValidatorsCount | 24788 | 72995 | 60520 | 138460 | 13 | -| updateTargetValidatorsLimits | 24279 | 118175 | 137409 | 210329 | 19 | +| setRemovalCharge | 24003 | 47004 | 47123 | 47135 | 195 | +| settleELRewardsStealingPenalty | 24690 | 78423 | 110087 | 123475 | 9 | +| submitInitialSlashing | 24058 | 97736 | 129506 | 133715 | 13 | +| submitWithdrawal | 24283 | 121486 | 135812 | 234753 | 15 | +| unsafeUpdateValidatorsCount | 24263 | 61247 | 35929 | 159844 | 10 | +| updateExitedValidatorsCount | 24788 | 58489 | 47466 | 110194 | 11 | +| updateRefundedValidatorsCount | 24114 | 27701 | 27701 | 31288 | 2 | +| updateStuckValidatorsCount | 24810 | 73044 | 60542 | 138579 | 13 | +| updateTargetValidatorsLimits | 24301 | 118155 | 137527 | 210447 | 19 | | src/CSVerifier.sol:CSVerifier contract | | | | | | diff --git a/src/CSAccounting.sol b/src/CSAccounting.sol index f225bdcc..9260534d 100644 --- a/src/CSAccounting.sol +++ b/src/CSAccounting.sol @@ -26,7 +26,6 @@ abstract contract CSAccountingBase { error AlreadyInitialized(); error InvalidSender(); error SenderIsNotCSM(); - error NodeOperatorDoesNotExist(); } /// @author vgorkavenko @@ -374,16 +373,8 @@ contract CSAccounting is function depositETH( address from, uint256 nodeOperatorId - ) - external - payable - whenResumed - onlyExistingNodeOperator(nodeOperatorId) - returns (uint256 shares) - { - from = _validateDepositSender(from); + ) external payable whenResumed onlyCSM returns (uint256 shares) { shares = CSBondCore._depositETH(from, nodeOperatorId); - CSM.onBondChanged(nodeOperatorId); } /// @notice Deposit user's stETH to the bond for the given Node Operator @@ -398,14 +389,7 @@ contract CSAccounting is uint256 nodeOperatorId, uint256 stETHAmount, PermitInput calldata permit - ) - external - whenResumed - onlyExistingNodeOperator(nodeOperatorId) - returns (uint256 shares) - { - // TODO: can it be two functions rather than one with `from` param and condition? - from = _validateDepositSender(from); + ) external whenResumed onlyCSM returns (uint256 shares) { // preventing revert for already used permit or avoid permit usage in case of value == 0 if ( permit.value > 0 && @@ -423,7 +407,6 @@ contract CSAccounting is ); } shares = CSBondCore._depositStETH(from, nodeOperatorId, stETHAmount); - CSM.onBondChanged(nodeOperatorId); } /// @notice Unwrap user's wstETH and make deposit in stETH to the bond for the given Node Operator @@ -438,14 +421,7 @@ contract CSAccounting is uint256 nodeOperatorId, uint256 wstETHAmount, PermitInput calldata permit - ) - external - whenResumed - onlyExistingNodeOperator(nodeOperatorId) - returns (uint256 shares) - { - // TODO: can it be two functions rather than one with `from` param and condition? - from = _validateDepositSender(from); + ) external whenResumed onlyCSM returns (uint256 shares) { // preventing revert for already used permit or avoid permit usage in case of value == 0 if ( permit.value > 0 && @@ -463,7 +439,6 @@ contract CSAccounting is ); } shares = CSBondCore._depositWstETH(from, nodeOperatorId, wstETHAmount); - CSM.onBondChanged(nodeOperatorId); } /// @dev only CSM can pass `from` != `msg.sender` @@ -672,7 +647,6 @@ contract CSAccounting is rewardsProof ); _increaseBond(nodeOperatorId, distributed); - CSM.onBondChanged(nodeOperatorId); } modifier onlyCSM() { diff --git a/src/CSModule.sol b/src/CSModule.sol index 497e6f66..f7424c2d 100644 --- a/src/CSModule.sol +++ b/src/CSModule.sol @@ -98,7 +98,6 @@ contract CSModuleBase { uint256 proposedBlockNumber, uint256 stolenAmount ); - event ELRewardsStealingPenaltyCancelled( uint256 indexed nodeOperatorId, uint256 amount @@ -140,7 +139,6 @@ contract CSModule is PausableUntil, AssetRecoverer { - /// @notice This contract stores minted stETH shares for further distribution between node operators using SafeERC20 for IERC20; using QueueLib for QueueLib.Queue; @@ -157,7 +155,6 @@ contract CSModule is bytes32 public constant SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE = keccak256("SETTLE_EL_REWARDS_STEALING_PENALTY_ROLE"); // 0xe85fdec10fe0f93d0792364051df7c3d73e37c17b3a954bffe593960e3cd3012 bytes32 public constant VERIFIER_ROLE = keccak256("VERIFIER_ROLE"); // 0x0ce23c3e399818cfee81a7ab0880f714e53d7672b08df0fa62f2843416e1ea09 - bytes32 public constant PENALIZE_ROLE = keccak256("PENALIZE_ROLE"); // 0x014ffee5f075680f5690d491d67de8e1aba5c4a88326c3be77d991796b44f86b bytes32 public constant RECOVERER_ROLE = keccak256("RECOVERER_ROLE"); // 0xb3e25b5404b87e5a838579cb5d7481d61ad96ee284d38ec1e97c07ba64e7f6fc uint8 public constant MAX_SIGNING_KEYS_BEFORE_PUBLIC_RELEASE = 10; @@ -170,7 +167,6 @@ contract CSModule is keccak256("lido.CommunityStakingModule.signingKeysPosition"); uint256 public constant EL_REWARDS_STEALING_FINE = 0.1 ether; - uint256 private constant ONE_YEAR = 365 days; bool public publicRelease; uint256 public removalCharge; @@ -288,7 +284,6 @@ contract CSModule is /// @param signatures Signatures of public keys /// @param eaProof Merkle proof of the sender being eligible for the Early Adoption /// @param referral Optional referral address - /// TODO consider splitting into methods with proof and without function addNodeOperatorETH( uint256 keysCount, bytes calldata publicKeys, @@ -312,7 +307,15 @@ contract CSModule is } _addSigningKeys(nodeOperatorId, keysCount, publicKeys, signatures); + accounting.depositETH{ value: msg.value }(msg.sender, nodeOperatorId); + + // Due to new bonded keys nonce update is required and normalize queue is required + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: true + }); } /// @notice Adds a new node operator with stETH bond @@ -346,6 +349,13 @@ contract CSModule is ), permit ); + + // Due to new bonded keys nonce update is required and normalize queue is required + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: true + }); } /// @notice Adds a new node operator with wstETH bond @@ -379,6 +389,13 @@ contract CSModule is ), permit ); + + // Due to new bonded keys nonce update is required and normalize queue is required + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: true + }); } /// @notice Adds a new keys to the node operator with ETH bond @@ -391,8 +408,9 @@ contract CSModule is uint256 keysCount, bytes calldata publicKeys, bytes calldata signatures - ) external payable whenResumed { + ) external payable whenResumed onlyExistingNodeOperator(nodeOperatorId) { // TODO: sanity checks + onlyNodeOperatorManager(nodeOperatorId); if ( msg.value != @@ -402,7 +420,15 @@ contract CSModule is } _addSigningKeys(nodeOperatorId, keysCount, publicKeys, signatures); + accounting.depositETH{ value: msg.value }(msg.sender, nodeOperatorId); + + // Due to new bonded keys nonce update is required and normalize queue is required + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: true + }); } /// @notice Adds a new keys to the node operator with stETH bond @@ -417,9 +443,9 @@ contract CSModule is bytes calldata publicKeys, bytes calldata signatures, ICSAccounting.PermitInput calldata permit - ) external whenResumed { + ) external whenResumed onlyExistingNodeOperator(nodeOperatorId) { // TODO: sanity checks - // TODO: allow only Node Operator manager + onlyNodeOperatorManager(nodeOperatorId); uint256 amount = accounting.getRequiredBondForNextKeys( nodeOperatorId, @@ -428,6 +454,13 @@ contract CSModule is _addSigningKeys(nodeOperatorId, keysCount, publicKeys, signatures); accounting.depositStETH(msg.sender, nodeOperatorId, amount, permit); + + // Due to new bonded keys nonce update is required and normalize queue is required + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: true + }); } /// @notice Adds a new keys to the node operator with wstETH bond @@ -442,9 +475,9 @@ contract CSModule is bytes calldata publicKeys, bytes calldata signatures, ICSAccounting.PermitInput calldata permit - ) external whenResumed { + ) external whenResumed onlyExistingNodeOperator(nodeOperatorId) { // TODO: sanity checks - // TODO: allow only Node Operator manager + onlyNodeOperatorManager(nodeOperatorId); uint256 amount = accounting.getRequiredBondForNextKeysWstETH( nodeOperatorId, @@ -453,6 +486,76 @@ contract CSModule is _addSigningKeys(nodeOperatorId, keysCount, publicKeys, signatures); accounting.depositWstETH(msg.sender, nodeOperatorId, amount, permit); + + // Due to new bonded keys nonce update is required and normalize queue is required + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: true + }); + } + + /// @notice Stake user's ETH to Lido and make deposit in stETH to the bond + /// @param nodeOperatorId id of the node operator to stake ETH and deposit stETH for + function depositETH( + uint256 nodeOperatorId + ) external payable onlyExistingNodeOperator(nodeOperatorId) { + accounting.depositETH{ value: msg.value }(msg.sender, nodeOperatorId); + + // Due to new bond nonce update might be required and normalize queue might be required + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: true + }); + } + + /// @notice Deposit user's stETH to the bond for the given Node Operator + /// @param nodeOperatorId id of the node operator to deposit stETH for + /// @param stETHAmount amount of stETH to deposit + /// @param permit stETH permit for the contract + function depositStETH( + uint256 nodeOperatorId, + uint256 stETHAmount, + ICSAccounting.PermitInput calldata permit + ) external onlyExistingNodeOperator(nodeOperatorId) { + accounting.depositStETH( + msg.sender, + nodeOperatorId, + stETHAmount, + permit + ); + + // Due to new bond nonce update might be required and normalize queue might be required + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: true + }); + } + + /// @notice Unwrap user's wstETH and make deposit in stETH to the bond for the given Node Operator + /// @param nodeOperatorId id of the node operator to deposit stETH for + /// @param wstETHAmount amount of wstETH to deposit + /// @param permit wstETH permit for the contract + function depositWstETH( + uint256 nodeOperatorId, + uint256 wstETHAmount, + ICSAccounting.PermitInput calldata permit + ) external onlyExistingNodeOperator(nodeOperatorId) { + accounting.depositWstETH( + msg.sender, + nodeOperatorId, + wstETHAmount, + permit + ); + + // Due to new bond nonce update might be required and normalize queue might be required + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: true + }); } /// @notice Claims full reward (fee + bond) in stETH for the given node operator with desirable value @@ -467,12 +570,20 @@ contract CSModule is bytes32[] memory rewardsProof ) external onlyExistingNodeOperator(nodeOperatorId) { onlyNodeOperatorManager(nodeOperatorId); + accounting.claimRewardsStETH( nodeOperatorId, stETHAmount, cumulativeFeeShares, rewardsProof ); + + // Due to possible missing bond compensation nonce update might be required and normalize queue might be required + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: true + }); } /// @notice Claims full reward (fee + bond) in wstETH for the given node operator available for this moment @@ -487,12 +598,20 @@ contract CSModule is bytes32[] memory rewardsProof ) external onlyExistingNodeOperator(nodeOperatorId) { onlyNodeOperatorManager(nodeOperatorId); + accounting.claimRewardsWstETH( nodeOperatorId, wstETHAmount, cumulativeFeeShares, rewardsProof ); + + // Due to possible missing bond compensation nonce update might be required and normalize queue might be required + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: true + }); } /// @notice Request full reward (fee + bond) in Withdrawal NFT (unstETH) for the given node operator available for this moment. @@ -508,18 +627,20 @@ contract CSModule is bytes32[] memory rewardsProof ) external onlyExistingNodeOperator(nodeOperatorId) { onlyNodeOperatorManager(nodeOperatorId); + accounting.requestRewardsETH( nodeOperatorId, ethAmount, cumulativeFeeShares, rewardsProof ); - } - /// @notice Notify the module about the operator's bond change. - function onBondChanged(uint256 nodeOperatorId) external { - _updateDepositableValidatorsCount(nodeOperatorId); - _normalizeQueue(nodeOperatorId); + // Due to possible missing bond compensation nonce update might be required and normalize queue might be required + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: true + }); } /// @notice Proposes a new manager address for the node operator @@ -720,7 +841,11 @@ contract CSModule is ); } - function _updateDepositableValidatorsCount(uint256 nodeOperatorId) private { + function _updateDepositableValidatorsCount( + uint256 nodeOperatorId, + bool doIncrementNonce, + bool doNormalizeQueue + ) private { NodeOperator storage no = _nodeOperators[nodeOperatorId]; uint256 newCount = no.totalVettedKeys - no.totalDepositedKeys; @@ -752,6 +877,12 @@ contract CSModule is no.depositableValidatorsCount + newCount; no.depositableValidatorsCount = newCount; + if (doIncrementNonce) { + _incrementModuleNonce(); + } + if (doNormalizeQueue) { + _normalizeQueue(nodeOperatorId); + } } } @@ -806,7 +937,13 @@ contract CSModule is _depositableValidatorsCount -= no.depositableValidatorsCount; no.depositableValidatorsCount = 0; } else { - _updateDepositableValidatorsCount(nodeOperatorId); + // Nonce will be updated on the top level once per call + // Node Operator should normalize queue himself in case of unstuck + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: false, + doNormalizeQueue: false + }); } } @@ -917,8 +1054,13 @@ contract CSModule is targetLimit ); - _updateDepositableValidatorsCount(nodeOperatorId); - _normalizeQueue(nodeOperatorId); + // Nonce will be updated below even if depositable count was not changed + // In case of targetLimit removal queue should be normalised + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: false, + doNormalizeQueue: true + }); _incrementModuleNonce(); } @@ -972,7 +1114,13 @@ contract CSModule is vettedKeysByOperator[i] ); - _updateDepositableValidatorsCount(nodeOperatorId); + // Nonce will be updated below once + // No need to normalize queue due to vetted decrease + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: false, + doNormalizeQueue: false + }); } _incrementModuleNonce(); @@ -1053,7 +1201,13 @@ contract CSModule is amount ); - _updateDepositableValidatorsCount(nodeOperatorId); + // Nonce should be updated if depositableValidators change + // No need to normalize queue due to only decrease in depositable possible + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: false + }); } /// @notice Cancel EL rewards stealing for the given node operator. @@ -1072,7 +1226,13 @@ contract CSModule is emit ELRewardsStealingPenaltyCancelled(nodeOperatorId, amount); - _updateDepositableValidatorsCount(nodeOperatorId); + // Nonce should be updated if depositableValidators change + // Normalize queue should be called due to only increase in depositable possible + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: true + }); } /// @dev Should be called by the committee. @@ -1088,7 +1248,13 @@ contract CSModule is uint256 settled = accounting.settleLockedBondETH(nodeOperatorId); if (settled > 0) { accounting.resetBondCurve(nodeOperatorId); - _updateDepositableValidatorsCount(nodeOperatorId); + // Nonce should be updated if depositableValidators change + // No need to normalize queue due to only decrease in depositable possible + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: false + }); } } } @@ -1099,8 +1265,13 @@ contract CSModule is uint256 nodeOperatorId ) external payable onlyExistingNodeOperator(nodeOperatorId) { accounting.compensateLockedBondETH{ value: msg.value }(nodeOperatorId); - _updateDepositableValidatorsCount(nodeOperatorId); - _normalizeQueue(nodeOperatorId); + // Nonce should be updated if depositableValidators change + // Normalize queue should be called due to only increase in depositable possible + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: true + }); } /// @notice Checks if the given node operator's key is proved as withdrawn. @@ -1151,9 +1322,13 @@ contract CSModule is accounting.penalize(nodeOperatorId, DEPOSIT_SIZE - amount); } - _updateDepositableValidatorsCount(nodeOperatorId); - _normalizeQueue(nodeOperatorId); - _incrementModuleNonce(); + // Nonce should be updated if depositableValidators change + // Normalize queue should be called due to possible increase in depositable possible + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: true + }); } /// @notice Checks if the given node operator's key is proved as slashed. @@ -1193,8 +1368,13 @@ contract CSModule is accounting.penalize(nodeOperatorId, INITIAL_SLASHING_PENALTY); - _updateDepositableValidatorsCount(nodeOperatorId); - _incrementModuleNonce(); + // Nonce should be updated if depositableValidators change + // Normalize queue should not be called due to only possible decrease in depositable possible + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: true, + doNormalizeQueue: false + }); } /// @dev both nodeOperatorId and keyIndex are limited to uint64 by the contract. @@ -1255,8 +1435,6 @@ contract CSModule is no.totalAddedKeys += keysCount; emit TotalSigningKeysCountChanged(nodeOperatorId, no.totalAddedKeys); - - _incrementModuleNonce(); } function _removeSigningKeys( @@ -1296,8 +1474,13 @@ contract CSModule is no.totalVettedKeys = newTotalSigningKeys; emit VettedSigningKeysCountChanged(nodeOperatorId, newTotalSigningKeys); - _updateDepositableValidatorsCount(nodeOperatorId); - _normalizeQueue(nodeOperatorId); + // Nonce is updated below due to keys state change + // Normalize queue should be called due to possible increase in depositable possible + _updateDepositableValidatorsCount({ + nodeOperatorId: nodeOperatorId, + doIncrementNonce: false, + doNormalizeQueue: true + }); _incrementModuleNonce(); } @@ -1372,7 +1555,7 @@ contract CSModule is no.totalDepositedKeys ); - // No need for `_updateDepositableValidatorsCount` call, we can update the number directly. + // No need for `_updateDepositableValidatorsCount` call since we update the number directly. // `keysCount` is min of `depositableValidatorsCount` and `depositsLeft`. no.depositableValidatorsCount -= keysCount; depositsLeft -= keysCount; diff --git a/src/interfaces/ICSModule.sol b/src/interfaces/ICSModule.sol index 13651bf0..c357fa0d 100644 --- a/src/interfaces/ICSModule.sol +++ b/src/interfaces/ICSModule.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.24; import { IStakingModule } from "./IStakingModule.sol"; +import { ICSAccounting } from "./ICSAccounting.sol"; /// @title Lido's Community Staking Module interface interface ICSModule is IStakingModule { @@ -54,6 +55,17 @@ interface ICSModule is IStakingModule { uint256 amount ) external; - /// @notice Notify the module about the operator's bond change. The hook call is optional. - function onBondChanged(uint256 nodeOperatorId) external; + function depositWstETH( + uint256 nodeOperatorId, + uint256 wstETHAmount, + ICSAccounting.PermitInput calldata permit + ) external; + + function depositStETH( + uint256 nodeOperatorId, + uint256 stETHAmount, + ICSAccounting.PermitInput calldata permit + ) external; + + function depositETH(uint256 nodeOperatorId) external payable; } diff --git a/test/CSAccounting.t.sol b/test/CSAccounting.t.sol index 03c6c955..87e1a6ca 100644 --- a/test/CSAccounting.t.sol +++ b/test/CSAccounting.t.sol @@ -125,13 +125,6 @@ contract CSAccountingBaseTest is address(stakingModule) ); vm.stopPrank(); - - // HACK: To avoid changing the Stub to a mock so far. - vm.mockCall( - address(stakingModule), - abi.encodeWithSelector(ICSModule.onBondChanged.selector), - "" - ); } function mock_getNodeOperatorsCount(uint256 returnValue) internal { @@ -235,11 +228,14 @@ contract CSAccountingPauseAffectingTest is CSAccountingBaseTest { } function test_depositETH_RevertWhen_Paused() public { + vm.deal(address(stakingModule), 1 ether); + vm.prank(address(stakingModule)); vm.expectRevert(PausableUntil.ResumedExpected.selector); accounting.depositETH{ value: 1 ether }(address(user), 0); } function test_depositStETH_RevertWhen_Paused() public { + vm.prank(address(stakingModule)); vm.expectRevert(PausableUntil.ResumedExpected.selector); accounting.depositStETH( address(user), @@ -256,6 +252,7 @@ contract CSAccountingPauseAffectingTest is CSAccountingBaseTest { } function test_depositWstETH_RevertWhen_Paused() public { + vm.prank(address(stakingModule)); vm.expectRevert(PausableUntil.ResumedExpected.selector); accounting.depositWstETH( address(user), @@ -272,16 +269,19 @@ contract CSAccountingPauseAffectingTest is CSAccountingBaseTest { } function test_claimRewardsStETH_RevertWhen_Paused() public { + vm.prank(address(stakingModule)); vm.expectRevert(PausableUntil.ResumedExpected.selector); accounting.claimRewardsStETH(0, 1 ether, 1 ether, new bytes32[](1)); } function test_claimRewardsWstETH_RevertWhen_Paused() public { + vm.prank(address(stakingModule)); vm.expectRevert(PausableUntil.ResumedExpected.selector); accounting.claimRewardsWstETH(0, 1 ether, 1 ether, new bytes32[](1)); } function test_requestRewardsETH_RevertWhen_Paused() public { + vm.prank(address(stakingModule)); vm.expectRevert(PausableUntil.ResumedExpected.selector); accounting.requestRewardsETH(0, 1 ether, 1 ether, new bytes32[](1)); } @@ -328,8 +328,8 @@ abstract contract CSAccountingBondStateBaseTest is } function _deposit(uint256 bond) internal virtual { - vm.deal(user, bond); - vm.prank(user); + vm.deal(address(stakingModule), bond); + vm.prank(address(stakingModule)); accounting.depositETH{ value: bond }({ from: address(0), nodeOperatorId: 0 @@ -2722,14 +2722,14 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { } function test_depositETH() public { - vm.deal(user, 32 ether); + vm.deal(address(stakingModule), 32 ether); uint256 sharesToDeposit = stETH.getSharesByPooledEth(32 ether); - vm.prank(user); + vm.prank(address(stakingModule)); accounting.depositETH{ value: 32 ether }(user, 0); assertEq( - address(user).balance, + address(stakingModule).balance, 0, "user balance should be 0 after deposit" ); @@ -2753,7 +2753,7 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { _referal: address(0) }); - vm.prank(user); + vm.prank(address(stakingModule)); accounting.depositStETH( user, 0, @@ -2795,7 +2795,7 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { ); vm.stopPrank(); - vm.prank(user); + vm.prank(address(stakingModule)); accounting.depositWstETH( user, 0, @@ -2834,10 +2834,10 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { _referal: address(0) }); + vm.prank(address(stakingModule)); vm.expectEmit(true, true, true, true, address(stETH)); emit Approval(user, address(accounting), 32 ether); - vm.prank(user); accounting.depositStETH( user, 0, @@ -2890,7 +2890,7 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { vm.recordLogs(); - vm.prank(user); + vm.prank(address(stakingModule)); accounting.depositStETH( user, 0, @@ -2927,9 +2927,10 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { abi.encode(UINT256_MAX) ); + vm.prank(address(stakingModule)); + vm.recordLogs(); - vm.prank(user); accounting.depositStETH( user, 0, @@ -2968,7 +2969,7 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { vm.recordLogs(); - vm.prank(user); + vm.prank(address(stakingModule)); accounting.depositStETH( user, 0, @@ -3003,7 +3004,7 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { vm.expectEmit(true, true, true, true, address(wstETH)); emit Approval(user, address(accounting), 32 ether); - vm.prank(user); + vm.prank(address(stakingModule)); accounting.depositWstETH( user, 0, @@ -3061,7 +3062,7 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { vm.recordLogs(); - vm.prank(user); + vm.prank(address(stakingModule)); accounting.depositWstETH( user, 0, @@ -3102,7 +3103,7 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { vm.recordLogs(); - vm.prank(user); + vm.prank(address(stakingModule)); accounting.depositWstETH( user, 0, @@ -3145,7 +3146,7 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { vm.recordLogs(); - vm.prank(user); + vm.prank(address(stakingModule)); accounting.depositWstETH( user, 0, @@ -3166,86 +3167,6 @@ contract CSAccountingDepositsTest is CSAccountingBaseTest { "should emit only one event about deposit" ); } - - function test_depositETH_RevertIfNotExistedOperator() public { - vm.expectRevert(NodeOperatorDoesNotExist.selector); - vm.prank(user); - accounting.depositETH{ value: 0 }(user, 1); - } - - function test_depositStETH_RevertIfNotExistedOperator() public { - vm.expectRevert(NodeOperatorDoesNotExist.selector); - vm.prank(user); - accounting.depositStETH( - user, - 1, - 0 ether, - CSAccounting.PermitInput({ - value: 0, - deadline: 0, - v: 0, - r: 0, - s: 0 - }) - ); - } - - function test_depositWstETH_RevertIfNotExistedOperator() public { - vm.expectRevert(NodeOperatorDoesNotExist.selector); - vm.prank(user); - accounting.depositWstETH( - user, - 1, - 0 ether, - CSAccounting.PermitInput({ - value: 0, - deadline: 0, - v: 0, - r: 0, - s: 0 - }) - ); - } - - 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, - CSAccounting.PermitInput({ - value: 0, - deadline: 0, - v: 0, - r: 0, - s: 0 - }) - ); - } - - function test_depositWstETH_RevertIfInvalidSender() public { - vm.expectRevert(InvalidSender.selector); - vm.prank(stranger); - accounting.depositWstETH( - user, - 0, - 32 ether, - CSAccounting.PermitInput({ - value: 0, - deadline: 0, - v: 0, - r: 0, - s: 0 - }) - ); - } } contract CSAccountingPenalizeTest is CSAccountingBaseTest { @@ -3262,8 +3183,8 @@ contract CSAccountingPenalizeTest is CSAccountingBaseTest { n.totalDepositedValidators = 0; mock_getNodeOperator(n); mock_getNodeOperatorsCount(1); - vm.deal(user, 32 ether); - vm.prank(user); + vm.deal(address(stakingModule), 32 ether); + vm.prank(address(stakingModule)); accounting.depositETH{ value: 32 ether }(user, 0); } @@ -3313,8 +3234,8 @@ contract CSAccountingChargeFeeTest is CSAccountingBaseTest { n.totalDepositedValidators = 0; mock_getNodeOperator(n); mock_getNodeOperatorsCount(1); - vm.deal(user, 32 ether); - vm.prank(user); + vm.deal(address(stakingModule), 32 ether); + vm.prank(address(stakingModule)); accounting.depositETH{ value: 32 ether }(user, 0); } @@ -3502,8 +3423,8 @@ contract CSAccountingBondCurveTest is CSAccountingBaseTest { contract CSAccountingMiscTest is CSAccountingBaseTest { function test_totalBondShares() public { mock_getNodeOperatorsCount(2); - vm.deal(user, 64 ether); - vm.startPrank(user); + vm.deal(address(stakingModule), 64 ether); + vm.startPrank(address(stakingModule)); accounting.depositETH{ value: 32 ether }(user, 0); accounting.depositETH{ value: 32 ether }(user, 1); vm.stopPrank(); @@ -3635,11 +3556,11 @@ contract CSAccountingAssetRecovererTest is CSAccountingBaseTest { function test_recoverStETHShares() public { mock_getNodeOperatorsCount(1); - vm.deal(user, 2 ether); - vm.startPrank(user); + vm.deal(address(stakingModule), 2 ether); + vm.startPrank(address(stakingModule)); stETH.submit{ value: 2 ether }(address(0)); accounting.depositStETH( - address(user), + address(stakingModule), 0, 1 ether, CSAccounting.PermitInput({ diff --git a/test/CSModule.t.sol b/test/CSModule.t.sol index ee0cb036..e439af75 100644 --- a/test/CSModule.t.sol +++ b/test/CSModule.t.sol @@ -97,7 +97,7 @@ abstract contract CSMFixtures is Test, Fixtures, Utilities, CSModuleBase { ); uint256 amount = accounting.getRequiredBondForNextKeys(noId, keysCount); vm.deal(nodeOperator, amount); - // NOTE: There's no check for the sender address to be a manager of the operator at the moment. + vm.prank(nodeOperator); csm.addValidatorKeysETH{ value: amount }( noId, keysCount, @@ -256,7 +256,6 @@ contract CSMCommonNoPublicRelease is CSMFixtures { csm.REPORT_EL_REWARDS_STEALING_PENALTY_ROLE(), address(this) ); - csm.grantRole(csm.PENALIZE_ROLE(), address(this)); csm.grantRole(csm.VERIFIER_ROLE(), address(this)); accounting.grantRole(accounting.ADD_BOND_CURVE_ROLE(), address(this)); accounting.grantRole(accounting.SET_BOND_CURVE_ROLE(), address(csm)); @@ -571,6 +570,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { uint256 noId = createNodeOperator(); uint256 toWrap = BOND_SIZE + 1 wei; vm.deal(nodeOperator, toWrap); + vm.startPrank(nodeOperator); stETH.submit{ value: toWrap }(address(0)); wstETH.wrap(toWrap); (bytes memory keys, bytes memory signatures) = keysSignatures(1, 1); @@ -813,6 +813,356 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { } } +contract CSMDeposit is CSMCommon, PermitTokenBase { + function test_DepositETH() public { + uint256 noId = createNodeOperator(); + uint256 preShares = accounting.getBondShares(noId); + vm.deal(nodeOperator, 32 ether); + uint256 sharesToDeposit = stETH.getSharesByPooledEth(32 ether); + + vm.prank(nodeOperator); + csm.depositETH{ value: 32 ether }(noId); + + assertEq( + nodeOperator.balance, + 0, + "user balance should be 0 after deposit" + ); + assertEq( + accounting.getBondShares(noId), + sharesToDeposit + preShares, + "bond shares should be equal to deposited shares + pre shares" + ); + } + + function test_DepositETH_NotExistingNodeOperator() public { + uint256 noId = createNodeOperator(); + vm.deal(nodeOperator, 32 ether); + + vm.expectRevert(NodeOperatorDoesNotExist.selector); + vm.prank(nodeOperator); + csm.depositETH{ value: 32 ether }(noId + 1); + } + + function test_DepositETH_NonceShouldChange() public { + uint256 noId = createNodeOperator(); + + csm.reportELRewardsStealingPenalty(noId, 100, BOND_SIZE / 2); + + uint256 nonce = csm.getNonce(); + + vm.deal(nodeOperator, 32 ether); + vm.prank(nodeOperator); + csm.depositETH{ value: 32 ether }(0); + + assertEq(csm.getNonce(), nonce + 1); + } + + function test_DepositETH_NonceShouldNotChange() public { + uint256 noId = createNodeOperator(); + + uint256 nonce = csm.getNonce(); + + vm.deal(nodeOperator, 32 ether); + vm.prank(nodeOperator); + csm.depositETH{ value: 32 ether }(0); + + assertEq(csm.getNonce(), nonce); + } + + function test_DepositStETH() public { + uint256 noId = createNodeOperator(); + uint256 preShares = accounting.getBondShares(noId); + vm.deal(nodeOperator, 32 ether); + vm.startPrank(nodeOperator); + uint256 sharesToDeposit = stETH.submit{ value: 32 ether }({ + _referal: address(0) + }); + csm.depositStETH( + noId, + 32 ether, + ICSAccounting.PermitInput({ + value: 0, + deadline: 0, + v: 0, + r: 0, + s: 0 + }) + ); + + assertEq( + stETH.balanceOf(nodeOperator), + 0, + "user balance should be 0 after deposit" + ); + assertEq( + accounting.getBondShares(noId), + sharesToDeposit + preShares, + "bond shares should be equal to deposited shares + pre shares" + ); + } + + function test_DepositStETH_NotExistingNodeOperator() public { + uint256 noId = createNodeOperator(); + vm.deal(nodeOperator, 32 ether); + vm.startPrank(nodeOperator); + stETH.submit{ value: 32 ether }({ _referal: address(0) }); + vm.expectRevert(NodeOperatorDoesNotExist.selector); + csm.depositStETH( + noId + 1, + 32 ether, + ICSAccounting.PermitInput({ + value: 0, + deadline: 0, + v: 0, + r: 0, + s: 0 + }) + ); + } + + function test_DepositStETH_withPermit() public { + uint256 noId = createNodeOperator(); + uint256 preShares = accounting.getBondShares(noId); + vm.deal(nodeOperator, 32 ether); + vm.startPrank(nodeOperator); + uint256 sharesToDeposit = stETH.submit{ value: 32 ether }({ + _referal: address(0) + }); + + vm.expectEmit(true, true, true, true, address(stETH)); + emit Approval(nodeOperator, address(accounting), 32 ether); + + csm.depositStETH( + noId, + 32 ether, + ICSAccounting.PermitInput({ + value: 32 ether, + deadline: type(uint256).max, + // mock permit signature + v: 0, + r: 0, + s: 0 + }) + ); + + assertEq( + stETH.balanceOf(nodeOperator), + 0, + "user balance should be 0 after deposit" + ); + assertEq( + accounting.getBondShares(noId), + sharesToDeposit + preShares, + "bond shares should be equal to deposited shares + pre shares" + ); + } + + function test_DepositStETH_NonceShouldChange() public { + uint256 noId = createNodeOperator(); + + csm.reportELRewardsStealingPenalty(noId, 100, BOND_SIZE / 2); + + uint256 nonce = csm.getNonce(); + + vm.deal(nodeOperator, 32 ether); + vm.startPrank(nodeOperator); + uint256 sharesToDeposit = stETH.submit{ value: 32 ether }({ + _referal: address(0) + }); + csm.depositStETH( + 0, + 32 ether, + ICSAccounting.PermitInput({ + value: 0, + deadline: 0, + v: 0, + r: 0, + s: 0 + }) + ); + + assertEq(csm.getNonce(), nonce + 1); + } + + function test_DepositStETH_NonceShouldNotChange() public { + uint256 noId = createNodeOperator(); + + uint256 nonce = csm.getNonce(); + + vm.deal(nodeOperator, 32 ether); + vm.startPrank(nodeOperator); + uint256 sharesToDeposit = stETH.submit{ value: 32 ether }({ + _referal: address(0) + }); + csm.depositStETH( + 0, + 32 ether, + ICSAccounting.PermitInput({ + value: 0, + deadline: 0, + v: 0, + r: 0, + s: 0 + }) + ); + + assertEq(csm.getNonce(), nonce); + } + + function test_DepositWstETH() public { + uint256 noId = createNodeOperator(); + uint256 preShares = accounting.getBondShares(noId); + vm.deal(nodeOperator, 32 ether); + vm.startPrank(nodeOperator); + stETH.submit{ value: 32 ether }({ _referal: address(0) }); + uint256 wstETHAmount = wstETH.wrap(32 ether); + uint256 sharesToDeposit = stETH.getSharesByPooledEth( + wstETH.getStETHByWstETH(wstETHAmount) + ); + + csm.depositWstETH( + noId, + wstETHAmount, + ICSAccounting.PermitInput({ + value: 0, + deadline: 0, + v: 0, + r: 0, + s: 0 + }) + ); + + assertEq( + wstETH.balanceOf(nodeOperator), + 0, + "user balance should be 0 after deposit" + ); + assertEq( + accounting.getBondShares(noId), + sharesToDeposit + preShares, + "bond shares should be equal to deposited shares + pre shares" + ); + } + + function test_DepositWstETH_NotExistingNodeOperator() public { + uint256 noId = createNodeOperator(); + vm.deal(nodeOperator, 32 ether); + vm.startPrank(nodeOperator); + stETH.submit{ value: 32 ether }({ _referal: address(0) }); + uint256 wstETHAmount = wstETH.wrap(32 ether); + vm.expectRevert(NodeOperatorDoesNotExist.selector); + csm.depositWstETH( + noId + 1, + wstETHAmount, + ICSAccounting.PermitInput({ + value: 0, + deadline: 0, + v: 0, + r: 0, + s: 0 + }) + ); + } + + function test_DepositWstETH_withPermit() public { + uint256 noId = createNodeOperator(); + uint256 preShares = accounting.getBondShares(noId); + vm.deal(nodeOperator, 32 ether); + vm.startPrank(nodeOperator); + stETH.submit{ value: 32 ether }({ _referal: address(0) }); + uint256 wstETHAmount = wstETH.wrap(32 ether); + uint256 sharesToDeposit = stETH.getSharesByPooledEth( + wstETH.getStETHByWstETH(wstETHAmount) + ); + + vm.expectEmit(true, true, true, true, address(wstETH)); + emit Approval(nodeOperator, address(accounting), 32 ether); + + csm.depositWstETH( + noId, + wstETHAmount, + ICSAccounting.PermitInput({ + value: 32 ether, + deadline: type(uint256).max, + // mock permit signature + v: 0, + r: 0, + s: 0 + }) + ); + + assertEq( + wstETH.balanceOf(nodeOperator), + 0, + "user balance should be 0 after deposit" + ); + assertEq( + accounting.getBondShares(noId), + sharesToDeposit + preShares, + "bond shares should be equal to deposited shares + pre shares" + ); + } + + function test_DepositWstETH_NonceShouldChange() public { + uint256 noId = createNodeOperator(); + + csm.reportELRewardsStealingPenalty(noId, 100, BOND_SIZE / 2); + + uint256 nonce = csm.getNonce(); + + vm.deal(nodeOperator, 32 ether); + vm.startPrank(nodeOperator); + stETH.submit{ value: 32 ether }({ _referal: address(0) }); + uint256 wstETHAmount = wstETH.wrap(32 ether); + uint256 sharesToDeposit = stETH.getSharesByPooledEth( + wstETH.getStETHByWstETH(wstETHAmount) + ); + + csm.depositWstETH( + 0, + wstETHAmount, + ICSAccounting.PermitInput({ + value: 0, + deadline: 0, + v: 0, + r: 0, + s: 0 + }) + ); + + assertEq(csm.getNonce(), nonce + 1); + } + + function test_DepositWstETH_NonceShouldNotChange() public { + uint256 noId = createNodeOperator(); + + uint256 nonce = csm.getNonce(); + + vm.deal(nodeOperator, 32 ether); + vm.startPrank(nodeOperator); + stETH.submit{ value: 32 ether }({ _referal: address(0) }); + uint256 wstETHAmount = wstETH.wrap(32 ether); + uint256 sharesToDeposit = stETH.getSharesByPooledEth( + wstETH.getStETHByWstETH(wstETHAmount) + ); + + csm.depositWstETH( + 0, + wstETHAmount, + ICSAccounting.PermitInput({ + value: 0, + deadline: 0, + v: 0, + r: 0, + s: 0 + }) + ); + + assertEq(csm.getNonce(), nonce); + } +} + contract CSMObtainDepositData is CSMCommon { function test_obtainDepositData() public { uint16 keysCount = 1; @@ -878,6 +1228,7 @@ contract CSMObtainDepositData is CSMCommon { } contract CSMClaimRewards is CSMCommon { + // TODO: Add nonce tests function test_claimRewardsStETH() public { uint256 noId = createNodeOperator(); csm.obtainDepositData(1, ""); @@ -2300,6 +2651,7 @@ contract CsmUnsafeUpdateValidatorsCount is CSMCommon { contract CsmReportELRewardsStealingPenalty is CSMCommon { function test_reportELRewardsStealingPenalty_HappyPath() public { uint256 noId = createNodeOperator(); + uint256 nonce = csm.getNonce(); vm.expectEmit(true, true, true, true, address(csm)); emit ELRewardsStealingPenaltyReported(noId, 100, BOND_SIZE / 2); @@ -2307,6 +2659,7 @@ contract CsmReportELRewardsStealingPenalty is CSMCommon { uint256 lockedBond = accounting.getActualLockedBond(noId); assertEq(lockedBond, BOND_SIZE / 2 + csm.EL_REWARDS_STEALING_FINE()); + assertEq(csm.getNonce(), nonce + 1); } function test_reportELRewardsStealingPenalty_RevertWhenNoNodeOperator() @@ -2315,6 +2668,20 @@ contract CsmReportELRewardsStealingPenalty is CSMCommon { vm.expectRevert(NodeOperatorDoesNotExist.selector); csm.reportELRewardsStealingPenalty(0, 100, 1 ether); } + + function test_reportELRewardsStealingPenalty_NoNonceChange() public { + uint256 noId = createNodeOperator(); + + vm.deal(nodeOperator, 32 ether); + vm.prank(nodeOperator); + csm.depositETH{ value: 32 ether }(0); + + uint256 nonce = csm.getNonce(); + + csm.reportELRewardsStealingPenalty(noId, 100, BOND_SIZE / 2); + + assertEq(csm.getNonce(), nonce); + } } contract CsmCancelELRewardsStealingPenalty is CSMCommon { @@ -2323,6 +2690,8 @@ contract CsmCancelELRewardsStealingPenalty is CSMCommon { csm.reportELRewardsStealingPenalty(noId, 100, BOND_SIZE / 2); + uint256 nonce = csm.getNonce(); + vm.expectEmit(true, true, true, true, address(csm)); emit ELRewardsStealingPenaltyCancelled( noId, @@ -2335,6 +2704,7 @@ contract CsmCancelELRewardsStealingPenalty is CSMCommon { uint256 lockedBond = accounting.getActualLockedBond(noId); assertEq(lockedBond, 0); + assertEq(csm.getNonce(), nonce + 1); } function test_cancelELRewardsStealingPenalty_Partial() public { @@ -2342,12 +2712,16 @@ contract CsmCancelELRewardsStealingPenalty is CSMCommon { csm.reportELRewardsStealingPenalty(noId, 100, BOND_SIZE / 2); + uint256 nonce = csm.getNonce(); + vm.expectEmit(true, true, true, true, address(csm)); emit ELRewardsStealingPenaltyCancelled(noId, BOND_SIZE / 2); csm.cancelELRewardsStealingPenalty(noId, BOND_SIZE / 2); uint256 lockedBond = accounting.getActualLockedBond(noId); assertEq(lockedBond, csm.EL_REWARDS_STEALING_FINE()); + // nonce should not change due to no changes in the depositable validators + assertEq(csm.getNonce(), nonce); } function test_cancelELRewardsStealingPenalty_RevertWhenNoNodeOperator() @@ -2448,6 +2822,84 @@ contract CsmSettleELRewardsStealingPenalty is CSMCommon { assertEq(lock.amount, 0 ether); assertEq(lock.retentionUntil, 0); } + + function test_settleELRewardsStealingPenalty_CurveReset_NoNewUnbonded() + public + { + uint256 noId = createNodeOperator(); + + uint256[] memory curvePoints = new uint256[](2); + curvePoints[0] = 2 ether; + curvePoints[1] = 3 ether; + + accounting.addBondCurve(curvePoints); + + vm.prank(address(csm)); + accounting.setBondCurve(0, 2); + + uploadMoreKeys(0, 1); + + vm.deal(nodeOperator, 3 ether); + vm.prank(nodeOperator); + csm.depositETH{ value: 3 ether }(0); + + uint256 amount = 1 ether; + uint256[] memory idsToSettle = new uint256[](1); + idsToSettle[0] = noId; + csm.reportELRewardsStealingPenalty(noId, block.number, amount); + + uint256 nonce = csm.getNonce(); + uint256 unbonded = accounting.getUnbondedKeysCount(noId); + + vm.expectCall( + address(accounting), + abi.encodeWithSelector(accounting.resetBondCurve.selector, noId) + ); + csm.settleELRewardsStealingPenalty(idsToSettle); + + assertEq(accounting.getBondCurve(noId).id, 1); + assertEq(csm.getNonce(), nonce); + assertEq(accounting.getUnbondedKeysCount(noId), unbonded); + } + + function test_settleELRewardsStealingPenalty_CurveReset_NewUnbonded() + public + { + uint256 noId = createNodeOperator(); + + uint256[] memory curvePoints = new uint256[](2); + curvePoints[0] = 2 ether; + curvePoints[1] = 3 ether; + + accounting.addBondCurve(curvePoints); + + vm.prank(address(csm)); + accounting.setBondCurve(0, 2); + + uploadMoreKeys(0, 1); + + vm.deal(nodeOperator, 2 ether); + vm.prank(nodeOperator); + csm.depositETH{ value: 2 ether }(0); + + uint256 amount = 1 ether; + uint256[] memory idsToSettle = new uint256[](1); + idsToSettle[0] = noId; + csm.reportELRewardsStealingPenalty(noId, block.number, amount); + + uint256 nonce = csm.getNonce(); + uint256 unbonded = accounting.getUnbondedKeysCount(noId); + + vm.expectCall( + address(accounting), + abi.encodeWithSelector(accounting.resetBondCurve.selector, noId) + ); + csm.settleELRewardsStealingPenalty(idsToSettle); + + assertEq(accounting.getBondCurve(noId).id, 1); + assertEq(csm.getNonce(), nonce + 1); + assertEq(accounting.getUnbondedKeysCount(noId), unbonded + 1); + } } contract CSMCompensateELRewardsStealingPenalty is CSMCommon { @@ -2457,6 +2909,8 @@ contract CSMCompensateELRewardsStealingPenalty is CSMCommon { uint256 fine = csm.EL_REWARDS_STEALING_FINE(); csm.reportELRewardsStealingPenalty(noId, block.number, amount); + uint256 nonce = csm.getNonce(); + vm.expectCall( address(accounting), abi.encodeWithSelector( @@ -2468,6 +2922,29 @@ contract CSMCompensateELRewardsStealingPenalty is CSMCommon { CSBondLock.BondLock memory lock = accounting.getLockedBondInfo(noId); assertEq(lock.amount, 0); + assertEq(csm.getNonce(), nonce + 1); + } + + function test_compensateELRewardsStealingPenalty_Partial() public { + uint256 noId = createNodeOperator(); + uint256 amount = 1 ether; + uint256 fine = csm.EL_REWARDS_STEALING_FINE(); + csm.reportELRewardsStealingPenalty(noId, block.number, amount); + + uint256 nonce = csm.getNonce(); + + vm.expectCall( + address(accounting), + abi.encodeWithSelector( + accounting.compensateLockedBondETH.selector, + noId + ) + ); + csm.compensateELRewardsStealingPenalty{ value: amount }(noId); + + CSBondLock.BondLock memory lock = accounting.getLockedBondInfo(noId); + assertEq(lock.amount, fine); + assertEq(csm.getNonce(), nonce); } function test_compensateELRewardsStealingPenalty_depositableValidatorsChanged() @@ -2505,12 +2982,41 @@ contract CsmSubmitWithdrawal is CSMCommon { uint256 noId = createNodeOperator(); csm.obtainDepositData(1, ""); + uint256 nonce = csm.getNonce(); + vm.expectEmit(true, true, true, true, address(csm)); emit WithdrawalSubmitted(noId, keyIndex, csm.DEPOSIT_SIZE()); csm.submitWithdrawal(noId, keyIndex, csm.DEPOSIT_SIZE()); CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); assertEq(no.totalWithdrawnValidators, 1); + // no chages in depositable keys or keys in general + assertEq(csm.getNonce(), nonce); + } + + function test_submitWithdrawal_changeNonce() public { + uint256 keyIndex = 0; + uint256 noId = createNodeOperator(2); + csm.obtainDepositData(1, ""); + + uint256 nonce = csm.getNonce(); + + vm.expectEmit(true, true, true, true, address(csm)); + emit WithdrawalSubmitted( + noId, + keyIndex, + csm.DEPOSIT_SIZE() - BOND_SIZE - 1 ether + ); + csm.submitWithdrawal( + noId, + keyIndex, + csm.DEPOSIT_SIZE() - BOND_SIZE - 1 ether + ); + + CSModule.NodeOperatorInfo memory no = csm.getNodeOperator(noId); + assertEq(no.totalWithdrawnValidators, 1); + // depositable decrease should + assertEq(csm.getNonce(), nonce + 1); } function test_submitWithdrawal_lowExitBalance() public { @@ -2585,10 +3091,12 @@ contract CsmSubmitWithdrawal is CSMCommon { contract CsmSubmitInitialSlashing is CSMCommon { function test_submitInitialSlashing() public { - uint256 noId = createNodeOperator(); + uint256 noId = createNodeOperator(2); csm.obtainDepositData(1, ""); uint256 penaltyAmount = csm.INITIAL_SLASHING_PENALTY(); + uint256 nonce = csm.getNonce(); + vm.expectEmit(true, true, true, true, address(csm)); emit InitialSlashingSubmitted(noId, 0); vm.expectCall( @@ -2600,6 +3108,33 @@ contract CsmSubmitInitialSlashing is CSMCommon { ) ); csm.submitInitialSlashing(noId, 0); + + assertEq(csm.getNonce(), nonce + 1); + } + + function test_submitInitialSlashing_Overbonded() public { + uint256 noId = createNodeOperator(2); + vm.deal(nodeOperator, 32 ether); + vm.prank(nodeOperator); + csm.depositETH{ value: 32 ether }(0); + csm.obtainDepositData(1, ""); + uint256 penaltyAmount = csm.INITIAL_SLASHING_PENALTY(); + + uint256 nonce = csm.getNonce(); + + vm.expectEmit(true, true, true, true, address(csm)); + emit InitialSlashingSubmitted(noId, 0); + vm.expectCall( + address(accounting), + abi.encodeWithSelector( + accounting.penalize.selector, + noId, + penaltyAmount + ) + ); + csm.submitInitialSlashing(noId, 0); + + assertEq(csm.getNonce(), nonce); } function test_submitInitialSlashing_differentKeys() public { @@ -3307,10 +3842,12 @@ contract CSMDepositableValidatorsCount is CSMCommon { function test_depositableValidatorsCountChanges_OnUnvetKeys() public { uint256 noId = createNodeOperator(7); + uint256 nonce = csm.getNonce(); assertEq(getNodeOperatorSummary(noId).depositableValidatorsCount, 7); csm.decreaseOperatorVettedKeys(UintArr(noId), UintArr(3)); assertEq(getNodeOperatorSummary(noId).depositableValidatorsCount, 3); assertEq(getStakingModuleSummary().depositableValidatorsCount, 3); + assertEq(csm.getNonce(), nonce + 1); } function test_depositableValidatorsCountChanges_OnInitialSlashing() public { diff --git a/test/integration/DepositInTokens.t.sol b/test/integration/DepositInTokens.t.sol index 63fda955..56512330 100644 --- a/test/integration/DepositInTokens.t.sol +++ b/test/integration/DepositInTokens.t.sol @@ -10,6 +10,7 @@ import { CSAccounting } from "../../src/CSAccounting.sol"; import { IWstETH } from "../../src/interfaces/IWstETH.sol"; import { ILido } from "../../src/interfaces/ILido.sol"; import { ILidoLocator } from "../../src/interfaces/ILidoLocator.sol"; +import { ICSAccounting } from "../../src/interfaces/ICSAccounting.sol"; import { Utilities } from "../helpers/Utilities.sol"; import { PermitHelper } from "../helpers/Permit.sol"; import { IntegrationFixtures } from "../helpers/Fixtures.sol"; @@ -23,7 +24,7 @@ contract DepositIntegrationTest is { uint256 networkFork; - CommunityStakingModuleMock public csm; + CSModule public csm; CSAccounting public accounting; ILidoLocator public locator; IWstETH public wstETH; @@ -42,7 +43,11 @@ contract DepositIntegrationTest is checkChainId(1); locator = ILidoLocator(LOCATOR_ADDRESS); - csm = new CommunityStakingModuleMock(); + csm = new CSModule( + "community-staking-module", + LOCATOR_ADDRESS, + address(this) + ); wstETH = IWstETH(WSTETH_ADDRESS); @@ -64,16 +69,23 @@ contract DepositIntegrationTest is locator.treasury() ); - csm.setNodeOperator({ - _nodeOperatorId: 0, - _active: true, - _rewardAddress: user, - _totalVettedValidators: 16, - _totalExitedValidators: 0, - _totalWithdrawnValidators: 1, - _totalAddedValidators: 16, - _totalDepositedValidators: 16 - }); + csm.grantRole(csm.INITIALIZE_ROLE(), address(this)); + csm.grantRole(csm.MODULE_MANAGER_ROLE(), address(this)); + csm.activatePublicRelease(); + + csm.setAccounting(address(accounting)); + + (bytes memory keys, bytes memory signatures) = keysSignatures(2); + address nodeOperator = address(2); + vm.deal(nodeOperator, 4 ether); + vm.prank(nodeOperator); + csm.addNodeOperatorETH{ value: 4 ether }( + 2, + keys, + signatures, + new bytes32[](0), + address(0) + ); } function test_depositStETH() public { @@ -83,12 +95,13 @@ contract DepositIntegrationTest is _referal: address(0) }); + uint256 preShares = accounting.getBondShares(0); + ILido(locator.lido()).approve(address(accounting), type(uint256).max); - accounting.depositStETH( - user, + csm.depositStETH( 0, 32 ether, - CSAccounting.PermitInput({ + ICSAccounting.PermitInput({ value: 0, deadline: 0, v: 0, @@ -98,18 +111,22 @@ contract DepositIntegrationTest is ); assertEq(ILido(locator.lido()).balanceOf(user), 0); - assertEq(accounting.getBondShares(0), shares); - assertEq(accounting.totalBondShares(), shares); + assertEq(accounting.getBondShares(0), shares + preShares); + assertEq(accounting.totalBondShares(), shares + preShares); } function test_depositETH() public { - vm.prank(user); + vm.startPrank(user); vm.deal(user, 32 ether); - uint256 shares = accounting.depositETH{ value: 32 ether }(user, 0); + + uint256 preShares = accounting.getBondShares(0); + + uint256 shares = ILido(locator.lido()).getSharesByPooledEth(32 ether); + csm.depositETH{ value: 32 ether }(0); assertEq(user.balance, 0); - assertEq(accounting.getBondShares(0), shares); - assertEq(accounting.totalBondShares(), shares); + assertEq(accounting.getBondShares(0), shares + preShares); + assertEq(accounting.totalBondShares(), shares + preShares); } function test_depositWstETH() public { @@ -121,13 +138,17 @@ contract DepositIntegrationTest is ILido(locator.lido()).approve(address(wstETH), type(uint256).max); uint256 wstETHAmount = wstETH.wrap(32 ether); - vm.startPrank(user); + uint256 shares = ILido(locator.lido()).getSharesByPooledEth( + wstETH.getStETHByWstETH(wstETHAmount) + ); + + uint256 preShares = accounting.getBondShares(0); + wstETH.approve(address(accounting), type(uint256).max); - uint256 shares = accounting.depositWstETH( - user, + csm.depositWstETH( 0, wstETHAmount, - CSAccounting.PermitInput({ + ICSAccounting.PermitInput({ value: 0, deadline: 0, v: 0, @@ -137,8 +158,8 @@ contract DepositIntegrationTest is ); assertEq(wstETH.balanceOf(user), 0); - assertEq(accounting.getBondShares(0), shares); - assertEq(accounting.totalBondShares(), shares); + assertEq(accounting.getBondShares(0), shares + preShares); + assertEq(accounting.totalBondShares(), shares + preShares); } function test_depositStETHWithPermit() public { @@ -157,14 +178,13 @@ contract DepositIntegrationTest is uint256 shares = ILido(locator.lido()).submit{ value: 32 ether }({ _referal: address(0) }); - vm.stopPrank(); - vm.prank(user); - accounting.depositStETH( - user, + uint256 preShares = accounting.getBondShares(0); + + csm.depositStETH( 0, 32 ether, - CSAccounting.PermitInput({ + ICSAccounting.PermitInput({ value: 32 ether, deadline: type(uint256).max, v: v, @@ -174,8 +194,8 @@ contract DepositIntegrationTest is ); assertEq(ILido(locator.lido()).balanceOf(user), 0); - assertEq(accounting.getBondShares(0), shares); - assertEq(accounting.totalBondShares(), shares); + assertEq(accounting.getBondShares(0), shares + preShares); + assertEq(accounting.totalBondShares(), shares + preShares); } function test_depositWstETHWithPermit() public { @@ -196,14 +216,17 @@ contract DepositIntegrationTest is }); ILido(locator.lido()).approve(address(wstETH), type(uint256).max); uint256 wstETHAmount = wstETH.wrap(32 ether); - vm.stopPrank(); - vm.prank(user); - uint256 shares = accounting.depositWstETH( - user, + uint256 shares = ILido(locator.lido()).getSharesByPooledEth( + wstETH.getStETHByWstETH(wstETHAmount) + ); + + uint256 preShares = accounting.getBondShares(0); + + csm.depositWstETH( 0, wstETHAmount, - CSAccounting.PermitInput({ + ICSAccounting.PermitInput({ value: 32 ether, deadline: type(uint256).max, v: v, @@ -213,7 +236,7 @@ contract DepositIntegrationTest is ); assertEq(wstETH.balanceOf(user), 0); - assertEq(accounting.getBondShares(0), shares); - assertEq(accounting.totalBondShares(), shares); + assertEq(accounting.getBondShares(0), shares + preShares); + assertEq(accounting.totalBondShares(), shares + preShares); } }