diff --git a/.gitignore b/.gitignore index 3c3431b4c..e74c012e3 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ docs/mocks .vscode/launch.json deploy_output.json deploy_parameters.json -deployments \ No newline at end of file +deployments +upgrade_parameters.json \ No newline at end of file diff --git a/deployment/README.md b/deployment/README.md index 39c98de70..7df09e304 100644 --- a/deployment/README.md +++ b/deployment/README.md @@ -23,7 +23,7 @@ Fill created `deploy_parameters.json` with appropiate parameters. To deploy contracts run `npm run deploy:ZkEVM:${network}`, for example: -> set `runs` parameter from the compiler settings in `hardhat.config.js` (i.e. `runs: 200`) +> set `runs` parameter from the compiler settings in `hardhat.config.js` (i.e. `runs: 100`) ``` npm run deploy:ZkEVM:goerli @@ -35,6 +35,8 @@ To verify contracts run `npm run verify:ZkEVM:${network}`, for example: npm run verify:ZkEVM:goerli ``` +A new folder will be created witth the following name `deployments/${network}_$(date +%s)` with all the output information and the OZ proxy information. + ## deploy-parameters.json - `trustedSequencerURL`: string, trustedSequencer URL @@ -46,7 +48,7 @@ npm run verify:ZkEVM:goerli ### Optional Parameters -- `privateKey`: string, privateKey of the deployment +- `deployerPvtKey`: string, deployerPvtKey of the deployer - `maxFeePerGas`:string, maxFeePerGas of all txs - `maxPriorityFeePerGas`:string, maxPriorityFeePerGas of all txs - `multiplierGas`: number, Gas multiplier. If maxFeePerGas and maxPriorityFeePerGas are set, will not take effect @@ -54,7 +56,9 @@ npm run verify:ZkEVM:goerli - `PolygonZkEVMBridgeMock`:Boolean, Wheather the PolygonZkEVMBridge will be mock or not ( the mock version has a ether limitation on deposits) - `admin`:address, Admin address - `trustedAggregator`:address, Trusted aggregator address +- `minDelayTimelock`: number, minimum timelock delay, +- `timelockAddress`: address, Timelock owner address ## Notes -- `gensis.json` has been generated using the tool: `https://github.com/0xPolygonHermez/zkevm-commonjs/blob/main/tools/fill-genesis/create-genesis.js` using as generator file: `genesis-gen.json` +- `genesis.json` has been generated using the tool: `src/create-genesis.js` using as generator file: `genesis-gen.json` diff --git a/deployment/deployContracts.js b/deployment/deployContracts.js index 99158431b..58c470bde 100644 --- a/deployment/deployContracts.js +++ b/deployment/deployContracts.js @@ -61,8 +61,8 @@ async function main() { // Load deployer let deployer; - if (deployParameters.privateKey) { - deployer = new ethers.Wallet(deployParameters.privateKey, currentProvider); + if (deployParameters.deployerPvtKey) { + deployer = new ethers.Wallet(deployParameters.deployerPvtKey, currentProvider); } else if (process.env.MNEMONIC) { deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(currentProvider); } else { diff --git a/deployment/deploy_parameters.json.example b/deployment/deploy_parameters.json.example index 054a97aec..b3b9f958c 100644 --- a/deployment/deploy_parameters.json.example +++ b/deployment/deploy_parameters.json.example @@ -5,7 +5,7 @@ "realVerifier": false, "chainID": 1001, "networkName": "zkevm", - "privateKey": "", + "deployerPvtKey": "", "maxFeePerGas":0, "maxPriorityFeePerGas":0, "multiplierGas": 0, diff --git a/deployment/simpleUpgradeScript.js b/deployment/simpleUpgradeScript.js deleted file mode 100644 index 3e6fb8c7d..000000000 --- a/deployment/simpleUpgradeScript.js +++ /dev/null @@ -1,54 +0,0 @@ -/* eslint-disable no-console, no-unused-vars */ -const hre = require('hardhat'); -const { ethers, upgrades } = require('hardhat'); -const path = require('path'); -require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); - -async function main() { - // Set multiplier Gas - const multiplierGas = 3; - const currentProvider = new ethers.providers.JsonRpcProvider(`https://${process.env.HARDHAT_NETWORK}.infura.io/v3/${process.env.INFURA_PROJECT_ID}`); - async function overrideFeeData() { - const feedata = await ethers.provider.getFeeData(); - return { - maxFeePerGas: feedata.maxFeePerGas.mul(multiplierGas), - maxPriorityFeePerGas: feedata.maxPriorityFeePerGas.mul(multiplierGas), - }; - } - currentProvider.getFeeData = overrideFeeData; - - let deployer; - if (process.env.MNEMONIC) { - deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(currentProvider); - } else { - [deployer] = (await ethers.getSigners()); - } - - // compìle contracts - await hre.run('compile'); - - const polygonZkEVMProxyAddress = '0xfefefefefefefefefefefefee'; - const polygonZkEVMFactory = await ethers.getContractFactory('PolygonZkEVMMock'); - - // Upgrade zkevm - const txZKEVM = await upgrades.upgradeProxy(polygonZkEVMProxyAddress, polygonZkEVMFactory); - - console.log(txZKEVM.deployTransaction); - console.log(await txZKEVM.deployTransaction.wait()); - console.log('upgrade succesfull ZKEVM'); - - const polygonZkEVMBridgeProxyAddress = '0xfefefefefefefefefefefefee'; - const polygonZkEVMBridgeFactory = await ethers.getContractFactory('PolygonZkEVMBridgeMock'); - - // Upgrade bridge - const txBridge = await upgrades.upgradeProxy(polygonZkEVMBridgeProxyAddress, polygonZkEVMBridgeFactory); - console.log(txBridge.deployTransaction); - console.log(await txBridge.deployTransaction.wait()); - console.log('upgrade succesfull Bridge'); -} -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); diff --git a/deployment/timeLockUpgrade.js b/deployment/timeLockUpgrade.js deleted file mode 100644 index b6abcc708..000000000 --- a/deployment/timeLockUpgrade.js +++ /dev/null @@ -1,108 +0,0 @@ -/* eslint-disable no-console, no-unused-vars, no-use-before-define */ -const hre = require('hardhat'); -const { ethers, upgrades } = require('hardhat'); -const path = require('path'); -require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); - -async function main() { - // Set multiplier Gas - const multiplierGas = 3; - const currentProvider = new ethers.providers.JsonRpcProvider(`https://${process.env.HARDHAT_NETWORK}.infura.io/v3/${process.env.INFURA_PROJECT_ID}`); - async function overrideFeeData() { - const feedata = await ethers.provider.getFeeData(); - return { - maxFeePerGas: feedata.maxFeePerGas.mul(multiplierGas), - maxPriorityFeePerGas: feedata.maxPriorityFeePerGas.mul(multiplierGas), - }; - } - currentProvider.getFeeData = overrideFeeData; - - let deployer; - if (process.env.MNEMONIC) { - deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(currentProvider); - } else { - [deployer] = (await ethers.getSigners()); - } - - // compìle contracts - await hre.run('compile'); - - const proxyPolygonZkEVMAddress = '0xFD44A8D8f28AadB1Ce916012c7C921f759056Ef7'; - const polygonZkEVMFactory = await ethers.getContractFactory('PolygonZkEVMMock'); - - // Upgrade zkevm - const newImplPolygonZkEVMAddress = await upgrades.prepareUpgrade(proxyPolygonZkEVMAddress, polygonZkEVMFactory); - const proxyAdmin = await upgrades.admin.getInstance(); - - console.log({ newImplPolygonZkEVMAddress }); - - // Use timelock - const operation = genOperation( - proxyAdmin.address, - 0, // value - proxyAdmin.interface.encodeFunctionData( - 'upgrade', - [proxyPolygonZkEVMAddress, - newImplPolygonZkEVMAddress], - ), - ethers.constants.HashZero, // predecesoor - ethers.constants.HashZero, // salt TODO - ); - - // Timelock operations - const TimelockFactory = await ethers.getContractFactory('PolygonZkEVMTimelock', deployer); - const minDelay = 10; // TODO upgrade parameter - - // Schedule operation - const scheduleData = TimelockFactory.interface.encodeFunctionData( - 'schedule', - [ - operation.target, - operation.value, - operation.data, - operation.predecessor, - operation.salt, - minDelay, - ], - ); - // Executre operation - const executeData = TimelockFactory.interface.encodeFunctionData( - 'execute', - [ - operation.target, - operation.value, - operation.data, - operation.predecessor, - operation.salt, - ], - ); - - console.log({ scheduleData }); - console.log({ executeData }); -} -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); - -// OZ test functions -function genOperation(target, value, data, predecessor, salt) { - const id = ethers.utils.solidityKeccak256([ - 'address', - 'uint256', - 'bytes', - 'uint256', - 'bytes32', - ], [ - target, - value, - data, - predecessor, - salt, - ]); - return { - id, target, value, data, predecessor, salt, - }; -} diff --git a/deployment/verifyContracts.js b/deployment/verifyContracts.js index 16aca5143..3a8e0331a 100644 --- a/deployment/verifyContracts.js +++ b/deployment/verifyContracts.js @@ -52,8 +52,8 @@ async function main() { // verify timeLock let deployer; - if (deployParameters.privateKey) { - deployer = new ethers.Wallet(deployParameters.privateKey); + if (deployParameters.deployerPvtKey) { + deployer = new ethers.Wallet(deployParameters.deployerPvtKey); } else if (process.env.MNEMONIC) { deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0'); } else { diff --git a/package.json b/package.json index afed44cdc..e2ca6fdaa 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "docgen": "npx solidity-docgen --solc-module solc-0.8 -t ./docs/templates -e ./contracts/verifiers,./contracts/mocks", "deploy:ZkEVM:hardhat": "npx hardhat run deployment/deployContracts.js --network hardhat", "deploy:ZkEVM:goerli": "npx hardhat run deployment/deployContracts.js --network goerli && npm run saveDeployment:goerli", - "upgrade:timelock:goerli": "npx hardhat run deployment/timeLockUpgrade.js --network goerli", + "upgrade:timelock:goerli": "npx hardhat run upgrade/timeLockUpgrade.js --network goerli", "verify:ZkEVM:goerli": "npx hardhat run deployment/verifyContracts.js --network goerli", "lint": "npx eslint ./test && npx eslint ./docker/scripts && npx eslint ./deployment && npx eslint ./src", "lint:fix": "npx eslint ./test --fix && npx eslint ./docker/scripts --fix && npx eslint ./deployment --fix && npx eslint ./src --fix", diff --git a/upgrade/README.md b/upgrade/README.md new file mode 100644 index 000000000..c8eb0ea57 --- /dev/null +++ b/upgrade/README.md @@ -0,0 +1,53 @@ +## Requirements + +- node version: 14.x +- npm version: 7.x + +## Upgrade + +``` +npm i +cp .env.example .env +``` + +Fill `.env` with your `MNEMONIC` and `INFURA_PROJECT_ID` + +In order to upgrade the contracts we will need the information on `deployments/${network}_$(date +%s)` + +In project root, copy the `${network}.json` of the deployment that you want to upgrade and copy it on the `./.openzeppelin` +e.g. `cp deployments/${network}_$(date +%s)/${network}.json ./.openzeppelin` + +Then fill the upgrade parameters: + +``` +cd deployment +cp upgrade_parameters.json.example upgrade_parameters.json +``` + +Fill created `upgrade_parameters.json` with appropiate parameters. +You should fullfill the upgrades array, with all the updates that you intend to do ( more information in `upgrade-parameters.json` section) + +if the deployment was deployed without a timelock you can use the `simpleUpgradeScript.js`: + +- Run the script + +Otherwise, in case of timelock use `timeLockUpgrade.js` + +- Run the script +- Now the necessary transactions to interact with the timelock are printed in the screen `schedule` and `execute`, also will be saved in + `./upgrade_output_${new Date().getTime() / 1000}.json` file +- With the owner of the timelock (multisig or account), send the data printed by `schedule` to the `Timelock` contract. +- Once the necessary `timelockMinDelay` has expired, with the same account you can now send the data printed by `execute` to the `Timelock` contract and the contracts will be upgraded. + +## upgrade-parameters.json + +- `timelockMinDelay`: number, timelock delay between the schedule and execution, must be bigger than current min delay +- `upgrades`: Object, Indicates which address and to which implementation must upgrade + - address: address of the current proxy + - contractName: string, contract name that the proxy will be updated to.s + +### Optional Parameters + +- `multiplierGas`: number, Gas multiplier. If maxFeePerGas and maxPriorityFeePerGas are set, will not take effect +- `deployerPvtKey`: string, deployerPvtKey of the deployer +- `timelockSalt`: string, Optional salt for the timelock diff --git a/upgrade/simpleUpgrade.js b/upgrade/simpleUpgrade.js new file mode 100644 index 000000000..25e0c61a3 --- /dev/null +++ b/upgrade/simpleUpgrade.js @@ -0,0 +1,53 @@ +/* eslint-disable no-console, no-unused-vars */ +const hre = require('hardhat'); +const { ethers, upgrades } = require('hardhat'); +const path = require('path'); +require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); +const upgradeParameters = require('./upgrade_parameters.json'); + +async function main() { + // Set multiplier Gas + let currentProvider = ethers.provider; + if (upgradeParameters.multiplierGas) { + if (process.env.HARDHAT_NETWORK !== 'hardhat') { + const multiplierGas = upgradeParameters.multiplierGas; + currentProvider = new ethers.providers.JsonRpcProvider(`https://${process.env.HARDHAT_NETWORK}.infura.io/v3/${process.env.INFURA_PROJECT_ID}`); + async function overrideFeeData() { + const feedata = await ethers.provider.getFeeData(); + return { + maxFeePerGas: feedata.maxFeePerGas.mul(multiplierGas), + maxPriorityFeePerGas: feedata.maxPriorityFeePerGas.mul(multiplierGas), + }; + } + currentProvider.getFeeData = overrideFeeData; + } + } + let deployer; + if (upgradeParameters.deployerPvtKey) { + deployer = new ethers.Wallet(upgradeParameters.deployerPvtKey, currentProvider); + } else if (process.env.MNEMONIC) { + deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(currentProvider); + } else { + [deployer] = (await ethers.getSigners()); + } + + // compìle contracts + await hre.run('compile'); + + for (const upgrade of upgradeParameters.upgrades) { + const proxyPolygonAddress = upgrade.address; + const polygonZkEVMFactory = await ethers.getContractFactory(upgrade.contractName, deployer); + + const txZKEVM = await upgrades.upgradeProxy(proxyPolygonAddress, polygonZkEVMFactory); + + console.log(txZKEVM.deployTransaction); + console.log(await txZKEVM.deployTransaction.wait()); + console.log('upgrade succesfull', upgrade.contractName); + } +} +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/upgrade/timeLockUpgrade.js b/upgrade/timeLockUpgrade.js new file mode 100644 index 000000000..37e433f89 --- /dev/null +++ b/upgrade/timeLockUpgrade.js @@ -0,0 +1,137 @@ +/* eslint-disable no-console, no-unused-vars, no-use-before-define */ +const hre = require('hardhat'); +const { ethers, upgrades } = require('hardhat'); +const path = require('path'); +require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); +const fs = require('fs'); + +const upgradeParameters = require('./upgrade_parameters.json'); +const pathOutputJson = path.join(__dirname, `./upgrade_output_${new Date().getTime() / 1000}.json`); + +async function main() { + // Set multiplier Gas + let currentProvider = ethers.provider; + if (upgradeParameters.multiplierGas) { + if (process.env.HARDHAT_NETWORK !== 'hardhat') { + const multiplierGas = upgradeParameters.multiplierGas; + currentProvider = new ethers.providers.JsonRpcProvider(`https://${process.env.HARDHAT_NETWORK}.infura.io/v3/${process.env.INFURA_PROJECT_ID}`); + async function overrideFeeData() { + const feedata = await ethers.provider.getFeeData(); + return { + maxFeePerGas: feedata.maxFeePerGas.mul(multiplierGas), + maxPriorityFeePerGas: feedata.maxPriorityFeePerGas.mul(multiplierGas), + }; + } + currentProvider.getFeeData = overrideFeeData; + } + } + + // Check contract name existence + for (const upgrade of upgradeParameters.upgrades) { + await ethers.getContractFactory(upgrade.contractName); + } + + let deployer; + if (upgradeParameters.deployerPvtKey) { + deployer = new ethers.Wallet(upgradeParameters.deployerPvtKey, currentProvider); + } else if (process.env.MNEMONIC) { + deployer = ethers.Wallet.fromMnemonic(process.env.MNEMONIC, 'm/44\'/60\'/0\'/0/0').connect(currentProvider); + } else { + [deployer] = (await ethers.getSigners()); + } + // compìle contracts + await hre.run('compile'); + + const proxyAdmin = await upgrades.admin.getInstance(); + const output = []; + + // Upgrade zkevm + for (const upgrade of upgradeParameters.upgrades) { + const proxyPolygonAddress = upgrade.address; + const polygonZkEVMFactory = await ethers.getContractFactory(upgrade.contractName, deployer); + + const newImplPolygonAddress = await upgrades.prepareUpgrade(proxyPolygonAddress, polygonZkEVMFactory); + + console.log({ newImplPolygonAddress }); + console.log("you can verify the new impl address with:") + console.log(`npx hardhat verify ${newImplPolygonAddress} --network ${process.env.HARDHAT_NETWORK}`); + + // Use timelock + const salt = upgradeParameters.timelockSalt || ethers.constants.HashZero; + const operation = genOperation( + proxyAdmin.address, + 0, // value + proxyAdmin.interface.encodeFunctionData( + 'upgrade', + [proxyPolygonAddress, + newImplPolygonAddress], + ), + ethers.constants.HashZero, // predecesoor + salt, // salt + ); + + // Timelock operations + const TimelockFactory = await ethers.getContractFactory('PolygonZkEVMTimelock', deployer); + const minDelay = upgradeParameters.timelockMinDelay || 0; + + // Schedule operation + const scheduleData = TimelockFactory.interface.encodeFunctionData( + 'schedule', + [ + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + minDelay, + ], + ); + // Execute operation + const executeData = TimelockFactory.interface.encodeFunctionData( + 'execute', + [ + operation.target, + operation.value, + operation.data, + operation.predecessor, + operation.salt, + ], + ); + + console.log({ scheduleData }); + console.log({ executeData }); + output.push({ + contractName: upgrade.contractName, + scheduleData, + executeData + }) + } + + fs.writeFileSync(pathOutputJson, JSON.stringify(output, null, 1)); +} +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); + +// OZ test functions +function genOperation(target, value, data, predecessor, salt) { + const id = ethers.utils.solidityKeccak256([ + 'address', + 'uint256', + 'bytes', + 'uint256', + 'bytes32', + ], [ + target, + value, + data, + predecessor, + salt, + ]); + return { + id, target, value, data, predecessor, salt, + }; +} diff --git a/upgrade/upgrade_parameters.json.example b/upgrade/upgrade_parameters.json.example new file mode 100644 index 000000000..3c6986ae3 --- /dev/null +++ b/upgrade/upgrade_parameters.json.example @@ -0,0 +1,16 @@ +{ + "timelockMinDelay":10, + "upgrades": [ + { + "address": "0x6958d5f1AFa6f668c7D7F37a444d09E2330eB104", + "contractName": "PolygonZkEVM" + }, + { + "address": "0x5cA95a1629e7dDD51b66Bf1505560cd7edA91d83", + "contractName": "PolygonZkEVMBridgeMock" + } + ], + "multiplierGas": 0, + "deployerPvtKey": "", + "timelockSalt": "" +} \ No newline at end of file