From 65c939627bdba527f0e3241e874aea114a9c5afd Mon Sep 17 00:00:00 2001 From: "Mike Leach (Wander)" <6549498+VVander@users.noreply.github.com> Date: Thu, 3 Oct 2024 17:43:35 -0500 Subject: [PATCH 01/15] add script to deploy bare implementation and encode it in an upgradeTo call --- scripts/environments/deploy_dev.ts | 1 - scripts/environments/deploy_prod.ts | 1 - scripts/environments/deploy_staging.ts | 1 - scripts/environments/timelock.ts | 47 +- scripts/utils/deployment.ts | 639 +++++++++++-------- scripts/{environments => utils}/keyReader.ts | 0 tasks/timelockTasks.ts | 281 ++++---- test/Deployments/test-deploy-staging.ts | 176 ++--- 8 files changed, 631 insertions(+), 515 deletions(-) rename scripts/{environments => utils}/keyReader.ts (100%) diff --git a/scripts/environments/deploy_dev.ts b/scripts/environments/deploy_dev.ts index 839513c0..de7dfb20 100644 --- a/scripts/environments/deploy_dev.ts +++ b/scripts/environments/deploy_dev.ts @@ -5,7 +5,6 @@ import { deployUsingEnv} from "../utils/deployment"; console.log("Successfully deployed dev..."); console.log("Directory address", directory?.address); console.log(await directory?.getProtocol()) - console.log(await directory?.getRocketIntegrations()) } diff --git a/scripts/environments/deploy_prod.ts b/scripts/environments/deploy_prod.ts index 60c68226..6527e7b7 100644 --- a/scripts/environments/deploy_prod.ts +++ b/scripts/environments/deploy_prod.ts @@ -6,7 +6,6 @@ async function main() { console.log("Successfully deployed production..."); console.log("Directory address", directory?.address); console.log(await directory?.getProtocol()) - console.log(await directory?.getRocketIntegrations()) } main() diff --git a/scripts/environments/deploy_staging.ts b/scripts/environments/deploy_staging.ts index a3ecbde8..97b4701f 100644 --- a/scripts/environments/deploy_staging.ts +++ b/scripts/environments/deploy_staging.ts @@ -5,7 +5,6 @@ async function main() { console.log("Successfully deployed staging..."); console.log("Directory address", directory?.address); console.log(await directory?.getProtocol()) - console.log(await directory?.getRocketIntegrations()) } main() diff --git a/scripts/environments/timelock.ts b/scripts/environments/timelock.ts index c73b3810..1b4dfe93 100644 --- a/scripts/environments/timelock.ts +++ b/scripts/environments/timelock.ts @@ -1,27 +1,30 @@ -import findConfig from "find-config"; -import dotenv from "dotenv"; -import { getWalletFromPath } from "./keyReader"; +import findConfig from 'find-config'; +import dotenv from 'dotenv'; +import { getWalletFromPath } from '../utils/keyReader'; -export async function deployTimelockFromEnv(hre: any, env: string, log: boolean, minDelaySeconds: any, proposers: string[], executors: string[]) { - const dotenvPath = findConfig(`.env.${env}`); - if (dotenvPath !== null) { - dotenv.config({ path: dotenvPath }); - } else { - console.error("File ", `.env.${env} could not be found`) - } +export async function deployTimelockFromEnv( + hre: any, + env: string, + log: boolean, + minDelaySeconds: any, + proposers: string[], + executors: string[] +) { + const dotenvPath = findConfig(`.env.${env}`); + if (dotenvPath !== null) { + dotenv.config({ path: dotenvPath }); + } else { + console.error('File ', `.env.${env} could not be found`); + } - const deployerWallet = await getWalletFromPath(hre.ethers, process.env.DEPLOYER_PRIVATE_KEY_PATH as string); + const deployerWallet = await getWalletFromPath(hre.ethers, process.env.DEPLOYER_PRIVATE_KEY_PATH as string); - const Timelock = await hre.ethers.getContractFactory("TimelockController", deployerWallet) - const timelock = await Timelock.deploy( - minDelaySeconds, - proposers, - executors - ); + const Timelock = await hre.ethers.getContractFactory('TimelockController', deployerWallet); + const timelock = await Timelock.deploy(minDelaySeconds, proposers, executors); - if(log) { - console.log(minDelaySeconds.toString(), "second timelock deployed to", timelock.address); - } + if (log) { + console.log(minDelaySeconds.toString(), 'second timelock deployed to', timelock.address); + } - return timelock.address -} \ No newline at end of file + return timelock.address; +} diff --git a/scripts/utils/deployment.ts b/scripts/utils/deployment.ts index 58c48636..c693acc1 100644 --- a/scripts/utils/deployment.ts +++ b/scripts/utils/deployment.ts @@ -1,310 +1,393 @@ -import { ethers, upgrades } from "hardhat"; +import { ethers, upgrades } from 'hardhat'; import fs from 'fs'; import path from 'path'; -import { getNextContractAddress } from "../../test/utils/utils"; -import { getInitializerData } from "@openzeppelin/hardhat-upgrades/dist/utils"; +import { getNextContractAddress } from '../../test/utils/utils'; +import { getInitializerData } from '@openzeppelin/hardhat-upgrades/dist/utils'; import readline from 'readline'; -import { Treasury, Directory, IRocketStorage, IConstellationOracle, OperatorDistributor, PriceFetcher, RPLVault, SuperNodeAccount, WETHVault, Whitelist, NodeSetOperatorRewardDistributor, PoAConstellationOracle, MerkleClaimStreamer } from "../../typechain-types"; -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; -import { Protocol, Signers } from "../../test/integration/integration"; -import { RocketStorage, RocketTokenRPL } from "../../test/rocketpool/_utils/artifacts"; -import { ERC20 } from "../../typechain-types/contracts/Testing/Rocketpool/contract/util"; -import { expect } from "chai"; +import { + Treasury, + Directory, + IRocketStorage, + IConstellationOracle, + OperatorDistributor, + PriceFetcher, + RPLVault, + SuperNodeAccount, + WETHVault, + Whitelist, + NodeSetOperatorRewardDistributor, + PoAConstellationOracle, + MerkleClaimStreamer, +} from '../../typechain-types'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { Protocol, Signers } from '../../test/integration/integration'; +import { RocketStorage, RocketTokenRPL } from '../../test/rocketpool/_utils/artifacts'; +import { ERC20 } from '../../typechain-types/contracts/Testing/Rocketpool/contract/util'; +import { expect } from 'chai'; import { Wallet } from 'ethers'; -import { getWalletFromPath } from "../environments/keyReader"; -import findConfig from "find-config"; -import dotenv from "dotenv"; +import { getWalletFromPath } from './keyReader'; +import findConfig from 'find-config'; +import dotenv from 'dotenv'; // Function to prompt user for input function askQuestion(query: string): Promise { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); - - return new Promise(resolve => rl.question(query, ans => { - rl.close(); - resolve(ans); - })); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + return new Promise((resolve) => + rl.question(query, (ans) => { + rl.close(); + resolve(ans); + }) + ); } // Updated retry operation function export async function retryOperation(operation: () => Promise, retries: number = 3, extendedRetries: number = 3) { - try { - return await operation(); - } catch (error) { - console.log(error); - - if (retries > 0) { - console.log(`Retrying operation, attempts remaining: ${retries}...`); - return await retryOperation(operation, retries - 1, extendedRetries); - } else if (extendedRetries > 0) { - const answer = await askQuestion('Operation failed. Do you want to retry? (y/n): '); - if (answer.toLowerCase() === 'y') { - console.log(`Extended retry, attempts remaining: ${extendedRetries}...`); - return await retryOperation(operation, 0, extendedRetries - 1); - } else { - throw new Error('Operation aborted by the user.'); - } - } else { - throw error; - } + try { + return await operation(); + } catch (error) { + console.log(error); + + if (retries > 0) { + console.log(`Retrying operation, attempts remaining: ${retries}...`); + return await retryOperation(operation, retries - 1, extendedRetries); + } else if (extendedRetries > 0) { + const answer = await askQuestion('Operation failed. Do you want to retry? (y/n): '); + if (answer.toLowerCase() === 'y') { + console.log(`Extended retry, attempts remaining: ${extendedRetries}...`); + return await retryOperation(operation, 0, extendedRetries - 1); + } else { + throw new Error('Operation aborted by the user.'); + } + } else { + throw error; } + } } -export async function deployWithDirectory(treasurerAddress: string, deployer: Wallet | SignerWithAddress, nodesetAdmin: string, nodesetServerAdmin: string, directoryDeployer: Wallet | SignerWithAddress, rocketStorage: string, weth: string, sanctions: string, multiSigAdmin: string, adminServer: string, adminOracle: string) { - const { directory, superNode } = await fastDeployProtocol( - treasurerAddress, - deployer, - nodesetAdmin, - nodesetServerAdmin, - adminServer, - directoryDeployer, - adminOracle, - rocketStorage, - weth, - sanctions, - multiSigAdmin, - true - ); - upgrades.silenceWarnings() - return directory +export async function deployWithDirectory( + treasurerAddress: string, + deployer: Wallet | SignerWithAddress, + nodesetAdmin: string, + nodesetServerAdmin: string, + directoryDeployer: Wallet | SignerWithAddress, + rocketStorage: string, + weth: string, + sanctions: string, + multiSigAdmin: string, + adminServer: string, + adminOracle: string +) { + const { directory, superNode } = await fastDeployProtocol( + treasurerAddress, + deployer, + nodesetAdmin, + nodesetServerAdmin, + adminServer, + directoryDeployer, + adminOracle, + rocketStorage, + weth, + sanctions, + multiSigAdmin, + true + ); + upgrades.silenceWarnings(); + return directory; } export async function deployUsingEnv(environment: string) { - const dotenvPath = findConfig(`.${environment}.env`); - - if (dotenvPath !== null) { - dotenv.config({ path: dotenvPath }); - } else { - // Handle the case where no .env file is found - console.error('No .env.' + environment + 'file found'); - return; - } - - if (!process.env.DEPLOYER_PRIVATE_KEY_PATH || !process.env.DIRECTORY_DEPLOYER_PRIVATE_KEY_PATH) { - console.error('Private key paths are missing in the environment variables.'); - return; - } + const dotenvPath = findConfig(`.${environment}.env`); + + if (dotenvPath !== null) { + dotenv.config({ path: dotenvPath }); + } else { + // Handle the case where no .env file is found + console.error('No .env.' + environment + 'file found'); + return; + } + + if (!process.env.DEPLOYER_PRIVATE_KEY_PATH || !process.env.DIRECTORY_DEPLOYER_PRIVATE_KEY_PATH) { + console.error('Private key paths are missing in the environment variables.'); + return; + } + + try { + const deployerWallet = await getWalletFromPath(ethers, process.env.DEPLOYER_PRIVATE_KEY_PATH as string); + const directoryDeployerWallet = await getWalletFromPath( + ethers, + process.env.DIRECTORY_DEPLOYER_PRIVATE_KEY_PATH as string + ); - try { - const deployerWallet = await getWalletFromPath(ethers, process.env.DEPLOYER_PRIVATE_KEY_PATH as string); - const directoryDeployerWallet = await getWalletFromPath(ethers, process.env.DIRECTORY_DEPLOYER_PRIVATE_KEY_PATH as string) - - return await deployWithDirectory( - process.env.TREASURER_ADDRESS as string, - deployerWallet, - process.env.NODESET_ADMIN as string, - process.env.NODESET_SERVER_ADMIN as string, - directoryDeployerWallet, - process.env.RP_STORAGE_CONTRACT_ADDRESS as string, - process.env.WETH_ADDRESS as string, - process.env.SANCTIONS_LIST_ADDRESS as string, - process.env.ADMIN_MULTISIG as string, - process.env.ADMIN_SERVER as string, - process.env.ADMIN_ORACLE as string, - - ); - } catch (err) { - console.error('Error reading private keys or deploying:', err); - } + return await deployWithDirectory( + process.env.TREASURER_ADDRESS as string, + deployerWallet, + process.env.NODESET_ADMIN as string, + process.env.NODESET_SERVER_ADMIN as string, + directoryDeployerWallet, + process.env.RP_STORAGE_CONTRACT_ADDRESS as string, + process.env.WETH_ADDRESS as string, + process.env.SANCTIONS_LIST_ADDRESS as string, + process.env.ADMIN_MULTISIG as string, + process.env.ADMIN_SERVER as string, + process.env.ADMIN_ORACLE as string + ); + } catch (err) { + console.error('Error reading private keys or deploying:', err); + } } // Function to generate bytes32 representation for contract identifiers export const generateBytes32Identifier = (identifier: string) => { - // Correctly concatenate 'contract.address' with the identifier before hashing - return ethers.utils.solidityKeccak256(["string"], [`contract.address${identifier}`]); + // Correctly concatenate 'contract.address' with the identifier before hashing + return ethers.utils.solidityKeccak256(['string'], [`contract.address${identifier}`]); }; export async function fastDeployProtocol( - treasurer: string, - deployer: SignerWithAddress | Wallet, - nodesetAdmin: string, - nodesetAdminServer: string, - adminServer: string, - directoryDeployer: SignerWithAddress | Wallet, - oracleAdmin: string, - rocketStorage: string, - weth: string, - sanctions: string, - admin: string, - log: boolean, - localDev: boolean = false) { - const directoryAddress = await getNextContractAddress(directoryDeployer, localDev ? 1 : 0) // for some reason HH signers start with a nonce of 1 instead of 0 - - const whitelistProxy = await retryOperation(async () => { - const whitelist = await upgrades.deployProxy(await ethers.getContractFactory("contracts/Constellation/Whitelist.sol:Whitelist", deployer), [directoryAddress], { 'initializer': 'initializeWhitelist', 'kind': 'uups', 'unsafeAllow': ['constructor'] }); - if (log) console.log("whitelist deployed to", whitelist.address) - return whitelist; - }); - - const vCWETHProxy = await retryOperation(async () => { - const vCWETH = await upgrades.deployProxy(await ethers.getContractFactory("WETHVault", deployer), [directoryAddress, weth], { 'initializer': 'initializeVault', 'kind': 'uups', 'unsafeAllow': ['constructor', 'delegatecall'] }); - if (log) console.log("vaulted constellation eth deployed to", vCWETH.address) - return vCWETH; - }); - - const oracleProxy = await retryOperation(async () => { - const oracle = await upgrades.deployProxy(await ethers.getContractFactory("PoAConstellationOracle", deployer), [directoryAddress], { 'initializer': 'initializeOracle', 'kind': 'uups', 'unsafeAllow': ['constructor', 'delegatecall'] }); - if (log) console.log("admin oracle deployed to", oracle.address) - return oracle; - }); - - const addressRplContract = await retryOperation(async () => { - const bytes32IdentifierRplContract = generateBytes32Identifier('rocketTokenRPL'); - const rocketStorageDeployment = await ethers.getContractAt("RocketStorage", rocketStorage); - const addressRplContract = await rocketStorageDeployment.getAddress(bytes32IdentifierRplContract); - return addressRplContract - }) - - const rplContract = await retryOperation(async function () { - return await ethers.getContractAt("@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20", addressRplContract); + treasurer: string, + deployer: SignerWithAddress | Wallet, + nodesetAdmin: string, + nodesetAdminServer: string, + adminServer: string, + directoryDeployer: SignerWithAddress | Wallet, + oracleAdmin: string, + rocketStorage: string, + weth: string, + sanctions: string, + admin: string, + log: boolean, + localDev: boolean = false +) { + const directoryAddress = await getNextContractAddress(directoryDeployer, localDev ? 1 : 0); // for some reason HH signers start with a nonce of 1 instead of 0 + + const whitelistProxy = await retryOperation(async () => { + const whitelist = await upgrades.deployProxy( + await ethers.getContractFactory('contracts/Constellation/Whitelist.sol:Whitelist', deployer), + [directoryAddress], + { initializer: 'initializeWhitelist', kind: 'uups', unsafeAllow: ['constructor'] } + ); + if (log) console.log('whitelist deployed to', whitelist.address); + return whitelist; + }); + + const vCWETHProxy = await retryOperation(async () => { + const vCWETH = await upgrades.deployProxy( + await ethers.getContractFactory('WETHVault', deployer), + [directoryAddress, weth], + { initializer: 'initializeVault', kind: 'uups', unsafeAllow: ['constructor', 'delegatecall'] } + ); + if (log) console.log('vaulted constellation eth deployed to', vCWETH.address); + return vCWETH; + }); + + const oracleProxy = await retryOperation(async () => { + const oracle = await upgrades.deployProxy( + await ethers.getContractFactory('PoAConstellationOracle', deployer), + [directoryAddress], + { initializer: 'initializeOracle', kind: 'uups', unsafeAllow: ['constructor', 'delegatecall'] } + ); + if (log) console.log('admin oracle deployed to', oracle.address); + return oracle; + }); + + const addressRplContract = await retryOperation(async () => { + const bytes32IdentifierRplContract = generateBytes32Identifier('rocketTokenRPL'); + const rocketStorageDeployment = await ethers.getContractAt('RocketStorage', rocketStorage); + const addressRplContract = await rocketStorageDeployment.getAddress(bytes32IdentifierRplContract); + return addressRplContract; + }); + + const rplContract = await retryOperation(async function () { + return await ethers.getContractAt('@openzeppelin/contracts/token/ERC20/ERC20.sol:ERC20', addressRplContract); + }); + + const timelockShort = await retryOperation(async function () { + const timelockShort = await ( + await ethers.getContractFactory('ConstellationTimelock', deployer) + ).deploy(1, [admin], [admin], admin); + await timelockShort.deployed(); + if (log) console.log('timelock short deployed to', timelockShort.address); + return timelockShort; + }); + + const timelockMed = await retryOperation(async function () { + const timelockMed = await ( + await ethers.getContractFactory('ConstellationTimelock', deployer) + ).deploy(2, [admin], [admin], admin); + await timelockMed.deployed(); + if (log) console.log('timelock med deployed to', timelockMed.address); + return timelockMed; + }); + + const timelockLong = await retryOperation(async function () { + const timelockLong = await ( + await ethers.getContractFactory('ConstellationTimelock', deployer) + ).deploy(3, [admin], [admin], admin); + await timelockLong.deployed(); + if (log) console.log('timelock long deployed to', timelockLong.address); + return timelockLong; + }); + + const vCRPLProxy = await retryOperation(async function () { + const vCRPL = await upgrades.deployProxy( + await ethers.getContractFactory('RPLVault', deployer), + [directoryAddress, rplContract.address], + { initializer: 'initializeVault', kind: 'uups', unsafeAllow: ['constructor', 'delegatecall'] } + ); + if (log) console.log('vaulted constellation rpl deployed to', vCRPL.address); + return vCRPL; + }); + + const operatorDistributorProxy = await retryOperation(async function () { + const od = await upgrades.deployProxy( + await ethers.getContractFactory('OperatorDistributor', deployer), + [directoryAddress], + { initializer: 'initialize', kind: 'uups', unsafeAllow: ['constructor', 'delegatecall'] } + ); + if (log) console.log('operator distributor deployed to', od.address); + return od; + }); + + const merkleClaimStreamerProxy = await retryOperation(async function () { + const od = await upgrades.deployProxy( + await ethers.getContractFactory('MerkleClaimStreamer', deployer), + [directoryAddress], + { initializer: 'initialize', kind: 'uups', unsafeAllow: ['constructor', 'delegatecall'] } + ); + if (log) console.log('merkle claim streamer deployed to', od.address); + return od; + }); + + const yieldDistributorProxy = await retryOperation(async function () { + const yd = await upgrades.deployProxy( + await ethers.getContractFactory('NodeSetOperatorRewardDistributor', deployer), + [nodesetAdmin, nodesetAdminServer], + { initializer: 'initialize', kind: 'uups', unsafeAllow: ['constructor', 'delegatecall'] } + ); + if (log) console.log('yield distributor deployed to', yd.address); + return yd; + }); + + const priceFetcherProxy = await retryOperation(async function () { + const pf = await upgrades.deployProxy( + await ethers.getContractFactory('PriceFetcher', deployer), + [directoryAddress], + { initializer: 'initialize', kind: 'uups', unsafeAllow: ['constructor'] } + ); + if (log) console.log('price fetcher deployed to', pf.address); + return pf; + }); + + const treasuryProxy = await retryOperation(async function () { + const at = await upgrades.deployProxy(await ethers.getContractFactory('Treasury', deployer), [treasurer], { + initializer: 'initialize', + kind: 'uups', + unsafeAllow: ['constructor'], }); + if (log) console.log('admin treasury deployed to', at.address); + return at; + }); + + const superNodeProxy = await retryOperation(async function () { + const snap = await upgrades.deployProxy( + await ethers.getContractFactory('SuperNodeAccount', deployer), + [directoryAddress], + { initializer: 'initialize', kind: 'uups', unsafeAllow: ['constructor'] } + ); + if (log) console.log('super node deployed to', snap.address); + return snap; + }); + + if (log) { + console.log('verify directory input'); + console.log('whitelistProxy.address', whitelistProxy.address); + console.log('vCWETHProxy.address', vCWETHProxy.address); + console.log('vCRPLProxy.address', vCRPLProxy.address); + console.log('operatorDistributorProxy.address', operatorDistributorProxy.address); + console.log('merkleClaimStreamerProxy.address', merkleClaimStreamerProxy.address); + console.log('yieldDistributorProxy.address', yieldDistributorProxy.address); + console.log('oracle', oracleProxy.address); + console.log('priceFetcherProxy.address', priceFetcherProxy.address); + console.log('snap.address', superNodeProxy.address); + console.log('rocketStorage', rocketStorage); + console.log('weth', weth); + console.log('sanctions', sanctions); + } + + let timeLockShortAddress = localDev ? admin : timelockShort.address; + let timeLockMedAddress = localDev ? admin : timelockMed.address; + let timeLockLongAddress = localDev ? admin : timelockLong.address; + const directoryProxy = await retryOperation(async () => { + const dir = await upgrades.deployProxy( + await ethers.getContractFactory('Directory', directoryDeployer), + [ + [ + whitelistProxy.address, + vCWETHProxy.address, + vCRPLProxy.address, + operatorDistributorProxy.address, + merkleClaimStreamerProxy.address, + oracleProxy.address, + priceFetcherProxy.address, + superNodeProxy.address, + rocketStorage, + weth, + sanctions, + ], + yieldDistributorProxy.address, + [ + admin, + treasurer, + treasuryProxy.address, + timeLockShortAddress, + timeLockMedAddress, + timeLockLongAddress, + adminServer, + oracleAdmin, + ], + ], + { initializer: 'initialize', kind: 'uups', unsafeAllow: ['constructor'] } + ); - const timelockShort = await retryOperation(async function () { - const timelockShort = await (await ethers.getContractFactory("ConstellationTimelock", deployer)).deploy(1, [admin], [admin], admin); - await timelockShort.deployed(); - if (log) console.log("timelock short deployed to", timelockShort.address) - return timelockShort - }) - - const timelockMed = await retryOperation(async function () { - const timelockMed = await (await ethers.getContractFactory("ConstellationTimelock", deployer)).deploy(2, [admin], [admin], admin); - await timelockMed.deployed(); - if (log) console.log("timelock med deployed to", timelockMed.address) - return timelockMed - }) - - const timelockLong = await retryOperation(async function () { - const timelockLong = await (await ethers.getContractFactory("ConstellationTimelock", deployer)).deploy(3, [admin], [admin], admin); - await timelockLong.deployed(); - if (log) console.log("timelock long deployed to", timelockLong.address) - return timelockLong - }) - - const vCRPLProxy = await retryOperation(async function () { - const vCRPL = await upgrades.deployProxy(await ethers.getContractFactory("RPLVault", deployer), [directoryAddress, rplContract.address], { 'initializer': 'initializeVault', 'kind': 'uups', 'unsafeAllow': ['constructor', 'delegatecall'] }); - if (log) console.log("vaulted constellation rpl deployed to", vCRPL.address) - return vCRPL - }) - - const operatorDistributorProxy = await retryOperation(async function () { - const od = await upgrades.deployProxy(await ethers.getContractFactory("OperatorDistributor", deployer), [directoryAddress], { 'initializer': 'initialize', 'kind': 'uups', 'unsafeAllow': ['constructor', 'delegatecall'] }); - if (log) console.log("operator distributor deployed to", od.address) - return od - }) - - const merkleClaimStreamerProxy = await retryOperation(async function () { - const od = await upgrades.deployProxy(await ethers.getContractFactory("MerkleClaimStreamer", deployer), [directoryAddress], { 'initializer': 'initialize', 'kind': 'uups', 'unsafeAllow': ['constructor', 'delegatecall'] }); - if (log) console.log("merkle claim streamer deployed to", od.address) - return od - }) - - const yieldDistributorProxy = await retryOperation(async function () { - const yd = await upgrades.deployProxy(await ethers.getContractFactory("NodeSetOperatorRewardDistributor", deployer), [nodesetAdmin, nodesetAdminServer], { 'initializer': 'initialize', 'kind': 'uups', 'unsafeAllow': ['constructor', 'delegatecall'] }); - if (log) console.log("yield distributor deployed to", yd.address) - return yd - }) - - const priceFetcherProxy = await retryOperation(async function () { - const pf = await upgrades.deployProxy(await ethers.getContractFactory("PriceFetcher", deployer), [directoryAddress], { 'initializer': 'initialize', 'kind': 'uups', 'unsafeAllow': ['constructor'] }); - if (log) console.log("price fetcher deployed to", pf.address) - return pf - }) - - const treasuryProxy = await retryOperation(async function () { - const at = await upgrades.deployProxy(await ethers.getContractFactory("Treasury", deployer), [treasurer], { 'initializer': 'initialize', 'kind': 'uups', 'unsafeAllow': ['constructor'] }); - if (log) console.log("admin treasury deployed to", at.address) - return at - }) - - const superNodeProxy = await retryOperation(async function () { - const snap = await upgrades.deployProxy(await ethers.getContractFactory("SuperNodeAccount", deployer), [directoryAddress], { 'initializer': 'initialize', 'kind': 'uups', 'unsafeAllow': ['constructor'] }); - if (log) console.log("super node deployed to", snap.address) - return snap - }) - - if (log) { - console.log("verify directory input"); - console.log("whitelistProxy.address", whitelistProxy.address) - console.log("vCWETHProxy.address", vCWETHProxy.address) - console.log("vCRPLProxy.address", vCRPLProxy.address) - console.log("operatorDistributorProxy.address", operatorDistributorProxy.address) - console.log("merkleClaimStreamerProxy.address", merkleClaimStreamerProxy.address) - console.log("yieldDistributorProxy.address", yieldDistributorProxy.address) - console.log("oracle", oracleProxy.address) - console.log("priceFetcherProxy.address", priceFetcherProxy.address) - console.log("snap.address", superNodeProxy.address) - console.log("rocketStorage", rocketStorage) - console.log("weth", weth) - console.log("sanctions", sanctions) - } - - let timeLockShortAddress = localDev ? admin : timelockShort.address; - let timeLockMedAddress = localDev ? admin : timelockMed.address; - let timeLockLongAddress = localDev ? admin : timelockLong.address; - const directoryProxy = await retryOperation(async () => { - const dir = await upgrades.deployProxy(await ethers.getContractFactory("Directory", directoryDeployer), - [ - [ - whitelistProxy.address, - vCWETHProxy.address, - vCRPLProxy.address, - operatorDistributorProxy.address, - merkleClaimStreamerProxy.address, - oracleProxy.address, - priceFetcherProxy.address, - superNodeProxy.address, - rocketStorage, - weth, - sanctions, - ], - yieldDistributorProxy.address, - [ - admin, - treasurer, - treasuryProxy.address, - timeLockShortAddress, - timeLockMedAddress, - timeLockLongAddress, - adminServer, - oracleAdmin - ] - ], { 'initializer': 'initialize', 'kind': 'uups', 'unsafeAllow': ['constructor'] }); - - if (log) console.log("directory deployed to", dir.address) - - await dir.deployed(); - - return dir - }) + if (log) console.log('directory deployed to', dir.address); - if (log) { - if (directoryAddress.toLocaleLowerCase() === directoryProxy.address.toLocaleLowerCase()) { - console.log("directory matches predicted address", directoryAddress) - } else { - console.error("failed to deploy directory address to predicted address", directoryAddress, directoryProxy.address) - throw new Error("Bad predicted directory") - } - } + await dir.deployed(); - await retryOperation(async () => { - console.log("trying to lazyInitialize superNodeProxy...") - await superNodeProxy.lazyInitialize(); - }) + return dir; + }); - return { - whitelist: whitelistProxy as Whitelist, - vCWETH: vCWETHProxy as WETHVault, - vCRPL: vCRPLProxy as RPLVault, - operatorDistributor: operatorDistributorProxy as OperatorDistributor, - merkleClaimStreamer: merkleClaimStreamerProxy as MerkleClaimStreamer, - yieldDistributor: yieldDistributorProxy as NodeSetOperatorRewardDistributor, - priceFetcher: priceFetcherProxy as PriceFetcher, - oracle: oracleProxy as PoAConstellationOracle, - superNode: superNodeProxy as SuperNodeAccount, - treasury: treasuryProxy as Treasury, - directory: directoryProxy as Directory + if (log) { + if (directoryAddress.toLocaleLowerCase() === directoryProxy.address.toLocaleLowerCase()) { + console.log('directory matches predicted address', directoryAddress); + } else { + console.error( + 'failed to deploy directory address to predicted address', + directoryAddress, + directoryProxy.address + ); + throw new Error('Bad predicted directory'); } + } + + await retryOperation(async () => { + console.log('trying to lazyInitialize superNodeProxy...'); + await superNodeProxy.lazyInitialize(); + }); + + return { + whitelist: whitelistProxy as Whitelist, + vCWETH: vCWETHProxy as WETHVault, + vCRPL: vCRPLProxy as RPLVault, + operatorDistributor: operatorDistributorProxy as OperatorDistributor, + merkleClaimStreamer: merkleClaimStreamerProxy as MerkleClaimStreamer, + yieldDistributor: yieldDistributorProxy as NodeSetOperatorRewardDistributor, + priceFetcher: priceFetcherProxy as PriceFetcher, + oracle: oracleProxy as PoAConstellationOracle, + superNode: superNodeProxy as SuperNodeAccount, + treasury: treasuryProxy as Treasury, + directory: directoryProxy as Directory, + }; } - diff --git a/scripts/environments/keyReader.ts b/scripts/utils/keyReader.ts similarity index 100% rename from scripts/environments/keyReader.ts rename to scripts/utils/keyReader.ts diff --git a/tasks/timelockTasks.ts b/tasks/timelockTasks.ts index 6b51997b..24234675 100644 --- a/tasks/timelockTasks.ts +++ b/tasks/timelockTasks.ts @@ -1,124 +1,157 @@ - -import { task, types } from "hardhat/config"; - -task("upgradeTo", "Encodes the upgradeTo(address) function call for an upgradable contract") - .addParam("newImplementation", "The address of the new implementation contract", undefined, types.string) - .setAction(async ({ newImplementation }, hre) => { - const sigs = ["upgradeTo(address)"]; - const params = [[newImplementation]]; - - console.log(`Encoding upgradeTo with new implementation: ${newImplementation}`); - return await hre.run("encodeProposal", { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); - }); - -task("upgradeToAndCall", "Encodes the upgradeToAndCall(address,bytes) function call for an upgradable contract") - .addParam("newImplementation", "The address of the new implementation contract", undefined, types.string) - .addParam("data", "The calldata to be executed after the upgrade", undefined, types.string) - .setAction(async ({ newImplementation, data }, hre) => { - const sigs = ["upgradeToAndCall(address,bytes)"]; - const params = [[newImplementation, data]]; - - console.log(`Encoding upgradeToAndCall with new implementation: ${newImplementation} and data: ${data}`); - return await hre.run("encodeProposal", { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); - }); - - -task("setTreasuryFeeRplVault", "Encodes the setTreasuryFee(uint256) function call for RPLVault") - .addParam("treasuryFee", "The new treasury fee (uint256)", undefined, types.string) - .setAction(async ({ treasuryFee }, hre) => { - const sigs = ["setTreasuryFee(uint256)"]; - const params = [[treasuryFee]]; - - console.log(`Encoding setTreasuryFee for RPLVault with fee: ${treasuryFee}`); - return await hre.run("encodeProposal", { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); - }); - -task("setTreasuryFeeWETHVault", "Encodes the setTreasuryFee(uint256) function call for WETHVault") - .addParam("treasuryFee", "The new treasury fee (uint256)", undefined, types.string) - .setAction(async ({ treasuryFee }, hre) => { - const sigs = ["setTreasuryFee(uint256)"]; - const params = [[treasuryFee]]; - - console.log(`Encoding setTreasuryFee for WETHVault with fee: ${treasuryFee}`); - return await hre.run("encodeProposal", { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); - }); - -task("setNodeOperatorFee", "Encodes the setNodeOperatorFee(uint256) function call for WETHVault") - .addParam("nodeOperatorFee", "The new node operator fee (uint256)", undefined, types.string) - .setAction(async ({ nodeOperatorFee }, hre) => { - const sigs = ["setNodeOperatorFee(uint256)"]; - const params = [[nodeOperatorFee]]; - - console.log(`Encoding setNodeOperatorFee for WETHVault with fee: ${nodeOperatorFee}`); - return await hre.run("encodeProposal", { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); - }); - -task("setProtocolFees", "Encodes the setProtocolFees(uint256,uint256) function call for WETHVault") - .addParam("nodeOperatorFee", "The new node operator fee (uint256)", undefined, types.string) - .addParam("treasuryFee", "The new treasury fee (uint256)", undefined, types.string) - .setAction(async ({ nodeOperatorFee, treasuryFee }, hre) => { - const sigs = ["setProtocolFees(uint256,uint256)"]; - const params = [[nodeOperatorFee, treasuryFee]]; - - console.log(`Encoding setProtocolFees for WETHVault with nodeOperatorFee: ${nodeOperatorFee} and treasuryFee: ${treasuryFee}`); - return await hre.run("encodeProposal", { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); - }); - -task("setMintFee", "Encodes the setMintFee(uint256) function call for WETHVault") - .addParam("newMintFee", "The new mint fee (uint256)", undefined, types.string) - .setAction(async ({ newMintFee }, hre) => { - const sigs = ["setMintFee(uint256)"]; - const params = [[newMintFee]]; - - console.log(`Encoding setMintFee for WETHVault with newMintFee: ${newMintFee}`); - return await hre.run("encodeProposal", { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); - }); - -task("setLiquidityReservePercentWETHVault", "Encodes the setLiquidityReservePercent(uint256) function call for WETHVault") - .addParam("liquidityReservePercent", "The new liquidity reserve percent (uint256)", undefined, types.string) - .setAction(async ({ liquidityReservePercent }, hre) => { - const sigs = ["setLiquidityReservePercent(uint256)"]; - const params = [[liquidityReservePercent]]; - - console.log(`Encoding setLiquidityReservePercent for MerkleClaimStreamer with percent: ${liquidityReservePercent}`); - return await hre.run("encodeProposal", { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); - }); - -task("setMinWethRplRatio", "Encodes the setMinWethRplRatio(uint256) function call for RPLVault") - .addParam("minWethRplRatio", "The new minimum WETH/RPL ratio (uint256)", undefined, types.string) - .setAction(async ({ minWethRplRatio }, hre) => { - const sigs = ["setMinWethRplRatio(uint256)"]; - const params = [[minWethRplRatio]]; - - console.log(`Encoding setMinWethRplRatio for RPLVault with ratio: ${minWethRplRatio}`); - return await hre.run("encodeProposal", { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); - }); -task("setLiquidityReservePercentRPLVault", "Encodes the setLiquidityReservePercent(uint256) function call for RPLVault") - .addParam("liquidityReservePercent", "The new liquidity reserve percent (uint256)", undefined, types.string) - .setAction(async ({ liquidityReservePercent }, hre) => { - const sigs = ["setLiquidityReservePercent(uint256)"]; - const params = [[liquidityReservePercent]]; - - console.log(`Encoding setLiquidityReservePercent for RPLVault with percent: ${liquidityReservePercent}`); - return await hre.run("encodeProposal", { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); - }); - -task("setLockAmount", "Encodes the setLockAmount(uint256) function call for SuperNodeAccount") - .addParam("newLockThreshold", "The new lock threshold amount (uint256)", undefined, types.string) - .setAction(async ({ newLockThreshold }, hre) => { - const sigs = ["setLockAmount(uint256)"]; - const params = [[newLockThreshold]]; - - console.log(`Encoding setLockAmount for SuperNodeAccount with threshold: ${newLockThreshold}`); - return await hre.run("encodeProposal", { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); - }); - -task("setMaxWethRplRatio", "Encodes the setMaxWethRplRatio(uint256) function call for WETHVault") - .addParam("maxWethRplRatio", "The new maximum WETH/RPL ratio (uint256)", undefined, types.string) - .setAction(async ({ maxWethRplRatio }, hre) => { - const sigs = ["setMaxWethRplRatio(uint256)"]; - const params = [[maxWethRplRatio]]; - - console.log(`Encoding setMaxWethRplRatio for WETHVault with ratio: ${maxWethRplRatio}`); - return await hre.run("encodeProposal", { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); - }); +import { task, types } from 'hardhat/config'; +import findConfig from 'find-config'; +import { getWalletFromPath } from '../scripts/utils/keyReader'; +import dotenv from 'dotenv'; +import { ContractFactory } from 'ethers'; +import { deployContract } from '@nomiclabs/hardhat-ethers/types'; + +task('upgradeTo', 'Encodes the upgradeTo(address) function call for an upgradable contract') + .addParam('newImplementation', 'The address of the new implementation contract', undefined, types.string) + .setAction(async ({ newImplementation }, hre) => { + const sigs = ['upgradeTo(address)']; + const params = [[newImplementation]]; + + console.log(`Encoding upgradeTo with new implementation: ${newImplementation}`); + return await hre.run('encodeProposal', { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); + }); + +task( + 'deployAndEncodeUpgrade', + 'Deploys a new implementation contract and encodes the upgradeTo(address) function call for an upgradable contract' +) + .addParam('contractName', 'The name of the contract', undefined, types.string) + .addParam('environmentName', 'The name of the env file to use (.environmentName.env)', undefined, types.string) + .setAction(async ({ contractName, environmentName }, hre) => { + const dotenvPath = findConfig(`.${environmentName}.env`); + if (dotenvPath !== null) { + dotenv.config({ path: dotenvPath }); + } else { + // Handle the case where no .env file is found + console.error('No .env.' + environmentName + 'file found'); + return; + } + const deployerWallet = await getWalletFromPath(ethers, process.env.DEPLOYER_PRIVATE_KEY_PATH as string); + + console.log(`Deploying new implementation contract for ${contractName}...`); + const contract = await (await hre.ethers.deployContract(contractName, [], deployerWallet)).deployed(); + + console.log(`Deployed new implementation contract for ${contractName}: ${contract.address}`); + + return await hre.run('upgradeTo', { newImplementation: contract.address }); + }); + +task('upgradeToAndCall', 'Encodes the upgradeToAndCall(address,bytes) function call for an upgradable contract') + .addParam('newImplementation', 'The address of the new implementation contract', undefined, types.string) + .addParam('data', 'The calldata to be executed after the upgrade', undefined, types.string) + .setAction(async ({ newImplementation, data }, hre) => { + const sigs = ['upgradeToAndCall(address,bytes)']; + const params = [[newImplementation, data]]; + + console.log(`Encoding upgradeToAndCall with new implementation: ${newImplementation} and data: ${data}`); + return await hre.run('encodeProposal', { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); + }); + +task('setTreasuryFeeRplVault', 'Encodes the setTreasuryFee(uint256) function call for RPLVault') + .addParam('treasuryFee', 'The new treasury fee (uint256)', undefined, types.string) + .setAction(async ({ treasuryFee }, hre) => { + const sigs = ['setTreasuryFee(uint256)']; + const params = [[treasuryFee]]; + + console.log(`Encoding setTreasuryFee for RPLVault with fee: ${treasuryFee}`); + return await hre.run('encodeProposal', { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); + }); + +task('setTreasuryFeeWETHVault', 'Encodes the setTreasuryFee(uint256) function call for WETHVault') + .addParam('treasuryFee', 'The new treasury fee (uint256)', undefined, types.string) + .setAction(async ({ treasuryFee }, hre) => { + const sigs = ['setTreasuryFee(uint256)']; + const params = [[treasuryFee]]; + + console.log(`Encoding setTreasuryFee for WETHVault with fee: ${treasuryFee}`); + return await hre.run('encodeProposal', { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); + }); + +task('setNodeOperatorFee', 'Encodes the setNodeOperatorFee(uint256) function call for WETHVault') + .addParam('nodeOperatorFee', 'The new node operator fee (uint256)', undefined, types.string) + .setAction(async ({ nodeOperatorFee }, hre) => { + const sigs = ['setNodeOperatorFee(uint256)']; + const params = [[nodeOperatorFee]]; + + console.log(`Encoding setNodeOperatorFee for WETHVault with fee: ${nodeOperatorFee}`); + return await hre.run('encodeProposal', { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); + }); + +task('setProtocolFees', 'Encodes the setProtocolFees(uint256,uint256) function call for WETHVault') + .addParam('nodeOperatorFee', 'The new node operator fee (uint256)', undefined, types.string) + .addParam('treasuryFee', 'The new treasury fee (uint256)', undefined, types.string) + .setAction(async ({ nodeOperatorFee, treasuryFee }, hre) => { + const sigs = ['setProtocolFees(uint256,uint256)']; + const params = [[nodeOperatorFee, treasuryFee]]; + + console.log( + `Encoding setProtocolFees for WETHVault with nodeOperatorFee: ${nodeOperatorFee} and treasuryFee: ${treasuryFee}` + ); + return await hre.run('encodeProposal', { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); + }); + +task('setMintFee', 'Encodes the setMintFee(uint256) function call for WETHVault') + .addParam('newMintFee', 'The new mint fee (uint256)', undefined, types.string) + .setAction(async ({ newMintFee }, hre) => { + const sigs = ['setMintFee(uint256)']; + const params = [[newMintFee]]; + + console.log(`Encoding setMintFee for WETHVault with newMintFee: ${newMintFee}`); + return await hre.run('encodeProposal', { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); + }); + +task( + 'setLiquidityReservePercentWETHVault', + 'Encodes the setLiquidityReservePercent(uint256) function call for WETHVault' +) + .addParam('liquidityReservePercent', 'The new liquidity reserve percent (uint256)', undefined, types.string) + .setAction(async ({ liquidityReservePercent }, hre) => { + const sigs = ['setLiquidityReservePercent(uint256)']; + const params = [[liquidityReservePercent]]; + + console.log(`Encoding setLiquidityReservePercent for MerkleClaimStreamer with percent: ${liquidityReservePercent}`); + return await hre.run('encodeProposal', { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); + }); + +task('setMinWethRplRatio', 'Encodes the setMinWethRplRatio(uint256) function call for RPLVault') + .addParam('minWethRplRatio', 'The new minimum WETH/RPL ratio (uint256)', undefined, types.string) + .setAction(async ({ minWethRplRatio }, hre) => { + const sigs = ['setMinWethRplRatio(uint256)']; + const params = [[minWethRplRatio]]; + + console.log(`Encoding setMinWethRplRatio for RPLVault with ratio: ${minWethRplRatio}`); + return await hre.run('encodeProposal', { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); + }); +task('setLiquidityReservePercentRPLVault', 'Encodes the setLiquidityReservePercent(uint256) function call for RPLVault') + .addParam('liquidityReservePercent', 'The new liquidity reserve percent (uint256)', undefined, types.string) + .setAction(async ({ liquidityReservePercent }, hre) => { + const sigs = ['setLiquidityReservePercent(uint256)']; + const params = [[liquidityReservePercent]]; + + console.log(`Encoding setLiquidityReservePercent for RPLVault with percent: ${liquidityReservePercent}`); + return await hre.run('encodeProposal', { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); + }); + +task('setLockAmount', 'Encodes the setLockAmount(uint256) function call for SuperNodeAccount') + .addParam('newLockThreshold', 'The new lock threshold amount (uint256)', undefined, types.string) + .setAction(async ({ newLockThreshold }, hre) => { + const sigs = ['setLockAmount(uint256)']; + const params = [[newLockThreshold]]; + + console.log(`Encoding setLockAmount for SuperNodeAccount with threshold: ${newLockThreshold}`); + return await hre.run('encodeProposal', { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); + }); + +task('setMaxWethRplRatio', 'Encodes the setMaxWethRplRatio(uint256) function call for WETHVault') + .addParam('maxWethRplRatio', 'The new maximum WETH/RPL ratio (uint256)', undefined, types.string) + .setAction(async ({ maxWethRplRatio }, hre) => { + const sigs = ['setMaxWethRplRatio(uint256)']; + const params = [[maxWethRplRatio]]; + + console.log(`Encoding setMaxWethRplRatio for WETHVault with ratio: ${maxWethRplRatio}`); + return await hre.run('encodeProposal', { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); + }); diff --git a/test/Deployments/test-deploy-staging.ts b/test/Deployments/test-deploy-staging.ts index a700f460..72c0c909 100644 --- a/test/Deployments/test-deploy-staging.ts +++ b/test/Deployments/test-deploy-staging.ts @@ -1,103 +1,103 @@ -import { expect } from "chai"; -import { ethers, network, web3 } from "hardhat"; -import { loadFixture } from "@nomicfoundation/hardhat-network-helpers" -import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers" -import { BigNumber as BN } from "ethers"; -import { deployRocketPool } from "../rocketpool/_helpers/deployment"; -import { setDefaultParameters } from "../rocketpool/_helpers/defaults"; -import { RocketStorage } from "../rocketpool/_utils/artifacts"; -import { IRocketStorage } from "../../typechain-types"; -import { deployDev, deployDevUsingEnv } from "../../scripts/environments/deploy_dev"; -import { getWalletFromPath } from "../../scripts/environments/keyReader"; -import { deployStagingUsingEnv } from "../../scripts/environments/deploy_staging"; +import { expect } from 'chai'; +import { ethers, network, web3 } from 'hardhat'; +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers'; +import { BigNumber as BN } from 'ethers'; +import { deployRocketPool } from '../rocketpool/_helpers/deployment'; +import { setDefaultParameters } from '../rocketpool/_helpers/defaults'; +import { RocketStorage } from '../rocketpool/_utils/artifacts'; +import { IRocketStorage } from '../../typechain-types'; +import { deployDev, deployDevUsingEnv } from '../../scripts/environments/deploy_dev'; +import { getWalletFromPath } from '../../scripts/utils/keyReader'; +import { deployStagingUsingEnv } from '../../scripts/environments/deploy_staging'; //describe(`Test Deploy Staging Env`, () => { - // beforeEach(async function () { - // await network.provider.request({ - // method: "hardhat_reset", - // params: [{ - // forking: { - // jsonRpcUrl: process.env.HOLESKY_RPC, - // blockNumber: 2322494 - // }, - // }], - // }); - // }); +// beforeEach(async function () { +// await network.provider.request({ +// method: "hardhat_reset", +// params: [{ +// forking: { +// jsonRpcUrl: process.env.HOLESKY_RPC, +// blockNumber: 2322494 +// }, +// }], +// }); +// }); - // after(async () => { - // await network.provider.request({ - // method: "hardhat_reset", - // params: [{ - // }], - // }); - // }) +// after(async () => { +// await network.provider.request({ +// method: "hardhat_reset", +// params: [{ +// }], +// }); +// }) - // it.skip("Should pass", async () => { - // const directory = await deployStagingUsingEnv(); +// it.skip("Should pass", async () => { +// const directory = await deployStagingUsingEnv(); - // expect(await directory?.getMerkleClaimStreamerAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getOperatorDistributorAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getOperatorDistributorAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getOperatorRewardAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getOracleAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getPriceFetcherAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getRPLAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getRPLVaultAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getRocketDAOProtocolProposalAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getRocketDAOProtocolSettingsMinipool()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getRocketDAOProtocolSettingsRewardsAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getRocketDepositPoolAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getRocketMerkleDistributorMainnetAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getRocketMinipoolManagerAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getRocketNetworkPenalties()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getRocketNetworkPrices()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getRocketNetworkVotingAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getRocketNodeDepositAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getRocketNodeManagerAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getRocketNodeStakingAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getRocketStorageAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getSuperNodeAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getTreasuryAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getWETHAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getWETHVaultAddress()).not.equals(ethers.constants.AddressZero); - // expect(await directory?.getWhitelistAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getMerkleClaimStreamerAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getOperatorDistributorAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getOperatorDistributorAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getOperatorRewardAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getOracleAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getPriceFetcherAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getRPLAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getRPLVaultAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getRocketDAOProtocolProposalAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getRocketDAOProtocolSettingsMinipool()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getRocketDAOProtocolSettingsRewardsAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getRocketDepositPoolAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getRocketMerkleDistributorMainnetAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getRocketMinipoolManagerAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getRocketNetworkPenalties()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getRocketNetworkPrices()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getRocketNetworkVotingAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getRocketNodeDepositAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getRocketNodeManagerAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getRocketNodeStakingAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getRocketStorageAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getSuperNodeAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getTreasuryAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getWETHAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getWETHVaultAddress()).not.equals(ethers.constants.AddressZero); +// expect(await directory?.getWhitelistAddress()).not.equals(ethers.constants.AddressZero); - // const protocol = await directory?.getProtocol(); +// const protocol = await directory?.getProtocol(); - // for (let i = 0; i < protocol!.length; i++) { - // console.log(protocol![i]) - // expect(protocol![i]).not.equals(ethers.constants.AddressZero); - // } +// for (let i = 0; i < protocol!.length; i++) { +// console.log(protocol![i]) +// expect(protocol![i]).not.equals(ethers.constants.AddressZero); +// } - // const integrations = await directory?.getRocketIntegrations(); - // for (let i = 0; i < integrations!.length; i++) { - // console.log(integrations![i]) - // expect(integrations![i]).not.equals(ethers.constants.AddressZero); - // } +// const integrations = await directory?.getRocketIntegrations(); +// for (let i = 0; i < integrations!.length; i++) { +// console.log(integrations![i]) +// expect(integrations![i]).not.equals(ethers.constants.AddressZero); +// } - // const protocolRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("CORE_PROTOCOL_ROLE")); - // const expectedProtocolSigner = await getWalletFromPath(ethers, process.env.DIRECTORY_DEPLOYER_PRIVATE_KEY_PATH as string) - // expect(await directory?.hasRole(protocolRole, expectedProtocolSigner.address)).equals(false) +// const protocolRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("CORE_PROTOCOL_ROLE")); +// const expectedProtocolSigner = await getWalletFromPath(ethers, process.env.DIRECTORY_DEPLOYER_PRIVATE_KEY_PATH as string) +// expect(await directory?.hasRole(protocolRole, expectedProtocolSigner.address)).equals(false) - // const treasurerRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("TREASURER_ROLE")); - // expect(await directory?.hasRole(treasurerRole, process.env.TREASURER_ADDRESS as string)).equals(true) +// const treasurerRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("TREASURER_ROLE")); +// expect(await directory?.hasRole(treasurerRole, process.env.TREASURER_ADDRESS as string)).equals(true) - // const adminRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_ROLE")); - // expect(await directory?.hasRole(adminRole, process.env.ADMIN_MULTISIG as string)).equals(true) - // expect(await directory?.hasRole(adminRole, (await getWalletFromPath(ethers, process.env.TEMPORAL_ADMIN_KEY_PATH as string)).address)).equals(false) +// const adminRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_ROLE")); +// expect(await directory?.hasRole(adminRole, process.env.ADMIN_MULTISIG as string)).equals(true) +// expect(await directory?.hasRole(adminRole, (await getWalletFromPath(ethers, process.env.TEMPORAL_ADMIN_KEY_PATH as string)).address)).equals(false) - // const nodeSetServerAdminRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("NODESET_ADMIN_SERVER_ROLE")); - // const nodeSetAdminRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("NODESET_ADMIN_ROLE")); - // const nodesetRewardsAddress = await directory?.getOperatorRewardAddress(); - // const nodesetRewards = await ethers.getContractAt("NodeSetOperatorRewardDistributor", nodesetRewardsAddress as string); - // expect(await nodesetRewards?.hasRole(nodeSetServerAdminRole, process.env.NODESET_SERVER_ADMIN as string)).equals(true) - // expect(await nodesetRewards?.hasRole(nodeSetAdminRole, process.env.NODESET_ADMIN as string)).equals(true) +// const nodeSetServerAdminRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("NODESET_ADMIN_SERVER_ROLE")); +// const nodeSetAdminRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("NODESET_ADMIN_ROLE")); +// const nodesetRewardsAddress = await directory?.getOperatorRewardAddress(); +// const nodesetRewards = await ethers.getContractAt("NodeSetOperatorRewardDistributor", nodesetRewardsAddress as string); +// expect(await nodesetRewards?.hasRole(nodeSetServerAdminRole, process.env.NODESET_SERVER_ADMIN as string)).equals(true) +// expect(await nodesetRewards?.hasRole(nodeSetAdminRole, process.env.NODESET_ADMIN as string)).equals(true) - // const adminOracleRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_ORACLE_ROLE")); - // expect(await directory?.hasRole(adminOracleRole, process.env.ADMIN_ORACLE as string)).equals(true) +// const adminOracleRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_ORACLE_ROLE")); +// expect(await directory?.hasRole(adminOracleRole, process.env.ADMIN_ORACLE as string)).equals(true) - // const adminServerRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_SERVER_ROLE")); - // expect(await directory?.hasRole(adminServerRole, process.env.ADMIN_SERVER as string)).equals(true) +// const adminServerRole = ethers.utils.keccak256(ethers.utils.toUtf8Bytes("ADMIN_SERVER_ROLE")); +// expect(await directory?.hasRole(adminServerRole, process.env.ADMIN_SERVER as string)).equals(true) - // }) -//}); \ No newline at end of file +// }) +//}); From 64de3aa3319b9166513f23019a77ab6c2bcea074 Mon Sep 17 00:00:00 2001 From: "Mike Leach (Wander)" <6549498+VVander@users.noreply.github.com> Date: Fri, 4 Oct 2024 14:56:24 -0500 Subject: [PATCH 02/15] move to new script file, add prepareFullUpgrade task, import upgradeProxy task from other PR --- tasks/adminTasks.ts | 5 +-- tasks/timelockTasks.ts | 30 -------------- tasks/upgradeTasks.ts | 92 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 34 deletions(-) create mode 100644 tasks/upgradeTasks.ts diff --git a/tasks/adminTasks.ts b/tasks/adminTasks.ts index 0e0f6963..a9b3e1de 100644 --- a/tasks/adminTasks.ts +++ b/tasks/adminTasks.ts @@ -1,8 +1,5 @@ + import { task, types } from "hardhat/config"; -//import { retryOperation } from "../scripts/utils/deployment"; -import findConfig from "find-config"; -import dotenv from "dotenv"; -import { deployTimelockFromEnv } from "../scripts/environments/timelock"; task("encodeProposal", "Encodes a proposal for execution") .addParam("sigs", "Array of function signatures as a JSON string") diff --git a/tasks/timelockTasks.ts b/tasks/timelockTasks.ts index 24234675..73432dc6 100644 --- a/tasks/timelockTasks.ts +++ b/tasks/timelockTasks.ts @@ -1,9 +1,4 @@ import { task, types } from 'hardhat/config'; -import findConfig from 'find-config'; -import { getWalletFromPath } from '../scripts/utils/keyReader'; -import dotenv from 'dotenv'; -import { ContractFactory } from 'ethers'; -import { deployContract } from '@nomiclabs/hardhat-ethers/types'; task('upgradeTo', 'Encodes the upgradeTo(address) function call for an upgradable contract') .addParam('newImplementation', 'The address of the new implementation contract', undefined, types.string) @@ -15,31 +10,6 @@ task('upgradeTo', 'Encodes the upgradeTo(address) function call for an upgradabl return await hre.run('encodeProposal', { sigs: JSON.stringify(sigs), params: JSON.stringify(params) }); }); -task( - 'deployAndEncodeUpgrade', - 'Deploys a new implementation contract and encodes the upgradeTo(address) function call for an upgradable contract' -) - .addParam('contractName', 'The name of the contract', undefined, types.string) - .addParam('environmentName', 'The name of the env file to use (.environmentName.env)', undefined, types.string) - .setAction(async ({ contractName, environmentName }, hre) => { - const dotenvPath = findConfig(`.${environmentName}.env`); - if (dotenvPath !== null) { - dotenv.config({ path: dotenvPath }); - } else { - // Handle the case where no .env file is found - console.error('No .env.' + environmentName + 'file found'); - return; - } - const deployerWallet = await getWalletFromPath(ethers, process.env.DEPLOYER_PRIVATE_KEY_PATH as string); - - console.log(`Deploying new implementation contract for ${contractName}...`); - const contract = await (await hre.ethers.deployContract(contractName, [], deployerWallet)).deployed(); - - console.log(`Deployed new implementation contract for ${contractName}: ${contract.address}`); - - return await hre.run('upgradeTo', { newImplementation: contract.address }); - }); - task('upgradeToAndCall', 'Encodes the upgradeToAndCall(address,bytes) function call for an upgradable contract') .addParam('newImplementation', 'The address of the new implementation contract', undefined, types.string) .addParam('data', 'The calldata to be executed after the upgrade', undefined, types.string) diff --git a/tasks/upgradeTasks.ts b/tasks/upgradeTasks.ts new file mode 100644 index 00000000..be56c5ff --- /dev/null +++ b/tasks/upgradeTasks.ts @@ -0,0 +1,92 @@ +import { task, types } from 'hardhat/config'; +import findConfig from 'find-config'; +import dotenv from 'dotenv'; +import { getWalletFromPath } from '../scripts/utils/keyReader'; +import { ContractFactory } from 'ethers'; +import { deployContract } from '@nomiclabs/hardhat-ethers/types'; + +type UpgradeInfo = { + address: string; + encoding: string; +}; + +task( + 'deployAndEncodeUpgrade', + 'Deploys a new implementation contract and encodes the upgradeTo(address) function call for an upgradable contract' +) + .addParam('contractName', 'The name of the contract', undefined, types.string) + .addParam('environmentName', 'The name of the env file to use (.environmentName.env)', undefined, types.string) + .setAction(async ({ contractName, environmentName }, hre) => { + const dotenvPath = findConfig(`.${environmentName}.env`); + if (dotenvPath !== null) { + dotenv.config({ path: dotenvPath }); + } else { + // Handle the case where no .env file is found + console.error('No .env.' + environmentName + 'file found'); + return; + } + const deployerWallet = await getWalletFromPath(ethers, process.env.DEPLOYER_PRIVATE_KEY_PATH as string); + + const contract = await (await hre.ethers.deployContract(contractName, [], deployerWallet)).deployed(); + const address = contract.address; + console.log(`Deployed new implementation contract for ${contractName}: ${address}`); + + const encoding = await hre.run('upgradeTo', { newImplementation: address }); + + return { address, encoding }; + }); + +task( + 'prepareFullUpgrade', + 'Deploys new implementations for all contracts, encodes them, and returns the addresses and encodings' +) + .addParam('contractName', 'The name of the contract', undefined, types.string) + .addParam('environmentName', 'The name of the env file to use (.environmentName.env)', undefined, types.string) + .setAction(async ({ environmentName }, hre) => { + const contractNames = [ + 'Directory', + 'MerkleClaimStreamer', + 'SuperNodeAccount', + 'OperatorDistributor', + 'WETHVault', + 'RPLVault', + 'Whitelist', + 'PoAConstellationOracle', + 'PriceFetcher', + ]; + + let contractInfos: UpgradeInfo[] = []; + for (const contract of contractNames){ + contractInfos.push((await hre.run('deployAndEncodeUpgrade', { + contractName: contract, + environmentName, + })) as UpgradeInfo) + }; + + const addresses = contractInfos.map((info) => info.address); + const encodings = contractInfos.map((info) => info.encoding); + + return { addresses, encodings }; + }); + +// note that this should only be used for testing. Real contract deployments will use a timelock which requires encoding the upgrade proposal, then subsequent execution +task('upgradeProxy', 'Upgrades a proxy contract to a new implementation using upgrades.upgradeProxy') + .addParam('proxy', 'The address of the proxy contract', undefined, types.string) + .addParam('implementation', 'The name of the new implementation contract factory', undefined, types.string) + .setAction(async ({ proxy, implementation }, hre) => { + try { + console.log(`Upgrading proxy at address: ${proxy} to new implementation: ${implementation}`); + + const ImplFactory: any = await hre.ethers.getContractFactory(implementation); + const upgradedContract = await hre.upgrades.upgradeProxy(proxy, ImplFactory, { + kind: 'uups', + unsafeAllow: ['constructor'], + }); + + console.log(`Proxy upgraded. Implementation is now at: ${upgradedContract.address}`); + return upgradedContract.address; + } catch (error) { + console.error('An error occurred during the upgrade:', error); + throw error; + } + }); From 26c08d4543b9c1849d331f5e835cec874e671f6a Mon Sep 17 00:00:00 2001 From: "Mike Leach (Wander)" <6549498+VVander@users.noreply.github.com> Date: Fri, 4 Oct 2024 15:01:27 -0500 Subject: [PATCH 03/15] add tasks to config --- hardhat.config.ts | 53 +++++++++++++++++++++---------------------- tasks/upgradeTasks.ts | 3 --- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/hardhat.config.ts b/hardhat.config.ts index 1aad8bc9..d8924801 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -1,19 +1,20 @@ -import { HardhatUserConfig } from "hardhat/config"; -import "@nomicfoundation/hardhat-toolbox"; +import { HardhatUserConfig } from 'hardhat/config'; +import '@nomicfoundation/hardhat-toolbox'; import '@openzeppelin/hardhat-upgrades'; -import "hardhat-contract-sizer"; -import "hardhat-gas-reporter"; +import 'hardhat-contract-sizer'; +import 'hardhat-gas-reporter'; import '@nomiclabs/hardhat-truffle5'; import 'solidity-docgen'; import 'hardhat-contract-sizer'; // task commands -import './tasks/adminTasks' -import './tasks/timelockTasks' -import './tasks/viewOperatorDistributorTasks' -import './tasks/viewSuperNodeAccountTasks' +import './tasks/adminTasks'; +import './tasks/timelockTasks'; +import './tasks/upgradeTasks'; +import './tasks/viewOperatorDistributorTasks'; +import './tasks/viewSuperNodeAccountTasks'; -import dotenv from "dotenv"; +import dotenv from 'dotenv'; import findConfig from 'find-config'; const dotenvPath = findConfig('.env'); @@ -29,7 +30,7 @@ const config: HardhatUserConfig = { solidity: { compilers: [ { - version: "0.8.18", + version: '0.8.18', settings: { optimizer: { enabled: true, @@ -38,7 +39,7 @@ const config: HardhatUserConfig = { }, }, { - version: "0.8.17", + version: '0.8.17', settings: { optimizer: { enabled: true, @@ -54,17 +55,17 @@ const config: HardhatUserConfig = { runs: 15000, }, }, - } - ] + }, + ], }, - + gasReporter: { enabled: process.env.REPORT_GAS === 'true', // outputFile: 'gas-report.txt', }, docgen: { - exclude: ["Testing", "Interfaces"], + exclude: ['Testing', 'Interfaces'], }, networks: { @@ -77,11 +78,11 @@ const config: HardhatUserConfig = { }, holesky: { - url: process.env.HOLESKY_RPC || "" + url: process.env.HOLESKY_RPC || '', }, ethereum: { - url: process.env.ETHEREUM_MAINNET_RPC || "" + url: process.env.ETHEREUM_MAINNET_RPC || '', }, }, mocha: { @@ -90,19 +91,19 @@ const config: HardhatUserConfig = { etherscan: { apiKey: { - holesky: process.env.ETHERSCAN_HOLESKY_API_KEY || "" as string, - mainnet: process.env.ETHERSCAN_MAINNET_API_KEY || "" as string, + holesky: process.env.ETHERSCAN_HOLESKY_API_KEY || ('' as string), + mainnet: process.env.ETHERSCAN_MAINNET_API_KEY || ('' as string), }, customChains: [ { - network: "holesky", + network: 'holesky', chainId: 17000, urls: { - apiURL: "https://api-holesky.etherscan.io/api", - browserURL: "https://holesky.etherscan.io" - } - } - ] + apiURL: 'https://api-holesky.etherscan.io/api', + browserURL: 'https://holesky.etherscan.io', + }, + }, + ], }, contractSizer: { @@ -110,8 +111,6 @@ const config: HardhatUserConfig = { runOnCompile: true, disambiguatePaths: false, }, - }; - export default config; diff --git a/tasks/upgradeTasks.ts b/tasks/upgradeTasks.ts index be56c5ff..9e49343b 100644 --- a/tasks/upgradeTasks.ts +++ b/tasks/upgradeTasks.ts @@ -2,8 +2,6 @@ import { task, types } from 'hardhat/config'; import findConfig from 'find-config'; import dotenv from 'dotenv'; import { getWalletFromPath } from '../scripts/utils/keyReader'; -import { ContractFactory } from 'ethers'; -import { deployContract } from '@nomiclabs/hardhat-ethers/types'; type UpgradeInfo = { address: string; @@ -40,7 +38,6 @@ task( 'prepareFullUpgrade', 'Deploys new implementations for all contracts, encodes them, and returns the addresses and encodings' ) - .addParam('contractName', 'The name of the contract', undefined, types.string) .addParam('environmentName', 'The name of the env file to use (.environmentName.env)', undefined, types.string) .setAction(async ({ environmentName }, hre) => { const contractNames = [ From 4ab84a95918d96c88d2de5c03803b7ef75fb661c Mon Sep 17 00:00:00 2001 From: "Mike Leach (Wander)" <6549498+VVander@users.noreply.github.com> Date: Fri, 4 Oct 2024 15:32:28 -0500 Subject: [PATCH 04/15] cleanup --- tasks/upgradeTasks.ts | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/tasks/upgradeTasks.ts b/tasks/upgradeTasks.ts index 9e49343b..b43001d0 100644 --- a/tasks/upgradeTasks.ts +++ b/tasks/upgradeTasks.ts @@ -52,17 +52,24 @@ task( 'PriceFetcher', ]; - let contractInfos: UpgradeInfo[] = []; - for (const contract of contractNames){ - contractInfos.push((await hre.run('deployAndEncodeUpgrade', { - contractName: contract, - environmentName, - })) as UpgradeInfo) - }; - + // todo: this could be done in parallel, but we'd have to increment the tx nonce manually + // (this functionality would need to be added to deployAndEncodeUpgrade) + let contractInfos: UpgradeInfo[] = []; + for (const contract of contractNames) { + contractInfos.push( + (await hre.run('deployAndEncodeUpgrade', { + contractName: contract, + environmentName, + })) as UpgradeInfo + ); + } + const addresses = contractInfos.map((info) => info.address); const encodings = contractInfos.map((info) => info.encoding); - + + console.log('All addresses:\n', addresses); + console.log('All upgradeTo encodings:\n', encodings); + return { addresses, encodings }; }); From 65798eae719bb92827bb9929c1e476a8d23bba69 Mon Sep 17 00:00:00 2001 From: "Mike Leach (Wander)" <6549498+VVander@users.noreply.github.com> Date: Fri, 4 Oct 2024 16:57:52 -0500 Subject: [PATCH 05/15] throw exceptions on encodeProposal failure --- tasks/adminTasks.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/tasks/adminTasks.ts b/tasks/adminTasks.ts index a9b3e1de..0677e654 100644 --- a/tasks/adminTasks.ts +++ b/tasks/adminTasks.ts @@ -12,19 +12,15 @@ task("encodeProposal", "Encodes a proposal for execution") sigsArray = JSON.parse(sigs); paramsArray = JSON.parse(params); } catch (error) { - console.error("Error parsing JSON inputs:", error); - return; + throw new Error("Error parsing JSON inputs:" + error); } if (!Array.isArray(sigsArray) || !Array.isArray(paramsArray)) { - console.error("Both sigs and params must be JSON arrays."); - return; + throw new Error("Both sigs and params must be JSON arrays."); } if (sigsArray.length !== paramsArray.length) { - console.error("The number of signatures and parameter sets must match."); - console.error(`sigs length: ${sigsArray.length}, params length: ${paramsArray.length}`); - return; + throw new Error(`The number of signatures and parameter sets must match.\n sigs length: ${sigsArray.length}, params length: ${paramsArray.length}`); } const calldata = []; @@ -36,8 +32,7 @@ task("encodeProposal", "Encodes a proposal for execution") // Extract function name and parameter types const functionNameMatch = sig.match(/^(\w+)\((.*)\)$/); if (!functionNameMatch) { - console.error(`Invalid function signature format: ${sig}`); - return; + throw new Error(`Invalid function signature format: ${sig}`); } const functionName = functionNameMatch[1]; @@ -49,8 +44,7 @@ task("encodeProposal", "Encodes a proposal for execution") const encodedData = iface.encodeFunctionData(functionName, param); calldata.push(encodedData); } catch (error) { - console.error(`Error encoding function ${sig} with params ${JSON.stringify(param)}:`, error); - return; + throw new Error(`Error encoding function ${sig} with params ${JSON.stringify(param)}:` + error); } } From b6c08492dd17c54bbe205dd17aea8e606d351e25 Mon Sep 17 00:00:00 2001 From: "Mike Leach (Wander)" <6549498+VVander@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:09:36 -0500 Subject: [PATCH 06/15] get proxy addresses, format for tx --- tasks/upgradeTasks.ts | 94 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 74 insertions(+), 20 deletions(-) diff --git a/tasks/upgradeTasks.ts b/tasks/upgradeTasks.ts index b43001d0..810d4d3c 100644 --- a/tasks/upgradeTasks.ts +++ b/tasks/upgradeTasks.ts @@ -3,11 +3,6 @@ import findConfig from 'find-config'; import dotenv from 'dotenv'; import { getWalletFromPath } from '../scripts/utils/keyReader'; -type UpgradeInfo = { - address: string; - encoding: string; -}; - task( 'deployAndEncodeUpgrade', 'Deploys a new implementation contract and encodes the upgradeTo(address) function call for an upgradable contract' @@ -34,12 +29,48 @@ task( return { address, encoding }; }); +type UpgradeTxData = { + targets: string[]; + values: string; + payloads: string[]; + predecessor: string; + salt: string; +}; + +task('getProxyAddress', 'Gets the address of the proxy address for a given contract name and directory address') + .addParam('contractName', 'The name of the contract to retrieve the proxy address for', undefined, types.string) + .addParam('directoryAddress', 'The directory address for the deployment to check', undefined, types.string) + .setAction(async ({ contractName, directoryAddress }, hre) => { + if (contractName === 'Directory') { + console.log(directoryAddress); + return directoryAddress; + } + + const directory = await hre.ethers.getContractAt('Directory', directoryAddress); + let address = ''; + if (contractName === 'MerkleClaimStreamer') address = await directory.getMerkleClaimStreamerAddress(); + else if (contractName === 'SuperNodeAccount') address = await directory.getSuperNodeAddress(); + else if (contractName === 'OperatorDistributor') address = await directory.getOperatorDistributorAddress(); + else if (contractName === 'WETHVault') address = await directory.getWETHVaultAddress(); + else if (contractName === 'RPLVault') address = await directory.getRPLVaultAddress(); + else if (contractName === 'Whitelist') address = await directory.getWhitelistAddress(); + else if (contractName === 'PoAConstellationOracle') address = await directory.getOracleAddress(); + else if (contractName === 'PriceFetcher') address = await directory.getPriceFetcherAddress(); + else { + throw new Error('Invalid contract name'); + } + + console.log(address); + return address; + }); + task( 'prepareFullUpgrade', 'Deploys new implementations for all contracts, encodes them, and returns the addresses and encodings' ) + .addParam('directoryAddress', 'The directory address for the deployment to be upgraded', undefined, types.string) .addParam('environmentName', 'The name of the env file to use (.environmentName.env)', undefined, types.string) - .setAction(async ({ environmentName }, hre) => { + .setAction(async ({ directoryAddress, environmentName }, hre) => { const contractNames = [ 'Directory', 'MerkleClaimStreamer', @@ -52,29 +83,52 @@ task( 'PriceFetcher', ]; - // todo: this could be done in parallel, but we'd have to increment the tx nonce manually - // (this functionality would need to be added to deployAndEncodeUpgrade) - let contractInfos: UpgradeInfo[] = []; + // todo: this could be done in parallel, but we'd have to increment the tx nonce manually + // (this functionality would need to be added to deployAndEncodeUpgrade) + let targets: string[] = []; + let encodings: string[] = []; for (const contract of contractNames) { - contractInfos.push( - (await hre.run('deployAndEncodeUpgrade', { - contractName: contract, - environmentName, - })) as UpgradeInfo + targets.push( + ( + await hre.run('getProxyAddress', { + contractName: contract, + directoryAddress: directoryAddress, + }) + ).address + ); + + encodings.push( + ( + await hre.run('deployAndEncodeUpgrade', { + contractName: contract, + environmentName, + }) + ).encoding ); } - const addresses = contractInfos.map((info) => info.address); - const encodings = contractInfos.map((info) => info.encoding); + const values: string = '[' + '0,'.repeat(contractNames.length - 1) + '0' + ']'; + // the array of encodings comes in as an array of arrays with 1 element each due to the way encodeProposal works + //const encodings = contractInfos.map((info) => info.encoding).flat(); + const predecessor = '0x00000000000000000000000000000000'; + const salt = '0x00000000000000000000000000000000'; + const txData: UpgradeTxData = { targets, values, payloads: encodings, predecessor, salt }; - console.log('All addresses:\n', addresses); - console.log('All upgradeTo encodings:\n', encodings); + console.log('\n==== TRANSACTION DATA ===='); + console.log('Targets:\n', targets); + console.log('Values\n', values); + console.log('Payloads:\n', encodings); + console.log('Predecessor:\n', predecessor); + console.log('Salt:\n', salt); - return { addresses, encodings }; + return txData; }); // note that this should only be used for testing. Real contract deployments will use a timelock which requires encoding the upgrade proposal, then subsequent execution -task('upgradeProxy', 'Upgrades a proxy contract to a new implementation using upgrades.upgradeProxy') +task( + 'upgradeProxy', + 'Upgrades a proxy contract to a new implementation using upgrades.upgradeProxy. WILL NOT WORK ON DEPLOYMENTS WITH A TIMELOCK.' +) .addParam('proxy', 'The address of the proxy contract', undefined, types.string) .addParam('implementation', 'The name of the new implementation contract factory', undefined, types.string) .setAction(async ({ proxy, implementation }, hre) => { From 10f102927e0f593575b45e0b66a0f37b0fb86ec8 Mon Sep 17 00:00:00 2001 From: "Mike Leach (Wander)" <6549498+VVander@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:19:05 -0500 Subject: [PATCH 07/15] fix bugs --- tasks/upgradeTasks.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tasks/upgradeTasks.ts b/tasks/upgradeTasks.ts index 810d4d3c..8a7b9052 100644 --- a/tasks/upgradeTasks.ts +++ b/tasks/upgradeTasks.ts @@ -42,11 +42,11 @@ task('getProxyAddress', 'Gets the address of the proxy address for a given contr .addParam('directoryAddress', 'The directory address for the deployment to check', undefined, types.string) .setAction(async ({ contractName, directoryAddress }, hre) => { if (contractName === 'Directory') { - console.log(directoryAddress); + console.log('Directory proxy address:' + directoryAddress); return directoryAddress; } - const directory = await hre.ethers.getContractAt('Directory', directoryAddress); + const directory = await hre.ethers.getContractAt('Directory', directoryAddress, ); let address = ''; if (contractName === 'MerkleClaimStreamer') address = await directory.getMerkleClaimStreamerAddress(); else if (contractName === 'SuperNodeAccount') address = await directory.getSuperNodeAddress(); @@ -60,7 +60,7 @@ task('getProxyAddress', 'Gets the address of the proxy address for a given contr throw new Error('Invalid contract name'); } - console.log(address); + console.log(contractName + ' proxy address:' + address); return address; }); @@ -94,7 +94,7 @@ task( contractName: contract, directoryAddress: directoryAddress, }) - ).address + ) ); encodings.push( @@ -103,13 +103,11 @@ task( contractName: contract, environmentName, }) - ).encoding + ).encoding[0] // encodings come in as an array with 1 element due to the way encodeProposal works ); } const values: string = '[' + '0,'.repeat(contractNames.length - 1) + '0' + ']'; - // the array of encodings comes in as an array of arrays with 1 element each due to the way encodeProposal works - //const encodings = contractInfos.map((info) => info.encoding).flat(); const predecessor = '0x00000000000000000000000000000000'; const salt = '0x00000000000000000000000000000000'; const txData: UpgradeTxData = { targets, values, payloads: encodings, predecessor, salt }; From eeadacc74aab025b2af8cc219e507e61782f96da Mon Sep 17 00:00:00 2001 From: "Mike Leach (Wander)" <6549498+VVander@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:51:59 -0500 Subject: [PATCH 08/15] add output file --- .gitignore | 4 ++++ tasks/upgradeTasks.ts | 29 ++++++++++++++++++----------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index 69592ef4..648d916f 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,7 @@ artifacts #OpenZeppelin .openzeppelin/unknown-*.json *.keystore + +# deployments and upgrades +.deployments +.upgrades \ No newline at end of file diff --git a/tasks/upgradeTasks.ts b/tasks/upgradeTasks.ts index 8a7b9052..29067d20 100644 --- a/tasks/upgradeTasks.ts +++ b/tasks/upgradeTasks.ts @@ -8,9 +8,10 @@ task( 'Deploys a new implementation contract and encodes the upgradeTo(address) function call for an upgradable contract' ) .addParam('contractName', 'The name of the contract', undefined, types.string) - .addParam('environmentName', 'The name of the env file to use (.environmentName.env)', undefined, types.string) + .addParam('environmentName', 'The name of the env file to use (.environmentName.env).', undefined, types.string) .setAction(async ({ contractName, environmentName }, hre) => { const dotenvPath = findConfig(`.${environmentName}.env`); + if (dotenvPath !== null) { dotenv.config({ path: dotenvPath }); } else { @@ -21,6 +22,7 @@ task( const deployerWallet = await getWalletFromPath(ethers, process.env.DEPLOYER_PRIVATE_KEY_PATH as string); const contract = await (await hre.ethers.deployContract(contractName, [], deployerWallet)).deployed(); + const address = contract.address; console.log(`Deployed new implementation contract for ${contractName}: ${address}`); @@ -42,11 +44,11 @@ task('getProxyAddress', 'Gets the address of the proxy address for a given contr .addParam('directoryAddress', 'The directory address for the deployment to check', undefined, types.string) .setAction(async ({ contractName, directoryAddress }, hre) => { if (contractName === 'Directory') { - console.log('Directory proxy address:' + directoryAddress); + console.log('Directory proxy address: ' + directoryAddress); return directoryAddress; } - const directory = await hre.ethers.getContractAt('Directory', directoryAddress, ); + const directory = await hre.ethers.getContractAt('Directory', directoryAddress); let address = ''; if (contractName === 'MerkleClaimStreamer') address = await directory.getMerkleClaimStreamerAddress(); else if (contractName === 'SuperNodeAccount') address = await directory.getSuperNodeAddress(); @@ -60,7 +62,7 @@ task('getProxyAddress', 'Gets the address of the proxy address for a given contr throw new Error('Invalid contract name'); } - console.log(contractName + ' proxy address:' + address); + console.log(contractName + ' proxy address: ' + address); return address; }); @@ -89,12 +91,10 @@ task( let encodings: string[] = []; for (const contract of contractNames) { targets.push( - ( - await hre.run('getProxyAddress', { - contractName: contract, - directoryAddress: directoryAddress, - }) - ) + await hre.run('getProxyAddress', { + contractName: contract, + directoryAddress: directoryAddress, + }) ); encodings.push( @@ -119,10 +119,17 @@ task( console.log('Predecessor:\n', predecessor); console.log('Salt:\n', salt); + const fs = require('fs'); + const dir = __dirname + '/../.upgrades'; + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir); + } + fs.writeFileSync(dir + '/' + Date.now(), JSON.stringify(txData, null, 2)); + return txData; }); -// note that this should only be used for testing. Real contract deployments will use a timelock which requires encoding the upgrade proposal, then subsequent execution +// note that this should only be used for testing. Real contract deployments will use a timelock which requires encoding the upgrade proposal and scheduling before execution task( 'upgradeProxy', 'Upgrades a proxy contract to a new implementation using upgrades.upgradeProxy. WILL NOT WORK ON DEPLOYMENTS WITH A TIMELOCK.' From 308e50cc8bed1636d9fdf031bc111491641a83ac Mon Sep 17 00:00:00 2001 From: "Mike Leach (Wander)" <6549498+VVander@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:54:30 -0500 Subject: [PATCH 09/15] clean up output --- tasks/upgradeTasks.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/tasks/upgradeTasks.ts b/tasks/upgradeTasks.ts index 29067d20..75c41698 100644 --- a/tasks/upgradeTasks.ts +++ b/tasks/upgradeTasks.ts @@ -113,18 +113,15 @@ task( const txData: UpgradeTxData = { targets, values, payloads: encodings, predecessor, salt }; console.log('\n==== TRANSACTION DATA ===='); - console.log('Targets:\n', targets); - console.log('Values\n', values); - console.log('Payloads:\n', encodings); - console.log('Predecessor:\n', predecessor); - console.log('Salt:\n', salt); + let output = 'Targets:\n' + targets + '\nValues\n' + values + '\nPayloads:\n' + encodings + '\nPredecessor:\n' + predecessor + '\nSalt:\n' + salt; + console.log(output); const fs = require('fs'); const dir = __dirname + '/../.upgrades'; if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } - fs.writeFileSync(dir + '/' + Date.now(), JSON.stringify(txData, null, 2)); + fs.writeFileSync(dir + '/' + Date.now(), JSON.stringify(output, null, 2)); return txData; }); From 0ab8bd76ad62b1642f01ab2a1dc0b7e28bc806ca Mon Sep 17 00:00:00 2001 From: "Mike Leach (Wander)" <6549498+VVander@users.noreply.github.com> Date: Fri, 4 Oct 2024 18:39:19 -0500 Subject: [PATCH 10/15] clean up output --- tasks/upgradeTasks.ts | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/tasks/upgradeTasks.ts b/tasks/upgradeTasks.ts index 75c41698..da267d41 100644 --- a/tasks/upgradeTasks.ts +++ b/tasks/upgradeTasks.ts @@ -8,20 +8,18 @@ task( 'Deploys a new implementation contract and encodes the upgradeTo(address) function call for an upgradable contract' ) .addParam('contractName', 'The name of the contract', undefined, types.string) - .addParam('environmentName', 'The name of the env file to use (.environmentName.env).', undefined, types.string) + .addParam('environmentName', 'The name of the env file to use (.environmentName.env).', undefined, types.string, true) .setAction(async ({ contractName, environmentName }, hre) => { const dotenvPath = findConfig(`.${environmentName}.env`); + let contract; if (dotenvPath !== null) { dotenv.config({ path: dotenvPath }); + const deployerWallet = await getWalletFromPath(ethers, process.env.DEPLOYER_PRIVATE_KEY_PATH as string); + contract = await (await hre.ethers.deployContract(contractName, [], deployerWallet)).deployed(); } else { - // Handle the case where no .env file is found - console.error('No .env.' + environmentName + 'file found'); - return; + contract = await (await hre.ethers.deployContract(contractName)).deployed(); } - const deployerWallet = await getWalletFromPath(ethers, process.env.DEPLOYER_PRIVATE_KEY_PATH as string); - - const contract = await (await hre.ethers.deployContract(contractName, [], deployerWallet)).deployed(); const address = contract.address; console.log(`Deployed new implementation contract for ${contractName}: ${address}`); @@ -71,7 +69,7 @@ task( 'Deploys new implementations for all contracts, encodes them, and returns the addresses and encodings' ) .addParam('directoryAddress', 'The directory address for the deployment to be upgraded', undefined, types.string) - .addParam('environmentName', 'The name of the env file to use (.environmentName.env)', undefined, types.string) + .addParam('environmentName', 'The name of the env file to use (.environmentName.env)', undefined, types.string, true) .setAction(async ({ directoryAddress, environmentName }, hre) => { const contractNames = [ 'Directory', @@ -113,8 +111,18 @@ task( const txData: UpgradeTxData = { targets, values, payloads: encodings, predecessor, salt }; console.log('\n==== TRANSACTION DATA ===='); - let output = 'Targets:\n' + targets + '\nValues\n' + values + '\nPayloads:\n' + encodings + '\nPredecessor:\n' + predecessor + '\nSalt:\n' + salt; - console.log(output); + let output = + "Targets:\n[" + + targets + + "]\nValues\n" + + values + + "\nPayloads:\n[" + + encodings + + "]\nPredecessor:\n" + + predecessor + + "\nSalt:\n" + + salt; + console.log(output); const fs = require('fs'); const dir = __dirname + '/../.upgrades'; @@ -126,6 +134,15 @@ task( return txData; }); +task('testPrepareFullUpgrade', 'Tests the prepareFullUpgrade task') + .setAction(async ({ }, hre) => { + const directory = await (await hre.ethers.deployContract('Directory')).deployed(); + await hre.run('prepareFullUpgrade', { + directoryAddress: directory.address, + }); +}); + + // note that this should only be used for testing. Real contract deployments will use a timelock which requires encoding the upgrade proposal and scheduling before execution task( 'upgradeProxy', From 89f3e880a98b358ad3469f09b2a7aba165007bbf Mon Sep 17 00:00:00 2001 From: "Mike Leach (Wander)" <6549498+VVander@users.noreply.github.com> Date: Sat, 5 Oct 2024 01:13:03 -0500 Subject: [PATCH 11/15] add proposal tx logic --- tasks/upgradeTasks.ts | 53 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 8 deletions(-) diff --git a/tasks/upgradeTasks.ts b/tasks/upgradeTasks.ts index da267d41..85c4b723 100644 --- a/tasks/upgradeTasks.ts +++ b/tasks/upgradeTasks.ts @@ -2,6 +2,7 @@ import { task, types } from 'hardhat/config'; import findConfig from 'find-config'; import dotenv from 'dotenv'; import { getWalletFromPath } from '../scripts/utils/keyReader'; +const { Defender } = require('@openzeppelin/defender-sdk'); task( 'deployAndEncodeUpgrade', @@ -112,15 +113,15 @@ task( console.log('\n==== TRANSACTION DATA ===='); let output = - "Targets:\n[" + + 'Targets:\n[' + targets + - "]\nValues\n" + + ']\nValues\n' + values + - "\nPayloads:\n[" + + '\nPayloads:\n[' + encodings + - "]\nPredecessor:\n" + + ']\nPredecessor:\n' + predecessor + - "\nSalt:\n" + + '\nSalt:\n' + salt; console.log(output); @@ -134,15 +135,51 @@ task( return txData; }); -task('testPrepareFullUpgrade', 'Tests the prepareFullUpgrade task') - .setAction(async ({ }, hre) => { +task('submitNewUpgrade', 'Deploys new implementations, encodes them, and submits the scheduled proposal') + .addParam('timelockAddress', 'The address of the TIMELOCK_LONG contract', undefined, types.string) + .addParam('directoryAddress', 'The directory address for the deployment to be upgraded', undefined, types.string) + .addParam('environmentName', 'The name of the env file to use (.environmentName.env)', undefined, types.string, true) + .setAction(async ({ directoryAddress, timelockAddress, environmentName }, hre) => { + const txData = await hre.run('prepareFullUpgrade', { directoryAddress, environmentName }); + const dotenvPath = findConfig(`.${environmentName}.env`); + if (dotenvPath === null) throw new Error('Environment file not found'); + + dotenv.config({ path: dotenvPath }); + const client = new Defender({ + relayerApiKey: `${process.env.DEFENDER_RELAY_KEY}`, + relayerApiSecret: `${process.env.DEFENDER_RELAY_SECRET}`, + }); + + const provider = client.relaySigner.getProvider(); + const signer = await client.relaySigner.getSigner(provider, { speed: 'fast' }); + const timelock = await hre.ethers.getContractAt('ConstellationTimelock', timelockAddress, signer); + const tx = await timelock.scheduleBatch( + txData.targets, + txData.values, + txData.payloads, + txData.predecessor, + txData.salt, + await timelock.getMinDelay() + ); + const networkName = hre.network.name; + console.log( + 'Scheduled upgrade proposal with tx hash: ' + + tx.hash + + '\nhttps://' + + (networkName === 'mainnet' ? '' : networkName + '.') + + 'etherscan.io/tx/' + + tx.hash + ); + return tx; + }); + +task('testPrepareFullUpgrade', 'Tests the prepareFullUpgrade task').setAction(async ({}, hre) => { const directory = await (await hre.ethers.deployContract('Directory')).deployed(); await hre.run('prepareFullUpgrade', { directoryAddress: directory.address, }); }); - // note that this should only be used for testing. Real contract deployments will use a timelock which requires encoding the upgrade proposal and scheduling before execution task( 'upgradeProxy', From 9fe2ca2072f9dc623b723a31698d88ee72427864 Mon Sep 17 00:00:00 2001 From: "Mike Leach (Wander)" <6549498+VVander@users.noreply.github.com> Date: Sat, 5 Oct 2024 14:07:18 -0500 Subject: [PATCH 12/15] fix tx data, fix output --- tasks/upgradeTasks.ts | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/tasks/upgradeTasks.ts b/tasks/upgradeTasks.ts index 85c4b723..bc2956be 100644 --- a/tasks/upgradeTasks.ts +++ b/tasks/upgradeTasks.ts @@ -2,6 +2,7 @@ import { task, types } from 'hardhat/config'; import findConfig from 'find-config'; import dotenv from 'dotenv'; import { getWalletFromPath } from '../scripts/utils/keyReader'; +import { Bytes32 } from '@chainsafe/lodestar-types'; const { Defender } = require('@openzeppelin/defender-sdk'); task( @@ -32,10 +33,10 @@ task( type UpgradeTxData = { targets: string[]; - values: string; - payloads: string[]; - predecessor: string; - salt: string; + values: number[]; + payloads: Bytes32[]; + predecessor: Bytes32; + salt: Bytes32; }; task('getProxyAddress', 'Gets the address of the proxy address for a given contract name and directory address') @@ -87,7 +88,7 @@ task( // todo: this could be done in parallel, but we'd have to increment the tx nonce manually // (this functionality would need to be added to deployAndEncodeUpgrade) let targets: string[] = []; - let encodings: string[] = []; + let encodings: Bytes32[] = []; for (const contract of contractNames) { targets.push( await hre.run('getProxyAddress', { @@ -106,22 +107,22 @@ task( ); } - const values: string = '[' + '0,'.repeat(contractNames.length - 1) + '0' + ']'; - const predecessor = '0x00000000000000000000000000000000'; - const salt = '0x00000000000000000000000000000000'; + const values: number[] = Array(targets.length).fill(0); + const predecessor = ethers.utils.hexZeroPad('0x0', 32); + const salt = ethers.utils.hexZeroPad('0x0', 32); const txData: UpgradeTxData = { targets, values, payloads: encodings, predecessor, salt }; console.log('\n==== TRANSACTION DATA ===='); let output = - 'Targets:\n[' + + "Targets:\n[" + targets + - ']\nValues\n' + + "]\nValues\n[" + values + - '\nPayloads:\n[' + + "]\nPayloads:\n[" + encodings + - ']\nPredecessor:\n' + + "]\nPredecessor:\n" + predecessor + - '\nSalt:\n' + + "\nSalt:\n" + salt; console.log(output); From 46f4d0cca855dc69c8c7e9be2fed27bb0ce2b21b Mon Sep 17 00:00:00 2001 From: "Mike Leach (Wander)" <6549498+VVander@users.noreply.github.com> Date: Sat, 5 Oct 2024 14:14:10 -0500 Subject: [PATCH 13/15] fix directory upgrade permissions, fix output filename --- contracts/Constellation/Utils/Directory.sol | 4 ++-- tasks/upgradeTasks.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/Constellation/Utils/Directory.sol b/contracts/Constellation/Utils/Directory.sol index 69b49cc3..81cda650 100644 --- a/contracts/Constellation/Utils/Directory.sol +++ b/contracts/Constellation/Utils/Directory.sol @@ -92,9 +92,9 @@ contract Directory is UUPSUpgradeable, AccessControlUpgradeable { /// @notice Internal function to authorize contract upgrades. /// @dev This function is used internally to ensure that only administrators can authorize contract upgrades. - /// It checks whether the sender has the required ADMIN_ROLE before allowing the upgrade. + /// It checks whether the sender has the required TIMELOCK_LONG before allowing the upgrade. function _authorizeUpgrade(address) internal view override { - require(hasRole(Constants.ADMIN_ROLE, msg.sender), Constants.ADMIN_ONLY_ERROR); + require(hasRole(Constants.TIMELOCK_LONG, msg.sender), Constants.TIMELOCK_LONG_ONLY_ERROR); } //---- diff --git a/tasks/upgradeTasks.ts b/tasks/upgradeTasks.ts index bc2956be..ed9b208c 100644 --- a/tasks/upgradeTasks.ts +++ b/tasks/upgradeTasks.ts @@ -131,7 +131,7 @@ task( if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } - fs.writeFileSync(dir + '/' + Date.now(), JSON.stringify(output, null, 2)); + fs.writeFileSync(dir + '/' + Date.now() + '.log', JSON.stringify(output, null, 2)); return txData; }); From 72307d252deecd94a322277784755c37c83ba405 Mon Sep 17 00:00:00 2001 From: "Mike Leach (Wander)" <6549498+VVander@users.noreply.github.com> Date: Sat, 5 Oct 2024 14:48:44 -0500 Subject: [PATCH 14/15] fix output --- tasks/upgradeTasks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/upgradeTasks.ts b/tasks/upgradeTasks.ts index ed9b208c..7918d05a 100644 --- a/tasks/upgradeTasks.ts +++ b/tasks/upgradeTasks.ts @@ -131,7 +131,7 @@ task( if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } - fs.writeFileSync(dir + '/' + Date.now() + '.log', JSON.stringify(output, null, 2)); + fs.writeFileSync(dir + '/' + Date.now() + '.log', output); return txData; }); From af2b5de6b9c96d14a13b248daeeffc85af6e57d9 Mon Sep 17 00:00:00 2001 From: "Mike Leach (Wander)" <6549498+VVander@users.noreply.github.com> Date: Sat, 5 Oct 2024 14:52:20 -0500 Subject: [PATCH 15/15] update logging with timelock address --- tasks/upgradeTasks.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tasks/upgradeTasks.ts b/tasks/upgradeTasks.ts index 7918d05a..7db62029 100644 --- a/tasks/upgradeTasks.ts +++ b/tasks/upgradeTasks.ts @@ -72,7 +72,8 @@ task( ) .addParam('directoryAddress', 'The directory address for the deployment to be upgraded', undefined, types.string) .addParam('environmentName', 'The name of the env file to use (.environmentName.env)', undefined, types.string, true) - .setAction(async ({ directoryAddress, environmentName }, hre) => { + .addParam('timelockAddress', 'Optional: the address of the timelock to log', undefined, types.string, true) + .setAction(async ({ directoryAddress, environmentName, timelockAddress }, hre) => { const contractNames = [ 'Directory', 'MerkleClaimStreamer', @@ -114,15 +115,17 @@ task( console.log('\n==== TRANSACTION DATA ===='); let output = - "Targets:\n[" + + 'Timelock:\n' + + timelockAddress + + '\nTargets:\n[' + targets + - "]\nValues\n[" + + ']\nValues\n[' + values + - "]\nPayloads:\n[" + + ']\nPayloads:\n[' + encodings + - "]\nPredecessor:\n" + + ']\nPredecessor:\n' + predecessor + - "\nSalt:\n" + + '\nSalt:\n' + salt; console.log(output); @@ -141,7 +144,7 @@ task('submitNewUpgrade', 'Deploys new implementations, encodes them, and submits .addParam('directoryAddress', 'The directory address for the deployment to be upgraded', undefined, types.string) .addParam('environmentName', 'The name of the env file to use (.environmentName.env)', undefined, types.string, true) .setAction(async ({ directoryAddress, timelockAddress, environmentName }, hre) => { - const txData = await hre.run('prepareFullUpgrade', { directoryAddress, environmentName }); + const txData = await hre.run('prepareFullUpgrade', { directoryAddress, environmentName, timelockAddress }); const dotenvPath = findConfig(`.${environmentName}.env`); if (dotenvPath === null) throw new Error('Environment file not found');