Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions contracts/deploy/mainnet/183_migrate_ssv_clusters_to_eth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const { deploymentWithGovernanceProposal } = require("../../utils/deploy");
const { getClusterInfo, splitOperatorIds } = require("../../utils/ssv");

const strategyConfigs = [
{
proxyName: "NativeStakingSSVStrategy2Proxy",
contractName: "NativeStakingSSVStrategy",
operatorids: "752,753,754,755",
},
{
proxyName: "NativeStakingSSVStrategy3Proxy",
contractName: "NativeStakingSSVStrategy",
operatorids: "338,339,340,341",
},
{
proxyName: "CompoundingStakingSSVStrategyProxy",
contractName: "CompoundingStakingSSVStrategy",
operatorids: "2070,2071,2072,2073",
},
];

module.exports = deploymentWithGovernanceProposal(
{
deployName: "183_migrate_ssv_clusters_to_eth",
forceDeploy: false,
// forceSkip: true,
reduceQueueTime: true,
deployerIsProposer: false,
proposalId: "",
},
async ({ ethers }) => {
const ethValue = ethers.utils.parseEther("1");
const { chainId } = await ethers.provider.getNetwork();
const actions = [];

for (const strategyConfig of strategyConfigs) {
const proxy = await ethers.getContract(strategyConfig.proxyName);
const strategy = await ethers.getContractAt(
strategyConfig.contractName,
proxy.address
);
const operatorIds = splitOperatorIds(strategyConfig.operatorids);
const { cluster } = await getClusterInfo({
chainId,
operatorids: operatorIds.join(","),
ownerAddress: strategy.address,
});

actions.push({
contract: strategy,
signature:
"migrateClusterToETH(uint64[],(uint32,uint64,uint64,bool,uint256))",
args: [operatorIds, cluster],
value: ethValue,
});
}

return {
name: "Migrate SSV clusters to ETH billing for OETH staking strategies",
actions,
};
}
);
13 changes: 11 additions & 2 deletions contracts/tasks/governance.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ const { sleep } = require("../utils/time.js");

async function execute(taskArguments, hre) {
const { isMainnet, isFork } = require("../test/helpers");
const { withConfirmation, impersonateGuardian } = require("../utils/deploy");
const {
withConfirmation,
impersonateGuardian,
getProposalExecutionValue,
} = require("../utils/deploy");

if (isMainnet) {
throw new Error("The execute task can not be used on mainnet");
Expand Down Expand Up @@ -54,7 +58,12 @@ async function execute(taskArguments, hre) {
if (isFork) {
// On the fork, impersonate the guardian and execute the proposal.
await impersonateGuardian();
await withConfirmation(governor.connect(sGuardian).execute(propId));
const executionValue = await getProposalExecutionValue(governor, propId);
await withConfirmation(
governor.connect(sGuardian).execute(propId, {
...(executionValue.gt(0) ? { value: executionValue } : {}),
})
);
} else {
// Localhost network. Execute as the governor account.
await governor.connect(sGovernor).execute(propId);
Expand Down
16 changes: 8 additions & 8 deletions contracts/utils/beacon.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ const getValidatorsIndividually = async (client, validatorIds, stateId) => {
return validators;
};

const getValidatorsByPost = async (
const getValidatorsByGet = async (
client,
validatorIds,
stateId,
Expand All @@ -237,31 +237,31 @@ const getValidatorsByPost = async (

for (let attempt = 1; attempt <= attempts; attempt++) {
try {
const postValidatorsRes = await client.beacon.postStateValidators({
const getValidatorsRes = await client.beacon.getStateValidators({
stateId,
validatorIds,
});

if (postValidatorsRes.ok) {
return postValidatorsRes.value();
if (getValidatorsRes.ok) {
return getValidatorsRes.value();
}

lastError = new Error(
`Bulk validator POST failed with status ${postValidatorsRes.status} ${postValidatorsRes.statusText}`
`Bulk validator GET failed with status ${getValidatorsRes.status} ${getValidatorsRes.statusText}`
);
log(`${lastError.message}. Attempt ${attempt} of ${attempts}.`);
} catch (err) {
lastError = err;
log(
`Bulk validator POST threw ${err.name || "Error"}: ${
`Bulk validator GET threw ${err.name || "Error"}: ${
err.message
}. Attempt ${attempt} of ${attempts}.`
);
}
}

if (lastError) {
log(`Bulk validator POST failed after ${attempts} attempts.`);
log(`Bulk validator GET failed after ${attempts} attempts.`);
}

return null;
Expand All @@ -274,7 +274,7 @@ const getValidators = async (pubkeys, stateId = "head") => {
log(
`Fetching ${validatorIds.length} validator details at state ${stateId} from the beacon node`
);
let validators = await getValidatorsByPost(client, validatorIds, stateId);
let validators = await getValidatorsByGet(client, validatorIds, stateId);

if (!validators) {
validators = await getValidatorsIndividually(client, validatorIds, stateId);
Expand Down
44 changes: 35 additions & 9 deletions contracts/utils/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ const _verifyProxyInitializedWithCorrectGovernor = (transactionData) => {
return;
}

if (isMainnet || isBase || isFork || isBaseFork) {
if (isMainnet || isBase || isFork) {
// TODO: Skip verification for Fork for now
return;
}
Expand Down Expand Up @@ -606,7 +606,10 @@ const executeGovernanceProposalOnFork = async ({
// Don't ask me why but this seems to force hardhat to
// update state and cause the random failures to stop
await getProposalState(proposalIdBn);
await governorSix.getActions(proposalIdBn);
const executionValue = await getProposalExecutionValue(
governorSix,
proposalIdBn
);

executionRetries = executionRetries - 1;
try {
Expand All @@ -615,6 +618,7 @@ const executeGovernanceProposalOnFork = async ({
"execute(uint256)"
](proposalIdBn, {
gasLimit: executeGasLimit || undefined,
...(executionValue.gt(0) ? { value: executionValue } : {}),
});
} catch (e) {
console.error(e);
Expand Down Expand Up @@ -974,30 +978,47 @@ async function buildGnosisSafeJson(
safeAddress,
targets,
contractMethods,
contractInputsValues
contractInputsValues,
values = []
) {
return buildSafeTransactionBuilderJson({
safeAddress: safeAddress || addresses.mainnet.Guardian,
name: "Transaction Batch",
transactions: targets.map((target, i) => ({
to: target,
value: "0",
value: BigNumber.from(values[i] ?? 0).toString(),
contractMethod: contractMethods[i],
contractInputsValues: contractInputsValues[i],
})),
});
}

async function getProposalExecutionValue(governor, proposalId) {
const actions = await governor.getActions(proposalId);
const rawValues =
actions[1] || (typeof actions.values === "function" ? [] : actions.values);
const values = Array.from(rawValues || []);

return values.reduce(
(sum, value) => sum.add(BigNumber.from(value)),
BigNumber.from(0)
);
}

async function simulateWithTimelockImpersonation(proposal) {
log("Simulating the proposal directly on the timelock...");
const { timelockAddr } = await getNamedAccounts();
const timelock = await impersonateAndFund(timelockAddr);

for (const action of proposal.actions) {
const { contract, signature, args } = action;
const { contract, signature, args, value } = action;
const txOpts = {
...(await getTxOpts()),
...(value ? { value } : {}),
};

log(`Sending governance action ${signature} to ${contract.address}`);
await contract.connect(timelock)[signature](...args, await getTxOpts());
await contract.connect(timelock)[signature](...args, txOpts);

console.log(`... ${signature} completed`);
}
Expand Down Expand Up @@ -1232,18 +1253,22 @@ function deploymentWithGuardianGovernor(opts, fn) {

const guardianActions = [];
for (const action of proposal.actions) {
const { contract, signature, args } = action;
const { contract, signature, args, value } = action;
const txOpts = {
...(await getTxOpts()),
...(value ? { value } : {}),
};

log(`Sending governance action ${signature} to ${contract.address}`);
const result = await withConfirmation(
contract.connect(sGuardian)[signature](...args, await getTxOpts())
contract.connect(sGuardian)[signature](...args, txOpts)
);
guardianActions.push({
sig: signature,
args: args,
to: contract.address,
data: result.data,
value: result.value.toString(),
value: BigNumber.from(value ?? result.value ?? 0).toString(),
});

console.log(`... ${signature} completed`);
Expand Down Expand Up @@ -1449,6 +1474,7 @@ module.exports = {

constructContractMethod,
buildGnosisSafeJson,
getProposalExecutionValue,

encodeSaltForCreateX,
createPoolBoosterSonic,
Expand Down
2 changes: 1 addition & 1 deletion contracts/utils/governor.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ async function proposeGovernanceArgs(governorArgsArray) {

return [
targets,
Array(governorArgsArray.length).fill(BigNumber.from(0)),
governorArgsArray.map((action) => BigNumber.from(action.value ?? 0)),
sigs,
calldata,
];
Expand Down
Loading