From 208fd7be3f42e8d64e433836d619ca9b50899c45 Mon Sep 17 00:00:00 2001 From: Victor Graf Date: Fri, 6 Sep 2024 12:50:45 -0700 Subject: [PATCH] WEB3-94: Updated deployment scripts and guide from 1.1 deployment (#211) During the 1.1 deployment, I implemented a number of improvements to the deployment process to make it a bit easier and less error prone. Primary among these changes is adding a `deployment.toml` file that records the addresses of and associated information about each chain we deploy to, and the contracts that are deployed there. My hope is to use this file to do additional work around further automating deployment, testing that deployments are correctly configured on-chain, and generating the table of addresses that goes into our docs. --- .gitignore | 3 + RELEASE.md | 18 +- contracts/deployment.toml | 373 +++++++++++++++++ contracts/script/Manage.s.sol | 94 +++-- contracts/script/README.md | 753 +++++++++++++++++++--------------- contracts/script/manage | 19 +- 6 files changed, 885 insertions(+), 375 deletions(-) create mode 100644 contracts/deployment.toml diff --git a/.gitignore b/.gitignore index 800466b7..f265a241 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,6 @@ out/ # Ignore .pb files generated by profiling *.pb + +# Ignore the file with RPC and Etherscan API keys for deployment. +deployment_secrets.toml diff --git a/RELEASE.md b/RELEASE.md index a816faa5..1fb23405 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -55,23 +55,15 @@ See the [Cargo docs](https://doc.rust-lang.org/cargo/reference/publishing.html) for more details. -5. When changes have been made to the verifier contract, deploy a new verifier contract. +5. When changes have been made to the verifier contract, deploy a new verifier contract and add it to the verifier router on each supported chain. - > [!WARNING] - > This section of the release process is out of date. It does not include the steps for the router or estop. + Refer to [contracts/script/README.md](./contracts/script/README.md) for instructions on the steps involved. - + 1. Deploy the verifier contract and schedule the update. - * Deploy the contract to Sepolia, and verify the source code. + 2. After the timelock delay has passed (7 days on mainnet chains and 1 second on testnet), finsh the operation to add the new verifier to the router. - Set the `ETHERSCAN_API_KEY` and `ETH_WALLET_PRIVATE_KEY` environment variables to a valid Etherscan API key and Sepolia private key respectively. - - ```sh - # In the contracts directory - forge script script/DeployVerifier.s.sol:DeployVerifier --rpc-url $ALCHEMY_API_URL --broadcast --verify -vvvv - ``` - - * Document the new address and version in the `dev.risczero.com` docs. + 3. Document the new addresses and version in the `dev.risczero.com` docs. [https://dev.risczero.com/api/blockchain-integration/contracts/verifier](https://dev.risczero.com/api/blockchain-integration/contracts/verifier) diff --git a/contracts/deployment.toml b/contracts/deployment.toml new file mode 100644 index 00000000..50e78cf5 --- /dev/null +++ b/contracts/deployment.toml @@ -0,0 +1,373 @@ +[chains.ethereum-mainnet] +name = "Ethereum Mainnet" +id = 1 +etherscan-url = "https://etherscan.io/" + +# Accounts +admin = "0xF616A4f81857CFEe54A4A049Ec187172574bd412" + +# Contracts +timelock-controller = "0x0b144E07A0826182B6b59788c34b32Bfa86Fb711" +router = "0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319" + +# TODO: Add information for 1.0 contracts. +[[chains.ethereum-mainnet.verifiers]] +selector = "0x4c630d87" +verifier = "0x5a99469f18a5863D3258E577892589386dFD965E" +estop = "0xB839eA7bBA8e6bB2893ca5252F3f3C13323D74F7" + +[[chains.ethereum-mainnet.verifiers]] +version = "1.1.0-rc.3" +selector = "0x50bd1769" +verifier = "0x94A4684d6F7085C19138Bd4f9F3295fa9943C622" +estop = "0x08AeD6C108E500540a9544beF7a8B8a05E056e87" + +### + +[chains.ethereum-sepolia] +name = "Ethereum Sepolia" +id = 11155111 +etherscan-url = "https://sepolia.etherscan.io/" + +# Accounts +admin = "0x3a54A45E44a71020Bd0Af42063B9f23e8b9E387D" + +# Contracts +timelock-controller = "0xB4E3306129208cC8e6E75157f75f62eAe0B920a0" +router = "0x925d8331ddc0a1F0d96E68CF073DFE1d92b69187" + +[[chains.ethereum-sepolia.verifiers]] +version = "1.1.0-rc.2" +selector = "0x4c630d87" +verifier = "0xf4D938c73Bcc124e02A2D6c7557e98838B5E91B4" +estop = "0x1C182869A6bF84DfAc0a70B46e2f9b3aeE9100e1" + +[[chains.ethereum-sepolia.verifiers]] +version = "1.1.0-rc.3" +selector = "0x50bd1769" +verifier = "0xd9b0d07CeCd808a8172F21fA7C97992168f045CA" +estop = "0x7a028d6f0BD603Ad2a47e3a3B1E504C0D6234877" + +### + +[chains.ethereum-holesky] +name = "Ethereum Holesky" +id = 17000 +etherscan-url = "https://holesky.etherscan.io/" + +# Accounts +admin = "0x3a54A45E44a71020Bd0Af42063B9f23e8b9E387D" + +# Contracts +timelock-controller = "0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319" +router = "0xf70aBAb028Eb6F4100A24B203E113D94E87DE93C" + +[[chains.ethereum-holesky.verifiers]] +selector = "0x4c630d87" +verifier = "0xDC986a09728F76110FF666eE7b20d99086501d15" +estop = "0x0b144E07A0826182B6b59788c34b32Bfa86Fb711" + +[[chains.ethereum-holesky.verifiers]] +version = "1.1.0-rc.3" +selector = "0x50bd1769" +verifier = "0x44c220f0598345195cE99AD6A57aDfFcb9Ea33e7" +estop = "0xBDaEd5bbf8016AfD05Fc4659572e5fEb5854aAD4" + +### + +[chains.arbitrum-mainnet] +name = "Arbitrum Mainnet" +id = 42161 +etherscan-url = "https://arbiscan.io/" + +# Accounts +admin = "0xF616A4f81857CFEe54A4A049Ec187172574bd412" + +# Contracts +timelock-controller = "0xdc986a09728f76110ff666ee7b20d99086501d15" +router = "0x0b144e07a0826182b6b59788c34b32bfa86fb711" + +[[chains.arbitrum-mainnet.verifiers]] +selector = "0x4c630d87" +verifier = "0xBDaEd5bbf8016AfD05Fc4659572e5fEb5854aAD4" +estop = "0x27983ee173aD10E171D17C9c5C14d5baFE997609" + +[[chains.arbitrum-mainnet.verifiers]] +version = "1.1.0-rc.3" +selector = "0x50bd1769" +verifier = "0x84b943E31e7fAe6072ce5F75eb4694C7D5F9b0cF" +estop = "0x5E36f0D56741013d864d8FEb5950AB0E7Eff9Ab1" + +### + +[chains.arbitrum-sepolia] +name = "Arbitrum Sepolia" +id = 421614 +etherscan-url = "https://sepolia.arbiscan.io/" + +# Accounts +admin = "0x3a54A45E44a71020Bd0Af42063B9f23e8b9E387D" + +# Contracts +timelock-controller = "0xdc986a09728f76110ff666ee7b20d99086501d15" +router = "0x0b144e07a0826182b6b59788c34b32bfa86fb711" + +[[chains.arbitrum-sepolia.verifiers]] +selector = "0x4c630d87" +verifier = "0xBDaEd5bbf8016AfD05Fc4659572e5fEb5854aAD4" +estop = "0x27983ee173aD10E171D17C9c5C14d5baFE997609" + +[[chains.arbitrum-sepolia.verifiers]] +version = "1.1.0-rc.3" +selector = "0x50bd1769" +verifier = "0x84b943E31e7fAe6072ce5F75eb4694C7D5F9b0cF" +estop = "0x5E36f0D56741013d864d8FEb5950AB0E7Eff9Ab1" + +### + +[chains.avalanche-mainnet] +name = "Avalanche Mainnet" +id = 43114 +etherscan-url = "https://snowtrace.io" + +# Accounts +admin = "0xF616A4f81857CFEe54A4A049Ec187172574bd412" + +# Contracts +timelock-controller = "0xDC986a09728F76110FF666eE7b20d99086501d15" +router = "0x0b144E07A0826182B6b59788c34b32Bfa86Fb711" + +[[chains.avalanche-mainnet.verifiers]] +selector = "0x4c630d87" +verifier = "0xBDaEd5bbf8016AfD05Fc4659572e5fEb5854aAD4" +estop = "0x27983ee173aD10E171D17C9c5C14d5baFE997609" + +[[chains.avalanche-mainnet.verifiers]] +version = "1.1.0-rc.3" +selector = "0x50bd1769" +verifier = "0x84b943E31e7fAe6072ce5F75eb4694C7D5F9b0cF" +estop = "0x5E36f0D56741013d864d8FEb5950AB0E7Eff9Ab1" + +### + +[chains.avalanche-fuji] +name = "Avalanche Fuji" +id = 43113 +etherscan-url = "https://testnet.snowtrace.io" + +# Accounts +admin = "0x3a54A45E44a71020Bd0Af42063B9f23e8b9E387D" + +# Contracts +timelock-controller = "0xDC986a09728F76110FF666eE7b20d99086501d15" +router = "0x0b144E07A0826182B6b59788c34b32Bfa86Fb711" + +[[chains.avalanche-fuji.verifiers]] +selector = "0x4c630d87" +verifier = "0xBDaEd5bbf8016AfD05Fc4659572e5fEb5854aAD4" +estop = "0x27983ee173aD10E171D17C9c5C14d5baFE997609" + +[[chains.avalanche-fuji.verifiers]] +version = "1.1.0-rc.3" +selector = "0x50bd1769" +verifier = "0x84b943E31e7fAe6072ce5F75eb4694C7D5F9b0cF" +estop = "0x5E36f0D56741013d864d8FEb5950AB0E7Eff9Ab1" + +### + +[chains.base-mainnet] +name = "Base Mainnet" +id = 8453 +etherscan-url = "https://basescan.org/" + +# Accounts +admin = "0xF616A4f81857CFEe54A4A049Ec187172574bd412" + +# Contracts +timelock-controller = "0xdc986a09728f76110ff666ee7b20d99086501d15" +router = "0x0b144e07a0826182b6b59788c34b32bfa86fb711" + +[[chains.base-mainnet.verifiers]] +selector = "0x4c630d87" +verifier = "0xBDaEd5bbf8016AfD05Fc4659572e5fEb5854aAD4" +estop = "0x27983ee173aD10E171D17C9c5C14d5baFE997609" + +[[chains.base-mainnet.verifiers]] +version = "1.1.0-rc.3" +selector = "0x50bd1769" +verifier = "0x84b943E31e7fAe6072ce5F75eb4694C7D5F9b0cF" +estop = "0x5E36f0D56741013d864d8FEb5950AB0E7Eff9Ab1" + +### + +[chains.base-sepolia] +name = "Base Sepolia" +id = 84532 +etherscan-url = "https://sepolia.basescan.org/" + +# Accounts +admin = "0x3a54A45E44a71020Bd0Af42063B9f23e8b9E387D" + +# Contracts +timelock-controller = "0xdc986a09728f76110ff666ee7b20d99086501d15" +router = "0x0b144e07a0826182b6b59788c34b32bfa86fb711" + +[[chains.base-sepolia.verifiers]] +selector = "0x4c630d87" +verifier = "0x84b943E31e7fAe6072ce5F75eb4694C7D5F9b0cF" +estop = "0x5E36f0D56741013d864d8FEb5950AB0E7Eff9Ab1" + +[[chains.base-sepolia.verifiers]] +version = "1.1.0-rc.3" +selector = "0x50bd1769" +verifier = "0x2DEfEA335392bb62d01f74e338697C7B31De254C" +estop = "0xB369b4dd27FBfb59921d3A4a3D23AC2fc32FB908" + +### + +[chains.optimism-mainnet] +name = "Optimism Mainnet" +id = 10 +etherscan-url = "https://optimistic.etherscan.io/" + +# Accounts +admin = "0xF616A4f81857CFEe54A4A049Ec187172574bd412" + +# Contracts +timelock-controller = "0xdc986a09728f76110ff666ee7b20d99086501d15" +router = "0x0b144e07a0826182b6b59788c34b32bfa86fb711" + +[[chains.optimism-mainnet.verifiers]] +selector = "0x4c630d87" +verifier = "0xBDaEd5bbf8016AfD05Fc4659572e5fEb5854aAD4" +estop = "0x27983ee173aD10E171D17C9c5C14d5baFE997609" + +[[chains.optimism-mainnet.verifiers]] +version = "1.1.0-rc.3" +selector = "0x50bd1769" +verifier = "0x84b943E31e7fAe6072ce5F75eb4694C7D5F9b0cF" +estop = "0x5E36f0D56741013d864d8FEb5950AB0E7Eff9Ab1" + +### + +[chains.optimism-sepolia] +name = "Optimism Sepolia" +id = 11110 +etherscan-url = "https://sepolia-optimism.etherscan.io/" + +# Accounts +admin = "0x3a54A45E44a71020Bd0Af42063B9f23e8b9E387D" + +# Contracts +timelock-controller = "0x2DEfEA335392bb62d01f74e338697C7B31De254C" +router = "0xB369b4dd27FBfb59921d3A4a3D23AC2fc32FB908" + +# NOTE: 1.0 was not deployed to OP Sepolia. + +[[chains.optimism-sepolia.verifiers]] +selector = "0x4c630d87" +verifier = "0xBDaEd5bbf8016AfD05Fc4659572e5fEb5854aAD4" +estop = "0x27983ee173aD10E171D17C9c5C14d5baFE997609" + +[[chains.optimism-sepolia.verifiers]] +version = "1.1.0-rc.3" +selector = "0x50bd1769" +verifier = "0x84b943E31e7fAe6072ce5F75eb4694C7D5F9b0cF" +estop = "0x5E36f0D56741013d864d8FEb5950AB0E7Eff9Ab1" + +### + +[chains.linea-mainnet] +name = "Linea Mainnet" +id = 59144 +etherscan-url = "https://lineascan.build/" + +# Accounts +admin = "0xF616A4f81857CFEe54A4A049Ec187172574bd412" + +# Contracts +timelock-controller = "0xdc986a09728f76110ff666ee7b20d99086501d15" +router = "0x0b144e07a0826182b6b59788c34b32bfa86fb711" + +[[chains.linea-mainnet.verifiers]] +selector = "0x4c630d87" +verifier = "0xBDaEd5bbf8016AfD05Fc4659572e5fEb5854aAD4" +estop = "0x27983ee173aD10E171D17C9c5C14d5baFE997609" + +[[chains.linea-mainnet.verifiers]] +version = "1.1.0-rc.3" +selector = "0x50bd1769" +verifier = "0x84b943E31e7fAe6072ce5F75eb4694C7D5F9b0cF" +estop = "0x5E36f0D56741013d864d8FEb5950AB0E7Eff9Ab1" + +### + +[chains.linea-sepolia] +name = "Linea Sepolia" +id = 59141 +etherscan-url = "https://sepolia.lineascan.build/" + +# Accounts +admin = "0x3a54A45E44a71020Bd0Af42063B9f23e8b9E387D" + +# Contracts +timelock-controller = "0xBDaEd5bbf8016AfD05Fc4659572e5fEb5854aAD4" +router = "0x27983ee173aD10E171D17C9c5C14d5baFE997609" + +# NOTE: As of the 1.1.0-rc.2 deployment on August 21, Forge could not verify the contracts. +# Apperent issue is that because Linea Sepolia is not in the list of chains named in alloy-chains, Forge doesn't know its Etherscan API URL. + +# NOTE: As of the 1.1.0-rc.3 Deployment on August 29, Fireblocks does not support sending transactions to Linea Sepolia. +# As a result, the router is deployed, but it is empty and useless. + +[[chains.linea-sepolia.verifiers]] +selector = "0x4c630d87" +verifier = "0x0b144E07A0826182B6b59788c34b32Bfa86Fb711" +estop = "0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319" + +[[chains.linea-sepolia.verifiers]] +version = "1.1.0-rc.3" +selector = "0x50bd1769" +verifier = "0xf70aBAb028Eb6F4100A24B203E113D94E87DE93C" +estop = "0x44c220f0598345195cE99AD6A57aDfFcb9Ea33e7" + +### + +[chains.polygon-zkevm-mainnet] +name = "Polygon zkEVM Mainnet" +id = 1101 +etherscan-url = "https://zkevm.polygonscan.com/" + +# Accounts +admin = "0xF616A4f81857CFEe54A4A049Ec187172574bd412" + +# Contracts +timelock-controller = "0xdc986a09728f76110ff666ee7b20d99086501d15" +router = "0x0b144e07a0826182b6b59788c34b32bfa86fb711" + +[[chains.polygon-zkevm-mainnet.verifiers]] +selector = "0x4c630d87" +verifier = "0xBDaEd5bbf8016AfD05Fc4659572e5fEb5854aAD4" +estop = "0x27983ee173aD10E171D17C9c5C14d5baFE997609" + +[[chains.polygon-zkevm-mainnet.verifiers]] +version = "1.1.0-rc.3" +selector = "0x50bd1769" +verifier = "0x84b943E31e7fAe6072ce5F75eb4694C7D5F9b0cF" +estop = "0x5E36f0D56741013d864d8FEb5950AB0E7Eff9Ab1" + +### + +[chains.polygon-zkevm-testnet] +name = "Polygon zkEVM Testnet" +id = 1442 +etherscan-url = "https://cardona-zkevm.polygonscan.com/" + +# Accounts +admin = "0x3a54A45E44a71020Bd0Af42063B9f23e8b9E387D" + +# Contracts +# TODO: Deploy TimelockController and router here? + +# NOTE: As of the 1.1.0-rc.2 deployment on August 21, deploying to polygon zkEVM testnet failed. +# On Polygon zkEVM Cardona testnet, gave an error of "Failed to get EIP-1559 fees". diff --git a/contracts/script/Manage.s.sol b/contracts/script/Manage.s.sol index 0d38ec70..a246ccaf 100644 --- a/contracts/script/Manage.s.sol +++ b/contracts/script/Manage.s.sol @@ -49,6 +49,24 @@ contract RiscZeroManagementScript is Script { RiscZeroVerifierEmergencyStop internal _verifierEstop; RiscZeroGroth16Verifier internal _verifier; + /// @notice Returns the address of the deployer, set in the DEPLOYER_PUBLIC_KEY env var. + function deployerAddress() internal returns (address) { + address deployer = vm.envAddress("DEPLOYER_PUBLIC_KEY"); + uint256 deployerKey = vm.envOr("DEPLOYER_PRIVATE_KEY", uint256(0)); + if (deployerKey != 0) { + require(vm.addr(deployerKey) == deployer, "DEPLOYER_PUBLIC_KEY and DEPLOYER_PRIVATE_KEY are inconsistent"); + vm.rememberKey(deployerKey); + } + return deployer; + } + + /// @notice Returns the address of the contract admin, set in the ADMIN_PUBLIC_KEY env var. + /// @dev This admin address will be set as the owner of the estop contracts, and the proposer + /// of for the timelock controller. Note that it is not the "admin" on the timelock. + function adminAddress() internal view returns (address) { + return vm.envAddress("ADMIN_PUBLIC_KEY"); + } + /// @notice Determines the contract address of TimelockController from the environment. /// @dev Uses the TIMELOCK_CONTROLLER environment variable. function timelockController() internal returns (TimelockController) { @@ -134,15 +152,16 @@ contract DeployTimelockRouter is RiscZeroManagementScript { console2.log("executors:", executors[0]); // optional account to be granted admin role; disable with zero address + // When the admin is unset, the contract is self-administered. address admin = vm.envOr("ADMIN", address(0)); console2.log("admin:", admin); // Deploy new contracts - vm.broadcast(); + vm.broadcast(deployerAddress()); _timelockController = new TimelockController(minDelay, proposers, executors, admin); console2.log("Deployed TimelockController to", address(timelockController())); - vm.broadcast(); + vm.broadcast(deployerAddress()); _verifierRouter = new RiscZeroVerifierRouter(address(timelockController())); console2.log("Deployed RiscZeroVerifierRouter to", address(verifierRouter())); } @@ -160,13 +179,18 @@ contract DeployEstopVerifier is RiscZeroManagementScript { console2.log("verifierEstopOwner:", verifierEstopOwner); // Deploy new contracts - vm.broadcast(); + // TODO: Prints here construct a kind of mangled TOML block that can be copy-pasted into + // deployment.toml. It should be fixed up to create a proper block. + vm.broadcast(deployerAddress()); _verifier = new RiscZeroGroth16Verifier(ControlID.CONTROL_ROOT, ControlID.BN254_CONTROL_ID); - console2.log("Deployed RiscZeroGroth16Verifier to", address(verifier())); + console2.log("version = \"", verifier().VERSION(), "\""); + console2.log("selector = \""); + console2.logBytes4(verifier().SELECTOR()); + console2.log("verifier = \"", address(verifier()), "\""); - vm.broadcast(); + vm.broadcast(deployerAddress()); _verifierEstop = new RiscZeroVerifierEmergencyStop(verifier(), verifierEstopOwner); - console2.log("Deployed RiscZeroVerifierEmergencyStop to", address(verifierEstop())); + console2.log("estop = \"", address(verifierEstop()), "\""); } } @@ -193,7 +217,7 @@ contract ScheduleAddVerifier is RiscZeroManagementScript { address dest = address(verifierRouter()); simulate(dest, data); - vm.broadcast(); + vm.broadcast(adminAddress()); timelockController().schedule(dest, 0, data, 0, 0, scheduleDelay); } } @@ -215,14 +239,14 @@ contract FinishAddVerifier is RiscZeroManagementScript { bytes memory data = abi.encodeCall(verifierRouter().addVerifier, (selector, verifierEstop())); - vm.broadcast(); + vm.broadcast(adminAddress()); timelockController().execute(address(verifierRouter()), 0, data, 0, 0); } } /// @notice Schedule removal of a verifier from the router. /// @dev Use the following environment variable to control the deployment: -/// * SELECTOR the selector associated with this verifier +/// * VERIFIER_SELECTOR the selector associated with this verifier /// * SCHEDULE_DELAY (optional) minimum delay in seconds for the scheduled action /// * TIMELOCK_CONTROLLER contract address of TimelockController /// * VERIFIER_ROUTER contract address of RiscZeroVerifierRouter @@ -231,7 +255,7 @@ contract FinishAddVerifier is RiscZeroManagementScript { /// https://book.getfoundry.sh/tutorials/solidity-scripting contract ScheduleRemoveVerifier is RiscZeroManagementScript { function run() external { - bytes4 selector = bytes4(vm.envBytes("SELECTOR")); + bytes4 selector = bytes4(vm.envBytes("VERIFIER_SELECTOR")); console2.log("selector:"); console2.logBytes4(selector); @@ -243,14 +267,14 @@ contract ScheduleRemoveVerifier is RiscZeroManagementScript { address dest = address(verifierRouter()); simulate(dest, data); - vm.broadcast(); + vm.broadcast(adminAddress()); timelockController().schedule(dest, 0, data, 0, 0, scheduleDelay); } } /// @notice Finish removal of a verifier from the router. /// @dev Use the following environment variable to control the deployment: -/// * SELECTOR the selector associated with this verifier +/// * VERIFIER_SELECTOR the selector associated with this verifier /// * TIMELOCK_CONTROLLER contract address of TimelockController /// * VERIFIER_ROUTER contract address of RiscZeroVerifierRouter /// @@ -258,14 +282,14 @@ contract ScheduleRemoveVerifier is RiscZeroManagementScript { /// https://book.getfoundry.sh/tutorials/solidity-scripting contract FinishRemoveVerifier is RiscZeroManagementScript { function run() external { - bytes4 selector = bytes4(vm.envBytes("SELECTOR")); + bytes4 selector = bytes4(vm.envBytes("VERIFIER_SELECTOR")); console2.log("selector:"); console2.logBytes4(selector); // Execute the 'removeVerifier()' request bytes memory data = abi.encodeCall(verifierRouter().removeVerifier, selector); - vm.broadcast(); + vm.broadcast(adminAddress()); timelockController().execute(address(verifierRouter()), 0, data, 0, 0); } } @@ -291,7 +315,7 @@ contract ScheduleUpdateDelay is RiscZeroManagementScript { address dest = address(timelockController()); simulate(dest, data); - vm.broadcast(); + vm.broadcast(adminAddress()); timelockController().schedule(dest, 0, data, 0, 0, scheduleDelay); } } @@ -311,11 +335,30 @@ contract FinishUpdateDelay is RiscZeroManagementScript { // Execute the 'updateDelay()' request bytes memory data = abi.encodeCall(timelockController().updateDelay, minDelay); - vm.broadcast(); + vm.broadcast(adminAddress()); timelockController().execute(address(timelockController()), 0, data, 0, 0); } } +// TODO: Add this command to the README.md +/// @notice Cancel a pending operation on the timelock controller +/// @dev Use the following environment variable to control the script: +/// * TIMELOCK_CONTROLLER contract address of TimelockController +/// * OPERATION_ID identifier for the operation to cancel +/// +/// See the Foundry documentation for more information about Solidity scripts. +/// https://book.getfoundry.sh/tutorials/solidity-scripting +contract CancelOperation is RiscZeroManagementScript { + function run() external { + bytes32 operationId = vm.envBytes32("OPERATION_ID"); + console2.log("operationId:", uint256(operationId)); + + // Execute the 'cancel()' request + vm.broadcast(adminAddress()); + timelockController().cancel(operationId); + } +} + /// @notice Schedule grant role. /// @dev Use the following environment variable to control the deployment: /// * ROLE the role to be granted @@ -345,7 +388,7 @@ contract ScheduleGrantRole is RiscZeroManagementScript { address dest = address(timelockController()); simulate(dest, data); - vm.broadcast(); + vm.broadcast(adminAddress()); timelockController().schedule(dest, 0, data, 0, 0, scheduleDelay); } } @@ -373,7 +416,7 @@ contract FinishGrantRole is RiscZeroManagementScript { bytes memory data = abi.encodeCall(timelockController().grantRole, (role, account)); - vm.broadcast(); + vm.broadcast(adminAddress()); timelockController().execute(address(timelockController()), 0, data, 0, 0); } } @@ -407,7 +450,7 @@ contract ScheduleRevokeRole is RiscZeroManagementScript { address dest = address(timelockController()); simulate(dest, data); - vm.broadcast(); + vm.broadcast(adminAddress()); timelockController().schedule(dest, 0, data, 0, 0, scheduleDelay); } } @@ -435,21 +478,24 @@ contract FinishRevokeRole is RiscZeroManagementScript { bytes memory data = abi.encodeCall(timelockController().revokeRole, (role, account)); - vm.broadcast(); + vm.broadcast(adminAddress()); timelockController().execute(address(timelockController()), 0, data, 0, 0); } } /// @notice Renounce role. /// @dev Use the following environment variable to control the deployment: -/// * ROLE the role to be renounced +/// * RENOUNCE_ADDRESS the address to send the renounce transaction +/// * RENOUNCE_ROLE the role to be renounced /// * TIMELOCK_CONTROLLER contract address of TimelockController /// /// See the Foundry documentation for more information about Solidity scripts. /// https://book.getfoundry.sh/tutorials/solidity-scripting contract RenounceRole is RiscZeroManagementScript { function run() external { - string memory roleStr = vm.envString("ROLE"); + address renouncer = vm.envAddress("RENOUNCE_ADDRESS"); + string memory roleStr = vm.envString("RENOUNCE_ROLE"); + console2.log("renouncer:", renouncer); console2.log("roleStr:", roleStr); console2.log("msg.sender:", msg.sender); @@ -459,7 +505,7 @@ contract RenounceRole is RiscZeroManagementScript { console2.log("role: "); console2.logBytes32(role); - vm.broadcast(); + vm.broadcast(renouncer); timelockController().renounceRole(role, msg.sender); } } @@ -477,7 +523,7 @@ contract ActivateEstop is RiscZeroManagementScript { console2.log("Using RiscZeroVerifierEmergencyStop at address", address(verifierEstop)); // Activate the emergency stop - vm.broadcast(); + vm.broadcast(adminAddress()); verifierEstop.estop(); } } diff --git a/contracts/script/README.md b/contracts/script/README.md index a67e0de7..be18d621 100644 --- a/contracts/script/README.md +++ b/contracts/script/README.md @@ -1,48 +1,103 @@ -# Scripts +# Contract Operations Guide + +An operations guide for the RISC Zero Ethereum contracts. + +> [!NOTE] +> All the commands in this guide assume your current working directory is the root of the repo. + +## Dependencies Requires [Foundry](https://book.getfoundry.sh/getting-started/installation). > [!NOTE] > Running the `manage` commands will run in simulation mode (i.e. will not send transactions) unless the `--broadcast` flag is passed. -## Setup your environment +Commands in this guide use `yq` to parse the TOML config files. + +You can install `yq` by following the [direction on GitHub][yq-install], or using `go install`. + +```bash +go install github.com/mikefarah/yq/v4@latest +``` + +## Configuration + +Configurations and deployment state information is stored in `deployment.toml`. +It contains information about each chain (e.g. name, ID, Etherscan URL), and addresses for the timelock, router, and verifier contracts on each chain. + +Accompanying the `deployment.toml` file is a `deployment_secrets.toml` file with the following schema. +It is used to store somewhat sensative API keys for RPC services and Etherscan. +Note that it does not contain private keys or API keys for Fireblocks. +It should never be committed to `git`, and the API keys should be rotated if this occurs. + +```toml +[chains.$CHAIN_KEY] +rpc-url = "..." +etherscan-api-key = "..." +``` + +## Environment ### Anvil +In development and to test the operations process, you can use Anvil. + Start Anvil: -```console +```bash anvil -a 10 --block-time 1 --host 0.0.0.0 --port 8545 ``` Set your RPC URL, as well as your public and private key: -```console +```bash export RPC_URL="http://localhost:8545" -export PUBLIC_KEY="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" -export PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" +export DEPLOYER_PUBLIC_KEY="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" +export DEPLOYER_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" ``` -### Sepolia or Mainnet +### Public Networks (Testnet or Mainnet) + +Set the chain you are operating on by the key from the `deployment.toml` file. +An example chain key is "ethereum-sepolia", and you can look at `deployment.toml` for the full list. + +> TODO: Instead of reading these into environment variables, we can have the Forge script directly read them from the TOML file. + +```zsh +export CHAIN_KEY="xxx-testnet" +``` Set your RPC URL, public and private key, and Etherscan API key: -```console -export RPC_URL="..." -export PUBLIC_KEY="..." -export PRIVATE_KEY="..." -export ETHERSCAN_API_KEY="..." -export FORGE_DEPLOY_FLAGS="--verify" +```bash +export RPC_URL=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].rpc-url" contracts/deployment_secrets.toml | tee /dev/stderr) +export ETHERSCAN_URL=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].etherscan-url" contracts/deployment.toml | tee /dev/stderr) +export ETHERSCAN_API_KEY=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].etherscan-api-key" contracts/deployment_secrets.toml | tee /dev/stderr) +export ADMIN_PUBLIC_KEY=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].admin" contracts/deployment.toml | tee /dev/stderr) ``` +> [!TIP] +> Foundry has a [config full of information about each chain][alloy-chains], mapped from chain ID. +> It includes the Etherscan compatible API URL, which is how only specifying the API key works. +> You can find this list in the following source file: + Example RPC URLs: -* Sepolia: - * https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY - * https://sepolia.infura.io/v3/YOUR_API_KEY -* Mainnet: - * https://eth-mainnet.g.alchemy.com/v2/YOUR_API_KEY - * https://mainnet.infura.io/v3/YOUR_API_KEY +* `https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY` +* `https://sepolia.infura.io/v3/YOUR_API_KEY` + +If the timelock and router contracts are already deployed, you can also load their addresses: + +```zsh +export TIMELOCK_CONTROLLER=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].timelock-controller" contracts/deployment.toml | tee /dev/stderr) +export VERIFIER_ROUTER=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].router" contracts/deployment.toml | tee /dev/stderr) +``` + +> TIP: If you want to see a contract in Etherscan, you can run a command like the example below: +> +> ```zsh +> open ${ETHERSCAN_URL:?}/address/${TIMELOCK_CONTROLLER:?} +> ``` ### Fireblocks @@ -52,19 +107,22 @@ Also requires that you have a [Fireblocks API account](https://developers.firebl Set your public key, your Etherscan API key, and the necessary parameters for Fireblocks: -```console -export RPC_URL="..." -export PUBLIC_KEY="..." -export ETHERSCAN_API_KEY="..." -export FORGE_DEPLOY_FLAGS="--verify" +> [!NOTE] +> Fireblocks only supports RSA for API request signing. +> `FIREBLOCKS_API_PRIVATE_KEY_PATH` can be the key itself, rather than a path. + +> [!NOTE] +> When this guide says "public key", it's equivalent to "address". + +```bash export FIREBLOCKS_API_KEY="..." -export FIREBLOCKS_API_PRIVATE_KEY_PATH="/path/to/secret.key" +export FIREBLOCKS_API_PRIVATE_KEY_PATH="..." # IF YOU ARE IN A SANDBOX ENVIRONMENT, be sure to also set this: export FIREBLOCKS_API_BASE_URL="https://sandbox-api.fireblocks.io" ``` -Then, in the instructions below, pass the `--fireblocks` flag to the `manage` script. +Then, in the instructions below, pass the `--fireblocks` (`-f`) flag to the `manage` script. > [!NOTE] > Your Fireblocks API user will need to have "Editor" permissions (i.e., ability to propose transactions for signing, but not necessarily the ability to sign transactions). You will also need a Transaction Authorization Policy (TAP) that specifies who the signers are for transactions initiated by your API user, and this policy will need to permit contract creation as well as contract calls. @@ -72,147 +130,176 @@ Then, in the instructions below, pass the `--fireblocks` flag to the `manage` sc > [!NOTE] > Before you approve any contract-call transactions, be sure you understand what the call does! When in doubt, use [Etherscan](https://etherscan.io/) to lookup the function selector, together with a [calldata decoder](https://openchain.xyz/tools/abi) ([alternative](https://calldata.swiss-knife.xyz/decoder)) to decode the call's arguments. +> [!TIP] +> Foundry and the Fireblocks JSON RPC shim don't quite get along. +> In order to avoid sending the same transaction for approval twice (or more), use ctrl-c to +> kill the forge script once you see that the transaction is pending approval in the Fireblocks +> console. + ## Deploy the timelocked router -Deploy the contracts: +1. Dry run the contract deployment: -```console -MIN_DELAY=1 \ -PROPOSER="${PUBLIC_KEY:?}" \ -EXECUTOR="${PUBLIC_KEY:?}" \ -bash contracts/script/manage DeployTimelockRouter + > [!IMPORTANT] + > Adjust the `MIN_DELAY` to a value appropriate for the environment (e.g. 1 second for testnet and 604800 seconds (7 days) for mainnet). -... + ```bash + MIN_DELAY=1 \ + PROPOSER="${ADMIN_PUBLIC_KEY:?}" \ + EXECUTOR="${ADMIN_PUBLIC_KEY:?}" \ + bash contracts/script/manage DeployTimelockRouter -== Logs == - minDelay: 1 - proposers: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - executors: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - admin: 0x0000000000000000000000000000000000000000 - Deployed TimelockController to 0x5FbDB2315678afecb367f032d93F642f64180aa3 - Deployed RiscZeroVerifierRouter to 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 -``` + ... -Look at the command logs and save the contract addresses: + == Logs == + minDelay: 1 + proposers: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + executors: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + admin: 0x0000000000000000000000000000000000000000 + Deployed TimelockController to 0x5FbDB2315678afecb367f032d93F642f64180aa3 + Deployed RiscZeroVerifierRouter to 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 + ``` -```console -export TIMELOCK_CONTROLLER="0x5FbDB2315678afecb367f032d93F642f64180aa3" -export VERIFIER_ROUTER="0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" -``` +2. Run the command again with `--broadcast`. -Test the deployment: + This will result in two transactions sent from the deployer address. -```console -cast call --rpc-url ${RPC_URL:?} \ - ${TIMELOCK_CONTROLLER:?} \ - 'getMinDelay()(uint256)' -1 +3. Verify the contracts on Etherscan (or its equivalent) by running the command again without `--broadcast` and add `--verify`. -cast call --rpc-url ${RPC_URL:?} \ - ${VERIFIER_ROUTER:?} \ - 'owner()(address)' -0x5FbDB2315678afecb367f032d93F642f64180aa3 -``` +4. Save the contract addresses to `deployment.toml`. + + Load the addresses into your environment. + + ```bash + export TIMELOCK_CONTROLLER=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].timelock-controller" contracts/deployment.toml | tee /dev/stderr) + export VERIFIER_ROUTER=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].router" contracts/deployment.toml | tee /dev/stderr) + ``` + +5. Test the deployment: + + ```bash + cast call --rpc-url ${RPC_URL:?} \ + ${TIMELOCK_CONTROLLER:?} \ + 'getMinDelay()(uint256)' + 1 + + cast call --rpc-url ${RPC_URL:?} \ + ${VERIFIER_ROUTER:?} \ + 'owner()(address)' + 0x5FbDB2315678afecb367f032d93F642f64180aa3 + ``` ## Deploy a verifier with emergency stop mechanism -This is a 3-step process, guarded by the `TimelockController`. +This is a two-step process, guarded by the `TimelockController`. ### Deploy the verifier -Deploy the contracts: +1. Set the verifier selector for the verifier you will be deploying: -```console -VERIFIER_ESTOP_OWNER=${PUBLIC_KEY:?} \ -bash contracts/script/manage DeployEstopVerifier + > TIP: One place to find this information is in `./contracts/test/RiscZeroGroth16Verifier.t.sol` -... + ```zsh + export VERIFIER_SELECTOR="0x..." + ``` -== Logs == - verifierEstopOwner: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - Deployed RiscZeroGroth16Verifier to 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 - Deployed RiscZeroVerifierEmergencyStop to 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 -``` +2. Dry run deployment of verifier and estop: -Look at the command logs and save the e-stop contract address: + ```zsh + VERIFIER_ESTOP_OWNER=${ADMIN_PUBLIC_KEY:?} \ + bash contracts/script/manage DeployEstopVerifier + ``` -```console -export VERIFIER_ESTOP="0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" -``` + > [!IMPORTANT] + > Check the logs from this dry run to verify the estop owner is the expected address. + > It should be equal to the RISC Zero admin address on the given chain. + > Note that it should not be the `TimelockController`. + > Also check the chain ID to ensure you are deploying to the chain you expect. + > And check the selector to make sure it matches what you expect. -Test the deployment: +3. Send deployment transactions for verifier and estop by running the command again with `--broadcast`. -```console -cast call --rpc-url ${RPC_URL:?} \ - ${VERIFIER_ESTOP:?} \ - 'paused()(bool)' -false + This will result in two transactions sent from the deployer address. -cast call --rpc-url ${RPC_URL:?} \ - ${VERIFIER_ESTOP:?} \ - 'owner()(address)' -0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -``` + > [!NOTE] + > When using Fireblocks, sending a transaction to a particular address may require allow-listing it. + > In order to ensure that estop operations are possible, make sure to allow-list the new estop contract. -### Schedule the update +4. Verify the contracts on Etherscan (or its equivalent) by running the command again without `--broadcast` and add `--verify`. -Schedule the action: - -```console -TIMELOCK_CONTROLLER=${TIMELOCK_CONTROLLER:?} \ -VERIFIER_ROUTER=${VERIFIER_ROUTER:?} \ -VERIFIER_ESTOP=${VERIFIER_ESTOP:?} \ -bash contracts/script/manage ScheduleAddVerifier - -... - -== Logs == - Using RiscZeroVerifierEmergencyStop at address 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 - Using RiscZeroGroth16Verifier at address 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 - selector: - 0x310fe598 - Using TimelockController at address 0x5FbDB2315678afecb367f032d93F642f64180aa3 - scheduleDelay: 1 - Using RiscZeroVerifierRouter at address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 - Simulating call to 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 - 0xd0a6af30310fe59800000000000000000000000000000000000000000000000000000000000000000000000000000000cf7ed3acca5a467e9e704c703e8d87f634fb0fc9 - Simulation successful -``` +5. Add the addresses for the newly deployed contract to the `deployment.toml` file. + + Load the deployed addresses into the environment: + + ```zsh + export TIMELOCK_CONTROLLER=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].timelock-controller" contracts/deployment.toml | tee /dev/stderr) + export VERIFIER_ROUTER=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].router" contracts/deployment.toml | tee /dev/stderr) + export VERIFIER_ESTOP=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].verifiers[] | select(.selector == \"${VERIFIER_SELECTOR:?}\") | .estop" contracts/deployment.toml | tee /dev/stderr) + ``` + +6. Test the deployment. + + ```console + cast call --rpc-url ${RPC_URL:?} \ + ${VERIFIER_ESTOP:?} \ + 'paused()(bool)' + false + + cast call --rpc-url ${RPC_URL:?} \ + ${VERIFIER_ESTOP:?} \ + 'owner()(address)' + 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 + ``` + +7. Dry run the operation to schedule the operation to add the verifier to the router. + + Fill in the addresses for the relevant chain below. + `ADMIN_PUBLIC_KEY` should be set to the Fireblocks admin address. + + ```zsh + bash contracts/script/manage ScheduleAddVerifier + ``` + +8. Send the transaction for the scheduled update by running the command again with `--broadcast`. + + This will send one transaction from the admin address. + + > [!IMPORTANT] + > If the admin address is in Fireblocks, this will prompt the admins for approval. ### Finish the update -Execute the action: +After the delay on the timelock controller has pass, the operation to add the new verifier to the router can be executed. -```console -TIMELOCK_CONTROLLER=${TIMELOCK_CONTROLLER:?} \ -VERIFIER_ROUTER=${VERIFIER_ROUTER:?} \ -VERIFIER_ESTOP=${VERIFIER_ESTOP:?} \ -bash contracts/script/manage FinishAddVerifier - -... - -== Logs == - Using RiscZeroVerifierEmergencyStop at address 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 - Using RiscZeroGroth16Verifier at address 0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0 - selector: - 0x310fe598 - Using RiscZeroVerifierRouter at address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 - Using TimelockController at address 0x5FbDB2315678afecb367f032d93F642f64180aa3 -``` +Make sure to set `TIMELOCK_CONTROLLER` and `VERIFIER_ROUTER`. -Test the deployment: +1. Set the verifier selector and estop address for the verifier: -```console -export VERIFIER="$(cast call --rpc-url ${RPC_URL:?} ${VERIFIER_ESTOP:?} 'verifier()(address)')" -export SELECTOR="$(cast call --rpc-url ${RPC_URL:?} ${VERIFIER:?} 'SELECTOR()(bytes4)' | head -c 10)" -``` + > TIP: One place to find this information is in `./contracts/test/RiscZeroGroth16Verifier.t.sol` -```console -cast call --rpc-url ${RPC_URL:?} \ - ${VERIFIER_ROUTER:?} \ - 'getVerifier(bytes4)(address)' ${SELECTOR:?} -0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 -``` + ```zsh + export VERIFIER_SELECTOR="0x..." + export VERIFIER_ESTOP=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].verifiers[] | select(.selector == \"${VERIFIER_SELECTOR:?}\") | .estop" contracts/deployment.toml | tee /dev/stderr) + ``` + +2. Dry the transaction to execute the add verifier operation: + + ```zsh + bash contracts/script/manage FinishAddVerifier + ``` + +3. Run the command again with `--broadcast` + + This will send one transaction from the admin address. + +4. Test the deployment. + + ```bash + cast call --rpc-url ${RPC_URL:?} \ + ${VERIFIER_ROUTER:?} \ + 'getVerifier(bytes4)(address)' ${VERIFIER_SELECTOR:?} + 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 + ``` ## Remove a verifier @@ -220,52 +307,52 @@ This is a two-step process, guarded by the `TimelockController`. ### Schedule the update -Schedule the action: +1. Set the verifier selector and estop address for the verifier: -```console -TIMELOCK_CONTROLLER=${TIMELOCK_CONTROLLER:?} \ -VERIFIER_ROUTER=${VERIFIER_ROUTER:?} \ -bash contracts/script/manage ScheduleRemoveVerifier + > TIP: One place to find this information is in `./contracts/test/RiscZeroGroth16Verifier.t.sol` -... + ```zsh + export VERIFIER_SELECTOR="0x..." + ``` -== Logs == - selector: - 0x310fe598 - Using TimelockController at address 0x5FbDB2315678afecb367f032d93F642f64180aa3 - scheduleDelay: 1 - Using RiscZeroVerifierRouter at address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 - Simulating call to 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 - 0x93d237f6310fe59800000000000000000000000000000000000000000000000000000000 - Simulation successful -``` +2. Dry the transaction to schedule the remove verifier operation: + + ```bash + bash contracts/script/manage ScheduleRemoveVerifier + ``` + +3. Run the command again with `--broadcast` + + This will send one transaction from the admin address. ### Finish the update -Execute the action: +1. Set the verifier selector and estop address for the verifier: -```console -TIMELOCK_CONTROLLER=${TIMELOCK_CONTROLLER:?} \ -VERIFIER_ROUTER=${VERIFIER_ROUTER:?} \ -bash contracts/script/manage FinishRemoveVerifier + > TIP: One place to find this information is in `./contracts/test/RiscZeroGroth16Verifier.t.sol` -... + ```zsh + export VERIFIER_SELECTOR="0x..." + ``` -== Logs == - selector: - 0x310fe598 - Using RiscZeroVerifierRouter at address 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512 - Using TimelockController at address 0x5FbDB2315678afecb367f032d93F642f64180aa3 -``` +2. Dry the transaction to execute the remove verifier operation: -Confirm it was removed: + ```bash + bash contracts/script/manage FinishRemoveVerifier + ``` -```console -cast call --rpc-url ${RPC_URL:?} \ - ${VERIFIER_ROUTER:?} \ - 'getVerifier(bytes4)(address)' ${SELECTOR:?} -Error: ... execution reverted -``` +3. Run the command again with `--broadcast` + + This will send one transaction from the admin address. + +4. Confirm it was removed. + + ```bash + cast call --rpc-url ${RPC_URL:?} \ + ${VERIFIER_ROUTER:?} \ + 'getVerifier(bytes4)(address)' ${VERIFIER_SELECTOR:?} + Error: ... execution reverted + ``` ## Update the TimelockController minimum delay @@ -273,48 +360,65 @@ This is a two-step process, guarded by the `TimelockController`. ### Schedule the update -Schedule the action: +1. Dry run the transaction: -```console -MIN_DELAY=10 \ -TIMELOCK_CONTROLLER=${TIMELOCK_CONTROLLER:?} \ -bash contracts/script/manage ScheduleUpdateDelay + ```bash + MIN_DELAY=10 \ + bash contracts/script/manage ScheduleUpdateDelay + ``` -... +2. Run the command again with `--broadcast` -== Logs == - minDelay: 10 - Using TimelockController at address 0x5FbDB2315678afecb367f032d93F642f64180aa3 - scheduleDelay: 1 - Simulating call to 0x5FbDB2315678afecb367f032d93F642f64180aa3 - 0x64d62353000000000000000000000000000000000000000000000000000000000000000a - Simulation successful -``` + This will send one transaction from the admin address. ### Finish the update Execute the action: -```console -MIN_DELAY=10 \ -TIMELOCK_CONTROLLER=${TIMELOCK_CONTROLLER:?} \ -bash contracts/script/manage FinishUpdateDelay +1. Dry run the transaction: -... + ```bash + MIN_DELAY=10 \ + bash contracts/script/manage FinishUpdateDelay + ``` -== Logs == - minDelay: 10 - Using TimelockController at address 0x5FbDB2315678afecb367f032d93F642f64180aa3 -``` +2. Run the command again with `--broadcast` -Confirm the update: + This will send one transaction from the admin address. -```console -cast call --rpc-url ${RPC_URL:?} \ - ${TIMELOCK_CONTROLLER:?} \ - 'getMinDelay()(uint256)' -10 -``` +3. Confirm the update. + + ```bash + cast call --rpc-url ${RPC_URL:?} \ + ${TIMELOCK_CONTROLLER:?} \ + 'getMinDelay()(uint256)' + 10 + ``` + +## Cancel a scheduled timelock operation + +Use the following steps to cancel an operation that is pending on the `TimelockController`. + +1. Identifier the operation ID and set the environment variable. + + > TIP: Once way to get the operation ID is to open the contract in Etherscan and look at the events. + > On the `CallScheduled` event, the ID is labeled as `[topic1]`. + > + > ```zsh + > open ${ETHERSCAN_URL:?}/address/${TIMELOCK_CONTROLLER:?}#events + > ``` + + ```zsh + export OPERATION_ID="0x..." \ + ``` + +2. Dry the transaction to cancel the operation. + + ```zsh + bash contracts/script/manage CancelOperation -f + ``` + +3. Run the command again with `--broadcast` ## Grant access to the TimelockController @@ -328,67 +432,49 @@ Three roles are supported: ### Schedule the update -Schedule the action: - -```console -ROLE="executor" \ -ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ -TIMELOCK_CONTROLLER=${TIMELOCK_CONTROLLER:?} \ -bash contracts/script/manage ScheduleGrantRole - -... - -== Logs == - roleStr: executor - account: 0x00000000000000AABBCcdDEefF00000000000000 - Using TimelockController at address 0x5FbDB2315678afecb367f032d93F642f64180aa3 - role: - 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 - scheduleDelay: 10 - Simulating call to 0x5FbDB2315678afecb367f032d93F642f64180aa3 - 0x2f2ff15dd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e6300000000000000000000000000000000000000aabbccddeeff00000000000000 - Simulation successful -``` +1. Dry run the transaction: -Confirm the role code: + ```bash + ROLE="executor" \ + ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ + bash contracts/script/manage ScheduleGrantRole + ``` -```console -cast call --rpc-url ${RPC_URL:?} \ - ${TIMELOCK_CONTROLLER:?} \ - 'EXECUTOR_ROLE()(bytes32)' -0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 -``` +2. Run the command again with `--broadcast` + + This will send one transaction from the admin address. ### Finish the update -Schedule the action: +1. Dry run the transaction: -```console -ROLE="executor" \ -ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ -TIMELOCK_CONTROLLER=${TIMELOCK_CONTROLLER:?} \ -bash contracts/script/manage FinishGrantRole + ```bash + ROLE="executor" \ + ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ + bash contracts/script/manage FinishGrantRole + ``` -... +2. Run the command again with `--broadcast` -== Logs == - roleStr: executor - account: 0x00000000000000AABBCcdDEefF00000000000000 - Using TimelockController at address 0x5FbDB2315678afecb367f032d93F642f64180aa3 - role: - 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 -``` + This will send one transaction from the admin address. -Confirm the update: +3. Confirm the update: -```console -cast call --rpc-url ${RPC_URL:?} \ - ${TIMELOCK_CONTROLLER:?} \ - 'hasRole(bytes32, address)(bool)' \ - 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 \ - 0x00000000000000aabbccddeeff00000000000000 -true -``` + ```bash + # Query the role code. + cast call --rpc-url ${RPC_URL:?} \ + ${TIMELOCK_CONTROLLER:?} \ + 'EXECUTOR_ROLE()(bytes32)' + 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 + + # Check that the account now has that role. + cast call --rpc-url ${RPC_URL:?} \ + ${TIMELOCK_CONTROLLER:?} \ + 'hasRole(bytes32, address)(bool)' \ + 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 \ + 0x00000000000000aabbccddeeff00000000000000 + true + ``` ## Revoke access to the TimelockController @@ -402,31 +488,21 @@ Three roles are supported: ### Schedule the update -Schedule the action: - -```console -ROLE="executor" \ -ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ -TIMELOCK_CONTROLLER=${TIMELOCK_CONTROLLER:?} \ -bash contracts/script/manage ScheduleRevokeRole - -... - -== Logs == - roleStr: executor - account: 0x00000000000000AABBCcdDEefF00000000000000 - Using TimelockController at address 0x5FbDB2315678afecb367f032d93F642f64180aa3 - role: - 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 - scheduleDelay: 10 - Simulating call to 0x5FbDB2315678afecb367f032d93F642f64180aa3 - 0xd547741fd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e6300000000000000000000000000000000000000aabbccddeeff00000000000000 - Simulation successful -``` +1. Dry run the transaction: + + ```bash + ROLE="executor" \ + ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ + bash contracts/script/manage ScheduleRevokeRole + ``` + +2. Run the command again with `--broadcast` + + This will send one transaction from the admin address. Confirm the role code: -```console +```bash cast call --rpc-url ${RPC_URL:?} \ ${TIMELOCK_CONTROLLER:?} \ 'EXECUTOR_ROLE()(bytes32)' @@ -435,34 +511,35 @@ cast call --rpc-url ${RPC_URL:?} \ ### Finish the update -Schedule the action: +1. Dry run the transaction: -```console -ROLE="executor" \ -ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ -TIMELOCK_CONTROLLER=${TIMELOCK_CONTROLLER:?} \ -bash contracts/script/manage FinishRevokeRole + ```bash + ROLE="executor" \ + ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ + bash contracts/script/manage FinishRevokeRole + ``` -... +2. Run the command again with `--broadcast` -== Logs == - roleStr: executor - account: 0x00000000000000AABBCcdDEefF00000000000000 - Using TimelockController at address 0x5FbDB2315678afecb367f032d93F642f64180aa3 - role: - 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 -``` + This will send one transaction from the admin address. -Confirm the update: +3. Confirm the update: -```console -cast call --rpc-url ${RPC_URL:?} \ - ${TIMELOCK_CONTROLLER:?} \ - 'hasRole(bytes32, address)(bool)' \ - 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 \ - 0x00000000000000aabbccddeeff00000000000000 -false -``` + ```bash + # Query the role code. + cast call --rpc-url ${RPC_URL:?} \ + ${TIMELOCK_CONTROLLER:?} \ + 'EXECUTOR_ROLE()(bytes32)' + 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 + + # Check that the account no longer has that role. + cast call --rpc-url ${RPC_URL:?} \ + ${TIMELOCK_CONTROLLER:?} \ + 'hasRole(bytes32, address)(bool)' \ + 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 \ + 0x00000000000000aabbccddeeff00000000000000 + false + ``` ## Renounce access to the TimelockController @@ -472,51 +549,67 @@ If your private key is compromised, you can renounce your role(s) without waitin * executor * canceller -```console -ROLE="executor" \ -TIMELOCK_CONTROLLER=${TIMELOCK_CONTROLLER:?} \ -bash contracts/script/manage RenounceRole +> ![WARNING] +> Renouncing authorization on the timelock controller may make permanently inoperable. -... +1. Dry run the transaction: -== Logs == - roleStr: executor - msg.sender: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 - Using TimelockController at address 0x5FbDB2315678afecb367f032d93F642f64180aa3 - role: - 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 -``` + ```bash + RENOUNCE_ROLE="executor" \ + RENOUNCE_ADDRESS="0x00000000000000aabbccddeeff00000000000000" \ + bash contracts/script/manage RenounceRole + ``` -Confirm: +2. Run the command again with `--broadcast` -```console -cast call --rpc-url ${RPC_URL:?} \ - ${TIMELOCK_CONTROLLER:?} \ - 'hasRole(bytes32, address)(bool)' \ - 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 \ - ${PUBLIC_KEY:?} -false -``` + This will send one transaction from the admin address. + +3. Confirm: + + ```bash + cast call --rpc-url ${RPC_URL:?} \ + ${TIMELOCK_CONTROLLER:?} \ + 'hasRole(bytes32, address)(bool)' \ + 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 \ + ${RENOUNCE_ADDRESS:?} + false + ``` ## Activate the emergency stop Activate the emergency stop: -```console -VERIFIER_ESTOP=${VERIFIER_ESTOP:?} \ -bash contracts/script/manage ActivateEstop +> ![WARNING] +> Activating the emergency stop will make that verifier permanently inoperable. -... +1. Set the verifier selector and estop address for the verifier: -== Logs == - Using RiscZeroVerifierEmergencyStop at address 0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9 -``` + > TIP: One place to find this information is in `./contracts/test/RiscZeroGroth16Verifier.t.sol` -Test the activation: + ```zsh + export VERIFIER_SELECTOR="0x..." + export VERIFIER_ESTOP=$(yq eval -e ".chains[\"${CHAIN_KEY:?}\"].verifiers[] | select(.selector == \"${VERIFIER_SELECTOR:?}\") | .estop" contracts/deployment.toml | tee /dev/stderr) + ``` -```console -cast call --rpc-url ${RPC_URL:?} \ - ${VERIFIER_ESTOP:?} \ - 'paused()(bool)' -true -``` +2. Dry run the transaction + + ```bash + VERIFIER_ESTOP=${VERIFIER_ESTOP:?} \ + bash contracts/script/manage ActivateEstop + ``` + +3. Run the command again with `--broadcast` + + This will send one transaction from the admin address. + +4. Test the activation: + + ```bash + cast call --rpc-url ${RPC_URL:?} \ + ${VERIFIER_ESTOP:?} \ + 'paused()(bool)' + true + ``` + +[yq-install]: https://github.com/mikefarah/yq?tab=readme-ov-file#install +[alloy-chains]: https://github.com/alloy-rs/chains/blob/main/src/named.rs diff --git a/contracts/script/manage b/contracts/script/manage index 8e684a06..5c6caf9a 100755 --- a/contracts/script/manage +++ b/contracts/script/manage @@ -19,6 +19,10 @@ while [[ $# -gt 0 ]]; do FORGE_SCRIPT_FLAGS+=("$1") shift ;; + --verify) + FORGE_SCRIPT_FLAGS+=("$1") + shift + ;; -*|--*) echo "Unknown option $1" exit 1 @@ -35,14 +39,14 @@ set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters # Set our function. If the function is "help", or if the function is # unspecified, then print some help. SCRIPT_FUNCTION="${1:-help}" -if [ "${SCRIPT_FUNCTION}" == "help" ]; then +if [ "${SCRIPT_FUNCTION:?}" == "help" ]; then echo "Usage:" echo " bash ${0} [--fireblocks] [--broadcast]" echo "See README.md for a list of functions" exit 0 fi -echo "Running ${SCRIPT_FILE}:${SCRIPT_FUNCTION}" +echo "Running ${SCRIPT_FILE:?}:${SCRIPT_FUNCTION:?}" # Check for forge if ! command -v forge &> /dev/null; then @@ -59,15 +63,14 @@ if [ $FIREBLOCKS -gt 0 ]; then fi # Run forge via fireblocks - fireblocks-json-rpc --verbose --rpcUrl ${RPC_URL} --http -- \ - forge script ${SCRIPT_FILE}:${SCRIPT_FUNCTION} \ + fireblocks-json-rpc --verbose --rpcUrl ${RPC_URL:?} --http --apiKey ${FIREBLOCKS_API_KEY:?} -- \ + forge script ${SCRIPT_FILE:?}:${SCRIPT_FUNCTION:?} \ --slow --unlocked ${FORGE_DEPLOY_FLAGS} ${FORGE_SCRIPT_FLAGS} \ - --sender ${PUBLIC_KEY} \ --rpc-url {} else # Run forge - forge script ${SCRIPT_FILE}:${SCRIPT_FUNCTION} \ + forge script ${SCRIPT_FILE:?}:${SCRIPT_FUNCTION:?} \ --slow ${FORGE_DEPLOY_FLAGS} ${FORGE_SCRIPT_FLAGS} \ - --sender ${PUBLIC_KEY} --private-key ${PRIVATE_KEY} \ - --rpc-url ${RPC_URL} + --private-key ${DEPLOYER_PRIVATE_KEY:?} \ + --rpc-url ${RPC_URL:?} fi