Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Devnet setup #40

Open
wants to merge 15 commits into
base: develop
Choose a base branch
from
Open
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
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ node_modules
.DS_Store

reports/*
wallets/*
wallets/*

/configs/deployed-local-devnet.json
/configs/extra-deployed-local-devnet.json

generated-keys
2 changes: 1 addition & 1 deletion configs/envs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ import * as dotenv from 'dotenv';

const { parsed } = dotenv.config();

export const envs = parsed;
export const envs = { ...parsed, ...process.env };
166 changes: 166 additions & 0 deletions programs/devnet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
import { program } from '@command';
import { votingNewVote } from '@scripts';
import {
CallScriptActionWithDescription,
encodeCallScript,
forwardVoteFromTm,
splitAddresses,
logger,
compareContractCalls,
authorizedCall,
} from '@utils';
import chalk from 'chalk';
import {
encodeScriptsAO,
encodeScriptsDSM,
encodeScriptsLidoResumeIfStopped,
encodeScriptsRoles,
encodeScriptsVEBO,
encodeScriptsWQResumeIfPaused,
joinVotingDesc,
DEFAULT_DEVNET_CONFIG,
} from './omnibus-scripts/generators';
import { getLocatorContract, getProxyContract, locatorContract } from '@contracts';
import { getCreateAddress } from 'ethers';
import { wallet } from '@providers';
import { findDeploymentTransaction } from 'utils';

const devnet = program.command('devnet').description('scripts for devnet');

const head = chalk.blue.bold;
const bold = chalk.white.bold;

devnet
.command('setup')
.option('-l, --staking-limit <number>', 'staking limit in eth', String(DEFAULT_DEVNET_CONFIG.STAKING_LIMIT))
.option('-m, --oracles-members <string>', 'VEBO and AO members separated by comma', '')
.option('-q, --oracles-quorum <number>', 'VEBO and AO members quorum, should be > 50%', '1')
.option('-e, --oracles-initial-epoch <number>', 'initial epoch for VEBO and AO')
.option('-g, --dsm-guardians <string>', 'DSM guardians separated by comma', '')
.option('-d, --dsm-quorum <number>', 'DSM guardians quorum', '1')
.option('-b, --roles-beneficiary <string>', 'roles beneficiary', DEFAULT_DEVNET_CONFIG.ROLES_BENEFICIARY)
.action(async (options) => {
// Lido
logger.log(head('Lido'));
const stakingLimit = Number(options.stakingLimit);
const lidoResumeScripts = await encodeScriptsLidoResumeIfStopped(stakingLimit);
logger.log();

// Withdrawal Queue
logger.log(head('Withdrawal Queue'));
const wqResumeScripts = await encodeScriptsWQResumeIfPaused();
logger.log();

// Oracles
logger.log(head('Oracles'));

const oraclesInitialEpoch = options.oraclesInitialEpoch ? Number(options.oraclesInitialEpoch) : undefined;
const oraclesMembers = splitAddresses(options.oraclesMembers);
const oraclesQuorum = Number(options.oraclesQuorum);

/**/ logger.log(bold('VEBO'));
/**/ const veboScripts = await encodeScriptsVEBO(oraclesMembers, oraclesQuorum, oraclesInitialEpoch);
/**/ logger.log();

/**/ logger.log(bold('AO'));
/**/ const aoScripts = await encodeScriptsAO(oraclesMembers, oraclesQuorum, oraclesInitialEpoch);
/**/ logger.log();

// DSM
logger.log(head('DSM'));
const guardians = splitAddresses(options.dsmGuardians);
const dsmQuorum = Number(options.dsmQuorum);
const dsmScripts = await encodeScriptsDSM(guardians, dsmQuorum);
logger.log();

// Roles
logger.log(head('Roles'));
const rolesBeneficiary = options.rolesBeneficiary;
const rolesScripts = await encodeScriptsRoles(rolesBeneficiary);
logger.log();

// Voting calls
const votingCalls: CallScriptActionWithDescription[] = [
...lidoResumeScripts,
...wqResumeScripts,
...veboScripts,
...aoScripts,
...dsmScripts,
...rolesScripts,
];

// Voting description
const description = joinVotingDesc(votingCalls);
logger.log(head('Voting description:'));
logger.log(description);
logger.log();

// Voting start
const voteEvmScript = encodeCallScript(votingCalls);
const [newVoteCalldata] = votingNewVote(voteEvmScript, description);
await forwardVoteFromTm(newVoteCalldata);
});

devnet
.command('replace-dsm-with-eoa')
.argument('<eoa>', 'EOA address')
.action(async (eoa) => {
const getProxyAddress = async () => await locatorContract.getAddress();
const locatorProxyContract = getProxyContract(getProxyAddress);
const curLocatorImplementationAddress = await locatorProxyContract.proxy__getImplementation();

const currentDSMAddress = await locatorContract.depositSecurityModule();
const currentDSMAddressBytes = currentDSMAddress.slice(2).toLowerCase();
const eoaBytes = eoa.slice(2).toLowerCase();

if (eoaBytes.length !== 40) {
logger.error('Invalid EOA address');
return;
}

const currentLocatorImplementationDeploymentTx = await findDeploymentTransaction(curLocatorImplementationAddress);
const newLocatorDeploymentData = currentLocatorImplementationDeploymentTx.data.replaceAll(
currentDSMAddressBytes,
eoaBytes,
);

const newLocatorDeployTx = {
data: newLocatorDeploymentData,
gasLimit: 5000000,
};

const txResponse = await wallet.sendTransaction(newLocatorDeployTx);
logger.log('New Locator deployment tx hash:', txResponse.hash);

await txResponse.wait();

const newLocatorImplementationAddress = getCreateAddress(txResponse);
logger.log('New Locator implementation address:', newLocatorImplementationAddress);

logger.log('Locator implementations diff');

const curLocatorImplementationContract = getLocatorContract(curLocatorImplementationAddress);
const newLocatorImplementationContract = getLocatorContract(newLocatorImplementationAddress);

await compareContractCalls(
[curLocatorImplementationContract, newLocatorImplementationContract],
[
{ method: 'accountingOracle' },
{ method: 'depositSecurityModule' },
{ method: 'elRewardsVault' },
{ method: 'legacyOracle' },
{ method: 'lido' },
{ method: 'oracleReportSanityChecker' },
{ method: 'postTokenRebaseReceiver' },
{ method: 'burner' },
{ method: 'stakingRouter' },
{ method: 'treasury' },
{ method: 'validatorsExitBusOracle' },
{ method: 'withdrawalQueue' },
{ method: 'withdrawalVault' },
{ method: 'oracleDaemonConfig' },
],
);

await authorizedCall(locatorProxyContract, 'proxy__upgradeTo', [newLocatorImplementationAddress]);
});
1 change: 1 addition & 0 deletions programs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export * from './csm-oracle';
export * from './csm';
export * from './deposit-contract';
export * from './deposit-data';
export * from './devnet';
export * from './dsm';
export * from './el-requests';
export * from './eth';
Expand Down
160 changes: 160 additions & 0 deletions programs/omnibus-scripts/devnet-csm-start.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import {
aragonAgentAddress,
burnerAddress,
burnerContract,
stakingRouterAddress,
stakingRouterContract,
} from '@contracts';
import { provider } from '@providers';
import { encodeFromAgent, votingNewVote } from '@scripts';
import { CallScriptAction, encodeCallScript, forwardVoteFromTm, getRoleHash } from '@utils';
import { Contract, Interface } from 'ethers';

export const devnetCSMStart = async () => {
// Module address: 0x26aBc20a47f7e8991F1d26Bf0fC2bE8f24E9eF2A
const CS_MODULE_ADDRESS = process.env.CS_MODULE_ADDRESS as string;
// Accounting address: 0x782c7c96959bE2258a7b67439435885b39946c9E
const CS_ACCOUNTING_ADDRESS = process.env.CS_ACCOUNTING_ADDRESS as string;
// Oracle hash consensus address: 0x64c48254123A4c62278Fa671BEA32B45099Aeb9b
const CS_ORACLE_HASH_CONSENSUS_ADDRESS = process.env.CS_ORACLE_HASH_CONSENSUS_ADDRESS as string;
// Settle EL stealing address: 0xC86D7B14BE8c1E8718EaC65F95306A71d0E75aA8

const CS_MODULE_NAME = process.env.CS_MODULE_NAME ?? 'CommunityStaking';
const CS_STAKE_SHARE_LIMIT = process.env.CS_STAKE_SHARE_LIMIT ?? 2000; // 20%
const CS_PRIORITY_EXIT_SHARE_THRESHOLD = process.env.CS_PRIORITY_EXIT_SHARE_THRESHOLD ?? 2500; // 25%
const CS_STAKING_MODULE_FEE = process.env.CS_STAKING_MODULE_FEE ?? 800; // 8%
const CS_TREASURY_FEE = process.env.CS_TREASURY_FEE ?? 200; // 2%
const CS_MAX_DEPOSITS_PER_BLOCK = process.env.CS_MAX_DEPOSITS_PER_BLOCK ?? 30;
const CS_MIN_DEPOSIT_BLOCK_DISTANCE = process.env.CS_MIN_DEPOSIT_BLOCK_DISTANCE ?? 25;
// 60 (50 + 10)
// https://github.com/lidofinance/community-staking-module/blob/e1bbb4133d18206fc3a1a63ae660a670be08b6ea/script/DeployLocalDevNet.s.sol#L22
const CS_ORACLE_INITIAL_EPOCH = process.env.CS_ORACLE_INITIAL_EPOCH ?? 60;

const iface = new Interface([
'function proxy__upgradeTo(address)',
'function finalizeUpgrade_v2(uint256[],uint256[],uint256[])',
'function finalizeUpgrade_v2(uint256)',
'function finalizeUpgrade_v3()',
'function revokeRole(bytes32,address)',
'function grantRole(bytes32,address)',
'function STAKING_MODULE_UNVETTING_ROLE() view returns (bytes32)',
'function RESUME_ROLE() view returns (bytes32)',
'function MODULE_MANAGER_ROLE() view returns (bytes32)',
'function resume()',
'function activatePublicRelease()',
'function updateInitialEpoch(uint256)',
'function addStakingModule(string,address,uint256,uint256,uint256,uint256,uint256,uint256)',
'function setConsensusVersion(uint256)',
'function addEVMScriptFactory(address,bytes)',
]);

/**
* CSM
*/

// 1. Grant staking module manage role to agent
const srModuleManageRoleHash = await getRoleHash(stakingRouterContract, 'STAKING_MODULE_MANAGE_ROLE');
const [, moduleManageRoleGrantToAgentScript] = encodeFromAgent({
to: stakingRouterAddress,
data: iface.encodeFunctionData('grantRole', [srModuleManageRoleHash, aragonAgentAddress]),
});

// 2. Add staking module
const [, addStakingModuleScript] = encodeFromAgent({
to: stakingRouterAddress,
data: iface.encodeFunctionData('addStakingModule', [
CS_MODULE_NAME,
CS_MODULE_ADDRESS,
CS_STAKE_SHARE_LIMIT,
CS_PRIORITY_EXIT_SHARE_THRESHOLD,
CS_STAKING_MODULE_FEE,
CS_TREASURY_FEE,
CS_MAX_DEPOSITS_PER_BLOCK,
CS_MIN_DEPOSIT_BLOCK_DISTANCE,
]),
});

// 3. Grant request burn role to CSAccounting contract
const burnerRequestBurnRoleHash = await getRoleHash(burnerContract, 'REQUEST_BURN_SHARES_ROLE');
const [, requestBurnRoleGrantScript] = encodeFromAgent({
to: burnerAddress,
data: iface.encodeFunctionData('grantRole', [burnerRequestBurnRoleHash, CS_ACCOUNTING_ADDRESS]),
});

// 4. Grant resume role to agent
const csModuleContract = new Contract(CS_MODULE_ADDRESS, iface, provider);
const csmResumeRoleHash = await getRoleHash(csModuleContract, 'RESUME_ROLE');
const [, resumeRoleGrantScript] = encodeFromAgent({
to: CS_MODULE_ADDRESS,
data: iface.encodeFunctionData('grantRole', [csmResumeRoleHash, aragonAgentAddress]),
});

// 5. Grant csmModuleManager role to agent
const csmModuleManagerRoleHash = await getRoleHash(csModuleContract, 'MODULE_MANAGER_ROLE');
const [, csmModuleManagerRoleGrantScript] = encodeFromAgent({
to: CS_MODULE_ADDRESS,
data: iface.encodeFunctionData('grantRole', [csmModuleManagerRoleHash, aragonAgentAddress]),
});

// 6. Resume staking module
const [, resumeScript] = encodeFromAgent({
to: CS_MODULE_ADDRESS,
data: iface.encodeFunctionData('resume', []),
});

// 7. Activate public release
const [, activatePublicReleaseScript] = encodeFromAgent({
to: CS_MODULE_ADDRESS,
data: iface.encodeFunctionData('activatePublicRelease', []),
});

// 8. Revoke resume role from agent
const [, resumeRoleRevokeScript] = encodeFromAgent({
to: CS_MODULE_ADDRESS,
data: iface.encodeFunctionData('revokeRole', [csmResumeRoleHash, aragonAgentAddress]),
});

// 9. Revoke csmModuleManager role from agent
const [, resumeCsmModuleManagerRoleRevokeScript] = encodeFromAgent({
to: CS_MODULE_ADDRESS,
data: iface.encodeFunctionData('revokeRole', [csmModuleManagerRoleHash, aragonAgentAddress]),
});

// 10. Update initial epoch
const [, updateInitialEpochScript] = encodeFromAgent({
to: CS_ORACLE_HASH_CONSENSUS_ADDRESS,
data: iface.encodeFunctionData('updateInitialEpoch', [CS_ORACLE_INITIAL_EPOCH]),
});

// Collect all calls
const calls: CallScriptAction[] = [
moduleManageRoleGrantToAgentScript,
addStakingModuleScript,
requestBurnRoleGrantScript,
resumeRoleGrantScript,
csmModuleManagerRoleGrantScript,
resumeScript,
activatePublicReleaseScript,
resumeRoleRevokeScript,
resumeCsmModuleManagerRoleRevokeScript,
updateInitialEpochScript,
];

const description = [
`1. Grant staking module manage role to agent ${aragonAgentAddress}`,
`2. Add staking module ${CS_MODULE_NAME} with address ${CS_MODULE_ADDRESS}`,
`3. Grant request burn shares role to CSAccounting contract with address ${CS_ACCOUNTING_ADDRESS}`,
`4. Grant resume role to agent ${aragonAgentAddress}`,
`5. Grant csmModuleManager role to agent ${aragonAgentAddress}`,
`6. Resume staking module`,
`7. Activate public release`,
`8. Revoke resume role from agent ${aragonAgentAddress}`,
`9. Revoke csmModuleManager role from agent ${aragonAgentAddress}`,
`10. Update initial epoch to ${CS_ORACLE_INITIAL_EPOCH}`,
].join('\n');

const voteEvmScript = encodeCallScript(calls);
const [newVoteCalldata] = votingNewVote(voteEvmScript, description);

await forwardVoteFromTm(newVoteCalldata);
};
Loading