Skip to content

Commit

Permalink
Stake funds with confirmations for front-running protection of Beacon…
Browse files Browse the repository at this point in the history
… Deposits (#2074)

* contract changes and tests for gated protection against front running

* Update off-chain validator registration process to also consider stake threshold

* front run protection changes (#2076)

* Added Holesky deploy script

* Fixed OUSD metapool fork test

* Generated latest NativeStakingSSVStrategy contract diagrams

* Deployed new NativeStakingSSVStrategy to Holesky

---------

Co-authored-by: Nicholas Addison <nick@addisonbrown.com.au>
  • Loading branch information
sparrowDom and naddison36 committed May 28, 2024
1 parent e853081 commit 12bba64
Show file tree
Hide file tree
Showing 19 changed files with 906 additions and 299 deletions.
53 changes: 0 additions & 53 deletions contracts/contracts/mocks/BeaconChainDepositContractMock.sol

This file was deleted.

80 changes: 80 additions & 0 deletions contracts/contracts/mocks/MockDepositContract.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { IDepositContract } from "./../interfaces/IDepositContract.sol";

contract MockDepositContract is IDepositContract {
uint256 deposit_count;

function deposit(
bytes calldata pubkey,
bytes calldata withdrawal_credentials,
bytes calldata signature,
bytes32 deposit_data_root
) external payable override {
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"
);
uint256 deposit_amount = msg.value / 1 gwei;
require(
deposit_amount <= type(uint64).max,
"DepositContract: deposit value too high"
);

// Emit `DepositEvent` log
bytes memory amount = to_little_endian_64(uint64(deposit_amount));
emit DepositEvent(
pubkey,
withdrawal_credentials,
amount,
signature,
to_little_endian_64(uint64(deposit_count))
);
require(
deposit_data_root != 0,
"DepositContract: invalid deposit_data_root"
);
}

function get_deposit_root() external view override returns (bytes32) {
// just return some bytes32
return sha256(abi.encodePacked(deposit_count, bytes16(0)));
}

/// @notice Query the current deposit count.
/// @return The deposit count encoded as a little endian 64-bit number.
function get_deposit_count() external view override returns (bytes memory) {
return to_little_endian_64(uint64(deposit_count));
}

function to_little_endian_64(uint64 value)
internal
pure
returns (bytes memory ret)
{
ret = new bytes(8);
bytes8 bytesValue = bytes8(value);
// Byteswapping during copying to bytes.
ret[0] = bytesValue[7];
ret[1] = bytesValue[6];
ret[2] = bytesValue[5];
ret[3] = bytesValue[4];
ret[4] = bytesValue[3];
ret[5] = bytesValue[2];
ret[6] = bytesValue[1];
ret[7] = bytesValue[0];
}
}
28 changes: 27 additions & 1 deletion contracts/contracts/mocks/MockSSVNetwork.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,32 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { Cluster } from "./../interfaces/ISSVNetwork.sol";

contract MockSSVNetwork {
constructor() {}
function registerValidator(
bytes calldata publicKey,
uint64[] calldata operatorIds,
bytes calldata sharesData,
uint256 amount,
Cluster memory cluster
) external {}

function exitValidator(
bytes calldata publicKey,
uint64[] calldata operatorIds
) external {}

function removeValidator(
bytes calldata publicKey,
uint64[] calldata operatorIds,
Cluster memory cluster
) external {}

function deposit(
address clusterOwner,
uint64[] calldata operatorIds,
uint256 amount,
Cluster memory cluster
) external {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,15 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
uint256 public activeDepositedValidators;
/// @notice State of the validators keccak256(pubKey) => state
mapping(bytes32 => VALIDATOR_STATE) public validatorsStates;

/// @notice The account that is allowed to modify stakeETHThreshold and reset stakeETHTally
address public stakingMonitor;
/// @notice Amount of ETH that can be staked before staking on the contract is suspended
/// and the governor needs to approve further staking
uint256 public stakeETHThreshold;
/// @notice Amount of ETH that can has been staked since the last governor approval.
uint256 public stakeETHTally;
// For future use
uint256[50] private __gap;
uint256[47] private __gap;

enum VALIDATOR_STATE {
REGISTERED, // validator is registered on the SSV network
Expand All @@ -49,10 +55,13 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
}

event RegistratorChanged(address newAddress);
event StakingMonitorChanged(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);
event StakeETHThresholdChanged(uint256 amount);
event StakeETHTallyReset();

/// @dev Throws if called by any account other than the Registrator
modifier onlyRegistrator() {
Expand All @@ -63,6 +72,12 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
_;
}

/// @dev Throws if called by any account other than the Staking monitor
modifier onlyStakingMonitor() {
require(msg.sender == stakingMonitor, "Caller is not the Monitor");
_;
}

/// @dev Throws if called by any account other than the Strategist
modifier onlyStrategist() {
require(
Expand Down Expand Up @@ -94,6 +109,25 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
validatorRegistrator = _address;
}

/// @notice Set the address of the staking monitor that is allowed to reset stakeETHTally
function setStakingMonitor(address _address) external onlyGovernor {
emit StakingMonitorChanged(_address);
stakingMonitor = _address;
}

/// @notice Set the amount of ETH that can be staked before staking monitor
// needs to a approve further staking by resetting the stake ETH tally
function setStakeETHThreshold(uint256 _amount) external onlyGovernor {
emit StakeETHThresholdChanged(_amount);
stakeETHThreshold = _amount;
}

/// @notice Reset the stakeETHTally
function resetStakeETHTally() external onlyStakingMonitor {
emit StakeETHTallyReset();
stakeETHTally = 0;
}

/// @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.
Expand All @@ -112,6 +146,12 @@ abstract contract ValidatorRegistrator is Governable, Pausable {
"insufficient WETH"
);

require(
stakeETHTally + requiredETH <= stakeETHThreshold,
"Staking ETH over threshold"
);
stakeETHTally += requiredETH;

// Convert required ETH from WETH
IWETH9(WETH_TOKEN_ADDRESS).withdraw(requiredETH);
_wethWithdrawnAndStaked(requiredETH);
Expand Down
48 changes: 48 additions & 0 deletions contracts/deploy/holesky/010_upgrade_strategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const { upgradeNativeStakingSSVStrategy } = require("../deployActions");
const { withConfirmation } = require("../../utils/deploy");
const { resolveContract } = require("../../utils/resolvers");
const addresses = require("../../utils/addresses");
const { parseEther } = require("ethers/lib/utils");
// const { impersonateAndFund } = require("../../utils/signers.js");

const mainExport = async () => {
console.log("Running 010 deployment on Holesky...");

console.log("Upgrading native staking strategy");
await upgradeNativeStakingSSVStrategy();

const cNativeStakingStrategy = await resolveContract(
"NativeStakingSSVStrategyProxy",
"NativeStakingSSVStrategy"
);

const { governorAddr } = await getNamedAccounts();
const sGovernor = await ethers.provider.getSigner(governorAddr);

await withConfirmation(
cNativeStakingStrategy
.connect(sGovernor)
// Holesky defender relayer
.setStakingMonitor(addresses.holesky.Guardian)
);

await withConfirmation(
cNativeStakingStrategy
.connect(sGovernor)
.setStakeETHThreshold(parseEther("64"))
);

console.log(
`Set the staking monitor to ${addresses.holesky.Guardian} and stake ETH threshold to 32 ETH`
);

console.log("Running 010 deployment done");
return true;
};

mainExport.id = "010_upgrade_strategy";
mainExport.tags = [];
mainExport.dependencies = [];
mainExport.skip = () => false;

module.exports = mainExport;
9 changes: 5 additions & 4 deletions contracts/deploy/mainnet/000_mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => {
from: deployerAddr,
});

// Mock SSV Network
await deploy("MockDepositContract", {
from: deployerAddr,
});

const dai = await ethers.getContract("MockDAI");
const usdc = await ethers.getContract("MockUSDC");
const usdt = await ethers.getContract("MockUSDT");
Expand Down Expand Up @@ -425,10 +430,6 @@ const deployMocks = async ({ getNamedAccounts, deployments }) => {
from: deployerAddr,
});

await deploy("BeaconChainDepositContractMock", {
from: deployerAddr,
});

console.log("000_mock deploy done.");

return true;
Expand Down
16 changes: 15 additions & 1 deletion contracts/deploy/mainnet/097_native_ssv_staking.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,21 @@ module.exports = deploymentWithGovernanceProposal(
signature: "setRegistrator(address)",
args: [addresses.mainnet.validatorRegistrator],
},
// 6. Upgrade the OETH Harvester
// 6. set staking threshold
{
contract: cStrategy,
signature: "setStakeETHThreshold(uint256)",
// TODO: confirm this number makes sense
args: [ethers.utils.parseEther("1024")], // 32ETH * 32
},
// 7. set staking monitor
{
contract: cStrategy,
signature: "setStakingMonitor(address)",
// The 5/8 multisig
args: [addresses.mainnet.Guardian],
},
// 8. Upgrade the OETH Harvester
{
contract: cOETHHarvesterProxy,
signature: "upgradeTo(address)",
Expand Down
3 changes: 2 additions & 1 deletion contracts/deployments/holesky/.migrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
"006_update_registrator": 1715184342,
"007_upgrade_strategy": 1715251466,
"008_upgrade_strategy": 1715341541,
"009_upgrade_strategy": 1716360052
"009_upgrade_strategy": 1716360052,
"010_upgrade_strategy": 1716890877
}
Loading

0 comments on commit 12bba64

Please sign in to comment.