From 1cb05ae00a50a1bc48b1bf597eff26aab8deec71 Mon Sep 17 00:00:00 2001 From: Don Perignom <10616301+madlabman@users.noreply.github.com> Date: Thu, 26 Oct 2023 09:04:26 +0200 Subject: [PATCH 1/2] feat: deposit queue (CS-55) (#18) --- .husky/pre-commit | 0 src/CommunityStakingModule.sol | 449 +++++++++++++----- .../ICommunityStakingBondManager.sol | 2 + src/lib/Batch.sol | 30 ++ src/lib/QueueLib.sol | 81 ++++ test/Batch.t.sol | 54 +++ test/CSMAddValidator.t.sol | 26 +- test/CSMInit.t.sol | 2 +- test/QueueLib.t.sol | 118 +++++ test/integration/StakingRouter.t.sol | 13 +- 10 files changed, 649 insertions(+), 126 deletions(-) mode change 100644 => 100755 .husky/pre-commit create mode 100644 src/lib/Batch.sol create mode 100644 src/lib/QueueLib.sol create mode 100644 test/Batch.t.sol create mode 100644 test/QueueLib.t.sol diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 diff --git a/src/CommunityStakingModule.sol b/src/CommunityStakingModule.sol index 80fb8c6a..0b81d96a 100644 --- a/src/CommunityStakingModule.sol +++ b/src/CommunityStakingModule.sol @@ -3,10 +3,16 @@ pragma solidity 0.8.21; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; + +import { ICommunityStakingBondManager } from "./interfaces/ICommunityStakingBondManager.sol"; import { IStakingModule } from "./interfaces/IStakingModule.sol"; -import "./interfaces/ICommunityStakingBondManager.sol"; -import "./interfaces/ILidoLocator.sol"; -import "./interfaces/ILido.sol"; +import { ILidoLocator } from "./interfaces/ILidoLocator.sol"; +import { ILido } from "./interfaces/ILido.sol"; + +import { QueueLib } from "./lib/QueueLib.sol"; +import { Batch } from "./lib/Batch.sol"; import "./lib/SigningKeys.sol"; import "./lib/StringToUint256WithZeroMap.sol"; @@ -36,26 +42,48 @@ contract CommunityStakingModuleBase { ); event NodeOperatorNameSet(uint256 indexed nodeOperatorId, string name); - event VettedKeysCountChanged( + event VettedSigningKeysCountChanged( uint256 indexed nodeOperatorId, - uint256 approvedKeysCount + uint256 approvedValidatorsCount ); - event DepositedKeysCountChanged( + event DepositedSigningKeysCountChanged( uint256 indexed nodeOperatorId, - uint256 depositedKeysCount + uint256 depositedValidatorsCount ); - event ExitedKeysCountChanged( + event ExitedSigningKeysCountChanged( uint256 indexed nodeOperatorId, - uint256 exitedKeysCount + uint256 exitedValidatorsCount ); - event TotalKeysCountChanged( + event TotalSigningKeysCountChanged( + uint256 indexed nodeOperatorId, + uint256 totalValidatorsCount + ); + + event BatchEnqueued( uint256 indexed nodeOperatorId, - uint256 totalKeysCount + uint256 startIndex, + uint256 count ); + + event StakingModuleTypeSet(bytes32 moduleType); + event LocatorContractSet(address locatorAddress); + event UnvettingFeeSet(uint256 unvettingFee); } contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { using StringToUint256WithZeroMap for mapping(string => uint256); + using QueueLib for QueueLib.Queue; + + uint256 public constant MAX_NODE_OPERATOR_NAME_LENGTH = 255; + bytes32 public constant SIGNING_KEYS_POSITION = + keccak256("lido.CommunityStakingModule.signingKeysPosition"); + + uint256 public unvettingFee; + QueueLib.Queue public queue; + + ICommunityStakingBondManager public bondManager; + ILidoLocator public lidoLocator; + uint256 private nodeOperatorsCount; uint256 private activeNodeOperatorsCount; bytes32 private moduleType; @@ -63,43 +91,55 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { mapping(uint256 => NodeOperator) private nodeOperators; mapping(string => uint256) private nodeOperatorIdsByName; - bytes32 public constant SIGNING_KEYS_POSITION = - keccak256("lido.CommunityStakingModule.signingKeysPosition"); - uint256 public constant MAX_NODE_OPERATOR_NAME_LENGTH = 255; + uint256 private _totalDepositedValidators; + uint256 private _totalExitedValidators; + uint256 private _totalAddedValidators; - address public bondManagerAddress; - address public lidoLocator; + modifier onlyActiveNodeOperator(uint256 _nodeOperatorId) { + require( + _nodeOperatorId < nodeOperatorsCount, + "node operator does not exist" + ); + require( + nodeOperators[_nodeOperatorId].active, + "node operator is not active" + ); + _; + } + + modifier onlyKeyValidatorOrNodeOperatorManager() { + // TODO: check the role + _; + } + + modifier onlyKeyValidator() { + // TODO: check the role + _; + } constructor(bytes32 _type, address _locator) { moduleType = _type; + emit StakingModuleTypeSet(_type); require(_locator != address(0), "lido locator is zero address"); - lidoLocator = _locator; - } - - function setBondManager(address _bondManagerAddress) external { - // TODO add role check - require( - address(bondManagerAddress) == address(0), - "already initialized" - ); - bondManagerAddress = _bondManagerAddress; + lidoLocator = ILidoLocator(_locator); + emit LocatorContractSet(_locator); } - function _bondManager() - internal - view - returns (ICommunityStakingBondManager) - { - return ICommunityStakingBondManager(bondManagerAddress); + function setBondManager(address _bondManager) external { + // TODO: add role check + require(address(bondManager) == address(0), "already initialized"); + bondManager = ICommunityStakingBondManager(_bondManager); } - function _lidoLocator() internal view returns (ILidoLocator) { - return ILidoLocator(lidoLocator); + function setUnvettingFee(uint256 unvettingFee_) external { + // TODO: add role check + unvettingFee = unvettingFee_; + emit UnvettingFeeSet(unvettingFee_); } function _lido() internal view returns (ILido) { - return ILido(_lidoLocator().lido()); + return ILido(lidoLocator.lido()); } function getType() external view returns (bytes32) { @@ -110,22 +150,15 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { external view returns ( - uint256 totalExitedValidators, - uint256 totalDepositedValidators, - uint256 depositableValidatorsCount + uint256 /* totalExitedValidators */, + uint256 /* totalDepositedValidators */, + uint256 /* depositableValidatorsCount */ ) { - for (uint256 i = 0; i < nodeOperatorsCount; i++) { - totalExitedValidators += nodeOperators[i].totalExitedKeys; - totalDepositedValidators += nodeOperators[i].totalDepositedKeys; - depositableValidatorsCount += - nodeOperators[i].totalAddedKeys - - nodeOperators[i].totalExitedKeys; - } return ( - totalExitedValidators, - totalDepositedValidators, - depositableValidatorsCount + _totalExitedValidators, + _totalDepositedValidators, + _totalAddedValidators - _totalExitedValidators ); } @@ -140,7 +173,7 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { _onlyValidNodeOperatorName(_name); require( - msg.value == _bondManager().getRequiredBondETHForKeys(_keysCount), + msg.value == bondManager.getRequiredBondETHForKeys(_keysCount), "eth value is not equal to required bond" ); @@ -153,7 +186,7 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { nodeOperatorsCount++; activeNodeOperatorsCount++; - _bondManager().depositETH{ value: msg.value }(msg.sender, id); + bondManager.depositETH{ value: msg.value }(msg.sender, id); _addSigningKeys(id, _keysCount, _publicKeys, _signatures); @@ -167,7 +200,7 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { bytes calldata _publicKeys, bytes calldata _signatures ) external { - // TODO sanity checks + // TODO: sanity checks _onlyValidNodeOperatorName(_name); uint256 id = nodeOperatorsCount; @@ -179,10 +212,10 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { nodeOperatorsCount++; activeNodeOperatorsCount++; - _bondManager().depositStETH( + bondManager.depositStETH( msg.sender, id, - _bondManager().getRequiredBondStETHForKeys(_keysCount) + bondManager.getRequiredBondStETHForKeys(_keysCount) ); _addSigningKeys(id, _keysCount, _publicKeys, _signatures); @@ -252,10 +285,10 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { nodeOperatorsCount++; activeNodeOperatorsCount++; - _bondManager().depositStETHWithPermit( + bondManager.depositStETHWithPermit( _from, id, - _bondManager().getRequiredBondStETHForKeys(_keysCount), + bondManager.getRequiredBondStETHForKeys(_keysCount), _permit ); @@ -283,10 +316,10 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { nodeOperatorsCount++; activeNodeOperatorsCount++; - _bondManager().depositWstETH( + bondManager.depositWstETH( msg.sender, id, - _bondManager().getRequiredBondWstETHForKeys(_keysCount) + bondManager.getRequiredBondWstETHForKeys(_keysCount) ); _addSigningKeys(id, _keysCount, _publicKeys, _signatures); @@ -356,10 +389,10 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { nodeOperatorsCount++; activeNodeOperatorsCount++; - _bondManager().depositWstETHWithPermit( + bondManager.depositWstETHWithPermit( _from, id, - _bondManager().getRequiredBondWstETHForKeys(_keysCount), + bondManager.getRequiredBondWstETHForKeys(_keysCount), _permit ); @@ -374,19 +407,15 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { bytes calldata _publicKeys, bytes calldata _signatures ) external payable onlyExistingNodeOperator(_nodeOperatorId) { - // TODO sanity checks - // TODO store keys + // TODO: sanity checks require( msg.value == - _bondManager().getRequiredBondETH(_nodeOperatorId, _keysCount), + bondManager.getRequiredBondETH(_nodeOperatorId, _keysCount), "eth value is not equal to required bond" ); - _bondManager().depositETH{ value: msg.value }( - msg.sender, - _nodeOperatorId - ); + bondManager.depositETH{ value: msg.value }(msg.sender, _nodeOperatorId); _addSigningKeys(_nodeOperatorId, _keysCount, _publicKeys, _signatures); } @@ -397,13 +426,12 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { bytes calldata _publicKeys, bytes calldata _signatures ) external onlyExistingNodeOperator(_nodeOperatorId) { - // TODO sanity checks - // TODO store keys + // TODO: sanity checks - _bondManager().depositStETH( + bondManager.depositStETH( msg.sender, _nodeOperatorId, - _bondManager().getRequiredBondStETH(_nodeOperatorId, _keysCount) + bondManager.getRequiredBondStETH(_nodeOperatorId, _keysCount) ); _addSigningKeys(_nodeOperatorId, _keysCount, _publicKeys, _signatures); @@ -457,10 +485,10 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { // TODO sanity checks // TODO store keys - _bondManager().depositStETHWithPermit( + bondManager.depositStETHWithPermit( _from, _nodeOperatorId, - _bondManager().getRequiredBondStETH(_nodeOperatorId, _keysCount), + bondManager.getRequiredBondStETH(_nodeOperatorId, _keysCount), _permit ); @@ -473,13 +501,12 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { bytes calldata _publicKeys, bytes calldata _signatures ) external onlyExistingNodeOperator(_nodeOperatorId) { - // TODO sanity checks - // TODO store keys + // TODO: sanity checks - _bondManager().depositWstETH( + bondManager.depositWstETH( msg.sender, _nodeOperatorId, - _bondManager().getRequiredBondWstETH(_nodeOperatorId, _keysCount) + bondManager.getRequiredBondWstETH(_nodeOperatorId, _keysCount) ); _addSigningKeys(_nodeOperatorId, _keysCount, _publicKeys, _signatures); @@ -533,10 +560,10 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { // TODO sanity checks // TODO store keys - _bondManager().depositWstETHWithPermit( + bondManager.depositWstETHWithPermit( _from, _nodeOperatorId, - _bondManager().getRequiredBondWstETH(_nodeOperatorId, _keysCount), + bondManager.getRequiredBondWstETH(_nodeOperatorId, _keysCount), _permit ); @@ -587,7 +614,7 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { uint256 totalDepositedValidators ) { - NodeOperator memory no = nodeOperators[_nodeOperatorId]; + NodeOperator storage no = nodeOperators[_nodeOperatorId]; active = no.active; name = _fullInfo ? no.name : ""; rewardAddress = no.rewardAddress; @@ -660,22 +687,22 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { } function onRewardsMinted(uint256 /*_totalShares*/) external { - // TODO implement + // TODO: implement } function updateStuckValidatorsCount( bytes calldata /*_nodeOperatorIds*/, bytes calldata /*_stuckValidatorsCounts*/ ) external { - // TODO implement + // TODO: implement } function updateExitedValidatorsCount( bytes calldata _nodeOperatorIds, bytes calldata _exitedValidatorsCounts ) external { - // TODO implement - // emit ExitedKeysCountChanged( + // TODO: implement + // emit ExitedSigningKeysCountChanged( // _nodeOperatorId, // _exitedValidatorsCount // ); @@ -685,7 +712,7 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { uint256 /*_nodeOperatorId*/, uint256 /*_refundedValidatorsCount*/ ) external { - // TODO implement + // TODO: implement } function updateTargetValidatorsLimits( @@ -693,11 +720,11 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { bool /*_isTargetLimitActive*/, uint256 /*_targetLimit*/ ) external { - // TODO implement + // TODO: implement } function onExitedAndStuckValidatorsCountsUpdated() external { - // TODO implement + // TODO: implement } function unsafeUpdateValidatorsCount( @@ -705,7 +732,60 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { uint256 /*_exitedValidatorsKeysCount*/, uint256 /*_stuckValidatorsKeysCount*/ ) external { - // TODO implement + // TODO: implement + } + + function vetKeys( + uint256 nodeOperatorId, + uint64 vettedKeysCount + ) external onlyKeyValidator { + NodeOperator storage no = nodeOperators[nodeOperatorId]; + + require( + vettedKeysCount > no.totalVettedKeys, + "Wrong vettedKeysCount: less than already vetted" + ); + require( + vettedKeysCount <= no.totalAddedKeys, + "Wrong vettedKeysCount: more than added" + ); + + uint64 count = SafeCast.toUint64(vettedKeysCount - no.totalVettedKeys); + uint64 start = SafeCast.toUint64( + no.totalVettedKeys == 0 ? 0 : no.totalVettedKeys - 1 + ); + + bytes32 pointer = Batch.serialize({ + nodeOperatorId: SafeCast.toUint128(nodeOperatorId), + start: start, + count: count + }); + + no.totalVettedKeys = vettedKeysCount; + queue.enqueue(pointer); + + emit BatchEnqueued(nodeOperatorId, start, count); + emit VettedSigningKeysCountChanged(nodeOperatorId, vettedKeysCount); + + _incrementNonce(); + } + + function unvetKeys( + uint256 nodeOperatorId + ) external onlyKeyValidatorOrNodeOperatorManager { + _unvetKeys(nodeOperatorId); + bondManager.penalize(nodeOperatorId, unvettingFee); + } + + function unsafeUnvetKeys(uint256 nodeOperatorId) external onlyKeyValidator { + _unvetKeys(nodeOperatorId); + } + + function _unvetKeys(uint256 nodeOperatorId) internal { + NodeOperator storage no = nodeOperators[nodeOperatorId]; + no.totalVettedKeys = no.totalDepositedKeys; + emit VettedSigningKeysCountChanged(nodeOperatorId, no.totalVettedKeys); + _incrementNonce(); } function onWithdrawalCredentialsChanged() external { @@ -730,8 +810,9 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { _signatures ); + _totalAddedValidators += _keysCount; nodeOperators[_nodeOperatorId].totalAddedKeys += _keysCount; - emit TotalKeysCountChanged( + emit TotalSigningKeysCountChanged( _nodeOperatorId, nodeOperators[_nodeOperatorId].totalAddedKeys ); @@ -741,44 +822,190 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { function obtainDepositData( uint256 _depositsCount, - bytes calldata /*_depositCalldata*/ + bytes calldata /* _depositCalldata */ ) external returns (bytes memory publicKeys, bytes memory signatures) { (publicKeys, signatures) = SigningKeys.initKeysSigsBuf(_depositsCount); + uint256 limit = _depositsCount; uint256 loadedKeysCount = 0; - for ( - uint256 nodeOperatorId; - nodeOperatorId < nodeOperatorsCount; - nodeOperatorId++ - ) { - NodeOperator storage no = nodeOperators[nodeOperatorId]; - // TODO replace total added to total vetted later - uint256 availableKeys = no.totalAddedKeys - no.totalDepositedKeys; - if (availableKeys == 0) continue; - - uint256 _startIndex = no.totalDepositedKeys; - uint256 _keysCount = _depositsCount > availableKeys - ? availableKeys - : _depositsCount; + + for (bytes32 p = queue.peek(); !Batch.isNil(p); ) { + ( + uint256 nodeOperatorId, + uint256 startIndex, + uint256 depositableKeysCount + ) = _depositableKeysInBatch(p); + + uint256 keysCount = Math.min(limit, depositableKeysCount); + if (depositableKeysCount == keysCount) { + queue.dequeue(); + } + SigningKeys.loadKeysSigs( SIGNING_KEYS_POSITION, nodeOperatorId, - _startIndex, - _keysCount, + startIndex, + keysCount, publicKeys, signatures, loadedKeysCount ); - loadedKeysCount += _keysCount; - // TODO maybe depositor bot should initiate this increment - no.totalDepositedKeys += _keysCount; - emit DepositedKeysCountChanged( + loadedKeysCount += keysCount; + + _totalDepositedValidators += keysCount; + NodeOperator storage no = nodeOperators[nodeOperatorId]; + no.totalDepositedKeys += keysCount; + require( + no.totalDepositedKeys <= no.totalVettedKeys, + "too many keys" + ); + + emit DepositedSigningKeysCountChanged( nodeOperatorId, no.totalDepositedKeys ); + + limit = limit - keysCount; + if (limit == 0) { + break; + } + + p = queue.peek(); + } + + require(loadedKeysCount == _depositsCount, "NOT_ENOUGH_KEYS"); + _incrementNonce(); + } + + function _depositableKeysInBatch( + bytes32 batch + ) + internal + view + returns ( + uint256 nodeOperatorId, + uint256 startIndex, + uint256 depositableKeysCount + ) + { + uint256 start; + uint256 count; + + (nodeOperatorId, start, count) = Batch.deserialize(batch); + + NodeOperator storage no = nodeOperators[nodeOperatorId]; + _assertIsValidBatch(no, start, count); + + startIndex = Math.max(start, no.totalDepositedKeys); + depositableKeysCount = start + count - startIndex; + } + + function _assertIsValidBatch( + NodeOperator storage no, + uint256 _start, + uint256 _count + ) internal view { + require(_count != 0, "Empty batch given"); + require( + _unvettedKeysInBatch(no, _start, _count) == false, + "Batch contains unvetted keys" + ); + require( + _start + _count <= no.totalAddedKeys, + "Invalid batch range: not enough keys" + ); + require( + _start <= no.totalDepositedKeys, + "Invalid batch range: skipped keys" + ); + } + + /// @dev returns the next pointer to start cleanup from + function cleanDepositQueue( + uint256 maxItems, + bytes32 pointer + ) external returns (bytes32) { + require(maxItems > 0, "Queue walkthrough limit is not set"); + + if (Batch.isNil(pointer)) { + pointer = queue.front; } - if (loadedKeysCount != _depositsCount) { - revert("NOT_ENOUGH_KEYS"); + + for (uint256 i; i < maxItems; i++) { + bytes32 item = queue.at(pointer); + if (Batch.isNil(item)) { + break; + } + + (uint256 nodeOperatorId, uint256 start, uint256 count) = Batch + .deserialize(item); + NodeOperator storage no = nodeOperators[nodeOperatorId]; + if (_unvettedKeysInBatch(no, start, count)) { + queue.remove(pointer, item); + } + + pointer = item; } + + return pointer; + } + + function depositQueue( + uint256 maxItems, + bytes32 pointer + ) + external + view + returns ( + bytes32[] memory items, + bytes32 /* pointer */, + uint256 /* count */ + ) + { + require(maxItems > 0, "Queue walkthrough limit is not set"); + + if (Batch.isNil(pointer)) { + pointer = queue.front; + } + + return queue.list(pointer, maxItems); + } + + /// @dev returns the next pointer to start check from + function isQueueHasUnvettedKeys( + uint256 maxItems, + bytes32 pointer + ) external view returns (bool, bytes32) { + require(maxItems > 0, "Queue walkthrough limit is not set"); + + if (Batch.isNil(pointer)) { + pointer = queue.front; + } + + for (uint256 i; i < maxItems; i++) { + bytes32 item = queue.at(pointer); + if (Batch.isNil(item)) { + break; + } + + (uint256 nodeOperatorId, uint256 start, uint256 count) = Batch + .deserialize(item); + NodeOperator storage no = nodeOperators[nodeOperatorId]; + if (_unvettedKeysInBatch(no, start, count)) { + return (true, pointer); + } + + pointer = item; + } + + return (false, pointer); + } + + function _unvettedKeysInBatch( + NodeOperator storage no, + uint256 _start, + uint256 _count + ) internal view returns (bool) { + return _start + _count > no.totalVettedKeys; } function _incrementNonce() internal { diff --git a/src/interfaces/ICommunityStakingBondManager.sol b/src/interfaces/ICommunityStakingBondManager.sol index 1545488e..77893ad4 100644 --- a/src/interfaces/ICommunityStakingBondManager.sol +++ b/src/interfaces/ICommunityStakingBondManager.sol @@ -101,4 +101,6 @@ interface ICommunityStakingBondManager { uint256 nodeOperatorId, uint256 newKeysCount ) external view returns (uint256); + + function penalize(uint256 nodeOperatorId, uint256 shares) external; } diff --git a/src/lib/Batch.sol b/src/lib/Batch.sol new file mode 100644 index 00000000..664f88f7 --- /dev/null +++ b/src/lib/Batch.sol @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.21; + +/// @author madlabman +library Batch { + /// @notice Serialize node operator id, batch start and count of keys into a single bytes32 value + function serialize( + uint128 nodeOperatorId, + uint64 start, + uint64 count + ) internal pure returns (bytes32 s) { + return bytes32(abi.encodePacked(nodeOperatorId, start, count)); + } + + /// @notice Deserialize node operator id, batch start and count of keys from a single bytes32 value + function deserialize( + bytes32 b + ) internal pure returns (uint128 nodeOperatorId, uint64 start, uint64 count) { + assembly { + nodeOperatorId := shr(128, b) + start := shr(64, b) + count := b + } + } + + function isNil(bytes32 b) internal pure returns (bool) { + return b == bytes32(0); + } +} diff --git a/src/lib/QueueLib.sol b/src/lib/QueueLib.sol new file mode 100644 index 00000000..0283d87f --- /dev/null +++ b/src/lib/QueueLib.sol @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.21; + + +/// @author madlabman +library QueueLib { + bytes32 public constant NULL_POINTER = bytes32(0); + + struct Queue { + mapping(bytes32 => bytes32) queue; + bytes32 front; + bytes32 back; + } + + function enqueue(Queue storage self, bytes32 item) internal { + require(item != NULL_POINTER, "Queue: item is zero"); + require(self.queue[item] == NULL_POINTER, "Queue: item already enqueued"); + + if (self.front == self.queue[self.front]) { + self.queue[self.front] = item; + } + + self.queue[self.back] = item; + self.back = item; + } + + function dequeue(Queue storage self) internal notEmpty(self) returns (bytes32 item) { + item = self.queue[self.front]; + self.front = item; + } + + function peek(Queue storage self) internal view returns (bytes32) { + return self.queue[self.front]; + } + + function at(Queue storage self, bytes32 pointer) internal view returns (bytes32) { + return self.queue[pointer]; + } + + function list(Queue storage self, bytes32 pointer, uint256 limit) internal notEmpty(self) view returns ( + bytes32[] memory items, + bytes32 /* pointer */, + uint256 /* count */ + ) { + items = new bytes32[](limit); + + uint256 i; + for (; i < limit; i++) { + bytes32 item = self.queue[pointer]; + if (item == NULL_POINTER) { + break; + } + + items[i] = item; + pointer = item; + } + + return (items, pointer, i); + } + + function isEmpty(Queue storage self) internal view returns (bool) { + return self.front == self.back; + } + + function remove(Queue storage self, bytes32 pointerToItem, bytes32 item) internal { + require(self.queue[pointerToItem] == item, "Queue: wrong pointer given"); + + self.queue[pointerToItem] = self.queue[item]; + self.queue[item] = NULL_POINTER; + + if (self.back == item) { + self.back = pointerToItem; + } + } + + modifier notEmpty(Queue storage self) { + require(!isEmpty(self), "Queue: empty"); + _; + } +} diff --git a/test/Batch.t.sol b/test/Batch.t.sol new file mode 100644 index 00000000..a5a197ad --- /dev/null +++ b/test/Batch.t.sol @@ -0,0 +1,54 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.21; + +import "forge-std/Test.sol"; + +import { Batch } from "../src/lib/Batch.sol"; + +contract BatchTest is Test { + function test_serialize() public { + bytes32 b = Batch.serialize({ + nodeOperatorId: 999, + start: 3, + count: 42 + }); + + assertEq( + b, + // noIndex | start | count | + 0x000000000000000000000000000003e70000000000000003000000000000002a + ); + } + + function test_deserialize() public { + (uint128 nodeOperatorId, uint64 start, uint64 count) = Batch + .deserialize( + 0x0000000000000000000000000000000000000000000000000000000000000000 + ); + + assertEq(nodeOperatorId, 0, "nodeOperatorId != 0"); + assertEq(start, 0, "start != 0"); + assertEq(count, 0, "count != 0"); + + (nodeOperatorId, start, count) = Batch.deserialize( + 0x000000000000000000000000000003e70000000000000003000000000000002a + ); + + assertEq(nodeOperatorId, 999, "nodeOperatorId != 999"); + assertEq(start, 3, "start != 3"); + assertEq(count, 42, "count != 42"); + + (nodeOperatorId, start, count) = Batch.deserialize( + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff + ); + + assertEq( + nodeOperatorId, + type(uint128).max, + "nodeOperatorId != uint128.max" + ); + assertEq(start, type(uint64).max, "start != uint64.max"); + assertEq(count, type(uint64).max, "count != uint64.max"); + } +} diff --git a/test/CSMAddValidator.t.sol b/test/CSMAddValidator.t.sol index f4c0b2b7..6e5fa11b 100644 --- a/test/CSMAddValidator.t.sol +++ b/test/CSMAddValidator.t.sol @@ -90,7 +90,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { { vm.expectEmit(true, true, false, true, address(csm)); - emit TotalKeysCountChanged(0, 1); + emit TotalSigningKeysCountChanged(0, 1); vm.expectEmit(true, true, false, true, address(csm)); emit NodeOperatorAdded(0, "test", nodeOperator); } @@ -111,7 +111,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { vm.expectEmit(true, true, true, true, address(wstETH)); emit Approval(nodeOperator, address(bondManager), wstETHAmount); vm.expectEmit(true, true, false, true, address(csm)); - emit TotalKeysCountChanged(0, 1); + emit TotalSigningKeysCountChanged(0, 1); vm.expectEmit(true, true, false, true, address(csm)); emit NodeOperatorAdded(0, "test", nodeOperator); } @@ -145,7 +145,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { (bytes memory keys, bytes memory signatures) = keysSignatures(1, 1); { vm.expectEmit(true, true, false, true, address(csm)); - emit TotalKeysCountChanged(0, 2); + emit TotalSigningKeysCountChanged(0, 2); } csm.addValidatorKeysWstETH(noId, 1, keys, signatures); } @@ -170,7 +170,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { vm.expectEmit(true, true, true, true, address(wstETH)); emit Approval(nodeOperator, address(bondManager), wstETHAmount); vm.expectEmit(true, true, false, true, address(csm)); - emit TotalKeysCountChanged(0, 2); + emit TotalSigningKeysCountChanged(0, 2); } vm.prank(stranger); csm.addValidatorKeysWstETHWithPermit( @@ -198,7 +198,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { { vm.expectEmit(true, true, false, true, address(csm)); - emit TotalKeysCountChanged(0, 1); + emit TotalSigningKeysCountChanged(0, 1); vm.expectEmit(true, true, false, true, address(csm)); emit NodeOperatorAdded(0, "test", nodeOperator); } @@ -218,7 +218,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { vm.expectEmit(true, true, true, true, address(stETH)); emit Approval(nodeOperator, address(bondManager), 2 ether); vm.expectEmit(true, true, false, true, address(csm)); - emit TotalKeysCountChanged(0, 1); + emit TotalSigningKeysCountChanged(0, 1); vm.expectEmit(true, true, false, true, address(csm)); emit NodeOperatorAdded(0, "test", nodeOperator); } @@ -252,7 +252,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { stETH.submit{ value: 2 ether }(address(0)); { vm.expectEmit(true, true, false, true, address(csm)); - emit TotalKeysCountChanged(0, 2); + emit TotalSigningKeysCountChanged(0, 2); } csm.addValidatorKeysStETH(noId, 1, keys, signatures); } @@ -274,7 +274,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { vm.expectEmit(true, true, true, true, address(stETH)); emit Approval(nodeOperator, address(bondManager), required); vm.expectEmit(true, true, false, true, address(csm)); - emit TotalKeysCountChanged(0, 2); + emit TotalSigningKeysCountChanged(0, 2); } vm.prank(stranger); csm.addValidatorKeysStETHWithPermit( @@ -303,7 +303,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { { vm.expectEmit(true, true, false, true, address(csm)); - emit TotalKeysCountChanged(0, 1); + emit TotalSigningKeysCountChanged(0, 1); vm.expectEmit(true, true, false, true, address(csm)); emit NodeOperatorAdded(0, "test", nodeOperator); } @@ -328,7 +328,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { vm.prank(nodeOperator); { vm.expectEmit(true, true, false, true, address(csm)); - emit TotalKeysCountChanged(0, 2); + emit TotalSigningKeysCountChanged(0, 2); } csm.addValidatorKeysETH{ value: required }(noId, 1, keys, signatures); } @@ -349,6 +349,12 @@ contract CSMObtainDepositData is CSMCommon { keys, signatures ); + + { + // Pretend to be a key validation oracle + csm.vetKeys(0, 1); + } + (bytes memory obtainedKeys, bytes memory obtainedSignatures) = csm .obtainDepositData(1, ""); assertEq(obtainedKeys, keys); diff --git a/test/CSMInit.t.sol b/test/CSMInit.t.sol index 0660f72c..f1653989 100644 --- a/test/CSMInit.t.sol +++ b/test/CSMInit.t.sol @@ -56,6 +56,6 @@ contract CSMInitTest is Test, Fixtures { function test_SetBondManager() public { csm.setBondManager(address(bondManager)); - assertEq(address(csm.bondManagerAddress()), address(bondManager)); + assertEq(address(csm.bondManager()), address(bondManager)); } } diff --git a/test/QueueLib.t.sol b/test/QueueLib.t.sol new file mode 100644 index 00000000..2f6acc66 --- /dev/null +++ b/test/QueueLib.t.sol @@ -0,0 +1,118 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 +pragma solidity 0.8.21; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; + +import { QueueLib } from "../src/lib/QueueLib.sol"; + +contract QueueLibTest is Test { + bytes32 p0 = keccak256("0x00"); // 0x27489e20a0060b723a1748bdff5e44570ee9fae64141728105692eac6031e8a4 + bytes32 p1 = keccak256("0x01"); // 0xe127292c8f7eb20e1ae830ed6055b6eb36e261836100610d12677231d0791f7f + bytes32 p2 = keccak256("0x02"); // 0xd3974deccfd8aa6b77f0fcc2c0014e6e0574d32e56c1d75717d2667b529cd073 + + bytes32 nil = bytes32(0); + bytes32 buf; + + using QueueLib for QueueLib.Queue; + QueueLib.Queue q; + + function test_enqueue() public { + assertEq(q.peek(), nil); + + q.enqueue(p0); + q.enqueue(p1); + + assertEq(q.peek(), p0); + assertEq(q.at(p0), p1); + } + + function test_dequeue() public { + assertTrue(q.isEmpty()); + + q.enqueue(p0); + q.enqueue(p1); + q.enqueue(p2); + + assertFalse(q.isEmpty()); + + buf = q.dequeue(); + assertEq(buf, p0); + assertEq(q.peek(), p1); + + buf = q.dequeue(); + assertEq(buf, p1); + assertEq(q.peek(), p2); + + q.dequeue(); + assertEq(q.peek(), nil); + assertTrue(q.isEmpty()); + } + + function test_list() public { + q.enqueue(p0); + q.enqueue(p1); + q.enqueue(p2); + + { + (bytes32[] memory items, bytes32 pointer, uint256 count) = q.list( + q.front, + 2 + ); + assertEq(count, 2); + assertEq(pointer, p1); + assertEq(items[0], p0); + assertEq(items[1], p1); + } + + { + (bytes32[] memory items, bytes32 pointer, uint256 count) = q.list( + p1, + 999 + ); + assertEq(count, 1); + assertEq(pointer, p2); + assertEq(items[0], p2); + } + + q.dequeue(); + + { + (, bytes32 pointer, uint256 count) = q.list(q.front, 0); + assertEq(count, 0); + assertEq(pointer, q.front); + } + } + + function test_remove() public { + q.enqueue(p0); + q.enqueue(p1); + q.enqueue(p2); + // [+*p0, p1, p2] + + q.remove(p0, p1); + // [+*p0, p2] + + q.dequeue(); + // [+p0, *p2] + buf = q.dequeue(); + // [p0, +*p2] + assertEq(buf, p2); + + q.enqueue(p1); + // [p0, +p2, *p1] + assertEq(q.peek(), p1); + + q.remove(p2, p1); + // [p0, +*p2] + assertEq(q.peek(), nil); + assertTrue(q.isEmpty()); + + q.remove(p0, p2); + // [+*p0] + assertEq(q.peek(), nil); + } + + // TODO: test with revert on library call +} diff --git a/test/integration/StakingRouter.t.sol b/test/integration/StakingRouter.t.sol index 0bccced4..5b5b5b2b 100644 --- a/test/integration/StakingRouter.t.sol +++ b/test/integration/StakingRouter.t.sol @@ -101,18 +101,23 @@ contract StakingRouterIntegrationTest is Test, Utilities { _treasuryFee: 500 }); uint256[] memory ids = stakingRouter.getStakingModuleIds(); - (bytes memory keys, bytes memory signatures) = keysSignatures(1); + (bytes memory keys, bytes memory signatures) = keysSignatures(2); address nodeOperator = address(2); - vm.deal(nodeOperator, 2 ether); + vm.deal(nodeOperator, 4 ether); vm.prank(nodeOperator); - csm.addNodeOperatorETH{ value: 2 ether }( + csm.addNodeOperatorETH{ value: 4 ether }( "test", nodeOperator, - 1, + 2, keys, signatures ); + { + // Pretend to be a key validation oracle + csm.vetKeys(0, 2); + } + // It's impossible to process deposits if withdrawal requests amount is more than the buffered ether, // so we need to make sure that the buffered ether is enough by submitting this tremendous amount. address whale = nextAddress(); From d2e0c25ed461d9d884e34281aab6f5a41e768551 Mon Sep 17 00:00:00 2001 From: Vladimir Gorkavenko <32727352+vgorkavenko@users.noreply.github.com> Date: Thu, 26 Oct 2023 14:18:47 +0400 Subject: [PATCH 2/2] feat: accounting remarks (#33) * feat: accounting remarks * fix: naming * fix: test --- script/Deploy.s.sol | 36 +- ...takingBondManager.sol => CSAccounting.sol} | 267 +++--------- ...eeDistributor.sol => CSFeeDistributor.sol} | 22 +- ...butorBase.sol => CSFeeDistributorBase.sol} | 2 +- src/{FeeOracle.sol => CSFeeOracle.sol} | 4 +- ...{FeeOracleBase.sol => CSFeeOracleBase.sol} | 2 +- ...ommunityStakingModule.sol => CSModule.sol} | 222 ++-------- ...akingBondManager.sol => ICSAccounting.sol} | 28 +- ...eDistributor.sol => ICSFeeDistributor.sol} | 2 +- .../{IFeeOracle.sol => ICSFeeOracle.sol} | 2 +- ...mmunityStakingModule.sol => ICSModule.sol} | 2 +- .../{BondManager.t.sol => CSAccounting.t.sol} | 393 +++++++++--------- ...stributor.t.sol => CSFeeDistributor.t.sol} | 14 +- test/{FeeOracle.t.sol => CSFeeOracle.t.sol} | 52 +-- test/CSMAddValidator.t.sol | 41 +- test/CSMInit.t.sol | 23 +- test/integration/DepositInTokens.t.sol | 52 +-- test/integration/StakingRouter.t.sol | 31 +- 18 files changed, 428 insertions(+), 767 deletions(-) rename src/{CommunityStakingBondManager.sol => CSAccounting.sol} (75%) rename src/{FeeDistributor.sol => CSFeeDistributor.sol} (81%) rename src/{FeeDistributorBase.sol => CSFeeDistributorBase.sol} (92%) rename src/{FeeOracle.sol => CSFeeOracle.sol} (98%) rename src/{FeeOracleBase.sol => CSFeeOracleBase.sol} (96%) rename src/{CommunityStakingModule.sol => CSModule.sol} (79%) rename src/interfaces/{ICommunityStakingBondManager.sol => ICSAccounting.sol} (73%) rename src/interfaces/{ICommunityStakingFeeDistributor.sol => ICSFeeDistributor.sol} (90%) rename src/interfaces/{IFeeOracle.sol => ICSFeeOracle.sol} (92%) rename src/interfaces/{ICommunityStakingModule.sol => ICSModule.sol} (94%) rename test/{BondManager.t.sol => CSAccounting.t.sol} (70%) rename test/{FeeDistributor.t.sol => CSFeeDistributor.t.sol} (90%) rename test/{FeeOracle.t.sol => CSFeeOracle.t.sol} (94%) diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 3a95e607..84a722bc 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -6,10 +6,10 @@ pragma solidity 0.8.21; import "forge-std/Script.sol"; import { ILidoLocator } from "../src/interfaces/ILidoLocator.sol"; -import { CommunityStakingModule } from "../src/CommunityStakingModule.sol"; -import { CommunityStakingBondManager, IWstETH } from "../src/CommunityStakingBondManager.sol"; -import { FeeDistributor } from "../src/FeeDistributor.sol"; -import { FeeOracle } from "../src/FeeOracle.sol"; +import { CSModule } from "../src/CSModule.sol"; +import { CSAccounting, IWstETH } from "../src/CSAccounting.sol"; +import { CSFeeDistributor } from "../src/CSFeeDistributor.sol"; +import { CSFeeOracle } from "../src/CSFeeOracle.sol"; contract Deploy is Script { ILidoLocator public locator; @@ -35,28 +35,28 @@ contract Deploy is Script { vm.startBroadcast(deployerPrivateKey); locator = ILidoLocator(LIDO_LOCATOR_ADDRESS); wstETH = IWstETH(WSTETH_ADDRESS); - CommunityStakingModule csm = new CommunityStakingModule( + CSModule csm = new CSModule( "community-staking-module", address(locator) ); - CommunityStakingBondManager bondManager = new CommunityStakingBondManager({ - _commonBondSize: 2 ether, - _admin: deployerAddress, - _lidoLocator: address(locator), - _communityStakingModule: address(csm), - _wstETH: address(wstETH), - _penalizeRoleMembers: penalizers - }); - FeeOracle feeOracle = new FeeOracle({ + CSAccounting accounting = new CSAccounting({ + _commonBondSize: 2 ether, + _admin: deployerAddress, + _lidoLocator: address(locator), + _communityStakingModule: address(csm), + _wstETH: address(wstETH), + _penalizeRoleMembers: penalizers + }); + CSFeeOracle feeOracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: uint64(CL_GENESIS_TIME) }); - FeeDistributor feeDistributor = new FeeDistributor({ + CSFeeDistributor feeDistributor = new CSFeeDistributor({ _CSM: address(csm), _stETH: locator.lido(), _oracle: address(feeOracle), - _bondManager: address(bondManager) + _accounting: address(accounting) }); feeOracle.initialize({ _initializationEpoch: uint64(INITIALIZATION_EPOCH), @@ -64,8 +64,8 @@ contract Deploy is Script { _feeDistributor: address(feeDistributor), admin: deployerAddress }); - bondManager.setFeeDistributor(address(feeDistributor)); - // TODO: csm.setBondManager(address(bondManager)); + accounting.setFeeDistributor(address(feeDistributor)); + // TODO: csm.setBondManager(address(accounting)); vm.stopBroadcast(); } diff --git a/src/CommunityStakingBondManager.sol b/src/CSAccounting.sol similarity index 75% rename from src/CommunityStakingBondManager.sol rename to src/CSAccounting.sol index e7ac3cd0..0b0db45e 100644 --- a/src/CommunityStakingBondManager.sol +++ b/src/CSAccounting.sol @@ -6,12 +6,12 @@ pragma solidity 0.8.21; import { AccessControlEnumerable } from "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; import { ILidoLocator } from "./interfaces/ILidoLocator.sol"; -import { ICommunityStakingModule } from "./interfaces/ICommunityStakingModule.sol"; +import { ICSModule } from "./interfaces/ICSModule.sol"; import { ILido } from "./interfaces/ILido.sol"; import { IWstETH } from "./interfaces/IWstETH.sol"; -import { ICommunityStakingFeeDistributor } from "./interfaces/ICommunityStakingFeeDistributor.sol"; +import { ICSFeeDistributor } from "./interfaces/ICSFeeDistributor.sol"; -contract CommunityStakingBondManagerBase { +contract CSAccountingBase { event ETHBondDeposited( uint256 indexed nodeOperatorId, address from, @@ -44,10 +44,7 @@ contract CommunityStakingBondManagerBase { ); } -contract CommunityStakingBondManager is - CommunityStakingBondManagerBase, - AccessControlEnumerable -{ +contract CSAccounting is CSAccountingBase, AccessControlEnumerable { struct PermitInput { uint256 value; uint256 deadline; @@ -66,7 +63,7 @@ contract CommunityStakingBondManager is mapping(uint256 => uint256) private bondShares; ILidoLocator private immutable LIDO_LOCATOR; - ICommunityStakingModule private immutable CSM; + ICSModule private immutable CSM; IWstETH private immutable WSTETH; uint256 private immutable COMMON_BOND_SIZE; @@ -107,7 +104,7 @@ contract CommunityStakingBondManager is } LIDO_LOCATOR = ILidoLocator(_lidoLocator); - CSM = ICommunityStakingModule(_communityStakingModule); + CSM = ICSModule(_communityStakingModule); WSTETH = IWstETH(_wstETH); COMMON_BOND_SIZE = _commonBondSize; @@ -353,27 +350,13 @@ contract CommunityStakingBondManager is return wstETHAmount / getRequiredBondWstETHForKeys(1); } - /// @notice Deposits ETH to the bond for the given node operator. - /// @param nodeOperatorId id of the node operator to deposit bond for. - function depositETH( - uint256 nodeOperatorId - ) external payable returns (uint256) { - return _depositETH(msg.sender, nodeOperatorId); - } - /// @notice Deposits ETH to the bond for the given node operator. /// @param nodeOperatorId id of the node operator to deposit bond for. function depositETH( address from, uint256 nodeOperatorId ) external payable returns (uint256) { - return _depositETH(from, nodeOperatorId); - } - - function _depositETH( - address from, - uint256 nodeOperatorId - ) internal returns (uint256) { + from = (from == address(0)) ? msg.sender : from; // TODO: should be modifier. condition might be changed as well require( nodeOperatorId < CSM.getNodeOperatorsCount(), @@ -386,16 +369,6 @@ contract CommunityStakingBondManager is return shares; } - /// @notice Deposits stETH to the bond for the given node operator. - /// @param nodeOperatorId id of the node operator to deposit bond for. - /// @param stETHAmount amount of stETH to deposit. - function depositStETH( - uint256 nodeOperatorId, - uint256 stETHAmount - ) external returns (uint256) { - return _depositStETH(msg.sender, nodeOperatorId, stETHAmount); - } - /// @notice Deposits stETH to the bond for the given node operator. /// @param nodeOperatorId id of the node operator to deposit bond for. /// @param stETHAmount amount of stETH to deposit. @@ -404,44 +377,10 @@ contract CommunityStakingBondManager is uint256 nodeOperatorId, uint256 stETHAmount ) external returns (uint256) { + from = (from == address(0)) ? msg.sender : from; return _depositStETH(from, nodeOperatorId, stETHAmount); } - function _depositStETH( - address from, - uint256 nodeOperatorId, - uint256 stETHAmount - ) internal returns (uint256) { - require( - nodeOperatorId < CSM.getNodeOperatorsCount(), - "node operator does not exist" - ); - uint256 shares = _sharesByEth(stETHAmount); - _lido().transferSharesFrom(from, address(this), shares); - bondShares[nodeOperatorId] += shares; - totalBondShares += shares; - emit StETHBondDeposited(nodeOperatorId, from, stETHAmount); - return shares; - } - - /// @notice Deposits stETH to the bond for the given node operator. - /// @param nodeOperatorId id of the node operator to deposit bond for. - /// @param stETHAmount amount of stETH to deposit. - /// @param permit permit to spend stETH. - function depositStETHWithPermit( - uint256 nodeOperatorId, - uint256 stETHAmount, - PermitInput calldata permit - ) external returns (uint256) { - return - _depositStETHWithPermit( - msg.sender, - nodeOperatorId, - stETHAmount, - permit - ); - } - /// @notice Deposits stETH to the bond for the given node operator. /// @param nodeOperatorId id of the node operator to deposit bond for. /// @param stETHAmount amount of stETH to deposit. @@ -452,84 +391,47 @@ contract CommunityStakingBondManager is uint256 stETHAmount, PermitInput calldata permit ) external returns (uint256) { - return - _depositStETHWithPermit(from, nodeOperatorId, stETHAmount, permit); - } - - function _depositStETHWithPermit( - address from, - uint256 nodeOperatorId, - uint256 stETHAmount, - PermitInput calldata _permit - ) internal returns (uint256) { + from = (from == address(0)) ? msg.sender : from; _lido().permit( from, address(this), - _permit.value, - _permit.deadline, - _permit.v, - _permit.r, - _permit.s + permit.value, + permit.deadline, + permit.v, + permit.r, + permit.s ); return _depositStETH(from, nodeOperatorId, stETHAmount); } - /// @notice Deposits wstETH to the bond for the given node operator. - /// @param nodeOperatorId id of the node operator to deposit bond for. - /// @param wstETHAmount amount of wstETH to deposit. - function depositWstETH( - uint256 nodeOperatorId, - uint256 wstETHAmount - ) external returns (uint256) { - return _depositWstETH(msg.sender, nodeOperatorId, wstETHAmount); - } - - /// @notice Deposits wstETH to the bond for the given node operator. - /// @param from address to deposit wstETH from. - /// @param nodeOperatorId id of the node operator to deposit bond for. - /// @param wstETHAmount amount of wstETH to deposit. - function depositWstETH( - address from, - uint256 nodeOperatorId, - uint256 wstETHAmount - ) external returns (uint256) { - return _depositWstETH(from, nodeOperatorId, wstETHAmount); - } - - function _depositWstETH( + function _depositStETH( address from, uint256 nodeOperatorId, - uint256 wstETHAmount + uint256 stETHAmount ) internal returns (uint256) { require( nodeOperatorId < CSM.getNodeOperatorsCount(), "node operator does not exist" ); - WSTETH.transferFrom(from, address(this), wstETHAmount); - uint256 stETHAmount = WSTETH.unwrap(wstETHAmount); uint256 shares = _sharesByEth(stETHAmount); + _lido().transferSharesFrom(from, address(this), shares); bondShares[nodeOperatorId] += shares; totalBondShares += shares; - emit WstETHBondDeposited(nodeOperatorId, from, wstETHAmount); + emit StETHBondDeposited(nodeOperatorId, from, stETHAmount); return shares; } /// @notice Deposits wstETH to the bond for the given node operator. + /// @param from address to deposit wstETH from. /// @param nodeOperatorId id of the node operator to deposit bond for. /// @param wstETHAmount amount of wstETH to deposit. - /// @param permit permit to spend wstETH. - function depositWstETHWithPermit( + function depositWstETH( + address from, uint256 nodeOperatorId, - uint256 wstETHAmount, - PermitInput calldata permit + uint256 wstETHAmount ) external returns (uint256) { - return - _depositWstETHWithPermit( - msg.sender, - nodeOperatorId, - wstETHAmount, - permit - ); + from = (from == address(0)) ? msg.sender : from; + return _depositWstETH(from, nodeOperatorId, wstETHAmount); } /// @notice Deposits wstETH to the bond for the given node operator. @@ -543,21 +445,6 @@ contract CommunityStakingBondManager is uint256 wstETHAmount, PermitInput calldata permit ) external returns (uint256) { - return - _depositWstETHWithPermit( - from, - nodeOperatorId, - wstETHAmount, - permit - ); - } - - function _depositWstETHWithPermit( - address from, - uint256 nodeOperatorId, - uint256 stETHAmount, - PermitInput calldata permit - ) internal returns (uint256) { WSTETH.permit( from, address(this), @@ -567,41 +454,25 @@ contract CommunityStakingBondManager is permit.r, permit.s ); - return _depositWstETH(from, nodeOperatorId, stETHAmount); + return _depositWstETH(from, nodeOperatorId, wstETHAmount); } - /// @notice Claims full reward (fee + bond) for the given node operator available for this moment - /// @param rewardsProof merkle proof of the rewards. - /// @param nodeOperatorId id of the node operator to claim rewards for. - /// @param cumulativeFeeShares cumulative fee shares for the node operator. - function claimRewardsStETH( - bytes32[] memory rewardsProof, + function _depositWstETH( + address from, uint256 nodeOperatorId, - uint256 cumulativeFeeShares - ) external { - address rewardAddress = _getNodeOperatorRewardAddress(nodeOperatorId); - _isSenderEligableToClaim(rewardAddress); - uint256 claimableShares = _pullFeeRewards( - rewardsProof, - nodeOperatorId, - cumulativeFeeShares - ); - if (claimableShares == 0) { - emit StETHRewardsClaimed(nodeOperatorId, rewardAddress, 0); - return; - } - _lido().transferSharesFrom( - address(this), - rewardAddress, - claimableShares - ); - bondShares[nodeOperatorId] -= claimableShares; - totalBondShares -= claimableShares; - emit StETHRewardsClaimed( - nodeOperatorId, - rewardAddress, - _ethByShares(claimableShares) + uint256 wstETHAmount + ) internal returns (uint256) { + require( + nodeOperatorId < CSM.getNodeOperatorsCount(), + "node operator does not exist" ); + WSTETH.transferFrom(from, address(this), wstETHAmount); + uint256 stETHAmount = WSTETH.unwrap(wstETHAmount); + uint256 shares = _sharesByEth(stETHAmount); + bondShares[nodeOperatorId] += shares; + totalBondShares += shares; + emit WstETHBondDeposited(nodeOperatorId, from, wstETHAmount); + return shares; } /// @notice Claims full reward (fee + bond) for the given node operator with desirable value @@ -622,51 +493,21 @@ contract CommunityStakingBondManager is nodeOperatorId, cumulativeFeeShares ); - uint256 shares = _sharesByEth(stETHAmount); - claimableShares = shares < claimableShares ? shares : claimableShares; if (claimableShares == 0) { emit StETHRewardsClaimed(nodeOperatorId, rewardAddress, 0); return; } - _lido().transferSharesFrom( - address(this), - rewardAddress, - claimableShares - ); - bondShares[nodeOperatorId] -= claimableShares; - totalBondShares -= claimableShares; + uint256 toClaim = stETHAmount < _ethByShares(claimableShares) + ? _sharesByEth(stETHAmount) + : claimableShares; + _lido().transferSharesFrom(address(this), rewardAddress, toClaim); + bondShares[nodeOperatorId] -= toClaim; + totalBondShares -= toClaim; emit StETHRewardsClaimed( nodeOperatorId, rewardAddress, - _ethByShares(claimableShares) - ); - } - - /// @notice Claims full reward (fee + bond) for the given node operator available for this moment - /// @param rewardsProof merkle proof of the rewards. - /// @param nodeOperatorId id of the node operator to claim rewards for. - /// @param cumulativeFeeShares cumulative fee shares for the node operator. - function claimRewardsWstETH( - bytes32[] memory rewardsProof, - uint256 nodeOperatorId, - uint256 cumulativeFeeShares - ) external { - address rewardAddress = _getNodeOperatorRewardAddress(nodeOperatorId); - _isSenderEligableToClaim(rewardAddress); - uint256 claimableShares = _pullFeeRewards( - rewardsProof, - nodeOperatorId, - cumulativeFeeShares + _ethByShares(toClaim) ); - if (claimableShares == 0) { - emit WstETHRewardsClaimed(nodeOperatorId, rewardAddress, 0); - return; - } - uint256 wstETHAmount = WSTETH.wrap(_ethByShares(claimableShares)); - WSTETH.transferFrom(address(this), rewardAddress, wstETHAmount); - bondShares[nodeOperatorId] -= wstETHAmount; - totalBondShares -= wstETHAmount; - emit WstETHRewardsClaimed(nodeOperatorId, rewardAddress, wstETHAmount); } /// @notice Claims full reward (fee + bond) for the given node operator available for this moment @@ -687,14 +528,14 @@ contract CommunityStakingBondManager is nodeOperatorId, cumulativeFeeShares ); - claimableShares = wstETHAmount < claimableShares - ? wstETHAmount - : claimableShares; if (claimableShares == 0) { emit WstETHRewardsClaimed(nodeOperatorId, rewardAddress, 0); return; } - wstETHAmount = WSTETH.wrap(_ethByShares(claimableShares)); + uint256 toClaim = wstETHAmount < claimableShares + ? wstETHAmount + : claimableShares; + wstETHAmount = WSTETH.wrap(_ethByShares(toClaim)); WSTETH.transferFrom(address(this), rewardAddress, wstETHAmount); bondShares[nodeOperatorId] -= wstETHAmount; totalBondShares -= wstETHAmount; @@ -724,12 +565,8 @@ contract CommunityStakingBondManager is return ILido(LIDO_LOCATOR.lido()); } - function _feeDistributor() - internal - view - returns (ICommunityStakingFeeDistributor) - { - return ICommunityStakingFeeDistributor(FEE_DISTRIBUTOR); + function _feeDistributor() internal view returns (ICSFeeDistributor) { + return ICSFeeDistributor(FEE_DISTRIBUTOR); } function _getNodeOperatorActiveKeys( diff --git a/src/FeeDistributor.sol b/src/CSFeeDistributor.sol similarity index 81% rename from src/FeeDistributor.sol rename to src/CSFeeDistributor.sol index 9e162ed8..944ddeb4 100644 --- a/src/FeeDistributor.sol +++ b/src/CSFeeDistributor.sol @@ -4,17 +4,17 @@ pragma solidity 0.8.21; import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol"; -import { FeeDistributorBase } from "./FeeDistributorBase.sol"; +import { CSFeeDistributorBase } from "./CSFeeDistributorBase.sol"; -import { IFeeOracle } from "./interfaces/IFeeOracle.sol"; +import { ICSFeeOracle } from "./interfaces/ICSFeeOracle.sol"; import { IStETH } from "./interfaces/IStETH.sol"; /// @author madlabman -contract FeeDistributor is FeeDistributorBase { +contract CSFeeDistributor is CSFeeDistributorBase { address public immutable CSM; address public immutable STETH; address public immutable ORACLE; - address public immutable BOND_MANAGER; + address public immutable ACCOUNTING; /// @notice Amount of shares sent to the BondManager in favor of the NO mapping(uint64 => uint64) public distributedShares; @@ -23,14 +23,14 @@ contract FeeDistributor is FeeDistributorBase { address _CSM, address _stETH, address _oracle, - address _bondManager + address _accounting ) { - if (_bondManager == address(0)) revert ZeroAddress("_bondManager"); + if (_accounting == address(0)) revert ZeroAddress("_accounting"); if (_oracle == address(0)) revert ZeroAddress("_oracle"); if (_stETH == address(0)) revert ZeroAddress("_stETH"); if (_CSM == address(0)) revert ZeroAddress("_CSM"); - BOND_MANAGER = _bondManager; + ACCOUNTING = _accounting; ORACLE = _oracle; STETH = _stETH; CSM = _CSM; @@ -47,8 +47,8 @@ contract FeeDistributor is FeeDistributorBase { ) public view returns (uint64) { bool isValid = MerkleProof.verifyCalldata( proof, - IFeeOracle(ORACLE).reportRoot(), - IFeeOracle(ORACLE).hashLeaf(noIndex, shares) + ICSFeeOracle(ORACLE).reportRoot(), + ICSFeeOracle(ORACLE).hashLeaf(noIndex, shares) ); if (!isValid) revert InvalidProof(); @@ -68,7 +68,7 @@ contract FeeDistributor is FeeDistributorBase { uint64 noIndex, uint64 shares ) external returns (uint64) { - if (msg.sender != BOND_MANAGER) revert NotBondManager(); + if (msg.sender != ACCOUNTING) revert NotBondManager(); uint64 sharesToDistribute = getFeesToDistribute(proof, noIndex, shares); if (sharesToDistribute == 0) { @@ -76,7 +76,7 @@ contract FeeDistributor is FeeDistributorBase { return 0; } distributedShares[noIndex] += sharesToDistribute; - IStETH(STETH).transferShares(BOND_MANAGER, sharesToDistribute); + IStETH(STETH).transferShares(ACCOUNTING, sharesToDistribute); emit FeeDistributed(noIndex, sharesToDistribute); return sharesToDistribute; diff --git a/src/FeeDistributorBase.sol b/src/CSFeeDistributorBase.sol similarity index 92% rename from src/FeeDistributorBase.sol rename to src/CSFeeDistributorBase.sol index c000f0b7..27ffe2ae 100644 --- a/src/FeeDistributorBase.sol +++ b/src/CSFeeDistributorBase.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.21; /// @author madlabman -contract FeeDistributorBase { +contract CSFeeDistributorBase { error ZeroAddress(string field); error NotBondManager(); diff --git a/src/FeeOracle.sol b/src/CSFeeOracle.sol similarity index 98% rename from src/FeeOracle.sol rename to src/CSFeeOracle.sol index c1978fef..dfb63c16 100644 --- a/src/FeeOracle.sol +++ b/src/CSFeeOracle.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.21; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { AccessControlEnumerable } from "@openzeppelin/contracts/access/AccessControlEnumerable.sol"; -import { FeeOracleBase } from "./FeeOracleBase.sol"; +import { CSFeeOracleBase } from "./CSFeeOracleBase.sol"; import { IStETH } from "./interfaces/IStETH.sol"; interface IFeeDistributor { @@ -13,7 +13,7 @@ interface IFeeDistributor { } /// @author madlabman -contract FeeOracle is FeeOracleBase, AccessControlEnumerable { +contract CSFeeOracle is CSFeeOracleBase, AccessControlEnumerable { /// @notice Merkle Tree root bytes32 public reportRoot; diff --git a/src/FeeOracleBase.sol b/src/CSFeeOracleBase.sol similarity index 96% rename from src/FeeOracleBase.sol rename to src/CSFeeOracleBase.sol index f321e75f..b156d7a6 100644 --- a/src/FeeOracleBase.sol +++ b/src/CSFeeOracleBase.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.21; import { Pausable } from "@openzeppelin/contracts/security/Pausable.sol"; /// @author madlabman -contract FeeOracleBase is Pausable { +contract CSFeeOracleBase is Pausable { error AlreadyMember(address member); error NotMember(address member); diff --git a/src/CommunityStakingModule.sol b/src/CSModule.sol similarity index 79% rename from src/CommunityStakingModule.sol rename to src/CSModule.sol index 0b81d96a..f79cd533 100644 --- a/src/CommunityStakingModule.sol +++ b/src/CSModule.sol @@ -6,7 +6,7 @@ pragma solidity 0.8.21; import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; -import { ICommunityStakingBondManager } from "./interfaces/ICommunityStakingBondManager.sol"; +import { ICSAccounting } from "./interfaces/ICSAccounting.sol"; import { IStakingModule } from "./interfaces/IStakingModule.sol"; import { ILidoLocator } from "./interfaces/ILidoLocator.sol"; import { ILido } from "./interfaces/ILido.sol"; @@ -34,7 +34,7 @@ struct NodeOperator { bool isTargetLimitActive; } -contract CommunityStakingModuleBase { +contract CSModuleBase { event NodeOperatorAdded( uint256 indexed nodeOperatorId, string name, @@ -70,7 +70,7 @@ contract CommunityStakingModuleBase { event UnvettingFeeSet(uint256 unvettingFee); } -contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { +contract CSModule is IStakingModule, CSModuleBase { using StringToUint256WithZeroMap for mapping(string => uint256); using QueueLib for QueueLib.Queue; @@ -81,7 +81,7 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { uint256 public unvettingFee; QueueLib.Queue public queue; - ICommunityStakingBondManager public bondManager; + ICSAccounting public accounting; ILidoLocator public lidoLocator; uint256 private nodeOperatorsCount; @@ -126,10 +126,10 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { emit LocatorContractSet(_locator); } - function setBondManager(address _bondManager) external { + function setAccounting(address _accounting) external { // TODO: add role check - require(address(bondManager) == address(0), "already initialized"); - bondManager = ICommunityStakingBondManager(_bondManager); + require(address(accounting) == address(0), "already initialized"); + accounting = ICSAccounting(_accounting); } function setUnvettingFee(uint256 unvettingFee_) external { @@ -173,7 +173,7 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { _onlyValidNodeOperatorName(_name); require( - msg.value == bondManager.getRequiredBondETHForKeys(_keysCount), + msg.value == accounting.getRequiredBondETHForKeys(_keysCount), "eth value is not equal to required bond" ); @@ -186,7 +186,7 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { nodeOperatorsCount++; activeNodeOperatorsCount++; - bondManager.depositETH{ value: msg.value }(msg.sender, id); + accounting.depositETH{ value: msg.value }(msg.sender, id); _addSigningKeys(id, _keysCount, _publicKeys, _signatures); @@ -212,10 +212,10 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { nodeOperatorsCount++; activeNodeOperatorsCount++; - bondManager.depositStETH( + accounting.depositStETH( msg.sender, id, - bondManager.getRequiredBondStETHForKeys(_keysCount) + accounting.getRequiredBondStETHForKeys(_keysCount) ); _addSigningKeys(id, _keysCount, _publicKeys, _signatures); @@ -223,26 +223,6 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { emit NodeOperatorAdded(id, _name, _rewardAddress); } - function addNodeOperatorStETHWithPermit( - string calldata _name, - address _rewardAddress, - uint256 _keysCount, - bytes calldata _publicKeys, - bytes calldata _signatures, - ICommunityStakingBondManager.PermitInput calldata _permit - ) external { - return - _addNodeOperatorStETHWithPermit( - msg.sender, - _name, - _rewardAddress, - _keysCount, - _publicKeys, - _signatures, - _permit - ); - } - function addNodeOperatorStETHWithPermit( address _from, string calldata _name, @@ -250,29 +230,8 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { uint256 _keysCount, bytes calldata _publicKeys, bytes calldata _signatures, - ICommunityStakingBondManager.PermitInput calldata _permit + ICSAccounting.PermitInput calldata _permit ) external { - return - _addNodeOperatorStETHWithPermit( - _from, - _name, - _rewardAddress, - _keysCount, - _publicKeys, - _signatures, - _permit - ); - } - - function _addNodeOperatorStETHWithPermit( - address _from, - string calldata _name, - address _rewardAddress, - uint256 _keysCount, - bytes calldata _publicKeys, - bytes calldata _signatures, - ICommunityStakingBondManager.PermitInput calldata _permit - ) internal { // TODO sanity checks _onlyValidNodeOperatorName(_name); @@ -285,10 +244,10 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { nodeOperatorsCount++; activeNodeOperatorsCount++; - bondManager.depositStETHWithPermit( + accounting.depositStETHWithPermit( _from, id, - bondManager.getRequiredBondStETHForKeys(_keysCount), + accounting.getRequiredBondStETHForKeys(_keysCount), _permit ); @@ -316,10 +275,10 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { nodeOperatorsCount++; activeNodeOperatorsCount++; - bondManager.depositWstETH( + accounting.depositWstETH( msg.sender, id, - bondManager.getRequiredBondWstETHForKeys(_keysCount) + accounting.getRequiredBondWstETHForKeys(_keysCount) ); _addSigningKeys(id, _keysCount, _publicKeys, _signatures); @@ -327,26 +286,6 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { emit NodeOperatorAdded(id, _name, _rewardAddress); } - function addNodeOperatorWstETHWithPermit( - string calldata _name, - address _rewardAddress, - uint256 _keysCount, - bytes calldata _publicKeys, - bytes calldata _signatures, - ICommunityStakingBondManager.PermitInput calldata _permit - ) external { - return - _addNodeOperatorWstETHWithPermit( - msg.sender, - _name, - _rewardAddress, - _keysCount, - _publicKeys, - _signatures, - _permit - ); - } - function addNodeOperatorWstETHWithPermit( address _from, string calldata _name, @@ -354,29 +293,8 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { uint256 _keysCount, bytes calldata _publicKeys, bytes calldata _signatures, - ICommunityStakingBondManager.PermitInput calldata _permit + ICSAccounting.PermitInput calldata _permit ) external { - return - _addNodeOperatorWstETHWithPermit( - _from, - _name, - _rewardAddress, - _keysCount, - _publicKeys, - _signatures, - _permit - ); - } - - function _addNodeOperatorWstETHWithPermit( - address _from, - string calldata _name, - address _rewardAddress, - uint256 _keysCount, - bytes calldata _publicKeys, - bytes calldata _signatures, - ICommunityStakingBondManager.PermitInput calldata _permit - ) internal { // TODO sanity checks _onlyValidNodeOperatorName(_name); @@ -389,10 +307,10 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { nodeOperatorsCount++; activeNodeOperatorsCount++; - bondManager.depositWstETHWithPermit( + accounting.depositWstETHWithPermit( _from, id, - bondManager.getRequiredBondWstETHForKeys(_keysCount), + accounting.getRequiredBondWstETHForKeys(_keysCount), _permit ); @@ -411,11 +329,11 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { require( msg.value == - bondManager.getRequiredBondETH(_nodeOperatorId, _keysCount), + accounting.getRequiredBondETH(_nodeOperatorId, _keysCount), "eth value is not equal to required bond" ); - bondManager.depositETH{ value: msg.value }(msg.sender, _nodeOperatorId); + accounting.depositETH{ value: msg.value }(msg.sender, _nodeOperatorId); _addSigningKeys(_nodeOperatorId, _keysCount, _publicKeys, _signatures); } @@ -428,67 +346,29 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { ) external onlyExistingNodeOperator(_nodeOperatorId) { // TODO: sanity checks - bondManager.depositStETH( + accounting.depositStETH( msg.sender, _nodeOperatorId, - bondManager.getRequiredBondStETH(_nodeOperatorId, _keysCount) + accounting.getRequiredBondStETH(_nodeOperatorId, _keysCount) ); _addSigningKeys(_nodeOperatorId, _keysCount, _publicKeys, _signatures); } - function addValidatorKeysStETHWithPermit( - uint256 _nodeOperatorId, - uint256 _keysCount, - bytes calldata _publicKeys, - bytes calldata _signatures, - ICommunityStakingBondManager.PermitInput calldata _permit - ) external { - return - _addValidatorKeysStETHWithPermit( - msg.sender, - _nodeOperatorId, - _keysCount, - _publicKeys, - _signatures, - _permit - ); - } - function addValidatorKeysStETHWithPermit( address _from, uint256 _nodeOperatorId, uint256 _keysCount, bytes calldata _publicKeys, bytes calldata _signatures, - ICommunityStakingBondManager.PermitInput calldata _permit + ICSAccounting.PermitInput calldata _permit ) external { - return - _addValidatorKeysStETHWithPermit( - _from, - _nodeOperatorId, - _keysCount, - _publicKeys, - _signatures, - _permit - ); - } - - function _addValidatorKeysStETHWithPermit( - address _from, - uint256 _nodeOperatorId, - uint256 _keysCount, - bytes calldata _publicKeys, - bytes calldata _signatures, - ICommunityStakingBondManager.PermitInput calldata _permit - ) internal onlyExistingNodeOperator(_nodeOperatorId) { // TODO sanity checks - // TODO store keys - bondManager.depositStETHWithPermit( + accounting.depositStETHWithPermit( _from, _nodeOperatorId, - bondManager.getRequiredBondStETH(_nodeOperatorId, _keysCount), + accounting.getRequiredBondStETH(_nodeOperatorId, _keysCount), _permit ); @@ -503,67 +383,29 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { ) external onlyExistingNodeOperator(_nodeOperatorId) { // TODO: sanity checks - bondManager.depositWstETH( + accounting.depositWstETH( msg.sender, _nodeOperatorId, - bondManager.getRequiredBondWstETH(_nodeOperatorId, _keysCount) + accounting.getRequiredBondWstETH(_nodeOperatorId, _keysCount) ); _addSigningKeys(_nodeOperatorId, _keysCount, _publicKeys, _signatures); } - function addValidatorKeysWstETHWithPermit( - uint256 _nodeOperatorId, - uint256 _keysCount, - bytes calldata _publicKeys, - bytes calldata _signatures, - ICommunityStakingBondManager.PermitInput calldata _permit - ) external { - return - _addValidatorKeysWstETHWithPermit( - msg.sender, - _nodeOperatorId, - _keysCount, - _publicKeys, - _signatures, - _permit - ); - } - function addValidatorKeysWstETHWithPermit( address _from, uint256 _nodeOperatorId, uint256 _keysCount, bytes calldata _publicKeys, bytes calldata _signatures, - ICommunityStakingBondManager.PermitInput calldata _permit + ICSAccounting.PermitInput calldata _permit ) external { - return - _addValidatorKeysWstETHWithPermit( - _from, - _nodeOperatorId, - _keysCount, - _publicKeys, - _signatures, - _permit - ); - } - - function _addValidatorKeysWstETHWithPermit( - address _from, - uint256 _nodeOperatorId, - uint256 _keysCount, - bytes calldata _publicKeys, - bytes calldata _signatures, - ICommunityStakingBondManager.PermitInput calldata _permit - ) internal onlyExistingNodeOperator(_nodeOperatorId) { // TODO sanity checks - // TODO store keys - bondManager.depositWstETHWithPermit( + accounting.depositWstETHWithPermit( _from, _nodeOperatorId, - bondManager.getRequiredBondWstETH(_nodeOperatorId, _keysCount), + accounting.getRequiredBondWstETH(_nodeOperatorId, _keysCount), _permit ); @@ -774,7 +616,7 @@ contract CommunityStakingModule is IStakingModule, CommunityStakingModuleBase { uint256 nodeOperatorId ) external onlyKeyValidatorOrNodeOperatorManager { _unvetKeys(nodeOperatorId); - bondManager.penalize(nodeOperatorId, unvettingFee); + accounting.penalize(nodeOperatorId, unvettingFee); } function unsafeUnvetKeys(uint256 nodeOperatorId) external onlyKeyValidator { diff --git a/src/interfaces/ICommunityStakingBondManager.sol b/src/interfaces/ICSAccounting.sol similarity index 73% rename from src/interfaces/ICommunityStakingBondManager.sol rename to src/interfaces/ICSAccounting.sol index 77893ad4..5c5d298f 100644 --- a/src/interfaces/ICommunityStakingBondManager.sol +++ b/src/interfaces/ICSAccounting.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.21; -interface ICommunityStakingBondManager { +interface ICSAccounting { struct PermitInput { uint256 value; uint256 deadline; @@ -18,32 +18,6 @@ interface ICommunityStakingBondManager { function getBondEth(uint256 nodeOperatorId) external view returns (uint256); - function depositWstETHWithPermit( - uint256 nodeOperatorId, - uint256 wstETHAmount, - PermitInput calldata permit - ) external returns (uint256); - - function depositWstETH( - uint256 nodeOperatorId, - uint256 wstETHAmount - ) external returns (uint256); - - function depositStETHWithPermit( - uint256 nodeOperatorId, - uint256 stETHAmount, - PermitInput calldata permit - ) external returns (uint256); - - function depositStETH( - uint256 nodeOperatorId, - uint256 stETHAmount - ) external returns (uint256); - - function depositETH( - uint256 nodeOperatorId - ) external payable returns (uint256); - function depositWstETHWithPermit( address from, uint256 nodeOperatorId, diff --git a/src/interfaces/ICommunityStakingFeeDistributor.sol b/src/interfaces/ICSFeeDistributor.sol similarity index 90% rename from src/interfaces/ICommunityStakingFeeDistributor.sol rename to src/interfaces/ICSFeeDistributor.sol index 1d16046e..36c11261 100644 --- a/src/interfaces/ICommunityStakingFeeDistributor.sol +++ b/src/interfaces/ICSFeeDistributor.sol @@ -3,7 +3,7 @@ pragma solidity 0.8.21; -interface ICommunityStakingFeeDistributor { +interface ICSFeeDistributor { function getFeesToDistribute( bytes32[] calldata rewardProof, uint256 noIndex, diff --git a/src/interfaces/IFeeOracle.sol b/src/interfaces/ICSFeeOracle.sol similarity index 92% rename from src/interfaces/IFeeOracle.sol rename to src/interfaces/ICSFeeOracle.sol index 1d22a921..18809c3c 100644 --- a/src/interfaces/IFeeOracle.sol +++ b/src/interfaces/ICSFeeOracle.sol @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.21; -interface IFeeOracle { +interface ICSFeeOracle { /// @notice Merkle Tree root function reportRoot() external view returns (bytes32); diff --git a/src/interfaces/ICommunityStakingModule.sol b/src/interfaces/ICSModule.sol similarity index 94% rename from src/interfaces/ICommunityStakingModule.sol rename to src/interfaces/ICSModule.sol index 5fc6cf43..8b0b6631 100644 --- a/src/interfaces/ICommunityStakingModule.sol +++ b/src/interfaces/ICSModule.sol @@ -6,7 +6,7 @@ pragma solidity 0.8.21; import { IStakingModule } from "./IStakingModule.sol"; /// @title Lido's Community Staking Module interface -interface ICommunityStakingModule is IStakingModule { +interface ICSModule is IStakingModule { /// @notice Returns the node operator by id /// @param _nodeOperatorId Node Operator id /// @param _fullInfo If true, name will be returned as well diff --git a/test/BondManager.t.sol b/test/CSAccounting.t.sol similarity index 70% rename from test/BondManager.t.sol rename to test/CSAccounting.t.sol index 0c95705f..3792010d 100644 --- a/test/BondManager.t.sol +++ b/test/CSAccounting.t.sol @@ -5,7 +5,7 @@ pragma solidity 0.8.21; import "forge-std/Test.sol"; -import { CommunityStakingBondManagerBase, CommunityStakingBondManager } from "../src/CommunityStakingBondManager.sol"; +import { CSAccountingBase, CSAccounting } from "../src/CSAccounting.sol"; import { PermitTokenBase } from "./helpers/Permit.sol"; import { Stub } from "./helpers/mocks/Stub.sol"; import { LidoMock } from "./helpers/mocks/LidoMock.sol"; @@ -15,20 +15,15 @@ import { CommunityStakingModuleMock } from "./helpers/mocks/CommunityStakingModu import { CommunityStakingFeeDistributorMock } from "./helpers/mocks/CommunityStakingFeeDistributorMock.sol"; import { Fixtures } from "./helpers/Fixtures.sol"; -contract CommunityStakingBondManagerTest is - Test, - Fixtures, - PermitTokenBase, - CommunityStakingBondManagerBase -{ +contract CSAccountingTest is Test, Fixtures, PermitTokenBase, CSAccountingBase { LidoLocatorMock internal locator; WstETHMock internal wstETH; LidoMock internal stETH; Stub internal burner; - CommunityStakingBondManager public bondManager; - CommunityStakingModuleMock public communityStakingModule; - CommunityStakingFeeDistributorMock public communityStakingFeeDistributor; + CSAccounting public accounting; + CommunityStakingModuleMock public stakingModule; + CommunityStakingFeeDistributorMock public feeDistributor; address internal admin; address internal user; @@ -45,82 +40,82 @@ contract CommunityStakingBondManagerTest is (locator, wstETH, stETH, burner) = initLido(); - communityStakingModule = new CommunityStakingModuleMock(); - bondManager = new CommunityStakingBondManager( + stakingModule = new CommunityStakingModuleMock(); + accounting = new CSAccounting( 2 ether, admin, address(locator), address(wstETH), - address(communityStakingModule), + address(stakingModule), penalizeRoleMembers ); - communityStakingFeeDistributor = new CommunityStakingFeeDistributorMock( + feeDistributor = new CommunityStakingFeeDistributorMock( address(locator), - address(bondManager) + address(accounting) ); vm.prank(admin); - bondManager.setFeeDistributor(address(communityStakingFeeDistributor)); + accounting.setFeeDistributor(address(feeDistributor)); } function test_totalBondShares() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 32 ether); vm.startPrank(user); - bondManager.depositETH{ value: 32 ether }(0); + accounting.depositETH{ value: 32 ether }(user, 0); uint256 sharesToDeposit = stETH.getSharesByPooledEth(32 ether); - assertEq(bondManager.totalBondShares(), sharesToDeposit); + assertEq(accounting.totalBondShares(), sharesToDeposit); } function test_getRequiredBondETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - assertEq(bondManager.getRequiredBondETH(0, 0), 32 ether); + assertEq(accounting.getRequiredBondETH(0, 0), 32 ether); } function test_getRequiredBondStETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - assertEq(bondManager.getRequiredBondStETH(0, 0), 32 ether); + assertEq(accounting.getRequiredBondStETH(0, 0), 32 ether); } function test_getRequiredBondWstETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); assertEq( - bondManager.getRequiredBondWstETH(0, 0), + accounting.getRequiredBondWstETH(0, 0), stETH.getSharesByPooledEth(32 ether) ); } function test_getRequiredBondETH_OneWithdrawnValidator() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 1 }); - assertEq(bondManager.getRequiredBondETH(0, 0), 30 ether); + assertEq(accounting.getRequiredBondETH(0, 0), 30 ether); } function test_getRequiredBondStETH_OneWithdrawnValidator() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 1 }); - assertEq(bondManager.getRequiredBondStETH(0, 0), 30 ether); + assertEq(accounting.getRequiredBondStETH(0, 0), 30 ether); } function test_getRequiredBondWstETH_OneWithdrawnValidator() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 1 }); assertEq( - bondManager.getRequiredBondWstETH(0, 0), + accounting.getRequiredBondWstETH(0, 0), stETH.getSharesByPooledEth(30 ether) ); } function test_getRequiredBondETH_OneWithdrawnOneAddedValidator() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 1 }); - assertEq(bondManager.getRequiredBondETH(0, 1), 32 ether); + assertEq(accounting.getRequiredBondETH(0, 1), 32 ether); } function test_getRequiredBondStETH_OneWithdrawnOneAddedValidator() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 1 }); - assertEq(bondManager.getRequiredBondStETH(0, 1), 32 ether); + assertEq(accounting.getRequiredBondStETH(0, 1), 32 ether); } function test_getRequiredBondWstETH_OneWithdrawnOneAddedValidator() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 1 }); assertEq( - bondManager.getRequiredBondWstETH(0, 1), + accounting.getRequiredBondWstETH(0, 1), stETH.getSharesByPooledEth(32 ether) ); } @@ -129,9 +124,9 @@ contract CommunityStakingBondManagerTest is _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 64 ether); vm.startPrank(user); - bondManager.depositETH{ value: 64 ether }(0); + accounting.depositETH{ value: 64 ether }(user, 0); assertApproxEqAbs( - bondManager.getRequiredBondETH(0, 16), + accounting.getRequiredBondETH(0, 16), 0, 1, // max accuracy error "required ETH should be ~0 for the next 16 validators to deposit" @@ -143,9 +138,9 @@ contract CommunityStakingBondManagerTest is vm.deal(user, 64 ether); vm.startPrank(user); stETH.submit{ value: 64 ether }({ _referal: address(0) }); - bondManager.depositStETH(0, 64 ether); + accounting.depositStETH(user, 0, 64 ether); assertApproxEqAbs( - bondManager.getRequiredBondStETH(0, 16), + accounting.getRequiredBondStETH(0, 16), 0, 1, // max accuracy error "required stETH should be ~0 for the next 16 validators to deposit" @@ -158,9 +153,9 @@ contract CommunityStakingBondManagerTest is vm.startPrank(user); stETH.submit{ value: 64 ether }({ _referal: address(0) }); uint256 amount = wstETH.wrap(64 ether); - bondManager.depositWstETH(0, amount); + accounting.depositWstETH(user, 0, amount); assertApproxEqAbs( - bondManager.getRequiredBondWstETH(0, 16), + accounting.getRequiredBondWstETH(0, 16), 0, 1, // max accuracy error "required wstETH should be ~0 for the next 16 validators to deposit" @@ -168,16 +163,16 @@ contract CommunityStakingBondManagerTest is } function test_getRequiredBondETHForKeys() public { - assertEq(bondManager.getRequiredBondETHForKeys(1), 2 ether); + assertEq(accounting.getRequiredBondETHForKeys(1), 2 ether); } function test_getRequiredBondStETHForKeys() public { - assertEq(bondManager.getRequiredBondStETHForKeys(1), 2 ether); + assertEq(accounting.getRequiredBondStETHForKeys(1), 2 ether); } function test_getRequiredBondWstETHForKeys() public { assertEq( - bondManager.getRequiredBondWstETHForKeys(1), + accounting.getRequiredBondWstETHForKeys(1), stETH.getSharesByPooledEth(2 ether) ); } @@ -187,11 +182,11 @@ contract CommunityStakingBondManagerTest is vm.deal(user, 32 ether); uint256 sharesToDeposit = stETH.getSharesByPooledEth(32 ether); - vm.expectEmit(true, true, true, true, address(bondManager)); + vm.expectEmit(true, true, true, true, address(accounting)); emit ETHBondDeposited(0, user, 32 ether); vm.prank(user); - bondManager.depositETH{ value: 32 ether }(0); + accounting.depositETH{ value: 32 ether }(user, 0); assertEq( address(user).balance, @@ -199,12 +194,12 @@ contract CommunityStakingBondManagerTest is "user balance should be 0 after deposit" ); assertEq( - bondManager.getBondShares(0), + accounting.getBondShares(0), sharesToDeposit, "bond shares should be equal to deposited shares" ); assertEq( - stETH.sharesOf(address(bondManager)), + stETH.sharesOf(address(accounting)), sharesToDeposit, "bond manager shares should be equal to deposited shares" ); @@ -214,23 +209,23 @@ contract CommunityStakingBondManagerTest is _createNodeOperator({ ongoingVals: 1, withdrawnVals: 0 }); vm.deal(user, 32 ether); - uint256 required = bondManager.getRequiredBondETHForKeys(1); + uint256 required = accounting.getRequiredBondETHForKeys(1); vm.startPrank(user); - bondManager.depositETH{ value: required }(0); + accounting.depositETH{ value: required }(user, 0); assertApproxEqAbs( - bondManager.getRequiredBondETH(0, 0), + accounting.getRequiredBondETH(0, 0), 0, 1, // max accuracy error "required ETH should be ~0 for 1 deposited validator" ); - required = bondManager.getRequiredBondETH(0, 1); - bondManager.depositETH{ value: required }(0); - communityStakingModule.addValidator(0, 1); + required = accounting.getRequiredBondETH(0, 1); + accounting.depositETH{ value: required }(user, 0); + stakingModule.addValidator(0, 1); assertApproxEqAbs( - bondManager.getRequiredBondETH(0, 0), + accounting.getRequiredBondETH(0, 0), 0, 1, // max accuracy error "required ETH should be ~0 for 2 deposited validators" @@ -245,10 +240,10 @@ contract CommunityStakingBondManagerTest is _referal: address(0) }); - vm.expectEmit(true, true, true, true, address(bondManager)); + vm.expectEmit(true, true, true, true, address(accounting)); emit StETHBondDeposited(0, user, 32 ether); - bondManager.depositStETH(0, 32 ether); + accounting.depositStETH(user, 0, 32 ether); assertEq( stETH.balanceOf(user), @@ -256,12 +251,12 @@ contract CommunityStakingBondManagerTest is "user balance should be 0 after deposit" ); assertEq( - bondManager.getBondShares(0), + accounting.getBondShares(0), sharesToDeposit, "bond shares should be equal to deposited shares" ); assertEq( - stETH.sharesOf(address(bondManager)), + stETH.sharesOf(address(accounting)), sharesToDeposit, "bond manager shares should be equal to deposited shares" ); @@ -273,21 +268,21 @@ contract CommunityStakingBondManagerTest is vm.startPrank(user); stETH.submit{ value: 32 ether }({ _referal: address(0) }); - uint256 required = bondManager.getRequiredBondStETHForKeys(1); - bondManager.depositStETH(0, required); + uint256 required = accounting.getRequiredBondStETHForKeys(1); + accounting.depositStETH(user, 0, required); assertApproxEqAbs( - bondManager.getRequiredBondStETH(0, 0), + accounting.getRequiredBondStETH(0, 0), 0, 1, // max accuracy error "required stETH should be ~0 for 1 deposited validator" ); - required = bondManager.getRequiredBondStETH(0, 1); - bondManager.depositStETH(0, required); - communityStakingModule.addValidator(0, 1); + required = accounting.getRequiredBondStETH(0, 1); + accounting.depositStETH(user, 0, required); + stakingModule.addValidator(0, 1); assertApproxEqAbs( - bondManager.getRequiredBondStETH(0, 0), + accounting.getRequiredBondStETH(0, 0), 0, 1, // max accuracy error "required stETH should be ~0 for 2 deposited validators" @@ -304,10 +299,10 @@ contract CommunityStakingBondManagerTest is wstETH.getStETHByWstETH(wstETHAmount) ); - vm.expectEmit(true, true, true, true, address(bondManager)); + vm.expectEmit(true, true, true, true, address(accounting)); emit WstETHBondDeposited(0, user, wstETHAmount); - bondManager.depositWstETH(0, wstETHAmount); + accounting.depositWstETH(user, 0, wstETHAmount); assertEq( wstETH.balanceOf(user), @@ -315,12 +310,12 @@ contract CommunityStakingBondManagerTest is "user balance should be 0 after deposit" ); assertEq( - bondManager.getBondShares(0), + accounting.getBondShares(0), sharesToDeposit, "bond shares should be equal to deposited shares" ); assertEq( - stETH.sharesOf(address(bondManager)), + stETH.sharesOf(address(accounting)), sharesToDeposit, "bond manager shares should be equal to deposited shares" ); @@ -333,22 +328,22 @@ contract CommunityStakingBondManagerTest is stETH.submit{ value: 32 ether }({ _referal: address(0) }); wstETH.wrap(32 ether); - uint256 required = bondManager.getRequiredBondWstETHForKeys(1); - bondManager.depositWstETH(0, required); + uint256 required = accounting.getRequiredBondWstETHForKeys(1); + accounting.depositWstETH(user, 0, required); assertApproxEqAbs( - bondManager.getRequiredBondWstETH(0, 0), + accounting.getRequiredBondWstETH(0, 0), 0, 1, // max accuracy error "required wstETH should be ~0 for 1 deposited validator" ); - required = bondManager.getRequiredBondStETH(0, 1); - bondManager.depositWstETH(0, required); - communityStakingModule.addValidator(0, 1); + required = accounting.getRequiredBondStETH(0, 1); + accounting.depositWstETH(user, 0, required); + stakingModule.addValidator(0, 1); assertApproxEqAbs( - bondManager.getRequiredBondWstETH(0, 0), + accounting.getRequiredBondWstETH(0, 0), 0, 1, // max accuracy error "required wstETH should be ~0 for 2 deposited validators" @@ -364,16 +359,16 @@ contract CommunityStakingBondManagerTest is }); vm.expectEmit(true, true, true, true, address(stETH)); - emit Approval(user, address(bondManager), 32 ether); - vm.expectEmit(true, true, true, true, address(bondManager)); + emit Approval(user, address(accounting), 32 ether); + vm.expectEmit(true, true, true, true, address(accounting)); emit StETHBondDeposited(0, user, 32 ether); vm.prank(stranger); - bondManager.depositStETHWithPermit( + accounting.depositStETHWithPermit( user, 0, 32 ether, - CommunityStakingBondManager.PermitInput({ + CSAccounting.PermitInput({ value: 32 ether, deadline: type(uint256).max, // mock permit signature @@ -389,12 +384,12 @@ contract CommunityStakingBondManagerTest is "user balance should be 0 after deposit" ); assertEq( - bondManager.getBondShares(0), + accounting.getBondShares(0), sharesToDeposit, "bond shares should be equal to deposited shares" ); assertEq( - stETH.sharesOf(address(bondManager)), + stETH.sharesOf(address(accounting)), sharesToDeposit, "bond manager shares should be equal to deposited shares" ); @@ -412,16 +407,16 @@ contract CommunityStakingBondManagerTest is vm.stopPrank(); vm.expectEmit(true, true, true, true, address(wstETH)); - emit Approval(user, address(bondManager), 32 ether); - vm.expectEmit(true, true, true, true, address(bondManager)); + emit Approval(user, address(accounting), 32 ether); + vm.expectEmit(true, true, true, true, address(accounting)); emit WstETHBondDeposited(0, user, wstETHAmount); vm.prank(stranger); - bondManager.depositWstETHWithPermit( + accounting.depositWstETHWithPermit( user, 0, wstETHAmount, - CommunityStakingBondManager.PermitInput({ + CSAccounting.PermitInput({ value: 32 ether, deadline: type(uint256).max, // mock permit signature @@ -437,12 +432,12 @@ contract CommunityStakingBondManagerTest is "user balance should be 0 after deposit" ); assertEq( - bondManager.getBondShares(0), + accounting.getBondShares(0), sharesToDeposit, "bond shares should be equal to deposited shares" ); assertEq( - bondManager.totalBondShares(), + accounting.totalBondShares(), sharesToDeposit, "bond manager shares should be equal to deposited shares" ); @@ -450,21 +445,21 @@ contract CommunityStakingBondManagerTest is function test_deposit_RevertIfNotExistedOperator() public { vm.expectRevert("node operator does not exist"); - bondManager.depositStETH(0, 32 ether); + accounting.depositStETH(user, 0, 32 ether); } function test_getTotalRewardsETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(communityStakingFeeDistributor), 0.1 ether); - vm.prank(address(communityStakingFeeDistributor)); + 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.startPrank(user); - bondManager.depositETH{ value: 32 ether }(0); + accounting.depositETH{ value: 32 ether }(user, 0); // todo: should we think about simulate rebase? - uint256 totalRewards = bondManager.getTotalRewardsETH( + uint256 totalRewards = accounting.getTotalRewardsETH( new bytes32[](1), 0, sharesAsFee @@ -475,17 +470,17 @@ contract CommunityStakingBondManagerTest is function test_getTotalRewardsStETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(communityStakingFeeDistributor), 0.1 ether); - vm.prank(address(communityStakingFeeDistributor)); + 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) }); - bondManager.depositStETH(0, 32 ether); + accounting.depositStETH(user, 0, 32 ether); // todo: should we think about simulate rebase? - uint256 totalRewards = bondManager.getTotalRewardsStETH( + uint256 totalRewards = accounting.getTotalRewardsStETH( new bytes32[](1), 0, sharesAsFee @@ -496,8 +491,8 @@ contract CommunityStakingBondManagerTest is function test_getTotalRewardsWstETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(communityStakingFeeDistributor), 0.1 ether); - vm.prank(address(communityStakingFeeDistributor)); + 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) @@ -505,10 +500,10 @@ contract CommunityStakingBondManagerTest is vm.deal(user, 32 ether); vm.startPrank(user); stETH.submit{ value: 32 ether }({ _referal: address(0) }); - bondManager.depositStETH(0, 32 ether); + accounting.depositStETH(user, 0, 32 ether); // todo: should we think about simulate rebase? - uint256 totalRewards = bondManager.getTotalRewardsWstETH( + uint256 totalRewards = accounting.getTotalRewardsWstETH( new bytes32[](1), 0, sharesAsFee @@ -521,28 +516,28 @@ contract CommunityStakingBondManagerTest is _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 64 ether); vm.startPrank(user); - bondManager.depositETH{ value: 64 ether }(0); + accounting.depositETH{ value: 64 ether }(user, 0); - assertApproxEqAbs(bondManager.getExcessBondETH(0), 32 ether, 1); + assertApproxEqAbs(accounting.getExcessBondETH(0), 32 ether, 1); } function test_getExcessBondStETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 64 ether); vm.startPrank(user); - bondManager.depositETH{ value: 64 ether }(0); + accounting.depositETH{ value: 64 ether }(user, 0); - assertApproxEqAbs(bondManager.getExcessBondStETH(0), 32 ether, 1); + assertApproxEqAbs(accounting.getExcessBondStETH(0), 32 ether, 1); } function test_getExcessBondWstETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 64 ether); vm.startPrank(user); - bondManager.depositETH{ value: 64 ether }(0); + accounting.depositETH{ value: 64 ether }(user, 0); assertApproxEqAbs( - bondManager.getExcessBondWstETH(0), + accounting.getExcessBondWstETH(0), wstETH.getWstETHByStETH(32 ether), 1 ); @@ -552,28 +547,28 @@ contract CommunityStakingBondManagerTest is _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 16 ether); vm.startPrank(user); - bondManager.depositETH{ value: 16 ether }(0); + accounting.depositETH{ value: 16 ether }(user, 0); - assertApproxEqAbs(bondManager.getMissingBondETH(0), 16 ether, 1); + assertApproxEqAbs(accounting.getMissingBondETH(0), 16 ether, 1); } function test_getMissingBondStETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 16 ether); vm.startPrank(user); - bondManager.depositETH{ value: 16 ether }(0); + accounting.depositETH{ value: 16 ether }(user, 0); - assertApproxEqAbs(bondManager.getMissingBondStETH(0), 16 ether, 1); + assertApproxEqAbs(accounting.getMissingBondStETH(0), 16 ether, 1); } function test_getMissingBondWstETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 16 ether); vm.startPrank(user); - bondManager.depositETH{ value: 16 ether }(0); + accounting.depositETH{ value: 16 ether }(user, 0); assertApproxEqAbs( - bondManager.getMissingBondWstETH(0), + accounting.getMissingBondWstETH(0), wstETH.getWstETHByStETH(16 ether), 1 ); @@ -583,32 +578,37 @@ contract CommunityStakingBondManagerTest is _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); vm.deal(user, 17.57 ether); vm.startPrank(user); - bondManager.depositETH{ value: 17.57 ether }(0); + accounting.depositETH{ value: 17.57 ether }(user, 0); - assertEq(bondManager.getUnbondedKeysCount(0), 7); + assertEq(accounting.getUnbondedKeysCount(0), 7); } function test_claimRewardsStETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(communityStakingFeeDistributor), 0.1 ether); - vm.prank(address(communityStakingFeeDistributor)); + 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) }); - bondManager.depositStETH(0, 32 ether); + accounting.depositStETH(user, 0, 32 ether); - vm.expectEmit(true, true, true, true, address(bondManager)); + vm.expectEmit(true, true, true, true, address(accounting)); emit StETHRewardsClaimed( 0, user, stETH.getPooledEthByShares(sharesAsFee) ); - uint256 bondSharesBefore = bondManager.getBondShares(0); - bondManager.claimRewardsStETH(new bytes32[](1), 0, sharesAsFee); - uint256 bondSharesAfter = bondManager.getBondShares(0); + uint256 bondSharesBefore = accounting.getBondShares(0); + accounting.claimRewardsStETH( + new bytes32[](1), + 0, + sharesAsFee, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); assertEq( stETH.balanceOf(address(user)), @@ -621,7 +621,7 @@ contract CommunityStakingBondManagerTest is "bond shares after claim should be equal to before" ); assertEq( - stETH.sharesOf(address(bondManager)), + stETH.sharesOf(address(accounting)), bondSharesAfter, "bond manager after claim should be equal to before" ); @@ -629,28 +629,28 @@ contract CommunityStakingBondManagerTest is function test_claimRewardsStETH_WithDesirableValue() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(communityStakingFeeDistributor), 0.1 ether); - vm.prank(address(communityStakingFeeDistributor)); + 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) }); - bondManager.depositStETH(0, 32 ether); + accounting.depositStETH(user, 0, 32 ether); - vm.expectEmit(true, true, true, true, address(bondManager)); + vm.expectEmit(true, true, true, true, address(accounting)); emit StETHRewardsClaimed(0, user, stETHToClaim); - uint256 bondSharesBefore = bondManager.getBondShares(0); + uint256 bondSharesBefore = accounting.getBondShares(0); - bondManager.claimRewardsStETH( + accounting.claimRewardsStETH( new bytes32[](1), 0, sharesAsFee, 0.05 ether ); - uint256 bondSharesAfter = bondManager.getBondShares(0); + uint256 bondSharesAfter = accounting.getBondShares(0); assertEq( stETH.balanceOf(address(user)), @@ -663,7 +663,7 @@ contract CommunityStakingBondManagerTest is "bond shares after should be equal to before and fee minus claimed shares" ); assertEq( - stETH.sharesOf(address(bondManager)), + stETH.sharesOf(address(accounting)), bondSharesAfter, "bond manager after should be equal to before and fee minus claimed shares" ); @@ -673,27 +673,27 @@ contract CommunityStakingBondManagerTest is public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(communityStakingFeeDistributor), 0.1 ether); - vm.prank(address(communityStakingFeeDistributor)); + 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) }); - bondManager.depositStETH(0, 32 ether); + accounting.depositStETH(user, 0, 32 ether); - vm.expectEmit(true, true, true, true, address(bondManager)); + vm.expectEmit(true, true, true, true, address(accounting)); emit StETHRewardsClaimed(0, user, stETHAsFee); - uint256 bondSharesBefore = bondManager.getBondShares(0); - bondManager.claimRewardsStETH( + uint256 bondSharesBefore = accounting.getBondShares(0); + accounting.claimRewardsStETH( new bytes32[](1), 0, sharesAsFee, 100 * 1e18 ); - uint256 bondSharesAfter = bondManager.getBondShares(0); + uint256 bondSharesAfter = accounting.getBondShares(0); assertEq( stETH.balanceOf(address(user)), @@ -706,7 +706,7 @@ contract CommunityStakingBondManagerTest is "bond shares after should be equal to before" ); assertEq( - stETH.sharesOf(address(bondManager)), + stETH.sharesOf(address(accounting)), bondSharesAfter, "bond manager after should be equal to before" ); @@ -714,21 +714,26 @@ contract CommunityStakingBondManagerTest is function test_claimRewardsStETH_WhenRequiredBondIsEqualActual() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(communityStakingFeeDistributor), 1 ether); - vm.prank(address(communityStakingFeeDistributor)); + 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) }); - bondManager.depositStETH(0, 31 ether); + accounting.depositStETH(user, 0, 31 ether); - vm.expectEmit(true, true, true, true, address(bondManager)); + vm.expectEmit(true, true, true, true, address(accounting)); emit StETHRewardsClaimed(0, user, 0); - uint256 bondSharesBefore = bondManager.getBondShares(0); - bondManager.claimRewardsStETH(new bytes32[](1), 0, sharesAsFee); - uint256 bondSharesAfter = bondManager.getBondShares(0); + uint256 bondSharesBefore = accounting.getBondShares(0); + accounting.claimRewardsStETH( + new bytes32[](1), + 0, + sharesAsFee, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); assertEq(stETH.balanceOf(address(user)), 0, "user balance should be 0"); assertEq( @@ -737,7 +742,7 @@ contract CommunityStakingBondManagerTest is "bond shares should be increased by fee" ); assertEq( - stETH.sharesOf(address(bondManager)), + stETH.sharesOf(address(accounting)), bondSharesAfter, "bond manager shares should be increased by fee" ); @@ -745,21 +750,26 @@ contract CommunityStakingBondManagerTest is function test_claimRewardsStETH_WhenRequiredBondIsHigherActual() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(communityStakingFeeDistributor), 1 ether); - vm.prank(address(communityStakingFeeDistributor)); + 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) }); - bondManager.depositStETH(0, 31 ether); + accounting.depositStETH(user, 0, 31 ether); - vm.expectEmit(true, true, true, true, address(bondManager)); + vm.expectEmit(true, true, true, true, address(accounting)); emit StETHRewardsClaimed(0, user, 0); - uint256 bondSharesBefore = bondManager.getBondShares(0); - bondManager.claimRewardsStETH(new bytes32[](1), 0, sharesAsFee); - uint256 bondSharesAfter = bondManager.getBondShares(0); + uint256 bondSharesBefore = accounting.getBondShares(0); + accounting.claimRewardsStETH( + new bytes32[](1), + 0, + sharesAsFee, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); assertEq(stETH.balanceOf(address(user)), 0, "user balance should be 0"); assertEq( @@ -768,7 +778,7 @@ contract CommunityStakingBondManagerTest is "bond shares should be increased by fee" ); assertEq( - stETH.sharesOf(address(bondManager)), + stETH.sharesOf(address(accounting)), bondSharesAfter, "bond manager shares should be increased by fee" ); @@ -781,19 +791,19 @@ contract CommunityStakingBondManagerTest is vm.expectRevert( abi.encodeWithSelector( - CommunityStakingBondManager.NotOwnerToClaim.selector, + CSAccounting.NotOwnerToClaim.selector, stranger, user ) ); vm.prank(stranger); - bondManager.claimRewardsStETH(new bytes32[](1), 0, 1, 1 ether); + accounting.claimRewardsStETH(new bytes32[](1), 0, 1, 1 ether); } function test_claimRewardsWstETH() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(communityStakingFeeDistributor), 0.1 ether); - vm.prank(address(communityStakingFeeDistributor)); + 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) @@ -801,14 +811,19 @@ contract CommunityStakingBondManagerTest is vm.deal(user, 32 ether); vm.startPrank(user); stETH.submit{ value: 32 ether }({ _referal: address(0) }); - bondManager.depositStETH(0, 32 ether); + accounting.depositStETH(user, 0, 32 ether); - vm.expectEmit(true, true, true, true, address(bondManager)); + vm.expectEmit(true, true, true, true, address(accounting)); emit WstETHRewardsClaimed(0, user, wstETHAsFee); - uint256 bondSharesBefore = bondManager.getBondShares(0); - bondManager.claimRewardsWstETH(new bytes32[](1), 0, sharesAsFee); - uint256 bondSharesAfter = bondManager.getBondShares(0); + uint256 bondSharesBefore = accounting.getBondShares(0); + accounting.claimRewardsWstETH( + new bytes32[](1), + 0, + sharesAsFee, + UINT256_MAX + ); + uint256 bondSharesAfter = accounting.getBondShares(0); assertEq( wstETH.balanceOf(address(user)), @@ -821,12 +836,12 @@ contract CommunityStakingBondManagerTest is "bond shares after claim should contain wrapped fee accuracy error" ); assertEq( - wstETH.balanceOf(address(bondManager)), + wstETH.balanceOf(address(accounting)), 0, "bond manager wstETH balance should be 0" ); assertEq( - stETH.sharesOf(address(bondManager)), + stETH.sharesOf(address(accounting)), bondSharesBefore + 1 wei, "bond manager after claim should contain wrapped fee accuracy error" ); @@ -834,8 +849,8 @@ contract CommunityStakingBondManagerTest is function test_claimRewardsWstETH_WithDesirableValue() public { _createNodeOperator({ ongoingVals: 16, withdrawnVals: 0 }); - vm.deal(address(communityStakingFeeDistributor), 0.1 ether); - vm.prank(address(communityStakingFeeDistributor)); + 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( @@ -844,19 +859,19 @@ contract CommunityStakingBondManagerTest is vm.deal(user, 32 ether); vm.startPrank(user); stETH.submit{ value: 32 ether }({ _referal: address(0) }); - bondManager.depositStETH(0, 32 ether); + accounting.depositStETH(user, 0, 32 ether); - vm.expectEmit(true, true, true, true, address(bondManager)); + vm.expectEmit(true, true, true, true, address(accounting)); emit WstETHRewardsClaimed(0, user, wstETHToClaim); - uint256 bondSharesBefore = bondManager.getBondShares(0); - bondManager.claimRewardsWstETH( + uint256 bondSharesBefore = accounting.getBondShares(0); + accounting.claimRewardsWstETH( new bytes32[](1), 0, sharesAsFee, stETH.getSharesByPooledEth(0.05 ether) ); - uint256 bondSharesAfter = bondManager.getBondShares(0); + uint256 bondSharesAfter = accounting.getBondShares(0); assertEq( wstETH.balanceOf(address(user)), @@ -869,12 +884,12 @@ contract CommunityStakingBondManagerTest is "bond shares after should be equal to before and fee minus claimed shares" ); assertEq( - wstETH.balanceOf(address(bondManager)), + wstETH.balanceOf(address(accounting)), 0, "bond manager wstETH balance should be 0" ); assertEq( - stETH.sharesOf(address(bondManager)), + stETH.sharesOf(address(accounting)), (bondSharesBefore + sharesAsFee) - wstETHToClaim, "bond shares after should be equal to before and fee minus claimed shares" ); @@ -887,13 +902,13 @@ contract CommunityStakingBondManagerTest is vm.expectRevert( abi.encodeWithSelector( - CommunityStakingBondManager.NotOwnerToClaim.selector, + CSAccounting.NotOwnerToClaim.selector, stranger, user ) ); vm.prank(stranger); - bondManager.claimRewardsWstETH(new bytes32[](1), 0, 1, 1 ether); + accounting.claimRewardsWstETH(new bytes32[](1), 0, 1, 1 ether); } function test_penalize_LessThanDeposit() public { @@ -901,23 +916,23 @@ contract CommunityStakingBondManagerTest is vm.deal(user, 32 ether); vm.startPrank(user); stETH.submit{ value: 32 ether }({ _referal: address(0) }); - bondManager.depositStETH(0, 32 ether); + accounting.depositStETH(user, 0, 32 ether); vm.stopPrank(); - vm.expectEmit(true, true, true, true, address(bondManager)); + vm.expectEmit(true, true, true, true, address(accounting)); emit BondPenalized(0, 1e18, 1e18); - uint256 bondSharesBefore = bondManager.getBondShares(0); + uint256 bondSharesBefore = accounting.getBondShares(0); vm.prank(admin); - bondManager.penalize(0, 1e18); + accounting.penalize(0, 1e18); assertEq( - bondManager.getBondShares(0), + accounting.getBondShares(0), bondSharesBefore - 1e18, "bond shares should be decreased by penalty" ); assertEq( - stETH.sharesOf(address(bondManager)), + stETH.sharesOf(address(accounting)), bondSharesBefore - 1e18, "bond manager shares should be decreased by penalty" ); @@ -933,24 +948,24 @@ contract CommunityStakingBondManagerTest is vm.deal(user, 32 ether); vm.startPrank(user); stETH.submit{ value: 32 ether }({ _referal: address(0) }); - bondManager.depositStETH(0, 32 ether); + accounting.depositStETH(user, 0, 32 ether); vm.stopPrank(); - uint256 bondSharesBefore = bondManager.getBondShares(0); + uint256 bondSharesBefore = accounting.getBondShares(0); - vm.expectEmit(true, true, true, true, address(bondManager)); + vm.expectEmit(true, true, true, true, address(accounting)); emit BondPenalized(0, 32 * 1e18, bondSharesBefore); vm.prank(admin); - bondManager.penalize(0, 32 * 1e18); + accounting.penalize(0, 32 * 1e18); assertEq( - bondManager.getBondShares(0), + accounting.getBondShares(0), 0, "bond shares should be 0 after penalty" ); assertEq( - stETH.sharesOf(address(bondManager)), + stETH.sharesOf(address(accounting)), 0, "bond manager shares should be 0 after penalty" ); @@ -966,23 +981,23 @@ contract CommunityStakingBondManagerTest is vm.deal(user, 32 ether); vm.startPrank(user); stETH.submit{ value: 32 ether }({ _referal: address(0) }); - bondManager.depositStETH(0, 32 ether); + accounting.depositStETH(user, 0, 32 ether); vm.stopPrank(); uint256 shares = stETH.getSharesByPooledEth(32 ether); - vm.expectEmit(true, true, true, true, address(bondManager)); + vm.expectEmit(true, true, true, true, address(accounting)); emit BondPenalized(0, shares, shares); vm.prank(admin); - bondManager.penalize(0, shares); + accounting.penalize(0, shares); assertEq( - bondManager.getBondShares(0), + accounting.getBondShares(0), 0, "bond shares should be 0 after penalty" ); assertEq( - stETH.sharesOf(address(bondManager)), + stETH.sharesOf(address(accounting)), 0, "bond manager shares should be 0 after penalty" ); @@ -998,14 +1013,14 @@ contract CommunityStakingBondManagerTest is "AccessControl: account 0x0000000000000000000000000000000000000309 is missing role 0xf3c54f9b8dbd8c6d8596d09d52b61d4bdce01620000dd9d49c5017dca6e62158" ); vm.prank(stranger); - bondManager.penalize(0, 20); + accounting.penalize(0, 20); } function _createNodeOperator( uint64 ongoingVals, uint64 withdrawnVals ) internal { - communityStakingModule.setNodeOperator({ + stakingModule.setNodeOperator({ _nodeOperatorId: 0, _active: true, _name: "User", diff --git a/test/FeeDistributor.t.sol b/test/CSFeeDistributor.t.sol similarity index 90% rename from test/FeeDistributor.t.sol rename to test/CSFeeDistributor.t.sol index 500975ef..e3329d1f 100644 --- a/test/FeeDistributor.t.sol +++ b/test/CSFeeDistributor.t.sol @@ -4,11 +4,11 @@ pragma solidity 0.8.21; import "forge-std/Test.sol"; -import { FeeDistributorBase } from "../src/FeeDistributorBase.sol"; -import { FeeDistributor } from "../src/FeeDistributor.sol"; -import { FeeOracle } from "../src/FeeOracle.sol"; +import { CSFeeDistributorBase } from "../src/CSFeeDistributorBase.sol"; +import { CSFeeDistributor } from "../src/CSFeeDistributor.sol"; +import { CSFeeOracle } from "../src/CSFeeOracle.sol"; -import { IFeeOracle } from "../src/interfaces/IFeeOracle.sol"; +import { ICSFeeOracle } from "../src/interfaces/ICSFeeOracle.sol"; import { IStETH } from "../src/interfaces/IStETH.sol"; import { Fixtures } from "./helpers/Fixtures.sol"; @@ -18,12 +18,12 @@ import { OracleMock } from "./helpers/mocks/OracleMock.sol"; import { StETHMock } from "./helpers/mocks/StETHMock.sol"; import { Stub } from "./helpers/mocks/Stub.sol"; -contract FeeDistributorTest is Test, Fixtures, FeeDistributorBase { +contract CSFeeDistributorTest is Test, Fixtures, CSFeeDistributorBase { using stdStorage for StdStorage; StETHMock internal stETH; - FeeDistributor internal feeDistributor; + CSFeeDistributor internal feeDistributor; CommunityStakingModuleMock internal csm; OracleMock internal oracle; Stub internal bondManager; @@ -36,7 +36,7 @@ contract FeeDistributorTest is Test, Fixtures, FeeDistributorBase { (, , stETH, ) = initLido(); - feeDistributor = new FeeDistributor( + feeDistributor = new CSFeeDistributor( address(csm), address(stETH), address(oracle), diff --git a/test/FeeOracle.t.sol b/test/CSFeeOracle.t.sol similarity index 94% rename from test/FeeOracle.t.sol rename to test/CSFeeOracle.t.sol index 51ff6400..4a33e015 100644 --- a/test/FeeOracle.t.sol +++ b/test/CSFeeOracle.t.sol @@ -5,13 +5,13 @@ pragma solidity 0.8.21; import "forge-std/Test.sol"; import "forge-std/console.sol"; -import { FeeOracleBase } from "../src/FeeOracleBase.sol"; -import { FeeOracle } from "../src/FeeOracle.sol"; +import { CSFeeOracleBase } from "../src/CSFeeOracleBase.sol"; +import { CSFeeOracle } from "../src/CSFeeOracle.sol"; import { DistributorMock } from "./helpers/mocks/DistributorMock.sol"; import { Utilities } from "./helpers/Utilities.sol"; -contract FeeOracleTest is Test, Utilities, FeeOracleBase { +contract FeeOracleTest is Test, Utilities, CSFeeOracleBase { using stdStorage for StdStorage; address internal constant ORACLE_ADMIN = @@ -22,7 +22,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { address internal FEE_DISTRIBUTOR; address[] internal members; - FeeOracle internal oracle; + CSFeeOracle internal oracle; function setUp() public { FEE_DISTRIBUTOR = address(new DistributorMock()); @@ -34,7 +34,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { function test_RevertIf_GenesisTimeInFuture() public { vm.expectRevert(GenesisTimeNotReached.selector); vm.warp(1); - new FeeOracle({ + new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 2 // > block.timestamp @@ -42,7 +42,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_Initialize() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -63,7 +63,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_currentEpoch() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -79,7 +79,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_nextReportEpoch() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -100,7 +100,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_RevertIf_LastConsolidationEpochInFuture() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -119,7 +119,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_reportFrame() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -139,7 +139,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_setReportInterval() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -162,7 +162,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_submitReport() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -216,7 +216,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_submitReport_NoQuorum() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -282,7 +282,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_RevertIf_TooEarly() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -311,7 +311,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { function test_RevertIf_TooLate() public { _vmSetEpoch(8); - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -342,7 +342,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { bytes32 hash = 0x20b6ee98002cfd33f27ed874d1aaebcd4ed99991dc504b273af77a78553c4afe; - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -352,7 +352,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_setQuorum() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -376,7 +376,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_RevertIf_SetQuorumNotAdmin() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -401,7 +401,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_RevertIf_QuorumTooSmall() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -422,7 +422,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_addMember() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -454,7 +454,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_RevertIf_NotAdmin_AddMember() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -481,7 +481,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_removeMember() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -518,7 +518,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_RevertIF_NotAdmin_RemoveMember() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -555,7 +555,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_RevertIF_NotExistent_RemoveMember() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -582,7 +582,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_pause() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 @@ -605,7 +605,7 @@ contract FeeOracleTest is Test, Utilities, FeeOracleBase { } function test_unpause() public { - oracle = new FeeOracle({ + oracle = new CSFeeOracle({ secondsPerBlock: 12, blocksPerEpoch: 32, genesisTime: 0 diff --git a/test/CSMAddValidator.t.sol b/test/CSMAddValidator.t.sol index 6e5fa11b..310a2e71 100644 --- a/test/CSMAddValidator.t.sol +++ b/test/CSMAddValidator.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.21; import "forge-std/Test.sol"; -import "../src/CommunityStakingModule.sol"; -import "../src/CommunityStakingBondManager.sol"; +import "../src/CSModule.sol"; +import "../src/CSAccounting.sol"; import "./helpers/Fixtures.sol"; import "./helpers/mocks/StETHMock.sol"; import "./helpers/mocks/CommunityStakingFeeDistributorMock.sol"; @@ -12,13 +12,13 @@ import "./helpers/mocks/LidoMock.sol"; import "./helpers/mocks/WstETHMock.sol"; import "./helpers/Utilities.sol"; -contract CSMCommon is Test, Fixtures, Utilities, CommunityStakingModuleBase { +contract CSMCommon is Test, Fixtures, Utilities, CSModuleBase { LidoLocatorMock public locator; WstETHMock public wstETH; LidoMock public stETH; Stub public burner; - CommunityStakingModule public csm; - CommunityStakingBondManager public bondManager; + CSModule public csm; + CSAccounting public accounting; CommunityStakingFeeDistributorMock public communityStakingFeeDistributor; address internal stranger; @@ -40,13 +40,10 @@ contract CSMCommon is Test, Fixtures, Utilities, CommunityStakingModuleBase { communityStakingFeeDistributor = new CommunityStakingFeeDistributorMock( address(locator), - address(bondManager) + address(accounting) ); - csm = new CommunityStakingModule( - "community-staking-module", - address(locator) - ); - bondManager = new CommunityStakingBondManager( + csm = new CSModule("community-staking-module", address(locator)); + accounting = new CSAccounting( 2 ether, alice, address(locator), @@ -54,7 +51,7 @@ contract CSMCommon is Test, Fixtures, Utilities, CommunityStakingModuleBase { address(csm), penalizeRoleMembers ); - csm.setBondManager(address(bondManager)); + csm.setAccounting(address(accounting)); } function createNodeOperator() internal returns (uint256) { @@ -109,7 +106,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { { vm.expectEmit(true, true, true, true, address(wstETH)); - emit Approval(nodeOperator, address(bondManager), wstETHAmount); + emit Approval(nodeOperator, address(accounting), wstETHAmount); vm.expectEmit(true, true, false, true, address(csm)); emit TotalSigningKeysCountChanged(0, 1); vm.expectEmit(true, true, false, true, address(csm)); @@ -124,7 +121,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { 1, keys, signatures, - ICommunityStakingBondManager.PermitInput({ + ICSAccounting.PermitInput({ value: wstETHAmount, deadline: type(uint256).max, // mock permit signature @@ -168,7 +165,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { (keys, signatures) = keysSignatures(keysCount, 1); { vm.expectEmit(true, true, true, true, address(wstETH)); - emit Approval(nodeOperator, address(bondManager), wstETHAmount); + emit Approval(nodeOperator, address(accounting), wstETHAmount); vm.expectEmit(true, true, false, true, address(csm)); emit TotalSigningKeysCountChanged(0, 2); } @@ -179,7 +176,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { 1, keys, signatures, - ICommunityStakingBondManager.PermitInput({ + ICSAccounting.PermitInput({ value: wstETHAmount, deadline: type(uint256).max, // mock permit signature @@ -216,7 +213,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { { vm.expectEmit(true, true, true, true, address(stETH)); - emit Approval(nodeOperator, address(bondManager), 2 ether); + emit Approval(nodeOperator, address(accounting), 2 ether); vm.expectEmit(true, true, false, true, address(csm)); emit TotalSigningKeysCountChanged(0, 1); vm.expectEmit(true, true, false, true, address(csm)); @@ -231,7 +228,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { 1, keys, signatures, - ICommunityStakingBondManager.PermitInput({ + ICSAccounting.PermitInput({ value: 2 ether, deadline: type(uint256).max, // mock permit signature @@ -266,13 +263,13 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { csm.addNodeOperatorStETH("test", nodeOperator, 1, keys, signatures); uint256 noId = csm.getNodeOperatorsCount() - 1; - uint256 required = bondManager.getRequiredBondStETH(0, 1); + uint256 required = accounting.getRequiredBondStETH(0, 1); vm.deal(nodeOperator, required); vm.prank(nodeOperator); stETH.submit{ value: required }(address(0)); { vm.expectEmit(true, true, true, true, address(stETH)); - emit Approval(nodeOperator, address(bondManager), required); + emit Approval(nodeOperator, address(accounting), required); vm.expectEmit(true, true, false, true, address(csm)); emit TotalSigningKeysCountChanged(0, 2); } @@ -283,7 +280,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { 1, keys, signatures, - ICommunityStakingBondManager.PermitInput({ + ICSAccounting.PermitInput({ value: required, deadline: type(uint256).max, // mock permit signature @@ -323,7 +320,7 @@ contract CSMAddNodeOperator is CSMCommon, PermitTokenBase { uint256 noId = createNodeOperator(); (bytes memory keys, bytes memory signatures) = keysSignatures(1, 1); - uint256 required = bondManager.getRequiredBondETH(0, 1); + uint256 required = accounting.getRequiredBondETH(0, 1); vm.deal(nodeOperator, required); vm.prank(nodeOperator); { diff --git a/test/CSMInit.t.sol b/test/CSMInit.t.sol index f1653989..c7d88191 100644 --- a/test/CSMInit.t.sol +++ b/test/CSMInit.t.sol @@ -2,8 +2,8 @@ pragma solidity 0.8.21; import "forge-std/Test.sol"; -import "../src/CommunityStakingModule.sol"; -import "../src/CommunityStakingBondManager.sol"; +import "../src/CSModule.sol"; +import "../src/CSAccounting.sol"; import "./helpers/Fixtures.sol"; import "./helpers/mocks/StETHMock.sol"; import "./helpers/mocks/CommunityStakingFeeDistributorMock.sol"; @@ -17,8 +17,8 @@ contract CSMInitTest is Test, Fixtures { LidoMock public stETH; Stub public burner; - CommunityStakingModule public csm; - CommunityStakingBondManager public bondManager; + CSModule public csm; + CSAccounting public accounting; CommunityStakingFeeDistributorMock public communityStakingFeeDistributor; address internal stranger; @@ -31,15 +31,12 @@ contract CSMInitTest is Test, Fixtures { (locator, wstETH, stETH, burner) = initLido(); - csm = new CommunityStakingModule( - "community-staking-module", - address(locator) - ); + csm = new CSModule("community-staking-module", address(locator)); communityStakingFeeDistributor = new CommunityStakingFeeDistributorMock( address(locator), - address(bondManager) + address(accounting) ); - bondManager = new CommunityStakingBondManager( + accounting = new CSAccounting( 2 ether, alice, address(locator), @@ -54,8 +51,8 @@ contract CSMInitTest is Test, Fixtures { assertEq(csm.getNodeOperatorsCount(), 0); } - function test_SetBondManager() public { - csm.setBondManager(address(bondManager)); - assertEq(address(csm.bondManager()), address(bondManager)); + function test_SetAccounting() public { + csm.setAccounting(address(accounting)); + assertEq(address(csm.accounting()), address(accounting)); } } diff --git a/test/integration/DepositInTokens.t.sol b/test/integration/DepositInTokens.t.sol index a4e6c722..6d924740 100644 --- a/test/integration/DepositInTokens.t.sol +++ b/test/integration/DepositInTokens.t.sol @@ -4,17 +4,19 @@ pragma solidity 0.8.21; import "forge-std/Test.sol"; -import { CommunityStakingModule } from "../../src/CommunityStakingModule.sol"; -import { IWstETH, ILido, CommunityStakingBondManager } from "../../src/CommunityStakingBondManager.sol"; +import { CSModule } from "../../src/CSModule.sol"; +import { CSAccounting } from "../../src/CSAccounting.sol"; import { PermitHelper } from "../helpers/Permit.sol"; import { CommunityStakingModuleMock } from "../helpers/mocks/CommunityStakingModuleMock.sol"; +import { IWstETH } from "../../src/interfaces/IWstETH.sol"; +import { ILido } from "../../src/interfaces/ILido.sol"; import { ILidoLocator } from "../../src/interfaces/ILidoLocator.sol"; contract DepositIntegrationTest is Test, PermitHelper { uint256 networkFork; CommunityStakingModuleMock public csm; - CommunityStakingBondManager public bondManager; + CSAccounting public accounting; ILidoLocator public locator; IWstETH public wstETH; @@ -56,7 +58,7 @@ contract DepositIntegrationTest is Test, PermitHelper { address[] memory penalizeRoleMembers = new address[](1); penalizeRoleMembers[0] = user; - bondManager = new CommunityStakingBondManager( + accounting = new CSAccounting( 2 ether, user, address(locator), @@ -85,22 +87,22 @@ contract DepositIntegrationTest is Test, PermitHelper { _referal: address(0) }); - ILido(locator.lido()).approve(address(bondManager), type(uint256).max); - bondManager.depositStETH(0, 32 ether); + ILido(locator.lido()).approve(address(accounting), type(uint256).max); + accounting.depositStETH(user, 0, 32 ether); assertEq(ILido(locator.lido()).balanceOf(user), 0); - assertEq(bondManager.getBondShares(0), shares); - assertEq(bondManager.totalBondShares(), shares); + assertEq(accounting.getBondShares(0), shares); + assertEq(accounting.totalBondShares(), shares); } function test_depositETH() public { vm.prank(user); vm.deal(user, 32 ether); - uint256 shares = bondManager.depositETH{ value: 32 ether }(0); + uint256 shares = accounting.depositETH{ value: 32 ether }(user, 0); assertEq(user.balance, 0); - assertEq(bondManager.getBondShares(0), shares); - assertEq(bondManager.totalBondShares(), shares); + assertEq(accounting.getBondShares(0), shares); + assertEq(accounting.totalBondShares(), shares); } function test_depositWstETH() public { @@ -113,18 +115,18 @@ contract DepositIntegrationTest is Test, PermitHelper { uint256 wstETHAmount = wstETH.wrap(32 ether); vm.startPrank(user); - wstETH.approve(address(bondManager), type(uint256).max); - uint256 shares = bondManager.depositWstETH(0, wstETHAmount); + wstETH.approve(address(accounting), type(uint256).max); + uint256 shares = accounting.depositWstETH(user, 0, wstETHAmount); assertEq(wstETH.balanceOf(user), 0); - assertEq(bondManager.getBondShares(0), shares); - assertEq(bondManager.totalBondShares(), shares); + assertEq(accounting.getBondShares(0), shares); + assertEq(accounting.totalBondShares(), shares); } function test_depositStETHWithPermit() public { bytes32 digest = stETHPermitDigest( user, - address(bondManager), + address(accounting), 32 ether, vm.getNonce(user), type(uint256).max, @@ -140,11 +142,11 @@ contract DepositIntegrationTest is Test, PermitHelper { vm.stopPrank(); vm.prank(stranger); - bondManager.depositStETHWithPermit( + accounting.depositStETHWithPermit( user, 0, 32 ether, - CommunityStakingBondManager.PermitInput({ + CSAccounting.PermitInput({ value: 32 ether, deadline: type(uint256).max, v: v, @@ -154,14 +156,14 @@ contract DepositIntegrationTest is Test, PermitHelper { ); assertEq(ILido(locator.lido()).balanceOf(user), 0); - assertEq(bondManager.getBondShares(0), shares); - assertEq(bondManager.totalBondShares(), shares); + assertEq(accounting.getBondShares(0), shares); + assertEq(accounting.totalBondShares(), shares); } function test_depositWstETHWithPermit() public { bytes32 digest = wstETHPermitDigest( user, - address(bondManager), + address(accounting), 32 ether, vm.getNonce(user), type(uint256).max, @@ -179,11 +181,11 @@ contract DepositIntegrationTest is Test, PermitHelper { vm.stopPrank(); vm.prank(stranger); - uint256 shares = bondManager.depositWstETHWithPermit( + uint256 shares = accounting.depositWstETHWithPermit( user, 0, wstETHAmount, - CommunityStakingBondManager.PermitInput({ + CSAccounting.PermitInput({ value: 32 ether, deadline: type(uint256).max, v: v, @@ -193,7 +195,7 @@ contract DepositIntegrationTest is Test, PermitHelper { ); assertEq(wstETH.balanceOf(user), 0); - assertEq(bondManager.getBondShares(0), shares); - assertEq(bondManager.totalBondShares(), shares); + assertEq(accounting.getBondShares(0), shares); + assertEq(accounting.totalBondShares(), shares); } } diff --git a/test/integration/StakingRouter.t.sol b/test/integration/StakingRouter.t.sol index 5b5b5b2b..2e754f73 100644 --- a/test/integration/StakingRouter.t.sol +++ b/test/integration/StakingRouter.t.sol @@ -4,18 +4,18 @@ pragma solidity 0.8.21; import "forge-std/Test.sol"; -import { CommunityStakingModule, NodeOperator } from "../../src/CommunityStakingModule.sol"; +import { CSModule, NodeOperator } from "../../src/CSModule.sol"; import { ILidoLocator } from "../../src/interfaces/ILidoLocator.sol"; import { IStakingRouter } from "../../src/interfaces/IStakingRouter.sol"; -import { IWstETH } from "../../src/CommunityStakingBondManager.sol"; +import { CSAccounting } from "../../src/CSAccounting.sol"; import { ILido } from "../../src/interfaces/ILido.sol"; +import { IWstETH } from "../../src/interfaces/IWstETH.sol"; import "../helpers/Utilities.sol"; -import "../../src/CommunityStakingBondManager.sol"; contract StakingRouterIntegrationTest is Test, Utilities { uint256 networkFork; - CommunityStakingModule public csm; + CSModule public csm; ILidoLocator public locator; IStakingRouter public stakingRouter; ILido public lido; @@ -48,21 +48,18 @@ contract StakingRouterIntegrationTest is Test, Utilities { vm.label(address(lido), "lido"); vm.label(address(stakingRouter), "stakingRouter"); - csm = new CommunityStakingModule( - "community-staking-module", - address(locator) - ); + csm = new CSModule("community-staking-module", address(locator)); address[] memory penalizeRoleMembers = new address[](1); penalizeRoleMembers[0] = address(csm); - CommunityStakingBondManager bondManager = new CommunityStakingBondManager( - 2 ether, - address(csm), - address(locator), - address(wstETH), - address(csm), - penalizeRoleMembers - ); - csm.setBondManager(address(bondManager)); + CSAccounting accounting = new CSAccounting( + 2 ether, + address(csm), + address(locator), + address(wstETH), + address(csm), + penalizeRoleMembers + ); + csm.setAccounting(address(accounting)); agent = stakingRouter.getRoleMember( stakingRouter.DEFAULT_ADMIN_ROLE(),