diff --git a/contracts/0.8.25/vaults/Delegation.sol b/contracts/0.8.25/vaults/Delegation.sol index 08429de3c..f35b860b8 100644 --- a/contracts/0.8.25/vaults/Delegation.sol +++ b/contracts/0.8.25/vaults/Delegation.sol @@ -13,91 +13,86 @@ import {Dashboard} from "./Dashboard.sol"; * * The delegation hierarchy is as follows: * - DEFAULT_ADMIN_ROLE is the underlying owner of StakingVault; - * - OPERATOR_ROLE is the node operator of StakingVault; and itself is the role admin, - * and the DEFAULT_ADMIN_ROLE cannot assign OPERATOR_ROLE; - * - CLAIM_OPERATOR_DUE_ROLE is the role that can claim operator due; is assigned by OPERATOR_ROLE; + * - NODE_OPERATOR_MANAGER_ROLE is the node operator manager of StakingVault; and itself is the role admin, + * and the DEFAULT_ADMIN_ROLE cannot assign NODE_OPERATOR_MANAGER_ROLE; + * - NODE_OPERATOR_FEE_CLAIMER_ROLE is the role that can claim node operator fee; is assigned by NODE_OPERATOR_MANAGER_ROLE; * - * Additionally, the following roles are assigned by the owner (DEFAULT_ADMIN_ROLE): - * - CURATOR_ROLE is the curator of StakingVault empowered by the owner; - * performs the daily operations of the StakingVault on behalf of the owner; - * - STAKER_ROLE funds and withdraws from the StakingVault; - * - TOKEN_MASTER_ROLE mints and burns shares of stETH backed by the StakingVault; + * Additionally, the following roles are assigned by DEFAULT_ADMIN_ROLE: + * - CURATOR_ROLE is the curator of StakingVault and perfoms some operations on behalf of DEFAULT_ADMIN_ROLE; + * - FUND_WITHDRAW_ROLE funds and withdraws from the StakingVault; + * - MINT_BURN_ROLE mints and burns shares of stETH backed by the StakingVault; * - * Operator and Curator have their respective fees and dues. - * The fee is calculated as a percentage (in basis points) of the StakingVault rewards. - * The due is the amount of ether that is owed to the Curator or Operator based on the fee. + * The curator and node operator have their respective fees. + * The feeBP is the percentage (in basis points) of the StakingVault rewards. + * The unclaimed fee is the amount of ether that is owed to the curator or node operator based on the feeBP. */ contract Delegation is Dashboard { /** - * @notice Maximum fee value; equals to 100%. + * @notice Maximum combined feeBP value; equals to 100%. */ - uint256 private constant MAX_FEE = TOTAL_BASIS_POINTS; + uint256 private constant MAX_FEE_BP = TOTAL_BASIS_POINTS; /** - * @notice Curator: + * @notice Curator role: * - sets curator fee; - * - votes operator fee; + * - claims curator fee; * - votes on vote lifetime; - * - votes on ownership transfer; - * - claims curator due. + * - votes on node operator fee; + * - votes on ownership transfer. */ bytes32 public constant CURATOR_ROLE = keccak256("Vault.Delegation.CuratorRole"); /** - * @notice Staker: - * - funds vault; - * - withdraws from vault. + * @notice Mint/burn role: + * - mints shares of stETH; + * - burns shares of stETH. */ - bytes32 public constant STAKER_ROLE = keccak256("Vault.Delegation.StakerRole"); + bytes32 public constant MINT_BURN_ROLE = keccak256("Vault.Delegation.MintBurnRole"); /** - * @notice Token master: - * - mints shares; - * - burns shares. + * @notice Fund/withdraw role: + * - funds StakingVault; + * - withdraws from StakingVault. */ - bytes32 public constant TOKEN_MASTER_ROLE = keccak256("Vault.Delegation.TokenMasterRole"); + bytes32 public constant FUND_WITHDRAW_ROLE = keccak256("Vault.Delegation.FundWithdrawRole"); /** - * @notice Node operator: + * @notice Node operator manager role: * - votes on vote lifetime; - * - votes on operator fee; + * - votes on node operator fee; * - votes on ownership transfer; - * - is the role admin for CLAIM_OPERATOR_DUE_ROLE. + * - assigns NODE_OPERATOR_FEE_CLAIMER_ROLE. */ - bytes32 public constant OPERATOR_ROLE = keccak256("Vault.Delegation.OperatorRole"); + bytes32 public constant NODE_OPERATOR_MANAGER_ROLE = keccak256("Vault.Delegation.NodeOperatorManagerRole"); /** - * @notice Claim operator due: - * - claims operator due. + * @notice Node operator fee claimer role: + * - claims node operator fee. */ - bytes32 public constant CLAIM_OPERATOR_DUE_ROLE = keccak256("Vault.Delegation.ClaimOperatorDueRole"); + bytes32 public constant NODE_OPERATOR_FEE_CLAIMER_ROLE = keccak256("Vault.Delegation.NodeOperatorFeeClaimerRole"); /** - * @notice Curator fee in basis points; combined with operator fee cannot exceed 100%. - * The term "fee" is used to represent the percentage (in basis points) of curator's share of the rewards. - * The term "due" is used to represent the actual amount of fees in ether. - * The curator due in ether is returned by `curatorDue()`. + * @notice Curator fee in basis points; combined with node operator fee cannot exceed 100%. + * The curator's unclaimed fee in ether is returned by `curatorUnclaimedFee()`. */ - uint256 public curatorFee; + uint256 public curatorFeeBP; /** - * @notice The last report for which curator due was claimed. Updated on each claim. + * @notice The last report for which curator fee was claimed. Updated on each claim. */ - IStakingVault.Report public curatorDueClaimedReport; + IStakingVault.Report public curatorFeeClaimedReport; /** - * @notice Operator fee in basis points; combined with curator fee cannot exceed 100%. - * The term "fee" is used to represent the percentage (in basis points) of operator's share of the rewards. - * The term "due" is used to represent the actual amount of fees in ether. - * The operator due in ether is returned by `operatorDue()`. + * @notice Node operator fee in basis points; combined with curator fee cannot exceed 100%, or 10,000 basis points. + * The node operator's unclaimed fee in ether is returned by `nodeOperatorUnclaimedFee()`. */ - uint256 public operatorFee; + uint256 public nodeOperatorFeeBP; /** - * @notice The last report for which operator due was claimed. Updated on each claim. + * @notice The last report for which node operator fee was claimed. Updated on each claim. */ - IStakingVault.Report public operatorDueClaimedReport; + IStakingVault.Report public nodeOperatorFeeClaimedReport; /** * @notice Tracks committee votes @@ -115,7 +110,8 @@ contract Delegation is Dashboard { uint256 public voteLifetime; /** - * @notice Initializes the contract with the stETH address. + * @notice Constructs the contract. + * @dev Stores token addresses in the bytecode to reduce gas costs. * @param _stETH The address of the stETH token. * @param _weth Address of the weth token contract. * @param _wstETH Address of the wstETH token contract. @@ -126,13 +122,11 @@ contract Delegation is Dashboard { * @notice Initializes the contract: * - sets the address of StakingVault; * - sets up the roles; - * - sets the vote lifetime to 7 days (can be changed later by CURATOR_ROLE and OPERATOR_ROLE). + * - sets the vote lifetime to 7 days (can be changed later by CURATOR_ROLE and NODE_OPERATOR_MANAGER_ROLE). * @param _stakingVault The address of StakingVault. - * @dev The msg.sender here is VaultFactory. It is given the OPERATOR_ROLE - * to be able to set initial operatorFee in VaultFactory, because only OPERATOR_ROLE - * is the admin role for itself. The rest of the roles are also temporarily given to - * VaultFactory to be able to set initial config in VaultFactory. - * All the roles are revoked from VaultFactory at the end of the initialization. + * @dev The msg.sender here is VaultFactory. The VaultFactory is temporarily granted + * DEFAULT_ADMIN_ROLE AND NODE_OPERATOR_MANAGER_ROLE to be able to set initial fees and roles in VaultFactory. + * All the roles are revoked from VaultFactory by the end of the initialization. */ function initialize(address _stakingVault) external override { _initialize(_stakingVault); @@ -140,51 +134,51 @@ contract Delegation is Dashboard { // the next line implies that the msg.sender is an operator // however, the msg.sender is the VaultFactory, and the role will be revoked // at the end of the initialization - _grantRole(OPERATOR_ROLE, msg.sender); - _setRoleAdmin(OPERATOR_ROLE, OPERATOR_ROLE); - _setRoleAdmin(CLAIM_OPERATOR_DUE_ROLE, OPERATOR_ROLE); + _grantRole(NODE_OPERATOR_MANAGER_ROLE, msg.sender); + _setRoleAdmin(NODE_OPERATOR_MANAGER_ROLE, NODE_OPERATOR_MANAGER_ROLE); + _setRoleAdmin(NODE_OPERATOR_FEE_CLAIMER_ROLE, NODE_OPERATOR_MANAGER_ROLE); voteLifetime = 7 days; } /** - * @notice Returns the accumulated curator due in ether, - * calculated as: CD = (SVR * CF) / TBP + * @notice Returns the accumulated unclaimed curator fee in ether, + * calculated as: U = (R * F) / T * where: - * - CD is the curator due; - * - SVR is the StakingVault rewards accrued since the last curator due claim; - * - CF is the curator fee in basis points; - * - TBP is the total basis points (100%). - * @return uint256: the amount of due ether. - */ - function curatorDue() public view returns (uint256) { - return _calculateDue(curatorFee, curatorDueClaimedReport); + * - U is the curator unclaimed fee; + * - R is the StakingVault rewards accrued since the last curator fee claim; + * - F is `curatorFeeBP`; + * - T is the total basis points, 10,000. + * @return uint256: the amount of unclaimed fee in ether. + */ + function curatorUnclaimedFee() public view returns (uint256) { + return _calculateFee(curatorFeeBP, curatorFeeClaimedReport); } /** - * @notice Returns the accumulated operator due in ether, - * calculated as: OD = (SVR * OF) / TBP + * @notice Returns the accumulated unclaimed node operator fee in ether, + * calculated as: U = (R * F) / T * where: - * - OD is the operator due; - * - SVR is the StakingVault rewards accrued since the last operator due claim; - * - OF is the operator fee in basis points; - * - TBP is the total basis points (100%). - * @return uint256: the amount of due ether. - */ - function operatorDue() public view returns (uint256) { - return _calculateDue(operatorFee, operatorDueClaimedReport); + * - U is the node operator unclaimed fee; + * - R is the StakingVault rewards accrued since the last node operator fee claim; + * - F is `nodeOperatorFeeBP`; + * - T is the total basis points, 10,000. + * @return uint256: the amount of unclaimed fee in ether. + */ + function nodeOperatorUnclaimedFee() public view returns (uint256) { + return _calculateFee(nodeOperatorFeeBP, nodeOperatorFeeClaimedReport); } /** * @notice Returns the unreserved amount of ether, * i.e. the amount of ether that is not locked in the StakingVault - * and not reserved for curator due and operator due. + * and not reserved for curator and node operator fees. * This amount does not account for the current balance of the StakingVault and * can return a value greater than the actual balance of the StakingVault. * @return uint256: the amount of unreserved ether. */ function unreserved() public view returns (uint256) { - uint256 reserved = stakingVault.locked() + curatorDue() + operatorDue(); + uint256 reserved = stakingVault.locked() + curatorUnclaimedFee() + nodeOperatorUnclaimedFee(); uint256 valuation = stakingVault.valuation(); return reserved > valuation ? 0 : valuation - reserved; @@ -193,33 +187,33 @@ contract Delegation is Dashboard { /** * @notice Returns the committee that can: * - change the vote lifetime; - * - set the operator fee; + * - set the node operator fee; * - transfer the ownership of the StakingVault. * @return committee is an array of roles that form the voting committee. */ function votingCommittee() public pure returns (bytes32[] memory committee) { committee = new bytes32[](2); committee[0] = CURATOR_ROLE; - committee[1] = OPERATOR_ROLE; + committee[1] = NODE_OPERATOR_MANAGER_ROLE; } /** * @notice Funds the StakingVault with ether. */ - function fund() external payable override onlyRole(STAKER_ROLE) { + function fund() external payable override onlyRole(FUND_WITHDRAW_ROLE) { _fund(); } /** * @notice Withdraws ether from the StakingVault. * Cannot withdraw more than the unreserved amount: which is the amount of ether - * that is not locked in the StakingVault and not reserved for curator due and operator due. + * that is not locked in the StakingVault and not reserved for curator and node operator fees. * Does not include a check for the balance of the StakingVault, this check is present * on the StakingVault itself. * @param _recipient The address to which the ether will be sent. * @param _ether The amount of ether to withdraw. */ - function withdraw(address _recipient, uint256 _ether) external override onlyRole(STAKER_ROLE) { + function withdraw(address _recipient, uint256 _ether) external override onlyRole(FUND_WITHDRAW_ROLE) { if (_recipient == address(0)) revert ZeroArgument("_recipient"); if (_ether == 0) revert ZeroArgument("_ether"); uint256 withdrawable = unreserved(); @@ -238,7 +232,7 @@ contract Delegation is Dashboard { function mint( address _recipient, uint256 _amountOfShares - ) external payable override onlyRole(TOKEN_MASTER_ROLE) fundAndProceed { + ) external payable override onlyRole(MINT_BURN_ROLE) fundAndProceed { _mint(_recipient, _amountOfShares); } @@ -249,7 +243,7 @@ contract Delegation is Dashboard { * NB: Delegation contract must have ERC-20 approved allowance to burn sender's shares. * @param _amountOfShares The amount of shares to burn. */ - function burn(uint256 _amountOfShares) external override onlyRole(TOKEN_MASTER_ROLE) { + function burn(uint256 _amountOfShares) external override onlyRole(MINT_BURN_ROLE) { _burn(_amountOfShares); } @@ -277,56 +271,56 @@ contract Delegation is Dashboard { /** * @notice Sets the curator fee. * The curator fee is the percentage (in basis points) of curator's share of the StakingVault rewards. - * The curator fee combined with the operator fee cannot exceed 100%. - * The curator due must be claimed before the curator fee can be changed to avoid - * @param _newCuratorFee The new curator fee in basis points. + * The curator and node operator fees combined cannot exceed 100%, or 10,000 basis points. + * The function will revert if the curator fee is unclaimed. + * @param _newCuratorFeeBP The new curator fee in basis points. */ - function setCuratorFee(uint256 _newCuratorFee) external onlyRole(CURATOR_ROLE) { - if (_newCuratorFee + operatorFee > MAX_FEE) revert CombinedFeesExceed100Percent(); - if (curatorDue() > 0) revert CuratorDueUnclaimed(); - uint256 oldCuratorFee = curatorFee; - curatorFee = _newCuratorFee; + function setCuratorFeeBP(uint256 _newCuratorFeeBP) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (_newCuratorFeeBP + nodeOperatorFeeBP > MAX_FEE_BP) revert CombinedFeesExceed100Percent(); + if (curatorUnclaimedFee() > 0) revert CuratorFeeUnclaimed(); + uint256 oldCuratorFeeBP = curatorFeeBP; + curatorFeeBP = _newCuratorFeeBP; - emit CuratorFeeSet(msg.sender, oldCuratorFee, _newCuratorFee); + emit CuratorFeeBPSet(msg.sender, oldCuratorFeeBP, _newCuratorFeeBP); } /** - * @notice Sets the operator fee. - * The operator fee is the percentage (in basis points) of operator's share of the StakingVault rewards. - * The operator fee combined with the curator fee cannot exceed 100%. - * Note that the function reverts if the operator due is not claimed and all the votes must be recasted to execute it again, - * which is why the deciding voter must make sure that the operator due is claimed before calling this function. - * @param _newOperatorFee The new operator fee in basis points. + * @notice Sets the node operator fee. + * The node operator fee is the percentage (in basis points) of node operator's share of the StakingVault rewards. + * The node operator fee combined with the curator fee cannot exceed 100%. + * Note that the function reverts if the node operator fee is unclaimed and all the votes must be recasted to execute it again, + * which is why the deciding voter must make sure that `nodeOperatorUnclaimedFee()` is 0 before calling this function. + * @param _newNodeOperatorFeeBP The new node operator fee in basis points. */ - function setOperatorFee(uint256 _newOperatorFee) external onlyIfVotedBy(votingCommittee()) { - if (_newOperatorFee + curatorFee > MAX_FEE) revert CombinedFeesExceed100Percent(); - if (operatorDue() > 0) revert OperatorDueUnclaimed(); - uint256 oldOperatorFee = operatorFee; - operatorFee = _newOperatorFee; + function setNodeOperatorFeeBP(uint256 _newNodeOperatorFeeBP) external onlyIfVotedBy(votingCommittee()) { + if (_newNodeOperatorFeeBP + curatorFeeBP > MAX_FEE_BP) revert CombinedFeesExceed100Percent(); + if (nodeOperatorUnclaimedFee() > 0) revert NodeOperatorFeeUnclaimed(); + uint256 oldNodeOperatorFeeBP = nodeOperatorFeeBP; + nodeOperatorFeeBP = _newNodeOperatorFeeBP; - emit OperatorFeeSet(msg.sender, oldOperatorFee, _newOperatorFee); + emit NodeOperatorFeeBPSet(msg.sender, oldNodeOperatorFeeBP, _newNodeOperatorFeeBP); } /** - * @notice Claims the curator due. - * @param _recipient The address to which the curator due will be sent. + * @notice Claims the curator fee. + * @param _recipient The address to which the curator fee will be sent. */ - function claimCuratorDue(address _recipient) external onlyRole(CURATOR_ROLE) { - uint256 due = curatorDue(); - curatorDueClaimedReport = stakingVault.latestReport(); - _claimDue(_recipient, due); + function claimCuratorFee(address _recipient) external onlyRole(CURATOR_ROLE) { + uint256 fee = curatorUnclaimedFee(); + curatorFeeClaimedReport = stakingVault.latestReport(); + _claimFee(_recipient, fee); } /** - * @notice Claims the operator due. - * Note that the authorized role is CLAIM_OPERATOR_DUE_ROLE, not OPERATOR_ROLE, - * although OPERATOR_ROLE is the admin role for CLAIM_OPERATOR_DUE_ROLE. - * @param _recipient The address to which the operator due will be sent. + * @notice Claims the node operator fee. + * Note that the authorized role is NODE_OPERATOR_FEE_CLAIMER_ROLE, not NODE_OPERATOR_MANAGER_ROLE, + * although NODE_OPERATOR_MANAGER_ROLE is the admin role for NODE_OPERATOR_FEE_CLAIMER_ROLE. + * @param _recipient The address to which the node operator fee will be sent. */ - function claimOperatorDue(address _recipient) external onlyRole(CLAIM_OPERATOR_DUE_ROLE) { - uint256 due = operatorDue(); - operatorDueClaimedReport = stakingVault.latestReport(); - _claimDue(_recipient, due); + function claimNodeOperatorFee(address _recipient) external onlyRole(NODE_OPERATOR_FEE_CLAIMER_ROLE) { + uint256 fee = nodeOperatorUnclaimedFee(); + nodeOperatorFeeClaimedReport = stakingVault.latestReport(); + _claimFee(_recipient, fee); } /** @@ -425,13 +419,13 @@ contract Delegation is Dashboard { } /** - * @dev Calculates the curator/operatordue amount based on the fee and the last claimed report. - * @param _fee The fee in basis points. + * @dev Calculates the curator/node operator fee amount based on the fee and the last claimed report. + * @param _feeBP The fee in basis points. * @param _lastClaimedReport The last claimed report. - * @return The accrued due amount. + * @return The accrued fee amount. */ - function _calculateDue( - uint256 _fee, + function _calculateFee( + uint256 _feeBP, IStakingVault.Report memory _lastClaimedReport ) internal view returns (uint256) { IStakingVault.Report memory latestReport = stakingVault.latestReport(); @@ -439,19 +433,19 @@ contract Delegation is Dashboard { int128 rewardsAccrued = int128(latestReport.valuation - _lastClaimedReport.valuation) - (latestReport.inOutDelta - _lastClaimedReport.inOutDelta); - return rewardsAccrued > 0 ? (uint256(uint128(rewardsAccrued)) * _fee) / TOTAL_BASIS_POINTS : 0; + return rewardsAccrued > 0 ? (uint256(uint128(rewardsAccrued)) * _feeBP) / TOTAL_BASIS_POINTS : 0; } /** - * @dev Claims the curator/operator due amount. - * @param _recipient The address to which the due will be sent. - * @param _due The accrued due amount. + * @dev Claims the curator/node operator fee amount. + * @param _recipient The address to which the fee will be sent. + * @param _fee The accrued fee amount. */ - function _claimDue(address _recipient, uint256 _due) internal { + function _claimFee(address _recipient, uint256 _fee) internal { if (_recipient == address(0)) revert ZeroArgument("_recipient"); - if (_due == 0) revert NoDueToClaim(); + if (_fee == 0) revert ZeroArgument("_fee"); - _withdraw(_recipient, _due); + _withdraw(_recipient, _fee); } /** @@ -463,17 +457,17 @@ contract Delegation is Dashboard { /** * @dev Emitted when the curator fee is set. - * @param oldCuratorFee The old curator fee. - * @param newCuratorFee The new curator fee. + * @param oldCuratorFeeBP The old curator fee. + * @param newCuratorFeeBP The new curator fee. */ - event CuratorFeeSet(address indexed sender, uint256 oldCuratorFee, uint256 newCuratorFee); + event CuratorFeeBPSet(address indexed sender, uint256 oldCuratorFeeBP, uint256 newCuratorFeeBP); /** - * @dev Emitted when the operator fee is set. - * @param oldOperatorFee The old operator fee. - * @param newOperatorFee The new operator fee. + * @dev Emitted when the node operator fee is set. + * @param oldNodeOperatorFeeBP The old node operator fee. + * @param newNodeOperatorFeeBP The new node operator fee. */ - event OperatorFeeSet(address indexed sender, uint256 oldOperatorFee, uint256 newOperatorFee); + event NodeOperatorFeeBPSet(address indexed sender, uint256 oldNodeOperatorFeeBP, uint256 newNodeOperatorFeeBP); /** * @dev Emitted when a committee member votes. @@ -490,17 +484,17 @@ contract Delegation is Dashboard { error NotACommitteeMember(); /** - * @dev Error emitted when the curator due is unclaimed. + * @dev Error emitted when the curator fee is unclaimed. */ - error CuratorDueUnclaimed(); + error CuratorFeeUnclaimed(); /** - * @dev Error emitted when the operator due is unclaimed. + * @dev Error emitted when the node operator fee is unclaimed. */ - error OperatorDueUnclaimed(); + error NodeOperatorFeeUnclaimed(); /** - * @dev Error emitted when the combined fees exceed 100%. + * @dev Error emitted when the combined feeBPs exceed 100%. */ error CombinedFeesExceed100Percent(); @@ -508,9 +502,4 @@ contract Delegation is Dashboard { * @dev Error emitted when the requested amount exceeds the unreserved amount. */ error RequestedAmountExceedsUnreserved(); - - /** - * @dev Error emitted when there is no due to claim. - */ - error NoDueToClaim(); } diff --git a/contracts/0.8.25/vaults/StakingVault.sol b/contracts/0.8.25/vaults/StakingVault.sol index bc6e585d9..79b6179ac 100644 --- a/contracts/0.8.25/vaults/StakingVault.sol +++ b/contracts/0.8.25/vaults/StakingVault.sol @@ -59,13 +59,13 @@ contract StakingVault is IStakingVault, IBeaconProxy, BeaconChainDepositLogistic * @custom:report Latest report containing valuation and inOutDelta * @custom:locked Amount of ether locked on StakingVault by VaultHub and cannot be withdrawn by owner * @custom:inOutDelta Net difference between ether funded and withdrawn from StakingVault - * @custom:operator Address of the node operator + * @custom:nodeOperator Address of the node operator */ struct ERC7201Storage { Report report; uint128 locked; int128 inOutDelta; - address operator; + address nodeOperator; } /** @@ -115,14 +115,14 @@ contract StakingVault is IStakingVault, IBeaconProxy, BeaconChainDepositLogistic } /** - * @notice Initializes `StakingVault` with an owner, operator, and optional parameters + * @notice Initializes `StakingVault` with an owner, node operator, and optional parameters * @param _owner Address that will own the vault - * @param _operator Address of the node operator + * @param _nodeOperator Address of the node operator * @param - Additional initialization parameters */ - function initialize(address _owner, address _operator, bytes calldata /* _params */ ) external onlyBeacon initializer { + function initialize(address _owner, address _nodeOperator, bytes calldata /* _params */ ) external onlyBeacon initializer { __Ownable_init(_owner); - _getStorage().operator = _operator; + _getStorage().nodeOperator = _nodeOperator; } /** @@ -242,8 +242,8 @@ contract StakingVault is IStakingVault, IBeaconProxy, BeaconChainDepositLogistic * Node operator address is set in the initialization and can never be changed. * @return Address of the node operator */ - function operator() external view returns (address) { - return _getStorage().operator; + function nodeOperator() external view returns (address) { + return _getStorage().nodeOperator; } /** @@ -316,7 +316,7 @@ contract StakingVault is IStakingVault, IBeaconProxy, BeaconChainDepositLogistic ) external { if (_numberOfDeposits == 0) revert ZeroArgument("_numberOfDeposits"); if (!isBalanced()) revert Unbalanced(); - if (msg.sender != _getStorage().operator) revert NotAuthorized("depositToBeaconChain", msg.sender); + if (msg.sender != _getStorage().nodeOperator) revert NotAuthorized("depositToBeaconChain", msg.sender); _makeBeaconChainDeposits32ETH(_numberOfDeposits, bytes.concat(withdrawalCredentials()), _pubkeys, _signatures); emit DepositedToBeaconChain(msg.sender, _numberOfDeposits, _numberOfDeposits * 32 ether); @@ -325,7 +325,7 @@ contract StakingVault is IStakingVault, IBeaconProxy, BeaconChainDepositLogistic /** * @notice Requests validator exit from the beacon chain * @param _pubkeys Concatenated validator public keys - * @dev Signals the operator to eject the specified validators from the beacon chain + * @dev Signals the node operator to eject the specified validators from the beacon chain */ function requestValidatorExit(bytes calldata _pubkeys) external onlyOwner { emit ValidatorsExitRequest(msg.sender, _pubkeys); @@ -422,7 +422,7 @@ contract StakingVault is IStakingVault, IBeaconProxy, BeaconChainDepositLogistic /** * @notice Emitted when a validator exit request is made - * @dev Signals `operator` to exit the validator + * @dev Signals `nodeOperator` to exit the validator * @param sender Address that requested the validator exit * @param pubkey Public key of the validator requested to exit */ diff --git a/contracts/0.8.25/vaults/VaultFactory.sol b/contracts/0.8.25/vaults/VaultFactory.sol index 2edf21e73..a32e841c9 100644 --- a/contracts/0.8.25/vaults/VaultFactory.sol +++ b/contracts/0.8.25/vaults/VaultFactory.sol @@ -12,31 +12,31 @@ pragma solidity 0.8.25; interface IDelegation { struct InitialState { address curator; - address staker; - address tokenMaster; - address operator; - address claimOperatorDueRole; - uint256 curatorFee; - uint256 operatorFee; + address minterBurner; + address funderWithdrawer; + address nodeOperatorManager; + address nodeOperatorFeeClaimer; + uint256 curatorFeeBP; + uint256 nodeOperatorFeeBP; } function DEFAULT_ADMIN_ROLE() external view returns (bytes32); function CURATOR_ROLE() external view returns (bytes32); - function STAKER_ROLE() external view returns (bytes32); + function FUND_WITHDRAW_ROLE() external view returns (bytes32); - function TOKEN_MASTER_ROLE() external view returns (bytes32); + function MINT_BURN_ROLE() external view returns (bytes32); - function OPERATOR_ROLE() external view returns (bytes32); + function NODE_OPERATOR_MANAGER_ROLE() external view returns (bytes32); - function CLAIM_OPERATOR_DUE_ROLE() external view returns (bytes32); + function NODE_OPERATOR_FEE_CLAIMER_ROLE() external view returns (bytes32); function initialize(address _stakingVault) external; - function setCuratorFee(uint256 _newCuratorFee) external; + function setCuratorFeeBP(uint256 _newCuratorFeeBP) external; - function setOperatorFee(uint256 _newOperatorFee) external; + function setNodeOperatorFeeBP(uint256 _newNodeOperatorFee) external; function grantRole(bytes32 role, address account) external; @@ -74,28 +74,28 @@ contract VaultFactory is UpgradeableBeacon { delegation = IDelegation(Clones.clone(delegationImpl)); // initialize StakingVault - vault.initialize(address(delegation), _delegationInitialState.operator, _stakingVaultInitializerExtraParams); + vault.initialize(address(delegation), _delegationInitialState.nodeOperatorManager, _stakingVaultInitializerExtraParams); // initialize Delegation delegation.initialize(address(vault)); // grant roles to owner, manager, operator delegation.grantRole(delegation.DEFAULT_ADMIN_ROLE(), msg.sender); delegation.grantRole(delegation.CURATOR_ROLE(), _delegationInitialState.curator); - delegation.grantRole(delegation.STAKER_ROLE(), _delegationInitialState.staker); - delegation.grantRole(delegation.TOKEN_MASTER_ROLE(), _delegationInitialState.tokenMaster); - delegation.grantRole(delegation.OPERATOR_ROLE(), _delegationInitialState.operator); - delegation.grantRole(delegation.CLAIM_OPERATOR_DUE_ROLE(), _delegationInitialState.claimOperatorDueRole); + delegation.grantRole(delegation.FUND_WITHDRAW_ROLE(), _delegationInitialState.funderWithdrawer); + delegation.grantRole(delegation.MINT_BURN_ROLE(), _delegationInitialState.minterBurner); + delegation.grantRole(delegation.NODE_OPERATOR_MANAGER_ROLE(), _delegationInitialState.nodeOperatorManager); + delegation.grantRole(delegation.NODE_OPERATOR_FEE_CLAIMER_ROLE(), _delegationInitialState.nodeOperatorFeeClaimer); // grant temporary roles to factory delegation.grantRole(delegation.CURATOR_ROLE(), address(this)); - delegation.grantRole(delegation.OPERATOR_ROLE(), address(this)); + delegation.grantRole(delegation.NODE_OPERATOR_MANAGER_ROLE(), address(this)); // set fees - delegation.setCuratorFee(_delegationInitialState.curatorFee); - delegation.setOperatorFee(_delegationInitialState.operatorFee); + delegation.setCuratorFeeBP(_delegationInitialState.curatorFeeBP); + delegation.setNodeOperatorFeeBP(_delegationInitialState.nodeOperatorFeeBP); // revoke temporary roles from factory delegation.revokeRole(delegation.CURATOR_ROLE(), address(this)); - delegation.revokeRole(delegation.OPERATOR_ROLE(), address(this)); + delegation.revokeRole(delegation.NODE_OPERATOR_MANAGER_ROLE(), address(this)); delegation.revokeRole(delegation.DEFAULT_ADMIN_ROLE(), address(this)); emit VaultCreated(address(delegation), address(vault)); diff --git a/contracts/0.8.25/vaults/interfaces/IStakingVault.sol b/contracts/0.8.25/vaults/interfaces/IStakingVault.sol index 54d597073..51ebe61c5 100644 --- a/contracts/0.8.25/vaults/interfaces/IStakingVault.sol +++ b/contracts/0.8.25/vaults/interfaces/IStakingVault.sol @@ -23,7 +23,7 @@ interface IStakingVault { function initialize(address _owner, address _operator, bytes calldata _params) external; function getInitializedVersion() external view returns (uint64); function vaultHub() external view returns (address); - function operator() external view returns (address); + function nodeOperator() external view returns (address); function locked() external view returns (uint256); function valuation() external view returns (uint256); function isBalanced() external view returns (bool); diff --git a/lib/proxy.ts b/lib/proxy.ts index 582a8312a..c86dacdc7 100644 --- a/lib/proxy.ts +++ b/lib/proxy.ts @@ -54,13 +54,13 @@ export async function createVaultProxy( ): Promise { // Define the parameters for the struct const initializationParams: DelegationInitializationParamsStruct = { - curatorFee: 100n, - operatorFee: 200n, + curatorFeeBP: 100n, + nodeOperatorFeeBP: 200n, curator: await _owner.getAddress(), - staker: await _owner.getAddress(), - tokenMaster: await _owner.getAddress(), - operator: await _operator.getAddress(), - claimOperatorDueRole: await _owner.getAddress(), + funderWithdrawer: await _owner.getAddress(), + minterBurner: await _owner.getAddress(), + nodeOperatorManager: await _operator.getAddress(), + nodeOperatorFeeClaimer: await _owner.getAddress(), }; const tx = await vaultFactory.connect(_owner).createVault(initializationParams, "0x"); diff --git a/test/0.8.25/vaults/dashboard/dashboard.test.ts b/test/0.8.25/vaults/dashboard/dashboard.test.ts index f678a6c92..5f0b57204 100644 --- a/test/0.8.25/vaults/dashboard/dashboard.test.ts +++ b/test/0.8.25/vaults/dashboard/dashboard.test.ts @@ -25,7 +25,7 @@ import { Snapshot } from "test/suite"; describe("Dashboard", () => { let factoryOwner: HardhatEthersSigner; let vaultOwner: HardhatEthersSigner; - let operator: HardhatEthersSigner; + let nodeOperator: HardhatEthersSigner; let stranger: HardhatEthersSigner; let steth: StETHPermit__HarnessForDashboard; @@ -45,7 +45,7 @@ describe("Dashboard", () => { const BP_BASE = 10_000n; before(async () => { - [factoryOwner, vaultOwner, operator, stranger] = await ethers.getSigners(); + [factoryOwner, vaultOwner, nodeOperator, stranger] = await ethers.getSigners(); steth = await ethers.deployContract("StETHPermit__HarnessForDashboard"); await steth.mock__setTotalShares(ether("1000000")); @@ -67,7 +67,7 @@ describe("Dashboard", () => { expect(await factory.implementation()).to.equal(vaultImpl); expect(await factory.dashboardImpl()).to.equal(dashboardImpl); - const createVaultTx = await factory.connect(vaultOwner).createVault(operator); + const createVaultTx = await factory.connect(vaultOwner).createVault(nodeOperator); const createVaultReceipt = await createVaultTx.wait(); if (!createVaultReceipt) throw new Error("Vault creation receipt not found"); @@ -139,7 +139,7 @@ describe("Dashboard", () => { context("initialized state", () => { it("post-initialization state is correct", async () => { expect(await vault.owner()).to.equal(dashboard); - expect(await vault.operator()).to.equal(operator); + expect(await vault.nodeOperator()).to.equal(nodeOperator); expect(await dashboard.isInitialized()).to.equal(true); expect(await dashboard.stakingVault()).to.equal(vault); expect(await dashboard.vaultHub()).to.equal(hub); diff --git a/test/0.8.25/vaults/delegation/delegation.test.ts b/test/0.8.25/vaults/delegation/delegation.test.ts index 5ad7b08ea..408ecc0c9 100644 --- a/test/0.8.25/vaults/delegation/delegation.test.ts +++ b/test/0.8.25/vaults/delegation/delegation.test.ts @@ -25,10 +25,10 @@ const MAX_FEE = BP_BASE; describe("Delegation.sol", () => { let vaultOwner: HardhatEthersSigner; let curator: HardhatEthersSigner; - let staker: HardhatEthersSigner; - let tokenMaster: HardhatEthersSigner; - let operator: HardhatEthersSigner; - let claimOperatorDueRole: HardhatEthersSigner; + let funderWithdrawer: HardhatEthersSigner; + let minterBurner: HardhatEthersSigner; + let nodeOperatorManager: HardhatEthersSigner; + let nodeOperatorFeeClaimer: HardhatEthersSigner; let stranger: HardhatEthersSigner; let factoryOwner: HardhatEthersSigner; let hubSigner: HardhatEthersSigner; @@ -49,8 +49,17 @@ describe("Delegation.sol", () => { let originalState: string; before(async () => { - [vaultOwner, curator, staker, tokenMaster, operator, claimOperatorDueRole, stranger, factoryOwner, rewarder] = - await ethers.getSigners(); + [ + vaultOwner, + curator, + funderWithdrawer, + minterBurner, + nodeOperatorManager, + nodeOperatorFeeClaimer, + stranger, + factoryOwner, + rewarder, + ] = await ethers.getSigners(); steth = await ethers.deployContract("StETH__MockForDelegation"); weth = await ethers.deployContract("WETH9__MockForVault"); @@ -74,12 +83,18 @@ describe("Delegation.sol", () => { expect(await factory.implementation()).to.equal(vaultImpl); expect(await factory.delegationImpl()).to.equal(delegationImpl); - const vaultCreationTx = await factory - .connect(vaultOwner) - .createVault( - { curator, staker, tokenMaster, operator, claimOperatorDueRole, curatorFee: 0n, operatorFee: 0n }, - "0x", - ); + const vaultCreationTx = await factory.connect(vaultOwner).createVault( + { + curator, + funderWithdrawer, + minterBurner, + nodeOperatorManager, + nodeOperatorFeeClaimer, + curatorFeeBP: 0n, + nodeOperatorFeeBP: 0n, + }, + "0x", + ); const vaultCreationReceipt = await vaultCreationTx.wait(); if (!vaultCreationReceipt) throw new Error("Vault creation receipt not found"); @@ -157,7 +172,7 @@ describe("Delegation.sol", () => { context("initialized state", () => { it("initializes the contract correctly", async () => { expect(await vault.owner()).to.equal(delegation); - expect(await vault.operator()).to.equal(operator); + expect(await vault.nodeOperator()).to.equal(nodeOperatorManager); expect(await delegation.stakingVault()).to.equal(vault); expect(await delegation.vaultHub()).to.equal(hub); @@ -166,21 +181,22 @@ describe("Delegation.sol", () => { expect(await delegation.getRoleMemberCount(await delegation.DEFAULT_ADMIN_ROLE())).to.equal(1); expect(await delegation.hasRole(await delegation.CURATOR_ROLE(), curator)).to.be.true; expect(await delegation.getRoleMemberCount(await delegation.CURATOR_ROLE())).to.equal(1); - expect(await delegation.hasRole(await delegation.STAKER_ROLE(), staker)).to.be.true; - expect(await delegation.getRoleMemberCount(await delegation.STAKER_ROLE())).to.equal(1); - expect(await delegation.hasRole(await delegation.TOKEN_MASTER_ROLE(), tokenMaster)).to.be.true; - expect(await delegation.getRoleMemberCount(await delegation.TOKEN_MASTER_ROLE())).to.equal(1); - expect(await delegation.hasRole(await delegation.OPERATOR_ROLE(), operator)).to.be.true; - expect(await delegation.getRoleMemberCount(await delegation.OPERATOR_ROLE())).to.equal(1); - expect(await delegation.hasRole(await delegation.CLAIM_OPERATOR_DUE_ROLE(), claimOperatorDueRole)).to.be.true; - expect(await delegation.getRoleMemberCount(await delegation.CLAIM_OPERATOR_DUE_ROLE())).to.equal(1); - - expect(await delegation.curatorFee()).to.equal(0n); - expect(await delegation.operatorFee()).to.equal(0n); - expect(await delegation.curatorDue()).to.equal(0n); - expect(await delegation.operatorDue()).to.equal(0n); - expect(await delegation.curatorDueClaimedReport()).to.deep.equal([0n, 0n]); - expect(await delegation.operatorDueClaimedReport()).to.deep.equal([0n, 0n]); + expect(await delegation.hasRole(await delegation.FUND_WITHDRAW_ROLE(), funderWithdrawer)).to.be.true; + expect(await delegation.getRoleMemberCount(await delegation.FUND_WITHDRAW_ROLE())).to.equal(1); + expect(await delegation.hasRole(await delegation.MINT_BURN_ROLE(), minterBurner)).to.be.true; + expect(await delegation.getRoleMemberCount(await delegation.MINT_BURN_ROLE())).to.equal(1); + expect(await delegation.hasRole(await delegation.NODE_OPERATOR_MANAGER_ROLE(), nodeOperatorManager)).to.be.true; + expect(await delegation.getRoleMemberCount(await delegation.NODE_OPERATOR_MANAGER_ROLE())).to.equal(1); + expect(await delegation.hasRole(await delegation.NODE_OPERATOR_FEE_CLAIMER_ROLE(), nodeOperatorFeeClaimer)).to.be + .true; + expect(await delegation.getRoleMemberCount(await delegation.NODE_OPERATOR_FEE_CLAIMER_ROLE())).to.equal(1); + + expect(await delegation.curatorFeeBP()).to.equal(0n); + expect(await delegation.nodeOperatorFeeBP()).to.equal(0n); + expect(await delegation.curatorUnclaimedFee()).to.equal(0n); + expect(await delegation.nodeOperatorUnclaimedFee()).to.equal(0n); + expect(await delegation.curatorFeeClaimedReport()).to.deep.equal([0n, 0n]); + expect(await delegation.nodeOperatorFeeClaimedReport()).to.deep.equal([0n, 0n]); }); }); @@ -188,7 +204,7 @@ describe("Delegation.sol", () => { it("returns the correct roles", async () => { expect(await delegation.votingCommittee()).to.deep.equal([ await delegation.CURATOR_ROLE(), - await delegation.OPERATOR_ROLE(), + await delegation.NODE_OPERATOR_MANAGER_ROLE(), ]); }); }); @@ -212,55 +228,54 @@ describe("Delegation.sol", () => { .withArgs(curator, await delegation.CURATOR_ROLE(), voteTimestamp, msgData); voteTimestamp = await getNextBlockTimestamp(); - await expect(delegation.connect(operator).setVoteLifetime(newVoteLifetime)) + await expect(delegation.connect(nodeOperatorManager).setVoteLifetime(newVoteLifetime)) .to.emit(delegation, "RoleMemberVoted") - .withArgs(operator, await delegation.OPERATOR_ROLE(), voteTimestamp, msgData) + .withArgs(nodeOperatorManager, await delegation.NODE_OPERATOR_MANAGER_ROLE(), voteTimestamp, msgData) .and.to.emit(delegation, "VoteLifetimeSet") - .withArgs(operator, oldVoteLifetime, newVoteLifetime); + .withArgs(nodeOperatorManager, oldVoteLifetime, newVoteLifetime); expect(await delegation.voteLifetime()).to.equal(newVoteLifetime); }); }); - context("claimCuratorDue", () => { + context("claimCuratorFee", () => { it("reverts if the caller is not a member of the curator due claim role", async () => { - await expect(delegation.connect(stranger).claimCuratorDue(stranger)) + await expect(delegation.connect(stranger).claimCuratorFee(stranger)) .to.be.revertedWithCustomError(delegation, "AccessControlUnauthorizedAccount") .withArgs(stranger, await delegation.CURATOR_ROLE()); }); it("reverts if the recipient is the zero address", async () => { - await expect(delegation.connect(curator).claimCuratorDue(ethers.ZeroAddress)) + await expect(delegation.connect(curator).claimCuratorFee(ethers.ZeroAddress)) .to.be.revertedWithCustomError(delegation, "ZeroArgument") .withArgs("_recipient"); }); it("reverts if the due is zero", async () => { - expect(await delegation.curatorDue()).to.equal(0n); - await expect(delegation.connect(curator).claimCuratorDue(stranger)).to.be.revertedWithCustomError( - delegation, - "NoDueToClaim", - ); + expect(await delegation.curatorUnclaimedFee()).to.equal(0n); + await expect(delegation.connect(curator).claimCuratorFee(stranger)) + .to.be.revertedWithCustomError(delegation, "ZeroArgument") + .withArgs("_fee"); }); it("claims the due", async () => { const curatorFee = 10_00n; // 10% - await delegation.connect(curator).setCuratorFee(curatorFee); - expect(await delegation.curatorFee()).to.equal(curatorFee); + await delegation.connect(vaultOwner).setCuratorFeeBP(curatorFee); + expect(await delegation.curatorFeeBP()).to.equal(curatorFee); const rewards = ether("1"); await vault.connect(hubSigner).report(rewards, 0n, 0n); const expectedDue = (rewards * curatorFee) / BP_BASE; - expect(await delegation.curatorDue()).to.equal(expectedDue); - expect(await delegation.curatorDue()).to.be.greaterThan(await ethers.provider.getBalance(vault)); + expect(await delegation.curatorUnclaimedFee()).to.equal(expectedDue); + expect(await delegation.curatorUnclaimedFee()).to.be.greaterThan(await ethers.provider.getBalance(vault)); expect(await ethers.provider.getBalance(vault)).to.equal(0n); await rewarder.sendTransaction({ to: vault, value: rewards }); expect(await ethers.provider.getBalance(vault)).to.equal(rewards); expect(await ethers.provider.getBalance(recipient)).to.equal(0n); - await expect(delegation.connect(curator).claimCuratorDue(recipient)) + await expect(delegation.connect(curator).claimCuratorFee(recipient)) .to.emit(vault, "Withdrawn") .withArgs(delegation, recipient, expectedDue); expect(await ethers.provider.getBalance(recipient)).to.equal(expectedDue); @@ -268,47 +283,46 @@ describe("Delegation.sol", () => { }); }); - context("claimOperatorDue", () => { + context("claimNodeOperatorFee", () => { it("reverts if the caller does not have the operator due claim role", async () => { - await expect(delegation.connect(stranger).claimOperatorDue(stranger)).to.be.revertedWithCustomError( + await expect(delegation.connect(stranger).claimNodeOperatorFee(stranger)).to.be.revertedWithCustomError( delegation, "AccessControlUnauthorizedAccount", ); }); it("reverts if the recipient is the zero address", async () => { - await expect(delegation.connect(claimOperatorDueRole).claimOperatorDue(ethers.ZeroAddress)) + await expect(delegation.connect(nodeOperatorFeeClaimer).claimNodeOperatorFee(ethers.ZeroAddress)) .to.be.revertedWithCustomError(delegation, "ZeroArgument") .withArgs("_recipient"); }); it("reverts if the due is zero", async () => { - expect(await delegation.operatorDue()).to.equal(0n); - await expect(delegation.connect(claimOperatorDueRole).claimOperatorDue(recipient)).to.be.revertedWithCustomError( - delegation, - "NoDueToClaim", - ); + expect(await delegation.nodeOperatorUnclaimedFee()).to.equal(0n); + await expect(delegation.connect(nodeOperatorFeeClaimer).claimNodeOperatorFee(recipient)) + .to.be.revertedWithCustomError(delegation, "ZeroArgument") + .withArgs("_fee"); }); it("claims the due", async () => { const operatorFee = 10_00n; // 10% - await delegation.connect(operator).setOperatorFee(operatorFee); - await delegation.connect(curator).setOperatorFee(operatorFee); - expect(await delegation.operatorFee()).to.equal(operatorFee); + await delegation.connect(nodeOperatorManager).setNodeOperatorFeeBP(operatorFee); + await delegation.connect(curator).setNodeOperatorFeeBP(operatorFee); + expect(await delegation.nodeOperatorFeeBP()).to.equal(operatorFee); const rewards = ether("1"); await vault.connect(hubSigner).report(rewards, 0n, 0n); const expectedDue = (rewards * operatorFee) / BP_BASE; - expect(await delegation.operatorDue()).to.equal(expectedDue); - expect(await delegation.operatorDue()).to.be.greaterThan(await ethers.provider.getBalance(vault)); + expect(await delegation.nodeOperatorUnclaimedFee()).to.equal(expectedDue); + expect(await delegation.nodeOperatorUnclaimedFee()).to.be.greaterThan(await ethers.provider.getBalance(vault)); expect(await ethers.provider.getBalance(vault)).to.equal(0n); await rewarder.sendTransaction({ to: vault, value: rewards }); expect(await ethers.provider.getBalance(vault)).to.equal(rewards); expect(await ethers.provider.getBalance(recipient)).to.equal(0n); - await expect(delegation.connect(claimOperatorDueRole).claimOperatorDue(recipient)) + await expect(delegation.connect(nodeOperatorFeeClaimer).claimNodeOperatorFee(recipient)) .to.emit(vault, "Withdrawn") .withArgs(delegation, recipient, expectedDue); expect(await ethers.provider.getBalance(recipient)).to.equal(expectedDue); @@ -345,7 +359,7 @@ describe("Delegation.sol", () => { expect(await vault.inOutDelta()).to.equal(0n); expect(await vault.valuation()).to.equal(0n); - await expect(delegation.connect(staker).fund({ value: amount })) + await expect(delegation.connect(funderWithdrawer).fund({ value: amount })) .to.emit(vault, "Funded") .withArgs(delegation, amount); @@ -364,14 +378,13 @@ describe("Delegation.sol", () => { }); it("reverts if the recipient is the zero address", async () => { - await expect(delegation.connect(staker).withdraw(ethers.ZeroAddress, ether("1"))).to.be.revertedWithCustomError( - delegation, - "ZeroArgument", - ); + await expect( + delegation.connect(funderWithdrawer).withdraw(ethers.ZeroAddress, ether("1")), + ).to.be.revertedWithCustomError(delegation, "ZeroArgument"); }); it("reverts if the amount is zero", async () => { - await expect(delegation.connect(staker).withdraw(recipient, 0n)).to.be.revertedWithCustomError( + await expect(delegation.connect(funderWithdrawer).withdraw(recipient, 0n)).to.be.revertedWithCustomError( delegation, "ZeroArgument", ); @@ -379,10 +392,9 @@ describe("Delegation.sol", () => { it("reverts if the amount is greater than the unreserved amount", async () => { const unreserved = await delegation.unreserved(); - await expect(delegation.connect(staker).withdraw(recipient, unreserved + 1n)).to.be.revertedWithCustomError( - delegation, - "RequestedAmountExceedsUnreserved", - ); + await expect( + delegation.connect(funderWithdrawer).withdraw(recipient, unreserved + 1n), + ).to.be.revertedWithCustomError(delegation, "RequestedAmountExceedsUnreserved"); }); it("withdraws the amount", async () => { @@ -396,7 +408,7 @@ describe("Delegation.sol", () => { expect(await ethers.provider.getBalance(vault)).to.equal(amount); expect(await ethers.provider.getBalance(recipient)).to.equal(0n); - await expect(delegation.connect(staker).withdraw(recipient, amount)) + await expect(delegation.connect(funderWithdrawer).withdraw(recipient, amount)) .to.emit(vault, "Withdrawn") .withArgs(delegation, recipient, amount); expect(await ethers.provider.getBalance(vault)).to.equal(0n); @@ -414,7 +426,7 @@ describe("Delegation.sol", () => { it("rebalances the vault by transferring ether", async () => { const amount = ether("1"); - await delegation.connect(staker).fund({ value: amount }); + await delegation.connect(funderWithdrawer).fund({ value: amount }); await expect(delegation.connect(curator).rebalanceVault(amount)) .to.emit(hub, "Mock__Rebalanced") @@ -441,7 +453,7 @@ describe("Delegation.sol", () => { it("mints the tokens", async () => { const amount = 100n; - await expect(delegation.connect(tokenMaster).mint(recipient, amount)) + await expect(delegation.connect(minterBurner).mint(recipient, amount)) .to.emit(steth, "Transfer") .withArgs(ethers.ZeroAddress, recipient, amount); }); @@ -457,25 +469,45 @@ describe("Delegation.sol", () => { it("burns the tokens", async () => { const amount = 100n; - await delegation.connect(tokenMaster).mint(tokenMaster, amount); + await delegation.connect(minterBurner).mint(minterBurner, amount); - await expect(delegation.connect(tokenMaster).burn(amount)) + await expect(delegation.connect(minterBurner).burn(amount)) .to.emit(steth, "Transfer") - .withArgs(tokenMaster, hub, amount) + .withArgs(minterBurner, hub, amount) .and.to.emit(steth, "Transfer") .withArgs(hub, ethers.ZeroAddress, amount); }); }); - context("setCuratorFee", () => { + context("setCuratorFeeBP", () => { it("reverts if caller is not curator", async () => { - await expect(delegation.connect(stranger).setCuratorFee(1000n)) + await expect(delegation.connect(stranger).setCuratorFeeBP(1000n)) .to.be.revertedWithCustomError(delegation, "AccessControlUnauthorizedAccount") - .withArgs(stranger, await delegation.CURATOR_ROLE()); + .withArgs(stranger, await delegation.DEFAULT_ADMIN_ROLE()); + }); + + it("reverts if curator fee is not zero", async () => { + // set the curator fee to 5% + const newCuratorFee = 500n; + await delegation.connect(vaultOwner).setCuratorFeeBP(newCuratorFee); + expect(await delegation.curatorFeeBP()).to.equal(newCuratorFee); + + // bring rewards + const totalRewards = ether("1"); + const inOutDelta = 0n; + const locked = 0n; + await vault.connect(hubSigner).report(totalRewards, inOutDelta, locked); + expect(await delegation.curatorUnclaimedFee()).to.equal((totalRewards * newCuratorFee) / BP_BASE); + + // attempt to change the performance fee to 6% + await expect(delegation.connect(vaultOwner).setCuratorFeeBP(600n)).to.be.revertedWithCustomError( + delegation, + "CuratorFeeUnclaimed", + ); }); it("reverts if new fee is greater than max fee", async () => { - await expect(delegation.connect(curator).setCuratorFee(MAX_FEE + 1n)).to.be.revertedWithCustomError( + await expect(delegation.connect(vaultOwner).setCuratorFeeBP(MAX_FEE + 1n)).to.be.revertedWithCustomError( delegation, "CombinedFeesExceed100Percent", ); @@ -483,66 +515,65 @@ describe("Delegation.sol", () => { it("sets the curator fee", async () => { const newCuratorFee = 1000n; - await delegation.connect(curator).setCuratorFee(newCuratorFee); - expect(await delegation.curatorFee()).to.equal(newCuratorFee); + await delegation.connect(vaultOwner).setCuratorFeeBP(newCuratorFee); + expect(await delegation.curatorFeeBP()).to.equal(newCuratorFee); }); }); context("setOperatorFee", () => { it("reverts if new fee is greater than max fee", async () => { const invalidFee = MAX_FEE + 1n; - await delegation.connect(curator).setOperatorFee(invalidFee); + await delegation.connect(curator).setNodeOperatorFeeBP(invalidFee); - await expect(delegation.connect(operator).setOperatorFee(invalidFee)).to.be.revertedWithCustomError( - delegation, - "CombinedFeesExceed100Percent", - ); + await expect( + delegation.connect(nodeOperatorManager).setNodeOperatorFeeBP(invalidFee), + ).to.be.revertedWithCustomError(delegation, "CombinedFeesExceed100Percent"); }); it("reverts if performance due is not zero", async () => { // set the performance fee to 5% const newOperatorFee = 500n; - await delegation.connect(curator).setOperatorFee(newOperatorFee); - await delegation.connect(operator).setOperatorFee(newOperatorFee); - expect(await delegation.operatorFee()).to.equal(newOperatorFee); + await delegation.connect(curator).setNodeOperatorFeeBP(newOperatorFee); + await delegation.connect(nodeOperatorManager).setNodeOperatorFeeBP(newOperatorFee); + expect(await delegation.nodeOperatorFeeBP()).to.equal(newOperatorFee); // bring rewards const totalRewards = ether("1"); const inOutDelta = 0n; const locked = 0n; await vault.connect(hubSigner).report(totalRewards, inOutDelta, locked); - expect(await delegation.operatorDue()).to.equal((totalRewards * newOperatorFee) / BP_BASE); + expect(await delegation.nodeOperatorUnclaimedFee()).to.equal((totalRewards * newOperatorFee) / BP_BASE); // attempt to change the performance fee to 6% - await delegation.connect(curator).setOperatorFee(600n); - await expect(delegation.connect(operator).setOperatorFee(600n)).to.be.revertedWithCustomError( + await delegation.connect(curator).setNodeOperatorFeeBP(600n); + await expect(delegation.connect(nodeOperatorManager).setNodeOperatorFeeBP(600n)).to.be.revertedWithCustomError( delegation, - "OperatorDueUnclaimed", + "NodeOperatorFeeUnclaimed", ); }); it("requires both curator and operator to set the operator fee and emits the RoleMemberVoted event", async () => { - const previousOperatorFee = await delegation.operatorFee(); + const previousOperatorFee = await delegation.nodeOperatorFeeBP(); const newOperatorFee = 1000n; let voteTimestamp = await getNextBlockTimestamp(); - const msgData = delegation.interface.encodeFunctionData("setOperatorFee", [newOperatorFee]); + const msgData = delegation.interface.encodeFunctionData("setNodeOperatorFeeBP", [newOperatorFee]); - await expect(delegation.connect(curator).setOperatorFee(newOperatorFee)) + await expect(delegation.connect(curator).setNodeOperatorFeeBP(newOperatorFee)) .to.emit(delegation, "RoleMemberVoted") .withArgs(curator, await delegation.CURATOR_ROLE(), voteTimestamp, msgData); // fee is unchanged - expect(await delegation.operatorFee()).to.equal(previousOperatorFee); + expect(await delegation.nodeOperatorFeeBP()).to.equal(previousOperatorFee); // check vote expect(await delegation.votings(keccak256(msgData), await delegation.CURATOR_ROLE())).to.equal(voteTimestamp); voteTimestamp = await getNextBlockTimestamp(); - await expect(delegation.connect(operator).setOperatorFee(newOperatorFee)) + await expect(delegation.connect(nodeOperatorManager).setNodeOperatorFeeBP(newOperatorFee)) .to.emit(delegation, "RoleMemberVoted") - .withArgs(operator, await delegation.OPERATOR_ROLE(), voteTimestamp, msgData) - .and.to.emit(delegation, "OperatorFeeSet") - .withArgs(operator, previousOperatorFee, newOperatorFee); + .withArgs(nodeOperatorManager, await delegation.NODE_OPERATOR_MANAGER_ROLE(), voteTimestamp, msgData) + .and.to.emit(delegation, "NodeOperatorFeeBPSet") + .withArgs(nodeOperatorManager, previousOperatorFee, newOperatorFee); - expect(await delegation.operatorFee()).to.equal(newOperatorFee); + expect(await delegation.nodeOperatorFeeBP()).to.equal(newOperatorFee); // resets the votes for (const role of await delegation.votingCommittee()) { @@ -552,23 +583,23 @@ describe("Delegation.sol", () => { it("reverts if the caller is not a member of the operator fee committee", async () => { const newOperatorFee = 1000n; - await expect(delegation.connect(stranger).setOperatorFee(newOperatorFee)).to.be.revertedWithCustomError( + await expect(delegation.connect(stranger).setNodeOperatorFeeBP(newOperatorFee)).to.be.revertedWithCustomError( delegation, "NotACommitteeMember", ); }); it("doesn't execute if an earlier vote has expired", async () => { - const previousOperatorFee = await delegation.operatorFee(); + const previousOperatorFee = await delegation.nodeOperatorFeeBP(); const newOperatorFee = 1000n; - const msgData = delegation.interface.encodeFunctionData("setOperatorFee", [newOperatorFee]); + const msgData = delegation.interface.encodeFunctionData("setNodeOperatorFeeBP", [newOperatorFee]); const callId = keccak256(msgData); let voteTimestamp = await getNextBlockTimestamp(); - await expect(delegation.connect(curator).setOperatorFee(newOperatorFee)) + await expect(delegation.connect(curator).setNodeOperatorFeeBP(newOperatorFee)) .to.emit(delegation, "RoleMemberVoted") .withArgs(curator, await delegation.CURATOR_ROLE(), voteTimestamp, msgData); // fee is unchanged - expect(await delegation.operatorFee()).to.equal(previousOperatorFee); + expect(await delegation.nodeOperatorFeeBP()).to.equal(previousOperatorFee); // check vote expect(await delegation.votings(callId, await delegation.CURATOR_ROLE())).to.equal(voteTimestamp); @@ -576,24 +607,26 @@ describe("Delegation.sol", () => { await advanceChainTime(days(7n) + 1n); const expectedVoteTimestamp = await getNextBlockTimestamp(); expect(expectedVoteTimestamp).to.be.greaterThan(voteTimestamp + days(7n)); - await expect(delegation.connect(operator).setOperatorFee(newOperatorFee)) + await expect(delegation.connect(nodeOperatorManager).setNodeOperatorFeeBP(newOperatorFee)) .to.emit(delegation, "RoleMemberVoted") - .withArgs(operator, await delegation.OPERATOR_ROLE(), expectedVoteTimestamp, msgData); + .withArgs(nodeOperatorManager, await delegation.NODE_OPERATOR_MANAGER_ROLE(), expectedVoteTimestamp, msgData); // fee is still unchanged - expect(await delegation.operatorFee()).to.equal(previousOperatorFee); + expect(await delegation.nodeOperatorFeeBP()).to.equal(previousOperatorFee); // check vote - expect(await delegation.votings(callId, await delegation.OPERATOR_ROLE())).to.equal(expectedVoteTimestamp); + expect(await delegation.votings(callId, await delegation.NODE_OPERATOR_MANAGER_ROLE())).to.equal( + expectedVoteTimestamp, + ); // curator has to vote again voteTimestamp = await getNextBlockTimestamp(); - await expect(delegation.connect(curator).setOperatorFee(newOperatorFee)) + await expect(delegation.connect(curator).setNodeOperatorFeeBP(newOperatorFee)) .to.emit(delegation, "RoleMemberVoted") .withArgs(curator, await delegation.CURATOR_ROLE(), voteTimestamp, msgData) - .and.to.emit(delegation, "OperatorFeeSet") + .and.to.emit(delegation, "NodeOperatorFeeBPSet") .withArgs(curator, previousOperatorFee, newOperatorFee); // fee is now changed - expect(await delegation.operatorFee()).to.equal(newOperatorFee); + expect(await delegation.nodeOperatorFeeBP()).to.equal(newOperatorFee); }); }); @@ -616,9 +649,9 @@ describe("Delegation.sol", () => { expect(await vault.owner()).to.equal(delegation); voteTimestamp = await getNextBlockTimestamp(); - await expect(delegation.connect(operator).transferStVaultOwnership(newOwner)) + await expect(delegation.connect(nodeOperatorManager).transferStVaultOwnership(newOwner)) .to.emit(delegation, "RoleMemberVoted") - .withArgs(operator, await delegation.OPERATOR_ROLE(), voteTimestamp, msgData); + .withArgs(nodeOperatorManager, await delegation.NODE_OPERATOR_MANAGER_ROLE(), voteTimestamp, msgData); // owner changed expect(await vault.owner()).to.equal(newOwner); }); diff --git a/test/0.8.25/vaults/staking-vault/staking-vault.test.ts b/test/0.8.25/vaults/staking-vault/staking-vault.test.ts index eb4b27468..b08d97b6c 100644 --- a/test/0.8.25/vaults/staking-vault/staking-vault.test.ts +++ b/test/0.8.25/vaults/staking-vault/staking-vault.test.ts @@ -120,8 +120,7 @@ describe("StakingVault", () => { expect(await stakingVault.DEPOSIT_CONTRACT()).to.equal(depositContractAddress); expect(await stakingVault.getBeacon()).to.equal(vaultFactoryAddress); expect(await stakingVault.owner()).to.equal(await vaultOwner.getAddress()); - expect(await stakingVault.operator()).to.equal(operator); - + expect(await stakingVault.nodeOperator()).to.equal(operator); expect(await stakingVault.locked()).to.equal(0n); expect(await stakingVault.unlocked()).to.equal(0n); expect(await stakingVault.inOutDelta()).to.equal(0n); diff --git a/test/integration/vaults-happy-path.integration.ts b/test/integration/vaults-happy-path.integration.ts index 6725c6086..258b349ff 100644 --- a/test/integration/vaults-happy-path.integration.ts +++ b/test/integration/vaults-happy-path.integration.ts @@ -43,10 +43,10 @@ describe("Scenario: Staking Vaults Happy Path", () => { let ethHolder: HardhatEthersSigner; let owner: HardhatEthersSigner; - let operator: HardhatEthersSigner; + let nodeOperator: HardhatEthersSigner; let curator: HardhatEthersSigner; - let staker: HardhatEthersSigner; - let tokenMaster: HardhatEthersSigner; + let funderWithdrawer: HardhatEthersSigner; + let minterBurner: HardhatEthersSigner; let depositContract: string; @@ -70,7 +70,7 @@ describe("Scenario: Staking Vaults Happy Path", () => { before(async () => { ctx = await getProtocolContext(); - [ethHolder, owner, operator, curator, staker, tokenMaster] = await ethers.getSigners(); + [ethHolder, owner, nodeOperator, curator, funderWithdrawer, minterBurner] = await ethers.getSigners(); const { depositSecurityModule } = ctx.contracts; depositContract = await depositSecurityModule.DEPOSIT_CONTRACT(); @@ -160,13 +160,13 @@ describe("Scenario: Staking Vaults Happy Path", () => { // Owner can create a vault with operator as a node operator const deployTx = await stakingVaultFactory.connect(owner).createVault( { - operatorFee: VAULT_OWNER_FEE, - curatorFee: VAULT_NODE_OPERATOR_FEE, + nodeOperatorFeeBP: VAULT_OWNER_FEE, + curatorFeeBP: VAULT_NODE_OPERATOR_FEE, curator: curator, - operator: operator, - staker: staker, - tokenMaster: tokenMaster, - claimOperatorDueRole: operator, + nodeOperatorManager: nodeOperator, + funderWithdrawer: funderWithdrawer, + minterBurner: minterBurner, + nodeOperatorFeeClaimer: nodeOperator, }, "0x", ); @@ -185,28 +185,28 @@ describe("Scenario: Staking Vaults Happy Path", () => { expect(await delegation.getRoleMemberCount(await delegation.CURATOR_ROLE())).to.be.equal(1n); expect(await delegation.hasRole(await delegation.CURATOR_ROLE(), curator)).to.be.true; - expect(await delegation.getRoleMemberCount(await delegation.OPERATOR_ROLE())).to.be.equal(1n); - expect(await delegation.hasRole(await delegation.OPERATOR_ROLE(), operator)).to.be.true; + expect(await delegation.getRoleMemberCount(await delegation.NODE_OPERATOR_MANAGER_ROLE())).to.be.equal(1n); + expect(await delegation.hasRole(await delegation.NODE_OPERATOR_MANAGER_ROLE(), nodeOperator)).to.be.true; - expect(await delegation.getRoleMemberCount(await delegation.CLAIM_OPERATOR_DUE_ROLE())).to.be.equal(1n); - expect(await delegation.hasRole(await delegation.CLAIM_OPERATOR_DUE_ROLE(), operator)).to.be.true; - expect(await delegation.getRoleAdmin(await delegation.CLAIM_OPERATOR_DUE_ROLE())).to.be.equal( - await delegation.OPERATOR_ROLE(), + expect(await delegation.getRoleMemberCount(await delegation.NODE_OPERATOR_FEE_CLAIMER_ROLE())).to.be.equal(1n); + expect(await delegation.hasRole(await delegation.NODE_OPERATOR_FEE_CLAIMER_ROLE(), nodeOperator)).to.be.true; + expect(await delegation.getRoleAdmin(await delegation.NODE_OPERATOR_FEE_CLAIMER_ROLE())).to.be.equal( + await delegation.NODE_OPERATOR_MANAGER_ROLE(), ); - expect(await delegation.getRoleMemberCount(await delegation.TOKEN_MASTER_ROLE())).to.be.equal(1n); - expect(await delegation.hasRole(await delegation.TOKEN_MASTER_ROLE(), tokenMaster)).to.be.true; + expect(await delegation.getRoleMemberCount(await delegation.MINT_BURN_ROLE())).to.be.equal(1n); + expect(await delegation.hasRole(await delegation.MINT_BURN_ROLE(), minterBurner)).to.be.true; - expect(await delegation.getRoleMemberCount(await delegation.STAKER_ROLE())).to.be.equal(1n); - expect(await delegation.hasRole(await delegation.STAKER_ROLE(), staker)).to.be.true; + expect(await delegation.getRoleMemberCount(await delegation.FUND_WITHDRAW_ROLE())).to.be.equal(1n); + expect(await delegation.hasRole(await delegation.FUND_WITHDRAW_ROLE(), funderWithdrawer)).to.be.true; }); it("Should allow Owner to assign Staker and Token Master roles", async () => { - await delegation.connect(owner).grantRole(await delegation.STAKER_ROLE(), staker); - await delegation.connect(owner).grantRole(await delegation.TOKEN_MASTER_ROLE(), tokenMaster); + await delegation.connect(owner).grantRole(await delegation.FUND_WITHDRAW_ROLE(), funderWithdrawer); + await delegation.connect(owner).grantRole(await delegation.MINT_BURN_ROLE(), minterBurner); - expect(await delegation.hasRole(await delegation.STAKER_ROLE(), staker)).to.be.true; - expect(await delegation.hasRole(await delegation.TOKEN_MASTER_ROLE(), tokenMaster)).to.be.true; + expect(await delegation.hasRole(await delegation.FUND_WITHDRAW_ROLE(), funderWithdrawer)).to.be.true; + expect(await delegation.hasRole(await delegation.MINT_BURN_ROLE(), minterBurner)).to.be.true; }); it("Should allow Lido to recognize vaults and connect them to accounting", async () => { @@ -231,7 +231,7 @@ describe("Scenario: Staking Vaults Happy Path", () => { }); it("Should allow Staker to fund vault via delegation contract", async () => { - const depositTx = await delegation.connect(staker).fund({ value: VAULT_DEPOSIT }); + const depositTx = await delegation.connect(funderWithdrawer).fund({ value: VAULT_DEPOSIT }); await trace("delegation.fund", depositTx); const vaultBalance = await ethers.provider.getBalance(stakingVault); @@ -245,7 +245,9 @@ describe("Scenario: Staking Vaults Happy Path", () => { pubKeysBatch = ethers.randomBytes(Number(keysToAdd * PUBKEY_LENGTH)); signaturesBatch = ethers.randomBytes(Number(keysToAdd * SIGNATURE_LENGTH)); - const topUpTx = await stakingVault.connect(operator).depositToBeaconChain(keysToAdd, pubKeysBatch, signaturesBatch); + const topUpTx = await stakingVault + .connect(nodeOperator) + .depositToBeaconChain(keysToAdd, pubKeysBatch, signaturesBatch); await trace("stakingVault.depositToBeaconChain", topUpTx); @@ -272,12 +274,12 @@ describe("Scenario: Staking Vaults Happy Path", () => { }); // Validate minting with the cap - const mintOverLimitTx = delegation.connect(tokenMaster).mint(tokenMaster, stakingVaultMaxMintingShares + 1n); + const mintOverLimitTx = delegation.connect(minterBurner).mint(minterBurner, stakingVaultMaxMintingShares + 1n); await expect(mintOverLimitTx) .to.be.revertedWithCustomError(accounting, "InsufficientValuationToMint") .withArgs(stakingVault, stakingVault.valuation()); - const mintTx = await delegation.connect(tokenMaster).mint(tokenMaster, stakingVaultMaxMintingShares); + const mintTx = await delegation.connect(minterBurner).mint(minterBurner, stakingVaultMaxMintingShares); const mintTxReceipt = await trace("delegation.mint", mintTx); const mintEvents = ctx.getEvents(mintTxReceipt, "MintedSharesOnVault"); @@ -324,25 +326,25 @@ describe("Scenario: Staking Vaults Happy Path", () => { expect(vaultReportedEvent[0].args?.inOutDelta).to.equal(VAULT_DEPOSIT); // TODO: add assertions or locked values and rewards - expect(await delegation.curatorDue()).to.be.gt(0n); - expect(await delegation.operatorDue()).to.be.gt(0n); + expect(await delegation.curatorUnclaimedFee()).to.be.gt(0n); + expect(await delegation.nodeOperatorUnclaimedFee()).to.be.gt(0n); }); it("Should allow Operator to claim performance fees", async () => { - const performanceFee = await delegation.operatorDue(); + const performanceFee = await delegation.nodeOperatorUnclaimedFee(); log.debug("Staking Vault stats", { "Staking Vault performance fee": ethers.formatEther(performanceFee), }); - const operatorBalanceBefore = await ethers.provider.getBalance(operator); + const operatorBalanceBefore = await ethers.provider.getBalance(nodeOperator); - const claimPerformanceFeesTx = await delegation.connect(operator).claimOperatorDue(operator); + const claimPerformanceFeesTx = await delegation.connect(nodeOperator).claimNodeOperatorFee(nodeOperator); const claimPerformanceFeesTxReceipt = await trace( - "delegation.claimOperatorDue", + "delegation.claimNodeOperatorFee", claimPerformanceFeesTx, ); - const operatorBalanceAfter = await ethers.provider.getBalance(operator); + const operatorBalanceAfter = await ethers.provider.getBalance(nodeOperator); const gasFee = claimPerformanceFeesTxReceipt.gasPrice * claimPerformanceFeesTxReceipt.cumulativeGasUsed; log.debug("Operator's StETH balance", { @@ -375,7 +377,7 @@ describe("Scenario: Staking Vaults Happy Path", () => { }); it("Should allow Manager to claim manager rewards in ETH after rebase with exited validator", async () => { - const feesToClaim = await delegation.curatorDue(); + const feesToClaim = await delegation.curatorUnclaimedFee(); log.debug("Staking Vault stats after operator exit", { "Staking Vault management fee": ethers.formatEther(feesToClaim), @@ -384,8 +386,8 @@ describe("Scenario: Staking Vaults Happy Path", () => { const managerBalanceBefore = await ethers.provider.getBalance(curator); - const claimEthTx = await delegation.connect(curator).claimCuratorDue(curator); - const { gasUsed, gasPrice } = await trace("delegation.claimCuratorDue", claimEthTx); + const claimEthTx = await delegation.connect(curator).claimCuratorFee(curator); + const { gasUsed, gasPrice } = await trace("delegation.claimCuratorFee", claimEthTx); const managerBalanceAfter = await ethers.provider.getBalance(curator); const vaultBalance = await ethers.provider.getBalance(stakingVaultAddress); @@ -406,11 +408,11 @@ describe("Scenario: Staking Vaults Happy Path", () => { // Token master can approve the vault to burn the shares const approveVaultTx = await lido - .connect(tokenMaster) + .connect(minterBurner) .approve(delegation, await lido.getPooledEthByShares(stakingVaultMaxMintingShares)); await trace("lido.approve", approveVaultTx); - const burnTx = await delegation.connect(tokenMaster).burn(stakingVaultMaxMintingShares); + const burnTx = await delegation.connect(minterBurner).burn(stakingVaultMaxMintingShares); await trace("delegation.burn", burnTx); const { elapsedProtocolReward, elapsedVaultReward } = await calculateReportParams();