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.
Requires Foundry.
Note
Running the manage
commands will run in simulation mode (i.e. will not send transactions) unless the --broadcast
flag is passed.
Commands in this guide use yq
to parse the TOML config files.
You can install yq
by following the direction on GitHub, or using go install
.
go install github.com/mikefarah/yq/v4@latest
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 sensitive 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.
[chains.$CHAIN_KEY]
rpc-url = "..."
etherscan-api-key = "..."
In development and to test the operations process, you can use Anvil.
Start Anvil:
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:
export RPC_URL="http://localhost:8545"
export DEPLOYER_ADDRESS="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
export DEPLOYER_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
export CHAIN_KEY="anvil"
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.
export CHAIN_KEY="xxx-testnet"
Based on the chain key, the manage
script will automatically load environment variables from deployment.toml and deployment_secrets.toml
If the chain you are deploying to is not in deployment_secrets.toml
, set your RPC URL, public and private key, and Etherscan API key:
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)
Tip
Foundry has a config full of information about each chain, 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:
https://eth-sepolia.g.alchemy.com/v2/YOUR_API_KEY
https://sepolia.infura.io/v3/YOUR_API_KEY
Requires the Fireblocks integration for Foundry.
Also requires that you have a Fireblocks API account.
Set your public key, your Etherscan API key, and the necessary parameters for Fireblocks:
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".
export FIREBLOCKS_API_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
(-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.
Note
Before you approve any contract-call transactions, be sure you understand what the call does! When in doubt, use Etherscan to lookup the function selector, together with a calldata decoder (alternative) 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.
-
Dry run the contract deployment:
[!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).MIN_DELAY=1 \ PROPOSER="${ADMIN_ADDRESS:?}" \ EXECUTOR="${ADMIN_ADDRESS:?}" \ bash contracts/script/manage DeployTimelockRouter ... == Logs == minDelay: 1 proposers: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 executors: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 admin: 0x0000000000000000000000000000000000000000 Deployed TimelockController to 0x5FbDB2315678afecb367f032d93F642f64180aa3 Deployed RiscZeroVerifierRouter to 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
-
Run the command again with
--broadcast
.This will result in two transactions sent from the deployer address.
-
Verify the contracts on Etherscan (or its equivalent) by running the command again without--broadcast
and add--verify
.[!WARNING] The verify functionality appears to be broken see #393
-
Save the contract addresses to
deployment.toml
.Load the addresses into your environment.
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)
-
Test the deployment.
FOUNDRY_PROFILE=deployment-test forge test -vv --fork-url=${RPC_URL:?}
This is a two-step process, guarded by the TimelockController
.
-
Dry run deployment of verifier and estop:
bash contracts/script/manage DeployEstopVerifier
[!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. -
Send deployment transactions for verifier and estop by running the command again with
--broadcast
.This will result in two transactions sent from the deployer address.
[!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.
-
Verify the contracts on Etherscan (or its equivalent) by running the command again without--broadcast
and add--verify
.[!WARNING] The verify functionality appears to be broken see #393
-
Add the addresses for the newly deployed contract to the
deployment.toml
file. -
Test the deployment.
FOUNDRY_PROFILE=deployment-test forge test -vv --fork-url=${RPC_URL:?}
-
Dry run the operation to schedule the operation to add the verifier to the router.
VERIFIER_SELECTOR="0x..." bash contracts/script/manage ScheduleAddVerifier
-
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.
After the delay on the timelock controller has pass, the operation to add the new verifier to the router can be executed.
-
Dry the transaction to execute the add verifier operation:
VERIFIER_SELECTOR="0x..." bash contracts/script/manage FinishAddVerifier
-
Run the command again with
--broadcast
This will send one transaction from the admin address.
-
Remove the
unroutable
field from the selected verifier. -
Test the deployment.
FOUNDRY_PROFILE=deployment-test forge test -vv --fork-url=${RPC_URL:?}
This is a two-step process, guarded by the TimelockController
.
-
Make available for download the
set-builder
elf and export its image ID and url in theSET_BUILDER_IMAGE_ID
andSET_BUILDER_GUEST_URL
env variables respectively.To generate a deterministic image ID run (from the repo root folder):
cargo risczero build --manifest-path aggregation/guest/set-builder/Cargo.toml
This will output the image ID and file location. Upload the ELF to some public HTTP location (such as Pinata), and get back a download URL. Finally export these values in the in the
SET_BUILDER_IMAGE_ID
andSET_BUILDER_GUEST_URL
env variables. -
Dry run deployment of the set verifier and estop:
bash contracts/script/manage DeployEstopSetVerifier
[!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. -
Send deployment transactions for the set verifier by running the command again with
--broadcast
.This will result in two transactions sent from the deployer address.
[!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.
-
Verify the contracts on Etherscan (or its equivalent) by running the command again without--broadcast
and add--verify
.[!WARNING] The verify functionality appears to be broken see #393
-
Test the deployment.
FOUNDRY_PROFILE=deployment-test forge test -vv --fork-url=${RPC_URL:?}
-
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_ADDRESS
should be set to the Fireblocks admin address.bash contracts/script/manage ScheduleAddVerifier
-
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.
After the delay on the timelock controller has pass, the operation to add the new set verifier to the router can be executed.
-
Set the verifier selector and estop address for the set verifier:
export VERIFIER_SELECTOR=$(bash contracts/script/manage SetVerifierSelector | grep selector | awk -F': ' '{print $2}' | tee /dev/stderr)
-
Dry the transaction to execute the add verifier operation:
bash contracts/script/manage FinishAddVerifier
-
Run the command again with
--broadcast
This will send one transaction from the admin address.
-
Test the deployment.
FOUNDRY_PROFILE=deployment-test forge test -vv --fork-url=${RPC_URL:?}
This is a two-step process, guarded by the TimelockController
.
-
Set the verifier selector and estop address for the verifier:
TIP: One place to find this information is in
./contracts/test/RiscZeroGroth16Verifier.t.sol
for theRiscZeroGroth16Verifier
or you can runbash contracts/script/manage SetVerifierSelector
for theRiscZeroSetVerifier
.export VERIFIER_SELECTOR="0x..."
-
Dry the transaction to schedule the remove verifier operation:
bash contracts/script/manage ScheduleRemoveVerifier
-
Run the command again with
--broadcast
This will send one transaction from the admin address.
-
Set the verifier selector and estop address for the verifier:
TIP: One place to find this information is in
./contracts/test/RiscZeroGroth16Verifier.t.sol
for theRiscZeroGroth16Verifier
or you can runbash contracts/script/manage SetVerifierSelector
for theRiscZeroSetVerifier
.export VERIFIER_SELECTOR="0x..."
-
Dry the transaction to execute the remove verifier operation:
bash contracts/script/manage FinishRemoveVerifier
-
Run the command again with
--broadcast
This will send one transaction from the admin address.
-
Update
deployment.toml
and setunroutable = true
on the removed verifier. -
Test the deployment.
FOUNDRY_PROFILE=deployment-test forge test -vv --fork-url=${RPC_URL:?}
This is a two-step process, guarded by the TimelockController
.
-
Dry run the transaction:
MIN_DELAY=10 \ bash contracts/script/manage ScheduleUpdateDelay
-
Run the command again with
--broadcast
This will send one transaction from the admin address.
Execute the action:
-
Dry run the transaction:
MIN_DELAY=10 \ bash contracts/script/manage FinishUpdateDelay
-
Run the command again with
--broadcast
This will send one transaction from the admin address.
-
Test the deployment.
FOUNDRY_PROFILE=deployment-test forge test -vv --fork-url=${RPC_URL:?}
Use the following steps to cancel an operation that is pending on the TimelockController
.
-
Identifier the operation ID and set the environment variable.
TIP: One 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]
.open ${ETHERSCAN_URL:?}/address/${TIMELOCK_CONTROLLER:?}#events
export OPERATION_ID="0x..." \
-
Dry the transaction to cancel the operation.
bash contracts/script/manage CancelOperation -f
-
Run the command again with
--broadcast
This is a two-step process, guarded by the TimelockController
.
Three roles are supported:
proposer
executor
canceller
-
Dry run the transaction:
ROLE="executor" \ ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ bash contracts/script/manage ScheduleGrantRole
-
Run the command again with
--broadcast
This will send one transaction from the admin address.
-
Dry run the transaction:
ROLE="executor" \ ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ bash contracts/script/manage FinishGrantRole
-
Run the command again with
--broadcast
This will send one transaction from the admin address.
-
Confirm the update:
# 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
This is a two-step process, guarded by the TimelockController
.
Three roles are supported:
proposer
executor
canceller
-
Dry run the transaction:
ROLE="executor" \ ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ bash contracts/script/manage ScheduleRevokeRole
-
Run the command again with
--broadcast
This will send one transaction from the admin address.
Confirm the role code:
cast call --rpc-url ${RPC_URL:?} \
${TIMELOCK_CONTROLLER:?} \
'EXECUTOR_ROLE()(bytes32)'
0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63
-
Dry run the transaction:
ROLE="executor" \ ACCOUNT="0x00000000000000aabbccddeeff00000000000000" \ bash contracts/script/manage FinishRevokeRole
-
Run the command again with
--broadcast
This will send one transaction from the admin address.
-
Confirm the update:
# 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
If your private key is compromised, you can renounce your role(s) without waiting for the time delay. Repeat this action for any of the roles you might have, such as:
- proposer
- executor
- canceller
![WARNING] Renouncing authorization on the timelock controller may make it permanently inoperable.
-
Dry run the transaction:
RENOUNCE_ROLE="executor" \ RENOUNCE_ADDRESS="0x00000000000000aabbccddeeff00000000000000" \ bash contracts/script/manage RenounceRole
-
Run the command again with
--broadcast
This will send one transaction from the admin address.
-
Confirm:
cast call --rpc-url ${RPC_URL:?} \ ${TIMELOCK_CONTROLLER:?} \ 'hasRole(bytes32, address)(bool)' \ 0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63 \ ${RENOUNCE_ADDRESS:?} false
Activate the emergency stop:
![WARNING] Activating the emergency stop will make that verifier permanently inoperable.
-
Set the verifier selector and estop address for the verifier:
TIP: One place to find this information is in
./contracts/test/RiscZeroGroth16Verifier.t.sol
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)
-
Dry run the transaction
VERIFIER_ESTOP=${VERIFIER_ESTOP:?} \ bash contracts/script/manage ActivateEstop
-
Run the command again with
--broadcast
This will send one transaction from the admin address.
-
Test the activation:
cast call --rpc-url ${RPC_URL:?} \ ${VERIFIER_ESTOP:?} \ 'paused()(bool)' true