From 137ced50e9fa2e246fa583be8aed62ebf840e047 Mon Sep 17 00:00:00 2001 From: Azat Serikov Date: Thu, 28 Nov 2024 18:01:04 +0500 Subject: [PATCH] test: update tests --- .../vaults/StVaultOwnerWithDashboard.sol | 4 +- .../vaults/StVaultOwnerWithDelegation.sol | 12 +- contracts/0.8.25/vaults/StakingVault.sol | 2 +- contracts/0.8.25/vaults/VaultDashboard.sol | 150 -------------- contracts/0.8.25/vaults/VaultFactory.sol | 97 +++++---- contracts/0.8.25/vaults/VaultStaffRoom.sol | 189 ------------------ .../vaults/interfaces/IStakingVault.sol | 2 +- lib/proxy.ts | 34 ++-- lib/state-file.ts | 2 +- scripts/scratch/steps/0145-deploy-vaults.ts | 4 +- ... => stvault-owner-with-delegation.test.ts} | 32 +-- .../vault-delegation-layer-voting.test.ts | 136 ++++++------- test/0.8.25/vaults/vault.test.ts | 15 +- test/0.8.25/vaults/vaultFactory.test.ts | 33 +-- .../vaults-happy-path.integration.ts | 37 ++-- 15 files changed, 218 insertions(+), 531 deletions(-) delete mode 100644 contracts/0.8.25/vaults/VaultDashboard.sol delete mode 100644 contracts/0.8.25/vaults/VaultStaffRoom.sol rename test/0.8.25/vaults/{vaultStaffRoom.test.ts => stvault-owner-with-delegation.test.ts} (65%) diff --git a/contracts/0.8.25/vaults/StVaultOwnerWithDashboard.sol b/contracts/0.8.25/vaults/StVaultOwnerWithDashboard.sol index 85d98f244..b4f206397 100644 --- a/contracts/0.8.25/vaults/StVaultOwnerWithDashboard.sol +++ b/contracts/0.8.25/vaults/StVaultOwnerWithDashboard.sol @@ -205,7 +205,7 @@ contract StVaultOwnerWithDashboard is AccessControlEnumerable { * @notice Rebalances the vault by transferring ether * @param _ether Amount of ether to rebalance */ - function rebalanceVault(uint256 _ether) external virtual onlyRole(DEFAULT_ADMIN_ROLE) { + function rebalanceVault(uint256 _ether) external payable virtual onlyRole(DEFAULT_ADMIN_ROLE) fundAndProceed { _rebalanceVault(_ether); } @@ -297,7 +297,7 @@ contract StVaultOwnerWithDashboard is AccessControlEnumerable { * @param _ether Amount of ether to rebalance */ function _rebalanceVault(uint256 _ether) internal { - stakingVault.rebalance(_ether); + stakingVault.rebalance{value: msg.value}(_ether); } // ==================== Events ==================== diff --git a/contracts/0.8.25/vaults/StVaultOwnerWithDelegation.sol b/contracts/0.8.25/vaults/StVaultOwnerWithDelegation.sol index 46f48cd27..40776e36f 100644 --- a/contracts/0.8.25/vaults/StVaultOwnerWithDelegation.sol +++ b/contracts/0.8.25/vaults/StVaultOwnerWithDelegation.sol @@ -138,15 +138,15 @@ contract StVaultOwnerWithDelegation is StVaultOwnerWithDashboard, IReportReceive _grantRole(LIDO_DAO_ROLE, _defaultAdmin); /** - * The node operator in the vault must be approved by Lido DAO. - * The vault owner (`DEFAULT_ADMIN_ROLE`) cannot change the node operator. + * Only Lido DAO can assign the Lido DAO role. */ - _setRoleAdmin(OPERATOR_ROLE, LIDO_DAO_ROLE); + _setRoleAdmin(LIDO_DAO_ROLE, LIDO_DAO_ROLE); /** - * Only Lido DAO can assign the Lido DAO role. + * The node operator in the vault must be approved by Lido DAO. + * The vault owner (`DEFAULT_ADMIN_ROLE`) cannot change the node operator. */ - _setRoleAdmin(LIDO_DAO_ROLE, LIDO_DAO_ROLE); + _setRoleAdmin(OPERATOR_ROLE, LIDO_DAO_ROLE); /** * The operator role can change the key master role. @@ -358,7 +358,7 @@ contract StVaultOwnerWithDelegation is StVaultOwnerWithDashboard, IReportReceive * @notice Rebalances the vault by transferring ether. * @param _ether Amount of ether to rebalance. */ - function rebalanceVault(uint256 _ether) external override onlyRole(MANAGER_ROLE) { + function rebalanceVault(uint256 _ether) external payable override onlyRole(MANAGER_ROLE) fundAndProceed { _rebalanceVault(_ether); } diff --git a/contracts/0.8.25/vaults/StakingVault.sol b/contracts/0.8.25/vaults/StakingVault.sol index 38e9084a7..5970b3853 100644 --- a/contracts/0.8.25/vaults/StakingVault.sol +++ b/contracts/0.8.25/vaults/StakingVault.sol @@ -162,7 +162,7 @@ contract StakingVault is IStakingVault, IBeaconProxy, VaultBeaconChainDepositor, } // TODO: SHOULD THIS BE PAYABLE? - function rebalance(uint256 _ether) external { + function rebalance(uint256 _ether) external payable { if (_ether == 0) revert ZeroArgument("_ether"); if (_ether > address(this).balance) revert InsufficientBalance(address(this).balance); // TODO: should we revert on msg.value > _ether diff --git a/contracts/0.8.25/vaults/VaultDashboard.sol b/contracts/0.8.25/vaults/VaultDashboard.sol deleted file mode 100644 index 0385c5fe3..000000000 --- a/contracts/0.8.25/vaults/VaultDashboard.sol +++ /dev/null @@ -1,150 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Lido -// SPDX-License-Identifier: GPL-3.0 - -// See contracts/COMPILERS.md -pragma solidity 0.8.25; - -import {IStakingVault} from "./interfaces/IStakingVault.sol"; -import {AccessControlEnumerable} from "@openzeppelin/contracts-v5.0.2/access/extensions/AccessControlEnumerable.sol"; -import {IERC20} from "@openzeppelin/contracts-v5.0.2/token/ERC20/IERC20.sol"; -import {OwnableUpgradeable} from "contracts/openzeppelin/5.0.2/upgradeable/access/OwnableUpgradeable.sol"; -import {VaultHub} from "./VaultHub.sol"; - -// TODO: natspec -// TODO: think about the name - -contract VaultDashboard is AccessControlEnumerable { - bytes32 public constant OWNER = DEFAULT_ADMIN_ROLE; - bytes32 public constant MANAGER_ROLE = keccak256("Vault.VaultDashboard.ManagerRole"); - - IERC20 public immutable stETH; - address private immutable _SELF; - - bool public isInitialized; - IStakingVault public stakingVault; - VaultHub public vaultHub; - - constructor(address _stETH) { - if (_stETH == address(0)) revert ZeroArgument("_stETH"); - - _SELF = address(this); - stETH = IERC20(_stETH); - } - - function initialize(address _defaultAdmin, address _stakingVault) external virtual { - _initialize(_defaultAdmin, _stakingVault); - } - - function _initialize(address _defaultAdmin, address _stakingVault) internal { - if (_defaultAdmin == address(0)) revert ZeroArgument("_defaultAdmin"); - if (_stakingVault == address(0)) revert ZeroArgument("_stakingVault"); - if (isInitialized) revert AlreadyInitialized(); - - if (address(this) == _SELF) { - revert NonProxyCallsForbidden(); - } - - isInitialized = true; - - _grantRole(OWNER, _defaultAdmin); - - stakingVault = IStakingVault(_stakingVault); - vaultHub = VaultHub(stakingVault.vaultHub()); - - emit Initialized(); - } - - /// GETTERS /// - - function vaultSocket() external view returns (VaultHub.VaultSocket memory) { - return vaultHub.vaultSocket(address(stakingVault)); - } - - function shareLimit() external view returns (uint96) { - return vaultHub.vaultSocket(address(stakingVault)).shareLimit; - } - - function sharesMinted() external view returns (uint96) { - return vaultHub.vaultSocket(address(stakingVault)).sharesMinted; - } - - function reserveRatio() external view returns (uint16) { - return vaultHub.vaultSocket(address(stakingVault)).reserveRatio; - } - - function thresholdReserveRatioBP() external view returns (uint16) { - return vaultHub.vaultSocket(address(stakingVault)).reserveRatioThreshold; - } - - function treasuryFeeBP() external view returns (uint16) { - return vaultHub.vaultSocket(address(stakingVault)).treasuryFeeBP; - } - - /// VAULT MANAGEMENT /// - - function transferStakingVaultOwnership(address _newOwner) public virtual onlyRole(OWNER) { - OwnableUpgradeable(address(stakingVault)).transferOwnership(_newOwner); - } - - function disconnectFromHub() external payable onlyRole(MANAGER_ROLE) { - vaultHub.disconnectVault(address(stakingVault)); - } - - /// OPERATION /// - - function fund() external payable virtual onlyRole(MANAGER_ROLE) { - stakingVault.fund{value: msg.value}(); - } - - function withdraw(address _recipient, uint256 _ether) external virtual onlyRole(MANAGER_ROLE) { - stakingVault.withdraw(_recipient, _ether); - } - - function requestValidatorExit(bytes calldata _validatorPublicKey) external onlyRole(MANAGER_ROLE) { - stakingVault.requestValidatorExit(_validatorPublicKey); - } - - function depositToBeaconChain( - uint256 _numberOfDeposits, - bytes calldata _pubkeys, - bytes calldata _signatures - ) external virtual onlyRole(MANAGER_ROLE) { - stakingVault.depositToBeaconChain(_numberOfDeposits, _pubkeys, _signatures); - } - - /// LIQUIDITY /// - - function mint(address _recipient, uint256 _tokens) external payable virtual onlyRole(MANAGER_ROLE) fundAndProceed { - vaultHub.mintStethBackedByVault(address(stakingVault), _recipient, _tokens); - } - - function burn(uint256 _tokens) external virtual onlyRole(MANAGER_ROLE) { - stETH.transferFrom(msg.sender, address(vaultHub), _tokens); - vaultHub.burnStethBackedByVault(address(stakingVault), _tokens); - } - - /// REBALANCE /// - - function rebalanceVault(uint256 _ether) external payable virtual onlyRole(MANAGER_ROLE) fundAndProceed { - stakingVault.rebalance(_ether); - } - - /// MODIFIERS /// - - modifier fundAndProceed() { - if (msg.value > 0) { - stakingVault.fund{value: msg.value}(); - } - _; - } - - /// EVENTS /// - event Initialized(); - - /// ERRORS /// - - error ZeroArgument(string); - error InsufficientWithdrawableAmount(uint256 withdrawable, uint256 requested); - error NonProxyCallsForbidden(); - error AlreadyInitialized(); -} diff --git a/contracts/0.8.25/vaults/VaultFactory.sol b/contracts/0.8.25/vaults/VaultFactory.sol index f66190911..143b727c1 100644 --- a/contracts/0.8.25/vaults/VaultFactory.sol +++ b/contracts/0.8.25/vaults/VaultFactory.sol @@ -9,89 +9,102 @@ import {IStakingVault} from "./interfaces/IStakingVault.sol"; pragma solidity 0.8.25; -interface IVaultStaffRoom { - struct VaultStaffRoomParams { +interface IStVaultOwnerWithDelegation { + struct InitializationParams { uint256 managementFee; uint256 performanceFee; address manager; address operator; } - function OWNER() external view returns (bytes32); + function DEFAULT_ADMIN_ROLE() external view returns (bytes32); + function MANAGER_ROLE() external view returns (bytes32); + function OPERATOR_ROLE() external view returns (bytes32); + function LIDO_DAO_ROLE() external view returns (bytes32); + function initialize(address admin, address stakingVault) external; + function setManagementFee(uint256 _newManagementFee) external; + function setPerformanceFee(uint256 _newPerformanceFee) external; + function grantRole(bytes32 role, address account) external; + function revokeRole(bytes32 role, address account) external; } contract VaultFactory is UpgradeableBeacon { - - address public immutable vaultStaffRoomImpl; + address public immutable stVaultOwnerWithDelegationImpl; /// @param _owner The address of the VaultFactory owner /// @param _stakingVaultImpl The address of the StakingVault implementation - /// @param _vaultStaffRoomImpl The address of the VaultStaffRoom implementation - constructor(address _owner, address _stakingVaultImpl, address _vaultStaffRoomImpl) UpgradeableBeacon(_stakingVaultImpl, _owner) { - if (_vaultStaffRoomImpl == address(0)) revert ZeroArgument("_vaultStaffRoom"); - - vaultStaffRoomImpl = _vaultStaffRoomImpl; + /// @param _stVaultOwnerWithDelegationImpl The address of the StVaultOwnerWithDelegation implementation + constructor( + address _owner, + address _stakingVaultImpl, + address _stVaultOwnerWithDelegationImpl + ) UpgradeableBeacon(_stakingVaultImpl, _owner) { + if (_stVaultOwnerWithDelegationImpl == address(0)) revert ZeroArgument("_stVaultOwnerWithDelegation"); + + stVaultOwnerWithDelegationImpl = _stVaultOwnerWithDelegationImpl; } - /// @notice Creates a new StakingVault and VaultStaffRoom contracts + /// @notice Creates a new StakingVault and StVaultOwnerWithDelegation contracts /// @param _stakingVaultParams The params of vault initialization - /// @param _vaultStaffRoomParams The params of vault initialization + /// @param _initializationParams The params of vault initialization function createVault( bytes calldata _stakingVaultParams, - IVaultStaffRoom.VaultStaffRoomParams calldata _vaultStaffRoomParams - ) - external - returns(IStakingVault vault, IVaultStaffRoom vaultStaffRoom) - { - if (_vaultStaffRoomParams.manager == address(0)) revert ZeroArgument("manager"); - if (_vaultStaffRoomParams.operator == address(0)) revert ZeroArgument("operator"); + IStVaultOwnerWithDelegation.InitializationParams calldata _initializationParams, + address _lidoAgent + ) external returns (IStakingVault vault, IStVaultOwnerWithDelegation stVaultOwnerWithDelegation) { + if (_initializationParams.manager == address(0)) revert ZeroArgument("manager"); + if (_initializationParams.operator == address(0)) revert ZeroArgument("operator"); vault = IStakingVault(address(new BeaconProxy(address(this), ""))); - vaultStaffRoom = IVaultStaffRoom(Clones.clone(vaultStaffRoomImpl)); + stVaultOwnerWithDelegation = IStVaultOwnerWithDelegation(Clones.clone(stVaultOwnerWithDelegationImpl)); - //grant roles for factory to set fees and roles - vaultStaffRoom.initialize(address(this), address(vault)); + stVaultOwnerWithDelegation.initialize(address(this), address(vault)); - vaultStaffRoom.grantRole(vaultStaffRoom.MANAGER_ROLE(), _vaultStaffRoomParams.manager); - vaultStaffRoom.grantRole(vaultStaffRoom.OPERATOR_ROLE(), _vaultStaffRoomParams.operator); - vaultStaffRoom.grantRole(vaultStaffRoom.OWNER(), msg.sender); + stVaultOwnerWithDelegation.grantRole(stVaultOwnerWithDelegation.LIDO_DAO_ROLE(), _lidoAgent); + stVaultOwnerWithDelegation.grantRole(stVaultOwnerWithDelegation.MANAGER_ROLE(), _initializationParams.manager); + stVaultOwnerWithDelegation.grantRole( + stVaultOwnerWithDelegation.OPERATOR_ROLE(), + _initializationParams.operator + ); + stVaultOwnerWithDelegation.grantRole(stVaultOwnerWithDelegation.DEFAULT_ADMIN_ROLE(), msg.sender); - vaultStaffRoom.grantRole(vaultStaffRoom.MANAGER_ROLE(), address(this)); - vaultStaffRoom.setManagementFee(_vaultStaffRoomParams.managementFee); - vaultStaffRoom.setPerformanceFee(_vaultStaffRoomParams.performanceFee); + stVaultOwnerWithDelegation.grantRole(stVaultOwnerWithDelegation.MANAGER_ROLE(), address(this)); + stVaultOwnerWithDelegation.setManagementFee(_initializationParams.managementFee); + stVaultOwnerWithDelegation.setPerformanceFee(_initializationParams.performanceFee); //revoke roles from factory - vaultStaffRoom.revokeRole(vaultStaffRoom.MANAGER_ROLE(), address(this)); - vaultStaffRoom.revokeRole(vaultStaffRoom.OWNER(), address(this)); + stVaultOwnerWithDelegation.revokeRole(stVaultOwnerWithDelegation.MANAGER_ROLE(), address(this)); + stVaultOwnerWithDelegation.revokeRole(stVaultOwnerWithDelegation.DEFAULT_ADMIN_ROLE(), address(this)); + stVaultOwnerWithDelegation.revokeRole(stVaultOwnerWithDelegation.LIDO_DAO_ROLE(), address(this)); - vault.initialize(address(vaultStaffRoom), _stakingVaultParams); + vault.initialize(address(stVaultOwnerWithDelegation), _stakingVaultParams); - emit VaultCreated(address(vaultStaffRoom), address(vault)); - emit VaultStaffRoomCreated(msg.sender, address(vaultStaffRoom)); + emit VaultCreated(address(stVaultOwnerWithDelegation), address(vault)); + emit StVaultOwnerWithDelegationCreated(msg.sender, address(stVaultOwnerWithDelegation)); } /** - * @notice Event emitted on a Vault creation - * @param owner The address of the Vault owner - * @param vault The address of the created Vault - */ + * @notice Event emitted on a Vault creation + * @param owner The address of the Vault owner + * @param vault The address of the created Vault + */ event VaultCreated(address indexed owner, address indexed vault); /** - * @notice Event emitted on a VaultStaffRoom creation - * @param admin The address of the VaultStaffRoom admin - * @param vaultStaffRoom The address of the created VaultStaffRoom - */ - event VaultStaffRoomCreated(address indexed admin, address indexed vaultStaffRoom); + * @notice Event emitted on a StVaultOwnerWithDelegation creation + * @param admin The address of the StVaultOwnerWithDelegation admin + * @param stVaultOwnerWithDelegation The address of the created StVaultOwnerWithDelegation + */ + event StVaultOwnerWithDelegationCreated(address indexed admin, address indexed stVaultOwnerWithDelegation); error ZeroArgument(string); } diff --git a/contracts/0.8.25/vaults/VaultStaffRoom.sol b/contracts/0.8.25/vaults/VaultStaffRoom.sol deleted file mode 100644 index 217597839..000000000 --- a/contracts/0.8.25/vaults/VaultStaffRoom.sol +++ /dev/null @@ -1,189 +0,0 @@ -// SPDX-FileCopyrightText: 2024 Lido -// SPDX-License-Identifier: GPL-3.0 - -// See contracts/COMPILERS.md -pragma solidity 0.8.25; - -import {AccessControlEnumerable} from "@openzeppelin/contracts-v5.0.2/access/extensions/AccessControlEnumerable.sol"; -import {IStakingVault} from "./interfaces/IStakingVault.sol"; -import {IReportReceiver} from "./interfaces/IReportReceiver.sol"; -import {VaultDashboard} from "./VaultDashboard.sol"; -import {Math256} from "contracts/common/lib/Math256.sol"; - -// TODO: natspec -// TODO: events - -// VaultStaffRoom: Delegates vault operations to different parties: -// - Manager: manages fees -// - Staker: can fund the vault and withdraw funds -// - Operator: can claim performance due and assigns Keymaster sub-role -// - Keymaster: Operator's sub-role for depositing to beacon chain -// - Plumber: manages liquidity, i.e. mints and burns stETH -contract VaultStaffRoom is VaultDashboard, IReportReceiver { - uint256 private constant BP_BASE = 100_00; - uint256 private constant MAX_FEE = BP_BASE; - - bytes32 public constant STAKER_ROLE = keccak256("Vault.VaultStaffRoom.StakerRole"); - bytes32 public constant OPERATOR_ROLE = keccak256("Vault.VaultStaffRoom.OperatorRole"); - bytes32 public constant KEYMASTER_ROLE = keccak256("Vault.VaultStaffRoom.KeymasterRole"); - bytes32 public constant PLUMBER_ROLE = keccak256("Vault.VaultStaffRoom.PlumberRole"); - - IStakingVault.Report public lastClaimedReport; - - uint256 public managementFee; - uint256 public performanceFee; - uint256 public managementDue; - - constructor( - address _stETH - ) VaultDashboard(_stETH) { - } - - function initialize(address _defaultAdmin, address _stakingVault) external override { - _initialize(_defaultAdmin, _stakingVault); - _setRoleAdmin(KEYMASTER_ROLE, OPERATOR_ROLE); - } - - /// * * * * * VIEW FUNCTIONS * * * * * /// - - function withdrawable() public view returns (uint256) { - uint256 reserved = Math256.max(stakingVault.locked(), managementDue + performanceDue()); - uint256 value = stakingVault.valuation(); - - if (reserved > value) { - return 0; - } - - return value - reserved; - } - - function performanceDue() public view returns (uint256) { - IStakingVault.Report memory latestReport = stakingVault.latestReport(); - - int128 rewardsAccrued = int128(latestReport.valuation - lastClaimedReport.valuation) - - (latestReport.inOutDelta - lastClaimedReport.inOutDelta); - - if (rewardsAccrued > 0) { - return (uint128(rewardsAccrued) * performanceFee) / BP_BASE; - } else { - return 0; - } - } - - /// * * * * * MANAGER FUNCTIONS * * * * * /// - - function setManagementFee(uint256 _newManagementFee) external onlyRole(MANAGER_ROLE) { - if (_newManagementFee > MAX_FEE) revert NewFeeCannotExceedMaxFee(); - - managementFee = _newManagementFee; - } - - function setPerformanceFee(uint256 _newPerformanceFee) external onlyRole(MANAGER_ROLE) { - if (_newPerformanceFee > MAX_FEE) revert NewFeeCannotExceedMaxFee(); - if (performanceDue() > 0) revert PerformanceDueUnclaimed(); - - performanceFee = _newPerformanceFee; - } - - function claimManagementDue(address _recipient, bool _liquid) external onlyRole(MANAGER_ROLE) { - if (_recipient == address(0)) revert ZeroArgument("_recipient"); - - if (!stakingVault.isHealthy()) { - revert VaultNotHealthy(); - } - - uint256 due = managementDue; - - if (due > 0) { - managementDue = 0; - - if (_liquid) { - vaultHub.mintStethBackedByVault(address(stakingVault), _recipient, due); - } else { - _withdrawDue(_recipient, due); - } - } - } - - /// * * * * * FUNDER FUNCTIONS * * * * * /// - - function fund() external payable override onlyRole(STAKER_ROLE) { - stakingVault.fund{value: msg.value}(); - } - - function withdraw(address _recipient, uint256 _ether) external override onlyRole(STAKER_ROLE) { - if (_recipient == address(0)) revert ZeroArgument("_recipient"); - if (_ether == 0) revert ZeroArgument("_ether"); - if (withdrawable() < _ether) revert InsufficientWithdrawableAmount(withdrawable(), _ether); - - stakingVault.withdraw(_recipient, _ether); - } - - /// * * * * * KEYMASTER FUNCTIONS * * * * * /// - - function depositToBeaconChain( - uint256 _numberOfDeposits, - bytes calldata _pubkeys, - bytes calldata _signatures - ) external override onlyRole(KEYMASTER_ROLE) { - stakingVault.depositToBeaconChain(_numberOfDeposits, _pubkeys, _signatures); - } - - /// * * * * * OPERATOR FUNCTIONS * * * * * /// - - function claimPerformanceDue(address _recipient, bool _liquid) external onlyRole(OPERATOR_ROLE) { - if (_recipient == address(0)) revert ZeroArgument("_recipient"); - - uint256 due = performanceDue(); - - if (due > 0) { - lastClaimedReport = stakingVault.latestReport(); - - if (_liquid) { - vaultHub.mintStethBackedByVault(address(stakingVault), _recipient, due); - } else { - _withdrawDue(_recipient, due); - } - } - } - - /// * * * * * PLUMBER FUNCTIONS * * * * * /// - - function mint(address _recipient, uint256 _tokens) external payable override onlyRole(PLUMBER_ROLE) fundAndProceed { - vaultHub.mintStethBackedByVault(address(stakingVault), _recipient, _tokens); - } - - function burn(uint256 _tokens) external override onlyRole(PLUMBER_ROLE) { - stETH.transferFrom(msg.sender, address(vaultHub), _tokens); - vaultHub.burnStethBackedByVault(address(stakingVault), _tokens); - } - - /// * * * * * VAULT CALLBACK * * * * * /// - - // solhint-disable-next-line no-unused-vars - function onReport(uint256 _valuation, int256 _inOutDelta, uint256 _locked) external { - if (msg.sender != address(stakingVault)) revert OnlyVaultCanCallOnReportHook(); - - managementDue += (_valuation * managementFee) / 365 / BP_BASE; - } - - /// * * * * * INTERNAL FUNCTIONS * * * * * /// - - function _withdrawDue(address _recipient, uint256 _ether) internal { - int256 unlocked = int256(stakingVault.valuation()) - int256(stakingVault.locked()); - uint256 unreserved = unlocked >= 0 ? uint256(unlocked) : 0; - if (unreserved < _ether) revert InsufficientUnlockedAmount(unreserved, _ether); - - stakingVault.withdraw(_recipient, _ether); - } - - /// * * * * * ERRORS * * * * * /// - - error SenderHasNeitherRole(address account, bytes32 role1, bytes32 role2); - error NewFeeCannotExceedMaxFee(); - error PerformanceDueUnclaimed(); - error InsufficientUnlockedAmount(uint256 unlocked, uint256 requested); - error VaultNotHealthy(); - error OnlyVaultCanCallOnReportHook(); - error FeeCannotExceed100(); -} diff --git a/contracts/0.8.25/vaults/interfaces/IStakingVault.sol b/contracts/0.8.25/vaults/interfaces/IStakingVault.sol index c98bb40e3..989629a09 100644 --- a/contracts/0.8.25/vaults/interfaces/IStakingVault.sol +++ b/contracts/0.8.25/vaults/interfaces/IStakingVault.sol @@ -40,7 +40,7 @@ interface IStakingVault { function requestValidatorExit(bytes calldata _validatorPublicKey) external; - function rebalance(uint256 _ether) external; + function rebalance(uint256 _ether) external payable; function report(uint256 _valuation, int256 _inOutDelta, uint256 _locked) external; } diff --git a/lib/proxy.ts b/lib/proxy.ts index 1a6564f05..60dd65110 100644 --- a/lib/proxy.ts +++ b/lib/proxy.ts @@ -8,14 +8,14 @@ import { OssifiableProxy, OssifiableProxy__factory, StakingVault, + StVaultOwnerWithDelegation, VaultFactory, - VaultStaffRoom, } from "typechain-types"; import { findEventsWithInterfaces } from "lib"; -import { IVaultStaffRoom } from "../typechain-types/contracts/0.8.25/vaults/VaultFactory.sol/VaultFactory"; -import VaultStaffRoomParamsStruct = IVaultStaffRoom.VaultStaffRoomParamsStruct; +import { IStVaultOwnerWithDelegation } from "../typechain-types/contracts/0.8.25/vaults/VaultFactory.sol/VaultFactory"; +import StVaultOwnerWithDelegationInitializationParamsStruct = IStVaultOwnerWithDelegation.InitializationParamsStruct; interface ProxifyArgs { impl: T; @@ -44,22 +44,23 @@ interface CreateVaultResponse { tx: ContractTransactionResponse; proxy: BeaconProxy; vault: StakingVault; - vaultStaffRoom: VaultStaffRoom; + stVaultOwnerWithDelegation: StVaultOwnerWithDelegation; } export async function createVaultProxy( vaultFactory: VaultFactory, _owner: HardhatEthersSigner, + _lidoAgent: HardhatEthersSigner, ): Promise { // Define the parameters for the struct - const vaultStaffRoomParams: VaultStaffRoomParamsStruct = { + const initializationParams: StVaultOwnerWithDelegationInitializationParamsStruct = { managementFee: 100n, performanceFee: 200n, manager: await _owner.getAddress(), operator: await _owner.getAddress(), }; - const tx = await vaultFactory.connect(_owner).createVault("0x", vaultStaffRoomParams); + const tx = await vaultFactory.connect(_owner).createVault("0x", initializationParams, _lidoAgent); // Get the receipt manually const receipt = (await tx.wait())!; @@ -70,23 +71,28 @@ export async function createVaultProxy( const event = events[0]; const { vault } = event.args; - const vaultStaffRoomEvents = findEventsWithInterfaces(receipt, "VaultStaffRoomCreated", [vaultFactory.interface]); - if (vaultStaffRoomEvents.length === 0) throw new Error("VaultStaffRoom creation event not found"); + const stVaultOwnerWithDelegationEvents = findEventsWithInterfaces( + receipt, + "StVaultOwnerWithDelegationCreated", + [vaultFactory.interface], + ); - const { vaultStaffRoom: vaultStaffRoomAddress } = vaultStaffRoomEvents[0].args; + if (stVaultOwnerWithDelegationEvents.length === 0) throw new Error("StVaultOwnerWithDelegation creation event not found"); + + const { stVaultOwnerWithDelegation: stVaultOwnerWithDelegationAddress } = stVaultOwnerWithDelegationEvents[0].args; const proxy = (await ethers.getContractAt("BeaconProxy", vault, _owner)) as BeaconProxy; const stakingVault = (await ethers.getContractAt("StakingVault", vault, _owner)) as StakingVault; - const vaultStaffRoom = (await ethers.getContractAt( - "VaultStaffRoom", - vaultStaffRoomAddress, + const stVaultOwnerWithDelegation = (await ethers.getContractAt( + "StVaultOwnerWithDelegation", + stVaultOwnerWithDelegationAddress, _owner, - )) as VaultStaffRoom; + )) as StVaultOwnerWithDelegation; return { tx, proxy, vault: stakingVault, - vaultStaffRoom: vaultStaffRoom, + stVaultOwnerWithDelegation, }; } diff --git a/lib/state-file.ts b/lib/state-file.ts index 5530fabf4..e791a09a8 100644 --- a/lib/state-file.ts +++ b/lib/state-file.ts @@ -90,7 +90,7 @@ export enum Sk { // Vaults stakingVaultImpl = "stakingVaultImpl", stakingVaultFactory = "stakingVaultFactory", - vaultStaffRoomImpl = "vaultStaffRoomImpl", + stVaultOwnerWithDelegationImpl = "stVaultOwnerWithDelegationImpl", } export function getAddress(contractKey: Sk, state: DeploymentState): string { diff --git a/scripts/scratch/steps/0145-deploy-vaults.ts b/scripts/scratch/steps/0145-deploy-vaults.ts index 10fc0834b..645c03f60 100644 --- a/scripts/scratch/steps/0145-deploy-vaults.ts +++ b/scripts/scratch/steps/0145-deploy-vaults.ts @@ -23,8 +23,8 @@ export async function main() { ]); const impAddress = await imp.getAddress(); - // Deploy VaultStaffRoom implementation contract - const room = await deployWithoutProxy(Sk.vaultStaffRoomImpl, "VaultStaffRoom", deployer, [lidoAddress]); + // Deploy StVaultOwnerWithDelegation implementation contract + const room = await deployWithoutProxy(Sk.stVaultOwnerWithDelegationImpl, "StVaultOwnerWithDelegation", deployer, [lidoAddress]); const roomAddress = await room.getAddress(); // Deploy VaultFactory contract diff --git a/test/0.8.25/vaults/vaultStaffRoom.test.ts b/test/0.8.25/vaults/stvault-owner-with-delegation.test.ts similarity index 65% rename from test/0.8.25/vaults/vaultStaffRoom.test.ts rename to test/0.8.25/vaults/stvault-owner-with-delegation.test.ts index 96ac1b33f..fda887f3d 100644 --- a/test/0.8.25/vaults/vaultStaffRoom.test.ts +++ b/test/0.8.25/vaults/stvault-owner-with-delegation.test.ts @@ -8,9 +8,9 @@ import { LidoLocator, StakingVault, StETH__HarnessForVaultHub, + StVaultOwnerWithDelegation, VaultFactory, VaultHub, - VaultStaffRoom, } from "typechain-types"; import { certainAddress, createVaultProxy, ether } from "lib"; @@ -18,17 +18,18 @@ import { certainAddress, createVaultProxy, ether } from "lib"; import { deployLidoLocator } from "test/deploy"; import { Snapshot } from "test/suite"; -describe("VaultStaffRoom.sol", () => { +describe("StVaultOwnerWithDelegation.sol", () => { let deployer: HardhatEthersSigner; let admin: HardhatEthersSigner; let holder: HardhatEthersSigner; let stranger: HardhatEthersSigner; + let lidoAgent: HardhatEthersSigner; let vaultOwner1: HardhatEthersSigner; let depositContract: DepositContract__MockForBeaconChainDepositor; let vaultHub: VaultHub; let implOld: StakingVault; - let vaultStaffRoom: VaultStaffRoom; + let stVaultOwnerWithDelegation: StVaultOwnerWithDelegation; let vaultFactory: VaultFactory; let steth: StETH__HarnessForVaultHub; @@ -40,7 +41,7 @@ describe("VaultStaffRoom.sol", () => { const treasury = certainAddress("treasury"); before(async () => { - [deployer, admin, holder, stranger, vaultOwner1] = await ethers.getSigners(); + [deployer, admin, holder, stranger, vaultOwner1, lidoAgent] = await ethers.getSigners(); locator = await deployLidoLocator(); steth = await ethers.deployContract("StETH__HarnessForVaultHub", [holder], { @@ -52,8 +53,8 @@ describe("VaultStaffRoom.sol", () => { // VaultHub vaultHub = await ethers.deployContract("Accounting", [admin, locator, steth, treasury], { from: deployer }); implOld = await ethers.deployContract("StakingVault", [vaultHub, depositContract], { from: deployer }); - vaultStaffRoom = await ethers.deployContract("VaultStaffRoom", [steth], { from: deployer }); - vaultFactory = await ethers.deployContract("VaultFactory", [admin, implOld, vaultStaffRoom], { from: deployer }); + stVaultOwnerWithDelegation = await ethers.deployContract("StVaultOwnerWithDelegation", [steth], { from: deployer }); + vaultFactory = await ethers.deployContract("VaultFactory", [admin, implOld, stVaultOwnerWithDelegation], { from: deployer }); //add role to factory await vaultHub.connect(admin).grantRole(await vaultHub.VAULT_MASTER_ROLE(), admin); @@ -68,30 +69,33 @@ describe("VaultStaffRoom.sol", () => { context("performanceDue", () => { it("performanceDue ", async () => { - const { vaultStaffRoom: vsr } = await createVaultProxy(vaultFactory, vaultOwner1); + const { stVaultOwnerWithDelegation } = await createVaultProxy(vaultFactory, vaultOwner1, lidoAgent); - await vsr.performanceDue(); + await stVaultOwnerWithDelegation.performanceDue(); }); }); context("initialize", async () => { it("reverts if initialize from implementation", async () => { - await expect(vaultStaffRoom.initialize(admin, implOld)).to.revertedWithCustomError( - vaultStaffRoom, + await expect(stVaultOwnerWithDelegation.initialize(admin, implOld)).to.revertedWithCustomError( + stVaultOwnerWithDelegation, "NonProxyCallsForbidden", ); }); it("reverts if already initialized", async () => { - const { vault: vault1, vaultStaffRoom: vsr } = await createVaultProxy(vaultFactory, vaultOwner1); + const { vault: vault1, stVaultOwnerWithDelegation } = await createVaultProxy(vaultFactory, vaultOwner1, lidoAgent); - await expect(vsr.initialize(admin, vault1)).to.revertedWithCustomError(vsr, "AlreadyInitialized"); + await expect(stVaultOwnerWithDelegation.initialize(admin, vault1)).to.revertedWithCustomError( + stVaultOwnerWithDelegation, + "AlreadyInitialized", + ); }); it("initialize", async () => { - const { tx, vaultStaffRoom: vsr } = await createVaultProxy(vaultFactory, vaultOwner1); + const { tx, stVaultOwnerWithDelegation } = await createVaultProxy(vaultFactory, vaultOwner1, lidoAgent); - await expect(tx).to.emit(vsr, "Initialized"); + await expect(tx).to.emit(stVaultOwnerWithDelegation, "Initialized"); }); }); }); diff --git a/test/0.8.25/vaults/vault-delegation-layer-voting.test.ts b/test/0.8.25/vaults/vault-delegation-layer-voting.test.ts index abd1ebf96..497cf5972 100644 --- a/test/0.8.25/vaults/vault-delegation-layer-voting.test.ts +++ b/test/0.8.25/vaults/vault-delegation-layer-voting.test.ts @@ -3,9 +3,9 @@ import { expect } from "chai"; import { ethers } from "hardhat"; import { advanceChainTime, certainAddress, days, proxify } from "lib"; import { Snapshot } from "test/suite"; -import { StakingVault__MockForVaultDelegationLayer, VaultDelegationLayer } from "typechain-types"; +import { StakingVault__MockForVaultDelegationLayer, StVaultOwnerWithDelegation } from "typechain-types"; -describe.only("VaultDelegationLayer:Voting", () => { +describe("VaultDelegationLayer:Voting", () => { let deployer: HardhatEthersSigner; let owner: HardhatEthersSigner; let manager: HardhatEthersSigner; @@ -14,7 +14,7 @@ describe.only("VaultDelegationLayer:Voting", () => { let stranger: HardhatEthersSigner; let stakingVault: StakingVault__MockForVaultDelegationLayer; - let vaultDelegationLayer: VaultDelegationLayer; + let stVaultOwnerWithDelegation: StVaultOwnerWithDelegation; let originalState: string; @@ -23,18 +23,18 @@ describe.only("VaultDelegationLayer:Voting", () => { const steth = certainAddress("vault-delegation-layer-voting-steth"); stakingVault = await ethers.deployContract("StakingVault__MockForVaultDelegationLayer"); - const impl = await ethers.deployContract("VaultDelegationLayer", [steth]); + const impl = await ethers.deployContract("StVaultOwnerWithDelegation", [steth]); // use a regular proxy for now - [vaultDelegationLayer] = await proxify({ impl, admin: owner, caller: deployer }); + [stVaultOwnerWithDelegation] = await proxify({ impl, admin: owner, caller: deployer }); - await vaultDelegationLayer.initialize(owner, stakingVault); - expect(await vaultDelegationLayer.isInitialized()).to.be.true; - expect(await vaultDelegationLayer.hasRole(await vaultDelegationLayer.OWNER(), owner)).to.be.true; - expect(await vaultDelegationLayer.vaultHub()).to.equal(await stakingVault.vaultHub()); + await stVaultOwnerWithDelegation.initialize(owner, stakingVault); + expect(await stVaultOwnerWithDelegation.isInitialized()).to.be.true; + expect(await stVaultOwnerWithDelegation.hasRole(await stVaultOwnerWithDelegation.DEFAULT_ADMIN_ROLE(), owner)).to.be.true; + expect(await stVaultOwnerWithDelegation.vaultHub()).to.equal(await stakingVault.vaultHub()); - await stakingVault.initialize(await vaultDelegationLayer.getAddress()); + await stakingVault.initialize(await stVaultOwnerWithDelegation.getAddress()); - vaultDelegationLayer = vaultDelegationLayer.connect(owner); + stVaultOwnerWithDelegation = stVaultOwnerWithDelegation.connect(owner); }); beforeEach(async () => { @@ -47,135 +47,135 @@ describe.only("VaultDelegationLayer:Voting", () => { describe("setPerformanceFee", () => { it("reverts if the caller does not have the required role", async () => { - expect(vaultDelegationLayer.connect(stranger).setPerformanceFee(100)).to.be.revertedWithCustomError( - vaultDelegationLayer, - "UnauthorizedCaller", + expect(stVaultOwnerWithDelegation.connect(stranger).setPerformanceFee(100)).to.be.revertedWithCustomError( + stVaultOwnerWithDelegation, + "NotACommitteeMember", ); }); it("executes if called by all distinct committee members", async () => { - await vaultDelegationLayer.grantRole(await vaultDelegationLayer.MANAGER_ROLE(), manager); - await vaultDelegationLayer.grantRole(await vaultDelegationLayer.LIDO_DAO_ROLE(), lidoDao); - await vaultDelegationLayer.connect(lidoDao).grantRole(await vaultDelegationLayer.OPERATOR_ROLE(), operator); + await stVaultOwnerWithDelegation.grantRole(await stVaultOwnerWithDelegation.MANAGER_ROLE(), manager); + await stVaultOwnerWithDelegation.grantRole(await stVaultOwnerWithDelegation.LIDO_DAO_ROLE(), lidoDao); + await stVaultOwnerWithDelegation.connect(lidoDao).grantRole(await stVaultOwnerWithDelegation.OPERATOR_ROLE(), operator); - expect(await vaultDelegationLayer.hasRole(await vaultDelegationLayer.MANAGER_ROLE(), manager)).to.be.true; - expect(await vaultDelegationLayer.hasRole(await vaultDelegationLayer.OPERATOR_ROLE(), operator)).to.be.true; + expect(await stVaultOwnerWithDelegation.hasRole(await stVaultOwnerWithDelegation.MANAGER_ROLE(), manager)).to.be.true; + expect(await stVaultOwnerWithDelegation.hasRole(await stVaultOwnerWithDelegation.OPERATOR_ROLE(), operator)).to.be.true; - const previousFee = await vaultDelegationLayer.performanceFee(); + const previousFee = await stVaultOwnerWithDelegation.performanceFee(); const newFee = previousFee + 1n; // remains unchanged - await vaultDelegationLayer.connect(manager).setPerformanceFee(newFee); - expect(await vaultDelegationLayer.performanceFee()).to.equal(previousFee); + await stVaultOwnerWithDelegation.connect(manager).setPerformanceFee(newFee); + expect(await stVaultOwnerWithDelegation.performanceFee()).to.equal(previousFee); // updated - await vaultDelegationLayer.connect(operator).setPerformanceFee(newFee); - expect(await vaultDelegationLayer.performanceFee()).to.equal(newFee); + await stVaultOwnerWithDelegation.connect(operator).setPerformanceFee(newFee); + expect(await stVaultOwnerWithDelegation.performanceFee()).to.equal(newFee); }); it("executes if called by a single member with all roles", async () => { - await vaultDelegationLayer.grantRole(await vaultDelegationLayer.MANAGER_ROLE(), manager); - await vaultDelegationLayer.grantRole(await vaultDelegationLayer.LIDO_DAO_ROLE(), lidoDao); - await vaultDelegationLayer.connect(lidoDao).grantRole(await vaultDelegationLayer.OPERATOR_ROLE(), manager); + await stVaultOwnerWithDelegation.grantRole(await stVaultOwnerWithDelegation.MANAGER_ROLE(), manager); + await stVaultOwnerWithDelegation.grantRole(await stVaultOwnerWithDelegation.LIDO_DAO_ROLE(), lidoDao); + await stVaultOwnerWithDelegation.connect(lidoDao).grantRole(await stVaultOwnerWithDelegation.OPERATOR_ROLE(), manager); - const previousFee = await vaultDelegationLayer.performanceFee(); + const previousFee = await stVaultOwnerWithDelegation.performanceFee(); const newFee = previousFee + 1n; // updated with a single transaction - await vaultDelegationLayer.connect(manager).setPerformanceFee(newFee); - expect(await vaultDelegationLayer.performanceFee()).to.equal(newFee); + await stVaultOwnerWithDelegation.connect(manager).setPerformanceFee(newFee); + expect(await stVaultOwnerWithDelegation.performanceFee()).to.equal(newFee); }) it("does not execute if the vote is expired", async () => { - await vaultDelegationLayer.grantRole(await vaultDelegationLayer.MANAGER_ROLE(), manager); - await vaultDelegationLayer.grantRole(await vaultDelegationLayer.LIDO_DAO_ROLE(), lidoDao); - await vaultDelegationLayer.connect(lidoDao).grantRole(await vaultDelegationLayer.OPERATOR_ROLE(), operator); + await stVaultOwnerWithDelegation.grantRole(await stVaultOwnerWithDelegation.MANAGER_ROLE(), manager); + await stVaultOwnerWithDelegation.grantRole(await stVaultOwnerWithDelegation.LIDO_DAO_ROLE(), lidoDao); + await stVaultOwnerWithDelegation.connect(lidoDao).grantRole(await stVaultOwnerWithDelegation.OPERATOR_ROLE(), operator); - expect(await vaultDelegationLayer.hasRole(await vaultDelegationLayer.MANAGER_ROLE(), manager)).to.be.true; - expect(await vaultDelegationLayer.hasRole(await vaultDelegationLayer.OPERATOR_ROLE(), operator)).to.be.true; + expect(await stVaultOwnerWithDelegation.hasRole(await stVaultOwnerWithDelegation.MANAGER_ROLE(), manager)).to.be.true; + expect(await stVaultOwnerWithDelegation.hasRole(await stVaultOwnerWithDelegation.OPERATOR_ROLE(), operator)).to.be.true; - const previousFee = await vaultDelegationLayer.performanceFee(); + const previousFee = await stVaultOwnerWithDelegation.performanceFee(); const newFee = previousFee + 1n; // remains unchanged - await vaultDelegationLayer.connect(manager).setPerformanceFee(newFee); - expect(await vaultDelegationLayer.performanceFee()).to.equal(previousFee); + await stVaultOwnerWithDelegation.connect(manager).setPerformanceFee(newFee); + expect(await stVaultOwnerWithDelegation.performanceFee()).to.equal(previousFee); await advanceChainTime(days(7n) + 1n); // remains unchanged - await vaultDelegationLayer.connect(operator).setPerformanceFee(newFee); - expect(await vaultDelegationLayer.performanceFee()).to.equal(previousFee); + await stVaultOwnerWithDelegation.connect(operator).setPerformanceFee(newFee); + expect(await stVaultOwnerWithDelegation.performanceFee()).to.equal(previousFee); }); }); describe("transferStakingVaultOwnership", () => { it("reverts if the caller does not have the required role", async () => { - expect(vaultDelegationLayer.connect(stranger).transferStakingVaultOwnership(certainAddress("vault-delegation-layer-voting-new-owner"))).to.be.revertedWithCustomError( - vaultDelegationLayer, - "UnauthorizedCaller", + expect(stVaultOwnerWithDelegation.connect(stranger).transferStVaultOwnership(certainAddress("vault-delegation-layer-voting-new-owner"))).to.be.revertedWithCustomError( + stVaultOwnerWithDelegation, + "NotACommitteeMember", ); }); it("executes if called by all distinct committee members", async () => { - await vaultDelegationLayer.grantRole(await vaultDelegationLayer.MANAGER_ROLE(), manager); - await vaultDelegationLayer.grantRole(await vaultDelegationLayer.LIDO_DAO_ROLE(), lidoDao); - await vaultDelegationLayer.connect(lidoDao).grantRole(await vaultDelegationLayer.OPERATOR_ROLE(), operator); + await stVaultOwnerWithDelegation.grantRole(await stVaultOwnerWithDelegation.MANAGER_ROLE(), manager); + await stVaultOwnerWithDelegation.grantRole(await stVaultOwnerWithDelegation.LIDO_DAO_ROLE(), lidoDao); + await stVaultOwnerWithDelegation.connect(lidoDao).grantRole(await stVaultOwnerWithDelegation.OPERATOR_ROLE(), operator); - expect(await vaultDelegationLayer.hasRole(await vaultDelegationLayer.MANAGER_ROLE(), manager)).to.be.true; - expect(await vaultDelegationLayer.hasRole(await vaultDelegationLayer.OPERATOR_ROLE(), operator)).to.be.true; + expect(await stVaultOwnerWithDelegation.hasRole(await stVaultOwnerWithDelegation.MANAGER_ROLE(), manager)).to.be.true; + expect(await stVaultOwnerWithDelegation.hasRole(await stVaultOwnerWithDelegation.OPERATOR_ROLE(), operator)).to.be.true; const newOwner = certainAddress("vault-delegation-layer-voting-new-owner"); // remains unchanged - await vaultDelegationLayer.connect(manager).transferStakingVaultOwnership(newOwner); - expect(await stakingVault.owner()).to.equal(vaultDelegationLayer); + await stVaultOwnerWithDelegation.connect(manager).transferStVaultOwnership(newOwner); + expect(await stakingVault.owner()).to.equal(stVaultOwnerWithDelegation); // remains unchanged - await vaultDelegationLayer.connect(operator).transferStakingVaultOwnership(newOwner); - expect(await stakingVault.owner()).to.equal(vaultDelegationLayer); + await stVaultOwnerWithDelegation.connect(operator).transferStVaultOwnership(newOwner); + expect(await stakingVault.owner()).to.equal(stVaultOwnerWithDelegation); // updated - await vaultDelegationLayer.connect(lidoDao).transferStakingVaultOwnership(newOwner); + await stVaultOwnerWithDelegation.connect(lidoDao).transferStVaultOwnership(newOwner); expect(await stakingVault.owner()).to.equal(newOwner); }); it("executes if called by a single member with all roles", async () => { - await vaultDelegationLayer.grantRole(await vaultDelegationLayer.MANAGER_ROLE(), lidoDao); - await vaultDelegationLayer.grantRole(await vaultDelegationLayer.LIDO_DAO_ROLE(), lidoDao); - await vaultDelegationLayer.connect(lidoDao).grantRole(await vaultDelegationLayer.OPERATOR_ROLE(), lidoDao); + await stVaultOwnerWithDelegation.grantRole(await stVaultOwnerWithDelegation.MANAGER_ROLE(), lidoDao); + await stVaultOwnerWithDelegation.grantRole(await stVaultOwnerWithDelegation.LIDO_DAO_ROLE(), lidoDao); + await stVaultOwnerWithDelegation.connect(lidoDao).grantRole(await stVaultOwnerWithDelegation.OPERATOR_ROLE(), lidoDao); const newOwner = certainAddress("vault-delegation-layer-voting-new-owner"); // updated with a single transaction - await vaultDelegationLayer.connect(lidoDao).transferStakingVaultOwnership(newOwner); + await stVaultOwnerWithDelegation.connect(lidoDao).transferStVaultOwnership(newOwner); expect(await stakingVault.owner()).to.equal(newOwner); }) it("does not execute if the vote is expired", async () => { - await vaultDelegationLayer.grantRole(await vaultDelegationLayer.MANAGER_ROLE(), manager); - await vaultDelegationLayer.grantRole(await vaultDelegationLayer.LIDO_DAO_ROLE(), lidoDao); - await vaultDelegationLayer.connect(lidoDao).grantRole(await vaultDelegationLayer.OPERATOR_ROLE(), operator); + await stVaultOwnerWithDelegation.grantRole(await stVaultOwnerWithDelegation.MANAGER_ROLE(), manager); + await stVaultOwnerWithDelegation.grantRole(await stVaultOwnerWithDelegation.LIDO_DAO_ROLE(), lidoDao); + await stVaultOwnerWithDelegation.connect(lidoDao).grantRole(await stVaultOwnerWithDelegation.OPERATOR_ROLE(), operator); - expect(await vaultDelegationLayer.hasRole(await vaultDelegationLayer.MANAGER_ROLE(), manager)).to.be.true; - expect(await vaultDelegationLayer.hasRole(await vaultDelegationLayer.OPERATOR_ROLE(), operator)).to.be.true; + expect(await stVaultOwnerWithDelegation.hasRole(await stVaultOwnerWithDelegation.MANAGER_ROLE(), manager)).to.be.true; + expect(await stVaultOwnerWithDelegation.hasRole(await stVaultOwnerWithDelegation.OPERATOR_ROLE(), operator)).to.be.true; const newOwner = certainAddress("vault-delegation-layer-voting-new-owner"); // remains unchanged - await vaultDelegationLayer.connect(manager).transferStakingVaultOwnership(newOwner); - expect(await stakingVault.owner()).to.equal(vaultDelegationLayer); + await stVaultOwnerWithDelegation.connect(manager).transferStVaultOwnership(newOwner); + expect(await stakingVault.owner()).to.equal(stVaultOwnerWithDelegation); // remains unchanged - await vaultDelegationLayer.connect(operator).transferStakingVaultOwnership(newOwner); - expect(await stakingVault.owner()).to.equal(vaultDelegationLayer); + await stVaultOwnerWithDelegation.connect(operator).transferStVaultOwnership(newOwner); + expect(await stakingVault.owner()).to.equal(stVaultOwnerWithDelegation); await advanceChainTime(days(7n) + 1n); // remains unchanged - await vaultDelegationLayer.connect(lidoDao).transferStakingVaultOwnership(newOwner); - expect(await stakingVault.owner()).to.equal(vaultDelegationLayer); + await stVaultOwnerWithDelegation.connect(lidoDao).transferStVaultOwnership(newOwner); + expect(await stakingVault.owner()).to.equal(stVaultOwnerWithDelegation); }); }); }); diff --git a/test/0.8.25/vaults/vault.test.ts b/test/0.8.25/vaults/vault.test.ts index 3dc531fb4..510d9087a 100644 --- a/test/0.8.25/vaults/vault.test.ts +++ b/test/0.8.25/vaults/vault.test.ts @@ -9,9 +9,9 @@ import { StakingVault, StakingVault__factory, StETH__HarnessForVaultHub, + StVaultOwnerWithDelegation, VaultFactory, VaultHub__MockForVault, - VaultStaffRoom, } from "typechain-types"; import { createVaultProxy, ether, impersonate } from "lib"; @@ -24,6 +24,7 @@ describe("StakingVault.sol", async () => { let executionLayerRewardsSender: HardhatEthersSigner; let stranger: HardhatEthersSigner; let holder: HardhatEthersSigner; + let lidoAgent: HardhatEthersSigner; let delegatorSigner: HardhatEthersSigner; let vaultHub: VaultHub__MockForVault; @@ -32,13 +33,13 @@ describe("StakingVault.sol", async () => { let stakingVault: StakingVault; let steth: StETH__HarnessForVaultHub; let vaultFactory: VaultFactory; - let vaultStaffRoomImpl: VaultStaffRoom; + let stVaulOwnerWithDelegation: StVaultOwnerWithDelegation; let vaultProxy: StakingVault; let originalState: string; before(async () => { - [deployer, owner, executionLayerRewardsSender, stranger, holder] = await ethers.getSigners(); + [deployer, owner, executionLayerRewardsSender, stranger, holder, lidoAgent] = await ethers.getSigners(); vaultHub = await ethers.deployContract("VaultHub__MockForVault", { from: deployer }); steth = await ethers.deployContract("StETH__HarnessForVaultHub", [holder], { @@ -51,16 +52,16 @@ describe("StakingVault.sol", async () => { vaultCreateFactory = new StakingVault__factory(owner); stakingVault = await ethers.getContractFactory("StakingVault").then((f) => f.deploy(vaultHub, depositContract)); - vaultStaffRoomImpl = await ethers.deployContract("VaultStaffRoom", [steth], { from: deployer }); + stVaulOwnerWithDelegation = await ethers.deployContract("StVaultOwnerWithDelegation", [steth], { from: deployer }); - vaultFactory = await ethers.deployContract("VaultFactory", [deployer, stakingVault, vaultStaffRoomImpl], { + vaultFactory = await ethers.deployContract("VaultFactory", [deployer, stakingVault, stVaulOwnerWithDelegation], { from: deployer, }); - const { vault, vaultStaffRoom } = await createVaultProxy(vaultFactory, owner); + const { vault, stVaultOwnerWithDelegation } = await createVaultProxy(vaultFactory, owner, lidoAgent); vaultProxy = vault; - delegatorSigner = await impersonate(await vaultStaffRoom.getAddress(), ether("100.0")); + delegatorSigner = await impersonate(await stVaultOwnerWithDelegation.getAddress(), ether("100.0")); }); beforeEach(async () => (originalState = await Snapshot.take())); diff --git a/test/0.8.25/vaults/vaultFactory.test.ts b/test/0.8.25/vaults/vaultFactory.test.ts index 4c6111012..64161862d 100644 --- a/test/0.8.25/vaults/vaultFactory.test.ts +++ b/test/0.8.25/vaults/vaultFactory.test.ts @@ -12,7 +12,7 @@ import { StETH__HarnessForVaultHub, VaultFactory, VaultHub, - VaultStaffRoom, + StVaultOwnerWithDelegation, } from "typechain-types"; import { certainAddress, createVaultProxy, ether } from "lib"; @@ -25,6 +25,7 @@ describe("VaultFactory.sol", () => { let admin: HardhatEthersSigner; let holder: HardhatEthersSigner; let stranger: HardhatEthersSigner; + let lidoAgent: HardhatEthersSigner; let vaultOwner1: HardhatEthersSigner; let vaultOwner2: HardhatEthersSigner; @@ -32,7 +33,7 @@ describe("VaultFactory.sol", () => { let vaultHub: VaultHub; let implOld: StakingVault; let implNew: StakingVault__HarnessForTestUpgrade; - let vaultStaffRoom: VaultStaffRoom; + let stVaultOwnerWithDelegation: StVaultOwnerWithDelegation; let vaultFactory: VaultFactory; let steth: StETH__HarnessForVaultHub; @@ -44,7 +45,7 @@ describe("VaultFactory.sol", () => { const treasury = certainAddress("treasury"); before(async () => { - [deployer, admin, holder, stranger, vaultOwner1, vaultOwner2] = await ethers.getSigners(); + [deployer, admin, holder, stranger, vaultOwner1, vaultOwner2, lidoAgent] = await ethers.getSigners(); locator = await deployLidoLocator(); steth = await ethers.deployContract("StETH__HarnessForVaultHub", [holder], { @@ -59,8 +60,8 @@ describe("VaultFactory.sol", () => { implNew = await ethers.deployContract("StakingVault__HarnessForTestUpgrade", [vaultHub, depositContract], { from: deployer, }); - vaultStaffRoom = await ethers.deployContract("VaultStaffRoom", [steth], { from: deployer }); - vaultFactory = await ethers.deployContract("VaultFactory", [admin, implOld, vaultStaffRoom], { from: deployer }); + stVaultOwnerWithDelegation = await ethers.deployContract("StVaultOwnerWithDelegation", [steth], { from: deployer }); + vaultFactory = await ethers.deployContract("VaultFactory", [admin, implOld, stVaultOwnerWithDelegation], { from: deployer }); //add role to factory await vaultHub.connect(admin).grantRole(await vaultHub.VAULT_MASTER_ROLE(), admin); @@ -86,10 +87,10 @@ describe("VaultFactory.sol", () => { .withArgs(ZeroAddress); }); - it("reverts if `_vaultStaffRoom` is zero address", async () => { + it("reverts if `_stVaultOwnerWithDelegation` is zero address", async () => { await expect(ethers.deployContract("VaultFactory", [admin, implOld, ZeroAddress], { from: deployer })) .to.be.revertedWithCustomError(vaultFactory, "ZeroArgument") - .withArgs("_vaultStaffRoom"); + .withArgs("_stVaultOwnerWithDelegation"); }); it("works and emit `OwnershipTransferred`, `Upgraded` events", async () => { @@ -112,21 +113,21 @@ describe("VaultFactory.sol", () => { context("createVault", () => { it("works with empty `params`", async () => { - const { tx, vault, vaultStaffRoom: vsr } = await createVaultProxy(vaultFactory, vaultOwner1); + const { tx, vault, stVaultOwnerWithDelegation } = await createVaultProxy(vaultFactory, vaultOwner1, lidoAgent); await expect(tx) .to.emit(vaultFactory, "VaultCreated") - .withArgs(await vsr.getAddress(), await vault.getAddress()); + .withArgs(await stVaultOwnerWithDelegation.getAddress(), await vault.getAddress()); await expect(tx) - .to.emit(vaultFactory, "VaultStaffRoomCreated") - .withArgs(await vaultOwner1.getAddress(), await vsr.getAddress()); + .to.emit(vaultFactory, "StVaultOwnerWithDelegationCreated") + .withArgs(await vaultOwner1.getAddress(), await stVaultOwnerWithDelegation.getAddress()); - expect(await vsr.getAddress()).to.eq(await vault.owner()); + expect(await stVaultOwnerWithDelegation.getAddress()).to.eq(await vault.owner()); expect(await vault.getBeacon()).to.eq(await vaultFactory.getAddress()); }); - it("works with non-empty `params`", async () => {}); + it("works with non-empty `params`", async () => { }); }); context("connect", () => { @@ -148,8 +149,8 @@ describe("VaultFactory.sol", () => { }; //create vault - const { vault: vault1, vaultStaffRoom: delegator1 } = await createVaultProxy(vaultFactory, vaultOwner1); - const { vault: vault2, vaultStaffRoom: delegator2 } = await createVaultProxy(vaultFactory, vaultOwner2); + const { vault: vault1, stVaultOwnerWithDelegation: delegator1 } = await createVaultProxy(vaultFactory, vaultOwner1, lidoAgent); + const { vault: vault2, stVaultOwnerWithDelegation: delegator2 } = await createVaultProxy(vaultFactory, vaultOwner2, lidoAgent); //owner of vault is delegator expect(await delegator1.getAddress()).to.eq(await vault1.owner()); @@ -223,7 +224,7 @@ describe("VaultFactory.sol", () => { expect(implAfter).to.eq(await implNew.getAddress()); //create new vault with new implementation - const { vault: vault3 } = await createVaultProxy(vaultFactory, vaultOwner1); + const { vault: vault3 } = await createVaultProxy(vaultFactory, vaultOwner1, lidoAgent); //we upgrade implementation and do not add it to whitelist await expect( diff --git a/test/integration/vaults-happy-path.integration.ts b/test/integration/vaults-happy-path.integration.ts index 6d9bd801f..391e2bf0f 100644 --- a/test/integration/vaults-happy-path.integration.ts +++ b/test/integration/vaults-happy-path.integration.ts @@ -4,7 +4,7 @@ import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; -import { StakingVault, VaultStaffRoom } from "typechain-types"; +import { StakingVault, StVaultOwnerWithDelegation } from "typechain-types"; import { impersonate, log, trace, updateBalance } from "lib"; import { getProtocolContext, ProtocolContext } from "lib/protocol"; @@ -45,6 +45,7 @@ describe("Scenario: Staking Vaults Happy Path", () => { let alice: HardhatEthersSigner; let bob: HardhatEthersSigner; let mario: HardhatEthersSigner; + let lidoAgent: HardhatEthersSigner; let depositContract: string; @@ -54,7 +55,7 @@ describe("Scenario: Staking Vaults Happy Path", () => { let vault101: StakingVault; let vault101Address: string; - let vault101AdminContract: VaultStaffRoom; + let vault101AdminContract: StVaultOwnerWithDelegation; let vault101BeaconBalance = 0n; let vault101MintingMaximum = 0n; @@ -68,7 +69,7 @@ describe("Scenario: Staking Vaults Happy Path", () => { before(async () => { ctx = await getProtocolContext(); - [ethHolder, alice, bob, mario] = await ethers.getSigners(); + [ethHolder, alice, bob, mario, lidoAgent] = await ethers.getSigners(); const { depositSecurityModule } = ctx.contracts; depositContract = await depositSecurityModule.DEPOSIT_CONTRACT(); @@ -138,10 +139,10 @@ describe("Scenario: Staking Vaults Happy Path", () => { const { stakingVaultFactory } = ctx.contracts; const implAddress = await stakingVaultFactory.implementation(); - const adminContractImplAddress = await stakingVaultFactory.vaultStaffRoomImpl(); + const adminContractImplAddress = await stakingVaultFactory.stVaultOwnerWithDelegationImpl(); const vaultImpl = await ethers.getContractAt("StakingVault", implAddress); - const vaultFactoryAdminContract = await ethers.getContractAt("VaultStaffRoom", adminContractImplAddress); + const vaultFactoryAdminContract = await ethers.getContractAt("StVaultOwnerWithDelegation", adminContractImplAddress); expect(await vaultImpl.VAULT_HUB()).to.equal(ctx.contracts.accounting.address); expect(await vaultImpl.DEPOSIT_CONTRACT()).to.equal(depositContract); @@ -159,7 +160,7 @@ describe("Scenario: Staking Vaults Happy Path", () => { performanceFee: VAULT_NODE_OPERATOR_FEE, manager: alice, operator: bob, - }); + }, lidoAgent); const createVaultTxReceipt = await trace("vaultsFactory.createVault", deployTx); const createVaultEvents = ctx.getEvents(createVaultTxReceipt, "VaultCreated"); @@ -167,31 +168,31 @@ describe("Scenario: Staking Vaults Happy Path", () => { expect(createVaultEvents.length).to.equal(1n); vault101 = await ethers.getContractAt("StakingVault", createVaultEvents[0].args?.vault); - vault101AdminContract = await ethers.getContractAt("VaultStaffRoom", createVaultEvents[0].args?.owner); + vault101AdminContract = await ethers.getContractAt("StVaultOwnerWithDelegation", createVaultEvents[0].args?.owner); - expect(await vault101AdminContract.hasRole(await vault101AdminContract.OWNER(), alice)).to.be.true; + expect(await vault101AdminContract.hasRole(await vault101AdminContract.DEFAULT_ADMIN_ROLE(), alice)).to.be.true; expect(await vault101AdminContract.hasRole(await vault101AdminContract.MANAGER_ROLE(), alice)).to.be.true; expect(await vault101AdminContract.hasRole(await vault101AdminContract.OPERATOR_ROLE(), bob)).to.be.true; - expect(await vault101AdminContract.hasRole(await vault101AdminContract.KEYMASTER_ROLE(), alice)).to.be.false; - expect(await vault101AdminContract.hasRole(await vault101AdminContract.KEYMASTER_ROLE(), bob)).to.be.false; + expect(await vault101AdminContract.hasRole(await vault101AdminContract.KEY_MASTER_ROLE(), alice)).to.be.false; + expect(await vault101AdminContract.hasRole(await vault101AdminContract.KEY_MASTER_ROLE(), bob)).to.be.false; - expect(await vault101AdminContract.hasRole(await vault101AdminContract.PLUMBER_ROLE(), alice)).to.be.false; - expect(await vault101AdminContract.hasRole(await vault101AdminContract.PLUMBER_ROLE(), bob)).to.be.false; + expect(await vault101AdminContract.hasRole(await vault101AdminContract.TOKEN_MASTER_ROLE(), alice)).to.be.false; + expect(await vault101AdminContract.hasRole(await vault101AdminContract.TOKEN_MASTER_ROLE(), bob)).to.be.false; }); it("Should allow Alice to assign staker and plumber roles", async () => { await vault101AdminContract.connect(alice).grantRole(await vault101AdminContract.STAKER_ROLE(), alice); - await vault101AdminContract.connect(alice).grantRole(await vault101AdminContract.PLUMBER_ROLE(), mario); + await vault101AdminContract.connect(alice).grantRole(await vault101AdminContract.TOKEN_MASTER_ROLE(), mario); - expect(await vault101AdminContract.hasRole(await vault101AdminContract.PLUMBER_ROLE(), mario)).to.be.true; - expect(await vault101AdminContract.hasRole(await vault101AdminContract.PLUMBER_ROLE(), mario)).to.be.true; + expect(await vault101AdminContract.hasRole(await vault101AdminContract.TOKEN_MASTER_ROLE(), mario)).to.be.true; + expect(await vault101AdminContract.hasRole(await vault101AdminContract.TOKEN_MASTER_ROLE(), mario)).to.be.true; }); it("Should allow Bob to assign the keymaster role", async () => { - await vault101AdminContract.connect(bob).grantRole(await vault101AdminContract.KEYMASTER_ROLE(), bob); + await vault101AdminContract.connect(bob).grantRole(await vault101AdminContract.KEY_MASTER_ROLE(), bob); - expect(await vault101AdminContract.hasRole(await vault101AdminContract.KEYMASTER_ROLE(), bob)).to.be.true; + expect(await vault101AdminContract.hasRole(await vault101AdminContract.KEY_MASTER_ROLE(), bob)).to.be.true; }); it("Should allow Lido to recognize vaults and connect them to accounting", async () => { @@ -444,7 +445,7 @@ describe("Scenario: Staking Vaults Happy Path", () => { }); it("Should allow Alice to disconnect vaults from the hub providing the debt in ETH", async () => { - const disconnectTx = await vault101AdminContract.connect(alice).disconnectFromHub(); + const disconnectTx = await vault101AdminContract.connect(alice).disconnectFromVaultHub(); const disconnectTxReceipt = await trace("vault.disconnectFromHub", disconnectTx); const disconnectEvents = ctx.getEvents(disconnectTxReceipt, "VaultDisconnected");