From 3534c231712ee60f69220209bb66c9996173b9e6 Mon Sep 17 00:00:00 2001 From: Victor Elias Date: Wed, 11 Oct 2023 15:22:21 -0300 Subject: [PATCH] doc: Address comments on deploy steps doc --- doc/deploy_delta.md | 82 +++++++++--- tasks/verify-delta-deployment.ts | 221 +++++++++++++++++++++++++++++++ 2 files changed, 284 insertions(+), 19 deletions(-) create mode 100644 tasks/verify-delta-deployment.ts diff --git a/doc/deploy_delta.md b/doc/deploy_delta.md index a36a4dd9..560b8246 100644 --- a/doc/deploy_delta.md +++ b/doc/deploy_delta.md @@ -1,6 +1,16 @@ # Livepeer Delta upgrade deployment steps -## Step 1: Deploy contracts +## Step 1: Setup deployer account + +Configure the private key for an account that has enough ETH to run the deploy transactions. + +``` +export PRIVATE_KEY=... (no 0x prefix) +``` + +Alternatively you can set it in the `.env` file in your repo. All commands listed below expect the same account to be configured. + +## Step 2: Deploy contracts Run the deployment script to deploy the new contracts: @@ -8,9 +18,21 @@ Run the deployment script to deploy the new contracts: npx hardhat deploy --tags DELTA_UPGRADE --network arbitrumMainnet ``` -You must configure the private key for an account that has enough ETH to run the transactions. +## Step 3: Verify contracts source code -## Step 2: Prepare governance update +Verify the contracts source code on Arbiscan: + +``` +yarn etherscan-verify --network arbitrumMainnet BondingVotesTarget BondingVotes Treasury LivepeerGovernorTarget LivepeerGovernor BondingManagerTarget +``` + +Then check the contracts on Arbiscan to make sure the source code is verified and matches the expected code (compare +with `npx hardhat flatten`). + +Also check the `Treasury` configuration, the only non-proxied contract, to make sure it wasn't manipulated by anyone +before the `initialize` function was called. + +## Step 4: Prepare governance update 1. Grab the full git hash from the current repository where you ran the deployment script, or get it from the output of the pending governance actions. @@ -20,8 +42,24 @@ You must configure the private key for an account that has enough ETH to run the a) the `0xPENDING_ADDRESS` address entries on the `updates/addresses.js` file with the deployed contracts. b) the `0xPENDING_GIT_HASH` references in `updates/l2-lip-delta-91-92.js` with the git hash from the protocol repository. +4. Commit and push the changes to the [`governance-scripts` PR](https://github.com/livepeer/governor-scripts/pull/7) and later merge it before deploy. + +## Step 5.1: Simulate governance update on a fork (optional) + +Make a fork of mainnet after the contracts deploy and make sure the Governance script can run cleanly. Also run the +validation script from Step 8 below to make sure everything is configured correctly. + +## Step 6: Renounce deployer admin role -## Step 3: Run governance update +Once the deployed contracts have been verified, the deployer admin role over the Treasury should be renounced. + +To do so, run the following command with the same account with which you ran the deploy: + +``` +npx hardhat treasury-renounce-admin-role --network arbitrumMainnet +``` + +## Step 7: Run governance update In the `governance-scripts` repository, run the governance update script: @@ -29,30 +67,36 @@ In the `governance-scripts` repository, run the governance update script: node index.js create ./updates/l2-lip-delta-91-92.js 0 ``` -This will print out the transaction that should be run by the governor owner to stage the update -on the governance contract. +This will print out the transaction that should be run by the **protocol** governor owner to stage the update. Note that +this refers to the existing `Governor` contract that manages the protocol governance, not the new `LivepeerGovernor` +that will only manage the treasury for now. -This should now be provided to the wallet interface to stage and then execute the governance update. +This output should be provided to the governor owner wallet interface to stage and then execute the governance update. -## Step 4: Validate the update +## Step 8: Validate the update -You can run the task at -[https://github.com/livepeer/protocol/blob/vg/fork-test-2/tasks/verify-delta-deployment.ts](verify-delta-deployment.ts) -to verify the deployment and governance update. +You can run the [verify-delta-deployment](../tasks/verify-delta-deployment.ts) task to verify the deployment and +governance update. -To do so, copy that task to your local task folder and then run: +To do so, run this with the same `deployer` account with which you ran the deploy: ``` npx hardhat verify-delta-deployment --network arbitrumMainnet ``` -## Step 5: Renounce deployer admin role +Keep in mind that it makes a voting power checkpoint of the top-stake orchestrator, in case they don't have one yet. -Once the governance update has been executed and the update validated (e.g. wait for next round and some treasury -contributions to take place as expected), the deployer admin role should be renounced. +## Step 9: Monitor the behavior -To do so, run the following command with the same account with which you ran the deploy: +Now wait until the next round so that the treasury reward cut rate gets updated. Check reward calls from orchestrators +and make sure they are being properly discounted by the expected 10% cut. -``` -npx hardhat treasury-renounce-admin-role --network arbitrumMainnet -``` +Also worth running the `verify-delta-deployment` script again and checking its output. + +## Step 10: Deploy subgraph and explorer + +Update the subgraph and explorer changes with any new contract addresses, merge them and deploy to production. This is +less risky as we can iterate quickly. Open PRs: + +- https://github.com/livepeer/subgraph/pull/157 +- https://github.com/livepeer/explorer/pull/224 diff --git a/tasks/verify-delta-deployment.ts b/tasks/verify-delta-deployment.ts new file mode 100644 index 00000000..a34022fd --- /dev/null +++ b/tasks/verify-delta-deployment.ts @@ -0,0 +1,221 @@ +import {task} from "hardhat/config" +import { + BondingManager, + BondingVotes, + Controller, + LivepeerGovernor, + RoundsManager, + Treasury +} from "../typechain" +import {contractId} from "../utils/helpers" +import {constants} from "../utils/constants" +import {ethers} from "ethers" + +const expected = { + bondingManager: { + nextRoundTreasuryRewardCutRate: constants.PERC_DIVISOR_PRECISE.div(10), + treasuryBalanceCeiling: ethers.utils.parseEther("750000") + }, + livepeerGovernor: { + name: "LivepeerGovernor", + votingDelay: 1, + votingPeriod: 10, + proposalThreshold: ethers.utils.parseEther("100"), + quorumNumerator: 333300, // 33.33% + quorumDenominator: 1000000, + quota: 500000 // 50% + }, + treasury: { + minDelay: 0 + } +} + +task( + "verify-delta-deployment", + "Verifies deployment of Delta upgrade contracts (LIP-91 and LIP-92)" +) + .addOptionalPositionalParam("substep", "Substep to verify (unimplemented)") + .setAction(async (taskArgs, hre) => { + const {ethers, deployments} = hre + + const controller = await deployments.get("Controller") + const Controller: Controller = await hre.ethers.getContractAt( + "Controller", + controller.address + ) + + const getContract = async ( + name: string + ): Promise => { + const address = await Controller.getContract(contractId(name)) + return await ethers.getContractAt(name, address) + } + + const checkParam = async ( + name: string, + actual: { toString: () => string }, + expected: { toString: () => string } + ) => { + console.log(`${name} is ${actual}`) + + if (actual.toString() !== expected.toString()) { + throw new Error(`${name} is ${actual} but expected ${expected}`) + } + } + + const BondingManager: BondingManager = await getContract( + "BondingManager" + ) + + const params = { + treasuryRewardCutRate: await BondingManager.treasuryRewardCutRate(), + nextRoundTreasuryRewardCutRate: + await BondingManager.nextRoundTreasuryRewardCutRate(), + treasuryBalanceCeiling: + await BondingManager.treasuryBalanceCeiling() + } + + await checkParam( + "BondingManager.nextRoundTreasuryRewardCutRate", + params.nextRoundTreasuryRewardCutRate, + expected.bondingManager.nextRoundTreasuryRewardCutRate + ) + + await checkParam( + "BondingManager.treasuryBalanceCeiling", + params.treasuryBalanceCeiling, + expected.bondingManager.treasuryBalanceCeiling + ) + + if ( + params.treasuryRewardCutRate.eq( + params.nextRoundTreasuryRewardCutRate + ) + ) { + console.log( + "Treasury reward cut rate of 10% already propagated to current round" + ) + } else { + console.log("Treasury reward cut rate hasn't propagated yet") + + const RoundsManager: RoundsManager = await getContract( + "RoundsManager" + ) + const initialized = await RoundsManager.currentRoundInitialized() + if (!initialized) { + console.log( + "Missing only current round initialization. Call RoundsManager.initializeRound()" + ) + } else { + const currentRound = await RoundsManager.currentRound() + const nextRound = currentRound.add(1) + const currRoundStartBlock = + await RoundsManager.currentRoundStartBlock() + const nextRoundStartBlock = currRoundStartBlock.add( + await RoundsManager.roundLength() + ) + const currBlock = await RoundsManager.blockNum() + + console.log( + `Cut rate will be initialized on round ${nextRound} starting at block ${nextRoundStartBlock} (${nextRoundStartBlock.sub( + currBlock + )} blocks left)` + ) + } + } + + const LivepeerGovernor: LivepeerGovernor = await getContract( + "LivepeerGovernor" + ) + const actual = { + name: await LivepeerGovernor.name(), + votingDelay: await LivepeerGovernor.votingDelay(), + votingPeriod: await LivepeerGovernor.votingPeriod(), + proposalThreshold: await LivepeerGovernor.proposalThreshold(), + quorumNumerator: await LivepeerGovernor["quorumNumerator()"](), + quorumDenominator: await LivepeerGovernor.quorumDenominator(), + quota: await LivepeerGovernor.quota() + } + + const allParams = Object.keys( + expected.livepeerGovernor + ) as (keyof typeof expected.livepeerGovernor)[] // ts sorcery + for (const param of allParams) { + await checkParam( + `LivepeerGovernor.${param}`, + actual[param], + expected.livepeerGovernor[param] + ) + } + + const Treasury: Treasury = await getContract("Treasury") + await checkParam( + "LivepeerGovernor.timelock", + await LivepeerGovernor.timelock(), + Treasury.address + ) + await checkParam( + "Treasury.minDelay", + await Treasury.getMinDelay(), + expected.treasury.minDelay + ) + + const roles = { + proposer: await Treasury.PROPOSER_ROLE(), + canceller: await Treasury.CANCELLER_ROLE(), + executor: await Treasury.EXECUTOR_ROLE(), + admin: await Treasury.TIMELOCK_ADMIN_ROLE() + } + const checkRole = async (role: keyof typeof roles) => { + const hasRole = await Treasury.hasRole( + roles[role], + LivepeerGovernor.address + ) + if (!hasRole) { + throw new Error( + `Treasury does not provide ${role} role for governor` + ) + } + console.log(`Treasury provides ${role} role for governor`) + } + + await checkRole("proposer") + await checkRole("canceller") + await checkRole("executor") + + const {deployer} = await hre.getNamedAccounts() // Fetch named accounts from hardhat.config.ts + const deployerHasAdmin = await Treasury.hasRole(roles.admin, deployer) + if (deployerHasAdmin) { + console.error( + `WARNING: Treasury still provides ADMIN role to deployer ${deployer}` + ) + } else { + console.log( + `Treasury does not provide admin role for deployer ${deployer}` + ) + } + + const BondingVotes: BondingVotes = await getContract("BondingVotes") + + const topTranscoder = await BondingManager.getFirstTranscoderInPool() + if (!(await BondingVotes.hasCheckpoint(topTranscoder))) { + console.log(`Checkpointing top transcoder ${topTranscoder}`) + await BondingManager.checkpointBondingState(topTranscoder).then( + tx => tx.wait() + ) + } + + await checkParam( + "BondingVotes.hasCheckpoint(topTranscoder)", + await BondingVotes.hasCheckpoint(topTranscoder), + true + ) + + await checkParam( + "BondingVotes.getVotes(topTranscoder)", + await BondingVotes.getVotes(topTranscoder), + await BondingManager.transcoderTotalStake(topTranscoder) + ) + + console.log("All good!") + })