From 5d5e9cb7e388d363ead2b87be7414e496c716b04 Mon Sep 17 00:00:00 2001 From: Domen Grabec Date: Thu, 18 Apr 2024 17:15:48 +0200 Subject: [PATCH] add functions to register and exit/remove the ssv validator --- .../contracts/interfaces/IDepositContract.sol | 34 +++ .../mocks/BeaconChainDepositContractMock.sol | 37 ++++ .../NativeStakingSSVStrategy.sol | 22 +- .../NativeStaking/ValidatorAccountant.sol | 15 +- .../NativeStaking/ValidatorRegistrator.sol | 207 +++++++++++++----- contracts/deploy/000_mock.js | 4 + contracts/deploy/001_core.js | 1 + contracts/deploy/091_native_ssv_staking.js | 11 +- contracts/test/_hot-deploy.js | 13 +- contracts/test/helpers.js | 3 + contracts/test/strategies/nativeSSVStaking.js | 4 +- contracts/utils/addresses.js | 1 + 12 files changed, 272 insertions(+), 80 deletions(-) create mode 100644 contracts/contracts/interfaces/IDepositContract.sol create mode 100644 contracts/contracts/mocks/BeaconChainDepositContractMock.sol diff --git a/contracts/contracts/interfaces/IDepositContract.sol b/contracts/contracts/interfaces/IDepositContract.sol new file mode 100644 index 0000000000..9d62b5d776 --- /dev/null +++ b/contracts/contracts/interfaces/IDepositContract.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +interface IDepositContract { + /// @notice A processed deposit event. + event DepositEvent( + bytes pubkey, + bytes withdrawal_credentials, + bytes amount, + bytes signature, + bytes index + ); + + /// @notice Submit a Phase 0 DepositData object. + /// @param pubkey A BLS12-381 public key. + /// @param withdrawal_credentials Commitment to a public key for withdrawals. + /// @param signature A BLS12-381 signature. + /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object. + /// Used as a protection against malformed input. + function deposit( + bytes calldata pubkey, + bytes calldata withdrawal_credentials, + bytes calldata signature, + bytes32 deposit_data_root + ) external payable; + + /// @notice Query the current deposit root hash. + /// @return The deposit root hash. + function get_deposit_root() external view returns (bytes32); + + /// @notice Query the current deposit count. + /// @return The deposit count encoded as a little endian 64-bit number. + function get_deposit_count() external view returns (bytes memory); +} \ No newline at end of file diff --git a/contracts/contracts/mocks/BeaconChainDepositContractMock.sol b/contracts/contracts/mocks/BeaconChainDepositContractMock.sol new file mode 100644 index 0000000000..d2e148cf46 --- /dev/null +++ b/contracts/contracts/mocks/BeaconChainDepositContractMock.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract BeaconChainDepositContractMock { + /// @notice A processed deposit event. + event DepositEvent( + bytes pubkey, + bytes withdrawal_credentials, + bytes amount, + bytes signature, + bytes index + ); + + /// @notice Submit a Phase 0 DepositData object. + /// @param pubkey A BLS12-381 public key. + /// @param withdrawal_credentials Commitment to a public key for withdrawals. + /// @param signature A BLS12-381 signature. + /// @param deposit_data_root The SHA-256 hash of the SSZ-encoded DepositData object. + /// Used as a protection against malformed input. + function deposit( + bytes calldata pubkey, + bytes calldata withdrawal_credentials, + bytes calldata signature, + bytes32 deposit_data_root + ) external payable { + // Extended ABI length checks since dynamic types are used. + require(pubkey.length == 48, "DepositContract: invalid pubkey length"); + require(withdrawal_credentials.length == 32, "DepositContract: invalid withdrawal_credentials length"); + require(signature.length == 96, "DepositContract: invalid signature length"); + + // Check deposit amount + require(msg.value >= 1 ether, "DepositContract: deposit value too low"); + require(msg.value % 1 gwei == 0, "DepositContract: deposit value not multiple of gwei"); + uint deposit_amount = msg.value / 1 gwei; + require(deposit_amount <= type(uint64).max, "DepositContract: deposit value too high"); + } +} \ No newline at end of file diff --git a/contracts/contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol b/contracts/contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol index 02df278590..7d06295e35 100644 --- a/contracts/contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol +++ b/contracts/contracts/strategies/NativeStaking/NativeStakingSSVStrategy.sol @@ -6,9 +6,9 @@ import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.s import { InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol"; import { IWETH9 } from "../../interfaces/IWETH9.sol"; -import { ISSVNetwork, Cluster } from "../../interfaces/ISSVNetwork.sol"; import { FeeAccumulator } from "./FeeAccumulator.sol"; import { ValidatorAccountant } from "./ValidatorAccountant.sol"; +import { Cluster } from "../../interfaces/ISSVNetwork.sol"; struct ValidatorStakeData { bytes pubkey; @@ -27,8 +27,6 @@ contract NativeStakingSSVStrategy is /// @notice SSV ERC20 token that serves as a payment for operating SSV validators address public immutable SSV_TOKEN_ADDRESS; - /// @notice SSV Network contract used to interface with - address public immutable SSV_NETWORK_ADDRESS; /// @notice Fee collector address /// @dev this address will receive Execution layer rewards - These are rewards earned for /// executing transactions on the Ethereum network as part of block proposals. They include @@ -51,18 +49,20 @@ contract NativeStakingSSVStrategy is /// @param _wethAddress Address of the Erc20 WETH Token contract /// @param _ssvToken Address of the Erc20 SSV Token contract /// @param _ssvNetwork Address of the SSV Network contract + /// @param _feeAccumulator Address of the fee accumulator receiving execution layer validator rewards + /// @param _beaconChainDepositContract Address of the beacon chain deposit contract constructor( BaseStrategyConfig memory _baseConfig, address _wethAddress, address _ssvToken, address _ssvNetwork, - address _feeAccumulator + address _feeAccumulator, + address _beaconChainDepositContract ) InitializableAbstractStrategy(_baseConfig) - ValidatorAccountant(_wethAddress, _baseConfig.vaultAddress) + ValidatorAccountant(_wethAddress, _baseConfig.vaultAddress, _beaconChainDepositContract, _ssvNetwork) { SSV_TOKEN_ADDRESS = _ssvToken; - SSV_NETWORK_ADDRESS = _ssvNetwork; FEE_ACCUMULATOR_ADDRESS = _feeAccumulator; } @@ -270,13 +270,17 @@ contract NativeStakingSSVStrategy is ); } - /// @dev Deposits more SSV Tokens to the SSV Network contract which is used to pay the SSV Operators - /// A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds + /// @notice Deposits more SSV Tokens to the SSV Network contract which is used to pay the SSV Operators. + /// @dev A SSV cluster is defined by the SSVOwnerAddress and the set of operatorIds + /// uses "onlyStrategist" modifier so continuous fron-running can't DOS our maintenance service + /// that tries to top us SSV tokens. function depositSSV( uint64[] memory operatorIds, uint256 amount, Cluster memory cluster - ) external { + ) + onlyStrategist + external { // address SSV_NETWORK_ADDRESS = lrtConfig.getContract(LRTConstants.SSV_NETWORK); // ISSVNetwork(SSV_NETWORK_ADDRESS).deposit(address(this), operatorIds, amount, cluster); } diff --git a/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol b/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol index 794003b216..e85b808129 100644 --- a/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol +++ b/contracts/contracts/strategies/NativeStaking/ValidatorAccountant.sol @@ -9,9 +9,7 @@ import { IWETH9 } from "../../interfaces/IWETH9.sol"; /// @notice This contract contains the logic to attribute the Beacon Chain swept ETH either to full /// or partial withdrawals /// @author Origin Protocol Inc -abstract contract ValidatorAccountant is ValidatorRegistrator, Pausable { - /// @notice The Wrapped ETH (WETH) contract address - address public immutable WETH_TOKEN_ADDRESS; +abstract contract ValidatorAccountant is ValidatorRegistrator { address public immutable VAULT_ADDRESS; /// @dev The WETH present on this contract will come from 2 sources: @@ -92,8 +90,11 @@ abstract contract ValidatorAccountant is ValidatorRegistrator, Pausable { } /// @param _wethAddress Address of the Erc20 WETH Token contract - constructor(address _wethAddress, address _vaultAddress) { - WETH_TOKEN_ADDRESS = _wethAddress; + /// @param _vaultAddress Address of the Vault + /// @param _beaconChainDepositContract Address of the beacon chain deposit contract + /// @param _ssvNetwork Address of the SSV Network contract + constructor(address _wethAddress, address _vaultAddress, address _beaconChainDepositContract, address _ssvNetwork) + ValidatorRegistrator(_wethAddress, _beaconChainDepositContract, _ssvNetwork) { VAULT_ADDRESS = _vaultAddress; } @@ -142,9 +143,10 @@ abstract contract ValidatorAccountant is ValidatorRegistrator, Pausable { /// accounting is valid and fuse isn't "blown". Returns false when fuse is blown /// @dev This function could in theory be permission-less but lets allow only the Registrator (Defender Action) to call it /// for now - function doAccounting() external onlyRegistrator returns (bool) { + function doAccounting() external onlyRegistrator returns (bool accountingValid) { uint256 ethBalance = address(this).balance; uint256 MAX_STAKE = 32 ether; + accountingValid = true; // send the WETH that is from fully withdrawn validators to the Vault if (ethBalance >= MAX_STAKE) { @@ -190,6 +192,7 @@ abstract contract ValidatorAccountant is ValidatorRegistrator, Pausable { else { // will emit a paused event _pause(); + accountingValid = false; } } diff --git a/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol b/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol index c65d2c2526..2ee2a87fa1 100644 --- a/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol +++ b/contracts/contracts/strategies/NativeStaking/ValidatorRegistrator.sol @@ -1,24 +1,58 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; +import { Pausable } from "@openzeppelin/contracts/security/Pausable.sol"; import { Governable } from "../../governance/Governable.sol"; +import { IDepositContract } from "../../interfaces/IDepositContract.sol"; +import { IWETH9 } from "../../interfaces/IWETH9.sol"; +import { ISSVNetwork, Cluster } from "../../interfaces/ISSVNetwork.sol"; + +struct ValidatorStakeData { + bytes pubkey; + bytes signature; + bytes32 depositDataRoot; +} /** * @title Registrator of the validators * @notice This contract implements all the required functionality to register validators * @author Origin Protocol Inc */ -abstract contract ValidatorRegistrator is Governable { +abstract contract ValidatorRegistrator is Governable, Pausable { + /// @notice The Wrapped ETH (WETH) contract address + address public immutable WETH_TOKEN_ADDRESS; + /// @notice Address of the beacon chain deposit contract + address public immutable BEACON_CHAIN_DEPOSIT_CONTRACT; + /// @notice SSV Network contract used to interface with + address public immutable SSV_NETWORK_ADDRESS; + /// @notice Address of the registrator - allowed to register, exit and remove validators address public validatorRegistrator; /// @notice The number of validators that have 32 (!) ETH actively deposited. When a new deposit /// to a validator happens this number increases, when a validator exit is detected this number /// decreases. uint256 activeDepositedValidators; + /// @notice State of the validators keccak256(pubKey) => state + mapping(bytes32 => VALIDATOR_STATE) public validatorsStates; + // For future use uint256[50] private __gap; + enum VALIDATOR_STATE { + REGISTERED, // validator is registered on the SSV network + STAKED, // validator has funds staked + EXITING, // exit message has been posted and validator is in the process of exiting + EXIT_COMPLETE // validator has funds withdrawn to the EigenPod and is removed from the SSV + } + event RegistratorAddressChanged(address oldAddress, address newAddress); + event ETHStaked(bytes pubkey, uint256 amount, bytes withdrawal_credentials); + event SSVValidatorRegistered(bytes pubkey, uint64[] operatorIds); + event SSVValidatorExitInitiated(bytes pubkey, uint64[] operatorIds); + event SSVValidatorExitCompleted(bytes pubkey, uint64[] operatorIds); + + error InsufficientWETH(uint256 wethBalance, uint256 requiredWethBalance); + error ValidatorInUnexpectedState(bytes pubkey, VALIDATOR_STATE state); /// @dev Throws if called by any account other than the Registrator modifier onlyRegistrator() { @@ -29,6 +63,15 @@ abstract contract ValidatorRegistrator is Governable { _; } + /// @param _wethAddress Address of the Erc20 WETH Token contract + /// @param _beaconChainDepositContract Address of the beacon chain deposit contract + /// @param _ssvNetwork Address of the SSV Network contract + constructor(address _wethAddress, address _beaconChainDepositContract, address _ssvNetwork) { + WETH_TOKEN_ADDRESS = _wethAddress; + BEACON_CHAIN_DEPOSIT_CONTRACT = _beaconChainDepositContract; + SSV_NETWORK_ADDRESS = _ssvNetwork; + } + /// @notice Set the address of the registrator function setRegistratorAddress(address _address) external onlyGovernor { emit RegistratorAddressChanged(validatorRegistrator, _address); @@ -43,53 +86,117 @@ abstract contract ValidatorRegistrator is Governable { virtual returns (uint256 _amount); - // /// @notice Stakes WETH to the NDC to the Node validators - // /// @param validators A list of validator data needed to stake. - // /// The ValidatorStakeData struct contains the pubkey, signature and depositDataRoot. - // /// @dev Only accounts with the Operator role can call this function. - // function stakeEth(ValidatorStakeData[] calldata validators) external { - // // Yield from the validators will come as native ETH. - // uint256 ethBalance = address(this).balance; - // uint256 requiredETH = validators.length * 32 ether; - // if (ethBalance < requiredETH) { - // // If not enough native ETH, convert WETH to native ETH - // uint256 wethBalance = getWETHBalanceEligibleForStaking(); - // if (wethBalance + ethBalance < requiredETH) { - // revert InsufficientWETH(wethBalance + ethBalance); - // } - // // Convert WETH asset to native ETH - // IWETH(WETH_TOKEN_ADDRESS).withdraw(requiredETH - ethBalance); - // } - - // // For each validator - // for (uint256 i = 0; i < validators.length;) { - // bytes32 pubkeyHash = keccak256(validators[i].pubkey); - - // if (validatorsStaked[pubkeyHash]) { - // revert ValidatorAlreadyStaked(validators[i].pubkey); - // } - - // _stakeEth(validators[i].pubkey, validators[i].signature, validators[i].depositDataRoot); - // validatorsStaked[pubkeyHash] = true; - - // unchecked { - // ++i; - // } - // } - // } - - // /// @dev Stake WETH and ETH in NDC in EigenLayer. It calls the `stake` function on the EigenPodManager - // /// which calls `stake` on the EigenPod contract which calls `stake` on the Beacon DepositContract. - // /// @dev The public functions that call this internal function are responsible for access control. - // function _stakeEth(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) internal { - // // Call the stake function in the EigenPodManager - // IEigenPodManager eigenPodManager = IEigenPodManager(lrtConfig.getContract(LRTConstants.EIGEN_POD_MANAGER)); - // eigenPodManager.stake{ value: 32 ether }(pubkey, signature, depositDataRoot); - - // // Increment the staked but not verified ETH - // stakedButNotVerifiedEth += 32 ether; - // activeDepositedValidators += 1; - - // emit ETHStaked(pubkey, 32 ether); - // } + /// @notice Stakes WETH to the node validators + /// @param validators A list of validator data needed to stake. + /// The ValidatorStakeData struct contains the pubkey, signature and depositDataRoot. + /// @dev Only accounts with the Operator role can call this function. + function stakeEth(ValidatorStakeData[] calldata validators) + onlyRegistrator + whenNotPaused + external { + uint256 requiredWETH = validators.length * 32 ether; + uint256 wethBalance = getWETHBalanceEligibleForStaking(); + if (wethBalance < requiredWETH) { + revert InsufficientWETH(wethBalance, requiredWETH); + } + + // Convert WETH asset to native ETH + IWETH9(WETH_TOKEN_ADDRESS).withdraw(wethBalance); + + // For each validator + for (uint256 i = 0; i < validators.length;) { + bytes32 pubkeyHash = keccak256(validators[i].pubkey); + VALIDATOR_STATE currentState = validatorsStates[pubkeyHash]; + + if (currentState != VALIDATOR_STATE.REGISTERED) { + revert ValidatorInUnexpectedState(validators[i].pubkey, currentState); + } + + _stakeEth(validators[i].pubkey, validators[i].signature, validators[i].depositDataRoot); + validatorsStates[pubkeyHash] = VALIDATOR_STATE.STAKED; + + unchecked { + ++i; + } + } + } + + /// @dev Deposit WETH to the beacon chain deposit contract + /// @dev The public functions that call this internal function are responsible for access control. + function _stakeEth(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) internal { + /* 0x01 to indicate that withdrawal credentials will contain an EOA address that the sweeping function + * can sweep funds to. + * bytes11(0) to fill up the required zeros + * remaining bytes20 are for the address + */ + bytes memory withdrawal_credentials = abi.encodePacked(bytes1(0x01), bytes11(0), address(this)); + IDepositContract(BEACON_CHAIN_DEPOSIT_CONTRACT).deposit( + pubkey, withdrawal_credentials, signature, depositDataRoot + ); + + activeDepositedValidators += 1; + emit ETHStaked(pubkey, 32 ether, withdrawal_credentials); + + } + + /// @dev Registers a new validator in the SSV Cluster + function registerSsvValidator( + bytes calldata publicKey, + uint64[] calldata operatorIds, + bytes calldata sharesData, + uint256 amount, + Cluster calldata cluster + ) + external + onlyRegistrator + whenNotPaused + { + ISSVNetwork(SSV_NETWORK_ADDRESS).registerValidator(publicKey, operatorIds, sharesData, amount, cluster); + validatorsStates[keccak256(publicKey)] = VALIDATOR_STATE.REGISTERED; + emit SSVValidatorRegistered(publicKey, operatorIds); + } + + /// @dev Exit a validator from the Beacon chain. + /// The staked ETH will be sent to the EigenPod. + function exitSsvValidator( + bytes calldata publicKey, + uint64[] calldata operatorIds + ) + external + onlyRegistrator + whenNotPaused + { + VALIDATOR_STATE currentState = validatorsStates[keccak256(publicKey)]; + if (currentState != VALIDATOR_STATE.STAKED) { + revert ValidatorInUnexpectedState(publicKey, currentState); + } + + ISSVNetwork(SSV_NETWORK_ADDRESS).exitValidator(publicKey, operatorIds); + emit SSVValidatorExitInitiated(publicKey, operatorIds); + + validatorsStates[keccak256(publicKey)] = VALIDATOR_STATE.EXITING; + } + + /// @dev Remove a validator from the SSV Cluster. + /// Make sure `exitSsvValidator` is called before and the validate has exited the Beacon chain. + /// If removed before the validator has exited the beacon chain will result in the validator being slashed. + function removeSsvValidator( + bytes calldata publicKey, + uint64[] calldata operatorIds, + Cluster calldata cluster + ) + external + onlyRegistrator + whenNotPaused + { + VALIDATOR_STATE currentState = validatorsStates[keccak256(publicKey)]; + if (currentState != VALIDATOR_STATE.EXITING) { + revert ValidatorInUnexpectedState(publicKey, currentState); + } + + ISSVNetwork(SSV_NETWORK_ADDRESS).removeValidator(publicKey, operatorIds, cluster); + emit SSVValidatorExitCompleted(publicKey, operatorIds); + + validatorsStates[keccak256(publicKey)] = VALIDATOR_STATE.EXIT_COMPLETE; + } } diff --git a/contracts/deploy/000_mock.js b/contracts/deploy/000_mock.js index eda17e4a71..ab09ffcc46 100644 --- a/contracts/deploy/000_mock.js +++ b/contracts/deploy/000_mock.js @@ -419,6 +419,10 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => { from: deployerAddr, }); + await deploy("BeaconChainDepositContractMock", { + from: deployerAddr, + }); + console.log("000_mock deploy done."); return true; diff --git a/contracts/deploy/001_core.js b/contracts/deploy/001_core.js index 6a634cc642..ed14d833ec 100644 --- a/contracts/deploy/001_core.js +++ b/contracts/deploy/001_core.js @@ -764,6 +764,7 @@ const deployNativeStakingSSVStrategy = async () => { assetAddresses.SSV, // ssvToken assetAddresses.SSVNetwork, // ssvNetwork dFeeAccumulatorProxy.address, // feeAccumulator + assetAddresses.beaconChainDepositContract // depositContractMock ] ); const cStrategyImpl = await ethers.getContractAt( diff --git a/contracts/deploy/091_native_ssv_staking.js b/contracts/deploy/091_native_ssv_staking.js index 60aecc157a..82f14f4bf7 100644 --- a/contracts/deploy/091_native_ssv_staking.js +++ b/contracts/deploy/091_native_ssv_staking.js @@ -11,7 +11,7 @@ module.exports = deploymentWithGovernanceProposal( // "", }, async ({ deployWithConfirmation, ethers, getTxOpts, withConfirmation }) => { - const { deployerAddr } = await getNamedAccounts(); + const { deployerAddr, strategistAddr } = await getNamedAccounts(); const sDeployer = await ethers.provider.getSigner(deployerAddr); // Current contracts @@ -57,6 +57,7 @@ module.exports = deploymentWithGovernanceProposal( addresses.mainnet.SSV, // ssvToken addresses.mainnet.SSVNetwork, // ssvNetwork dFeeAccumulatorProxy.address, // feeAccumulator + addresses.mainnet.beaconChainDepositContract, // beacon chain deposit contract ] ); const cStrategyImpl = await ethers.getContractAt( @@ -147,23 +148,23 @@ module.exports = deploymentWithGovernanceProposal( // 4. configure the fuse interval { contract: cStrategy, - signature: "setFuseInterval(uint256, uint256)", + signature: "setFuseInterval(uint256,uint256)", args: [ ethers.utils.parseEther("21.6"), ethers.utils.parseEther("25.6"), ], }, - // 5. configure the fuse interval + // 5. configure the accounting governor { contract: cStrategy, signature: "setAccountingGovernor(address)", args: [deployerAddr], // TODO: change this to the defender action }, - // 6. configure the fuse interval + // 6. configure strategist address { contract: cStrategy, signature: "setStrategist(address)", - args: [deployerAddr], + args: [strategistAddr], }, ], }; diff --git a/contracts/test/_hot-deploy.js b/contracts/test/_hot-deploy.js index dfcdde466d..1e395d04a6 100644 --- a/contracts/test/_hot-deploy.js +++ b/contracts/test/_hot-deploy.js @@ -89,12 +89,11 @@ async function constructNewContract( ].FEE_ACCUMULATOR_ADDRESS(); return [ [addresses.zero, addresses.mainnet.OETHVaultProxy], - [ - addresses.mainnet.WETH, - addresses.mainnet.SSV, - addresses.mainnet.SSVNetwork, - feeAccumulatorAddress, - ], + addresses.mainnet.WETH, + addresses.mainnet.SSV, + addresses.mainnet.SSVNetwork, + feeAccumulatorAddress, + addresses.mainnet.beaconChainDepositContract, ]; } }; @@ -167,7 +166,7 @@ async function hotDeployOption( } else if (fixtureName === "nativeStakingSSVStrategyFixture") { await hotDeployFixture( fixture, // fixture - "nativeStakingStrategy", // fixtureStrategyVarName + "nativeStakingSSVStrategy", // fixtureStrategyVarName "NativeStakingSSVStrategy" // implContractName ); } diff --git a/contracts/test/helpers.js b/contracts/test/helpers.js index 5bd9aeb785..62c72dfbd3 100644 --- a/contracts/test/helpers.js +++ b/contracts/test/helpers.js @@ -427,6 +427,8 @@ const getAssetAddresses = async (deployments) => { BAL: addresses.mainnet.BAL, SSV: addresses.mainnet.SSV, SSVNetwork: addresses.mainnet.SSVNetwork, + beaconChainDepositContract: addresses.mainnet.beaconChainDepositContract, + }; } else { const addressMap = { @@ -474,6 +476,7 @@ const getAssetAddresses = async (deployments) => { BAL: (await deployments.get("MockBAL")).address, SSV: (await deployments.get("MockSSV")).address, SSVNetwork: (await deployments.get("MockSSVNetwork")).address, + beaconChainDepositContract: (await deployments.get("BeaconChainDepositContractMock")).address, }; try { diff --git a/contracts/test/strategies/nativeSSVStaking.js b/contracts/test/strategies/nativeSSVStaking.js index 22eac6e183..037ff17c8f 100644 --- a/contracts/test/strategies/nativeSSVStaking.js +++ b/contracts/test/strategies/nativeSSVStaking.js @@ -17,7 +17,7 @@ const { const loadFixture = createFixtureLoader(nativeStakingSSVStrategyFixture); -describe("ForkTest: Native SSV Staking Strategy", function () { +describe("Unit test: Native SSV Staking Strategy", function () { this.timeout(0); // Retry up to 3 times on CI @@ -697,7 +697,5 @@ describe("ForkTest: Native SSV Staking Strategy", function () { } it("Should be able to collect the SSV reward token", async () => {}); - - it("Check balance should report correct values", async () => {}); }); }); diff --git a/contracts/utils/addresses.js b/contracts/utils/addresses.js index 23a3bb4be5..adc918518c 100644 --- a/contracts/utils/addresses.js +++ b/contracts/utils/addresses.js @@ -243,6 +243,7 @@ addresses.mainnet.CurveCVXPool = "0xB576491F1E6e5E62f1d8F26062Ee822B40B0E0d4"; // SSV network addresses.mainnet.SSV = "0x9D65fF81a3c488d585bBfb0Bfe3c7707c7917f54"; addresses.mainnet.SSVNetwork = "0xDD9BC35aE942eF0cFa76930954a156B3fF30a4E1"; +addresses.mainnet.beaconChainDepositContract = "0x00000000219ab540356cbb839cbe05303d7705fa"; // Arbitrum One addresses.arbitrumOne = {};