Skip to content

Commit

Permalink
mainnet native staking fork tests (#2037)
Browse files Browse the repository at this point in the history
* manuallyFixAccounting now uses delta values and only callable by the strategist
manuallyFixAccounting calls doAccounting to check the fuse is still not blown
Removed accountingGovernor

* Added pauseOnFail param to internal _doAccounting
Increased the allowed delta values of manuallyFixAccounting

* ran prettier

* Added Defender Relayer for validator registrator
Added ssv utils to get cluster data
Added native staking fork tests

* Removed now redundant IWETH9 import

* moved more logic into native staking fixture

* Removed unused imports

* fix native staking unit tests

* Fail accounting if activeDepositedValidators < fullyWithdrawnValidators
Changed Harvester to transfer WETH to dripper
Added more mainnet fork tests for native staking

* Updated the OETH value flows

* Added governable Hardhat tasks
Created a resolveContract util

* deconstruct params for Hardhat tasks

* WIP Hardhat tasks for validator registration

* Added depositSSV HH task

* Updated OETH contract dependency diagram

* Update to diagrams

* mini fixes

* fix bug and minor test improvement

* update yarn fulie

* unify the holesky and the mainnet fork tests

* prettier

* re-deploy holesky native staking strategy (#2046)

* test updates

* also re-deploy the harvester

* upgrade harvester as well

* fix test

* fix upgrade script and correct the bug in deploy actions

* Deployed new Native Staking strategy including the proxy

* Added Hardhat tasks for generic strategy functions

* remove nativeStakingSSVStrategyProxy from js addresses file

---------

Co-authored-by: Domen Grabec <grabec@gmail.com>
  • Loading branch information
naddison36 and sparrowDom authored May 7, 2024
1 parent 967fe49 commit 1af324d
Show file tree
Hide file tree
Showing 52 changed files with 3,826 additions and 837 deletions.
4 changes: 3 additions & 1 deletion contracts/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ module.exports = {
getNamedAccounts: "readable",
hre: "readable",
},
rules: {},
rules: {
"no-constant-condition": ["error", { checkLoops: false }],
},
};
25 changes: 20 additions & 5 deletions contracts/contracts/harvest/BaseHarvester.sol
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,23 @@ abstract contract BaseHarvester is Governable {
address _rewardTo,
IOracle _priceProvider
) internal virtual {
uint256 balance = IERC20(_swapToken).balanceOf(address(this));

// No need to swap if the reward token is the base token. eg USDT or WETH.
// There is also no limit on the transfer. Everything in the harvester will be transferred
// to the Dripper regardless of the liquidationLimit config.
if (_swapToken == baseTokenAddress) {
IERC20(_swapToken).safeTransfer(rewardProceedsAddress, balance);
// currently not paying the farmer any rewards as there is no swap
emit RewardProceedsTransferred(
baseTokenAddress,
address(0),
balance,
0
);
return;
}

RewardTokenConfig memory tokenConfig = rewardTokenConfigs[_swapToken];

/* This will trigger a return when reward token configuration has not yet been set
Expand All @@ -487,8 +504,6 @@ abstract contract BaseHarvester is Governable {
return;
}

uint256 balance = IERC20(_swapToken).balanceOf(address(this));

if (balance == 0) {
return;
}
Expand Down Expand Up @@ -548,14 +563,14 @@ abstract contract BaseHarvester is Governable {
tokenConfig.harvestRewardBps,
1e4
);
uint256 protcolYield = baseTokenBalance - farmerFee;
uint256 protocolYield = baseTokenBalance - farmerFee;

baseToken.safeTransfer(rewardProceedsAddress, protcolYield);
baseToken.safeTransfer(rewardProceedsAddress, protocolYield);
baseToken.safeTransfer(_rewardTo, farmerFee);
emit RewardProceedsTransferred(
baseTokenAddress,
_rewardTo,
protcolYield,
protocolYield,
farmerFee
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import { InitializableAbstractStrategy } from "../../utils/InitializableAbstractStrategy.sol";
import { ISSVNetwork, Cluster } from "../../interfaces/ISSVNetwork.sol";
import { IWETH9 } from "../../interfaces/IWETH9.sol";
import { FeeAccumulator } from "./FeeAccumulator.sol";
import { ValidatorAccountant } from "./ValidatorAccountant.sol";
import { Cluster } from "../../interfaces/ISSVNetwork.sol";

struct ValidatorStakeData {
bytes pubkey;
Expand Down
4 changes: 4 additions & 0 deletions contracts/contracts/strategies/NativeStaking/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@

## Fee Accumulator

### Hierarchy

![Fee Accumulator Hierarchy](../../../docs/FeeAccumulatorHierarchy.svg)

### Squashed

![Fee Accumulator Squashed](../../../docs/FeeAccumulatorSquashed.svg)
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { Pausable } from "@openzeppelin/contracts/security/Pausable.sol";
import { ValidatorRegistrator } from "./ValidatorRegistrator.sol";
import { IWETH9 } from "../../interfaces/IWETH9.sol";

Expand Down Expand Up @@ -108,12 +107,7 @@ abstract contract ValidatorAccountant is ValidatorRegistrator {
returns (bool accountingValid)
{
if (address(this).balance < consensusRewards) {
// pause if not already
if (pauseOnFail) {
_pause();
}
// fail the accounting
return false;
return _failAccounting(pauseOnFail);
}

// Calculate all the new ETH that has been swept to the contract since the last accounting
Expand All @@ -123,6 +117,9 @@ abstract contract ValidatorAccountant is ValidatorRegistrator {
// send the ETH that is from fully withdrawn validators to the Vault
if (newSweptETH >= MAX_STAKE) {
uint256 fullyWithdrawnValidators = newSweptETH / MAX_STAKE;
if (activeDepositedValidators < fullyWithdrawnValidators) {
return _failAccounting(pauseOnFail);
}
activeDepositedValidators -= fullyWithdrawnValidators;

uint256 wethToVault = MAX_STAKE * fullyWithdrawnValidators;
Expand Down Expand Up @@ -164,13 +161,21 @@ abstract contract ValidatorAccountant is ValidatorRegistrator {
}
// Oh no... Fuse is blown. The Strategist needs to adjust the accounting values.
else {
// pause if not already
if (pauseOnFail) {
_pause();
}
// fail the accounting
accountingValid = false;
return _failAccounting(pauseOnFail);
}
}

/// @dev pause any further accounting if required and return false
function _failAccounting(bool pauseOnFail)
internal
returns (bool accountingValid)
{
// pause if not already
if (pauseOnFail) {
_pause();
}
// fail the accounting
accountingValid = false;
}

/// @notice Allow the Strategist to fix the accounting of this strategy and unpause.
Expand Down
34 changes: 26 additions & 8 deletions contracts/deploy/deployActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -543,11 +543,26 @@ const deployOUSDHarvester = async (ousdDripper) => {
return dHarvesterProxy;
};

const upgradeOETHHarvester = async () => {
const assetAddresses = await getAssetAddresses(deployments);
const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy");
const cOETHHarvesterProxy = await ethers.getContract("OETHHarvesterProxy");

const dOETHHarvester = await deployWithConfirmation("OETHHarvester", [
cOETHVaultProxy.address,
assetAddresses.WETH,
]);

await withConfirmation(cOETHHarvesterProxy.upgradeTo(dOETHHarvester.address));

log("Upgraded OETHHarvesterProxy");
return cOETHHarvesterProxy;
};

const deployOETHHarvester = async (oethDripper) => {
const assetAddresses = await getAssetAddresses(deployments);
const { governorAddr } = await getNamedAccounts();
const sGovernor = await ethers.provider.getSigner(governorAddr);
const cVaultProxy = await ethers.getContract("VaultProxy");
const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy");

const dOETHHarvesterProxy = await deployWithConfirmation(
Expand All @@ -568,11 +583,13 @@ const deployOETHHarvester = async (oethDripper) => {
);

await withConfirmation(
cOETHHarvesterProxy["initialize(address,address,bytes)"](
dOETHHarvester.address,
governorAddr,
[]
)
// prettier-ignore
cOETHHarvesterProxy
.connect(sGovernor)["initialize(address,address,bytes)"](
dOETHHarvester.address,
governorAddr,
[]
)
);

log("Initialized OETHHarvesterProxy");
Expand All @@ -581,11 +598,11 @@ const deployOETHHarvester = async (oethDripper) => {
cOETHHarvester
.connect(sGovernor)
.setRewardProceedsAddress(
isMainnet || isHolesky ? oethDripper.address : cVaultProxy.address
isMainnet || isHolesky ? oethDripper.address : cOETHVaultProxy.address
)
);

return dOETHHarvesterProxy;
return cOETHHarvester;
};

/**
Expand Down Expand Up @@ -1509,6 +1526,7 @@ module.exports = {
deployHarvesters,
deployOETHHarvester,
deployOUSDHarvester,
upgradeOETHHarvester,
configureVault,
configureOETHVault,
configureStrategies,
Expand Down
41 changes: 41 additions & 0 deletions contracts/deploy/holesky/004_upgrade_strategy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
const {
upgradeNativeStakingSSVStrategy,
upgradeOETHHarvester,
} = require("../deployActions");
const { withConfirmation } = require("../../utils/deploy");

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

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

console.log("deploying harvester");
const cOETHDripperProxy = await ethers.getContract("OETHDripperProxy");
const cOETHHarvester = await upgradeOETHHarvester(cOETHDripperProxy.address);

const strategyProxy = await ethers.getContract(
"NativeStakingSSVStrategyProxy"
);
const cStrategy = await ethers.getContractAt(
"NativeStakingSSVStrategy",
strategyProxy.address
);

console.log("configuring harvester and the strategy");
await withConfirmation(
cOETHHarvester.setSupportedStrategy(strategyProxy.address, true)
);

await withConfirmation(cStrategy.setHarvesterAddress(cOETHHarvester.address));

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

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

module.exports = mainExport;
61 changes: 61 additions & 0 deletions contracts/deploy/holesky/005_new_harvester.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
const { parseEther } = require("ethers/lib/utils");

const { deployNativeStakingSSVStrategy } = require("../deployActions");
const { withConfirmation } = require("../../utils/deploy");
const { resolveContract } = require("../../utils/resolvers");

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

console.log("Deploying a new Native Staking strategy and proxy");

console.log("Deploying Native Staking");
const nativeStakingSSVStrategy = await deployNativeStakingSSVStrategy();

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

const cOETHHarvester = await resolveContract(
"OETHHarvesterProxy",
"OETHHarvester"
);
const cVault = await resolveContract("OETHVaultProxy", "VaultAdmin");

await withConfirmation(
nativeStakingSSVStrategy
.connect(sGovernor)
.setHarvesterAddress(cOETHHarvester.address)
);

console.log("configuring harvester and the strategy");
await withConfirmation(
cOETHHarvester
.connect(sGovernor)
.setSupportedStrategy(nativeStakingSSVStrategy.address, true)
);

await withConfirmation(
cVault.connect(sGovernor).approveStrategy(nativeStakingSSVStrategy.address)
);

await withConfirmation(
nativeStakingSSVStrategy.connect(sGovernor).setRegistrator(governorAddr)
);

const fuseStartBn = parseEther("21.6");
const fuseEndBn = parseEther("25.6");

await nativeStakingSSVStrategy
.connect(sGovernor)
.setFuseInterval(fuseStartBn, fuseEndBn);

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

mainExport.id = "005_deploy_new_harvester";
mainExport.tags = [];
mainExport.dependencies = [];
mainExport.skip = () => false;

module.exports = mainExport;
24 changes: 24 additions & 0 deletions contracts/deploy/mainnet/091_native_ssv_staking.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,23 @@ module.exports = deploymentWithGovernanceProposal(
// 7. Safe approve SSV token spending
await cStrategy.connect(sDeployer).safeApproveAllTokens();

// 8. Deploy Harvester
const cOETHHarvesterProxy = await ethers.getContract("OETHHarvesterProxy");
await deployWithConfirmation("OETHHarvester", [
cVaultProxy.address,
addresses.mainnet.WETH,
]);
const dOETHHarvesterImpl = await ethers.getContract("OETHHarvester");

console.log(
"Native Staking SSV Strategy address: ",
cStrategyProxy.address
);
console.log("Fee accumulator address: ", cFeeAccumulator.address);
console.log(
"New OETHHarvester implementation address: ",
dOETHHarvesterImpl.address
);

// Governance Actions
// ----------------
Expand Down Expand Up @@ -152,6 +164,18 @@ module.exports = deploymentWithGovernanceProposal(
ethers.utils.parseEther("25.6"),
],
},
// 5. set validator registrator
{
contract: cStrategy,
signature: "setRegistrator(address)",
args: [addresses.mainnet.validatorRegistrator],
},
// 6. Upgrade the OETH Harvester
{
contract: cOETHHarvesterProxy,
signature: "upgradeTo(address)",
args: [dOETHHarvesterImpl.address],
},
],
};
}
Expand Down
4 changes: 3 additions & 1 deletion contracts/deployments/holesky/.migrations.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"001_core": 1714168010,
"002_upgrade_strategy": 1714233842,
"003_deposit_to_native_strategy": 1714307581
"003_deposit_to_native_strategy": 1714307581,
"004_upgrade_strategy": 1714944723,
"005_deploy_new_harvester": 1714998707
}
Loading

0 comments on commit 1af324d

Please sign in to comment.