| title | Testing Governance Rollup Upgrade on Local Network | |||
|---|---|---|---|---|
| sidebar_position | 5 | |||
| tags |
|
|||
| description | Deploy a new rollup and execute a governance upgrade on a local Aztec network for testing. |
This guide walks through deploying a new rollup and executing a governance upgrade on a local Aztec network.
- Aztec tooling
- Node.js and yarn
The default governance configuration for local networks:
| Parameter | Value | Description |
|---|---|---|
| votingDelay | 60 seconds | Time before voting starts |
| votingDuration | 1 hour | Voting period length |
| executionDelay | 60 seconds | Delay after voting ends before execution |
| gracePeriod | 7 days | Window to execute after becoming executable |
| lockDelay | 30 days | Token lock period for proposers |
| lockAmount | 1,000,000 tokens | Tokens locked when proposing |
Ensure you are on the correct Aztec version:
aztec-up install #release_versionaztec start --local-networkWait for output showing deployed contract addresses. To get the Registry Address and other L1 contract addresses, query the running node:
curl -s http://localhost:8080 -X POST -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"aztec_getNodeInfo","params":[],"id":1}' | jq '.result.l1ContractAddresses'Note the registryAddress from the output.
The @aztec/l1-artifacts npm package bundles a self-contained Foundry project with the L1 contract sources, prebuilt artifacts, deploy scripts, and governance payload contracts. Download the version matching your Aztec installation (run aztec --version to find it):
npm pack @aztec/l1-artifacts@#release_version
mkdir l1-contracts
tar xzf aztec-l1-artifacts-#release_version.tgz --strip-components=2 -C l1-contracts package/l1-contracts
cd l1-contractsNo further setup is needed: the bundle includes the library dependencies and the generated verifier, and forge downloads the matching solc version automatically on first use.
:::note
The Aztec installer ships Foundry as aztec-forge, aztec-cast, and aztec-anvil. Substitute your own forge and cast installs in the commands below if you have them.
:::
# Anvil's default account 0
export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
export DEPLOYER_ADDRESS=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
# Replace with actual address from Step 1
export REGISTRY_ADDRESS=0x...
# L1 RPC
export L1_RPC_URL=http://localhost:8545
export L1_CHAIN_ID=31337
# Rollup configuration (local network defaults)
export AZTEC_SLOT_DURATION=36
export AZTEC_EPOCH_DURATION=16
export AZTEC_TARGET_COMMITTEE_SIZE=48
export AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET=2
export AZTEC_LAG_IN_EPOCHS_FOR_RANDAO=2
export AZTEC_PROOF_SUBMISSION_EPOCHS=2
export AZTEC_LOCAL_EJECTION_THRESHOLD=0
export AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS=1
export AZTEC_SLASHING_LIFETIME_IN_ROUNDS=10
export AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS=1
export AZTEC_SLASHING_OFFSET_IN_ROUNDS=0
export AZTEC_SLASHER_ENABLED=false
export AZTEC_SLASHING_VETOER=0x0000000000000000000000000000000000000000
export AZTEC_SLASHING_DISABLE_DURATION=0
export AZTEC_MANA_TARGET=100000000
export AZTEC_EXIT_DELAY_SECONDS=0
export AZTEC_PROVING_COST_PER_MANA=100
export AZTEC_SLASH_AMOUNT_SMALL=0
export AZTEC_SLASH_AMOUNT_MEDIUM=0
export AZTEC_SLASH_AMOUNT_LARGE=0
export AZTEC_INITIAL_ETH_PER_FEE_ASSET=10000000aztec-forge script script/deploy/DeployRollupForUpgrade.s.sol:DeployRollupForUpgrade \
--rpc-url $L1_RPC_URL \
--broadcast \
--private-key $PRIVATE_KEYNote the new rollup address from the JSON output.
export NEW_ROLLUP_ADDRESS=0x...:::tip
The deploy script in Step 4 also deploys this payload and prints it as payloadAddress in its JSON output. You can export that address as PAYLOAD_ADDRESS and skip this step.
:::
Important: Place flags before the contract path to avoid argument parsing issues.
cd l1-contracts
aztec-forge create \
--rpc-url $L1_RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast \
src/periphery/RegisterNewRollupVersionPayload.sol:RegisterNewRollupVersionPayload \
--constructor-args $REGISTRY_ADDRESS $NEW_ROLLUP_ADDRESSNote the payload address from the output.
export PAYLOAD_ADDRESS=0x...Mint and deposit tokens to get voting power. You need at least 1,000,000 tokens (1e24 wei) to propose:
aztec deposit-governance-tokens \
-r $REGISTRY_ADDRESS \
--recipient $DEPLOYER_ADDRESS \
--amount "2000000000000000000000000" \
--mint \
--l1-rpc-urls $L1_RPC_URL \
-c $L1_CHAIN_ID \
--private-key $PRIVATE_KEY:::warning Critical Step Tokens must be deposited before the proposal is created. The governance contract snapshots voting power at the proposal creation timestamp. If your deposit checkpoint timestamp >= proposal creation timestamp, your voting power will be 0 and the proposal will be rejected. :::
Advance Anvil's time to ensure the checkpoint is in the past when the proposal is created:
# Get current timestamp and add 120 seconds
CURRENT_TS=$(cast block latest --rpc-url $L1_RPC_URL --json | jq -r '.timestamp')
TARGET_TS=$((CURRENT_TS + 120))
cast rpc anvil_setNextBlockTimestamp $TARGET_TS --rpc-url $L1_RPC_URL
cast rpc anvil_mine 1 --rpc-url $L1_RPC_URLVerify the time has advanced:
NEW_TS=$(cast block latest --rpc-url $L1_RPC_URL --json | jq -r '.timestamp')
echo "New timestamp: $NEW_TS (should be > $CURRENT_TS)":::note
anvil_increaseTime may not reliably update block timestamps. For consistent results, always use anvil_setNextBlockTimestamp with an explicit timestamp.
:::
aztec propose-with-lock \
-r $REGISTRY_ADDRESS \
-p $PAYLOAD_ADDRESS \
--l1-rpc-urls $L1_RPC_URL \
-c $L1_CHAIN_ID \
--private-key $PRIVATE_KEY \
--jsonNote the proposal ID from output.
export PROPOSAL_ID=0The proposal must transition from Pending to Active (votingDelay = 60 seconds):
# Get current timestamp and add 120 seconds (buffer over 60s voting delay)
CURRENT_TS=$(cast block latest --rpc-url $L1_RPC_URL --json | jq -r '.timestamp')
TARGET_TS=$((CURRENT_TS + 120))
cast rpc anvil_setNextBlockTimestamp $TARGET_TS --rpc-url $L1_RPC_URL
cast rpc anvil_mine 1 --rpc-url $L1_RPC_URLVerify the proposal is now Active (state 1):
# Get governance address from node info or use the one from Step 1
cast call <GOVERNANCE_ADDRESS> "getProposalState(uint256)(uint8)" $PROPOSAL_ID --rpc-url $L1_RPC_URL
# Expected output: 1 (Active)aztec vote-on-governance-proposal \
-p $PROPOSAL_ID \
--in-favor yea \
--wait false \
-r $REGISTRY_ADDRESS \
--l1-rpc-urls $L1_RPC_URL \
-c $L1_CHAIN_ID \
--private-key $PRIVATE_KEYVerify the vote was recorded with your voting power. The CLI output should show non-zero summedBallot yea values. If it shows [0], your checkpoint timing was incorrect (see Troubleshooting).
Voting duration is 1 hour (3600s) and execution delay is 60 seconds:
# Get current timestamp and add 3700 seconds (voting duration + execution delay + buffer)
CURRENT_TS=$(cast block latest --rpc-url $L1_RPC_URL --json | jq -r '.timestamp')
TARGET_TS=$((CURRENT_TS + 3700))
cast rpc anvil_setNextBlockTimestamp $TARGET_TS --rpc-url $L1_RPC_URL
cast rpc anvil_mine 1 --rpc-url $L1_RPC_URLVerify the proposal is now Executable (state 3):
cast call <GOVERNANCE_ADDRESS> "getProposalState(uint256)(uint8)" $PROPOSAL_ID --rpc-url $L1_RPC_URL
# Expected output: 3 (Executable)aztec execute-governance-proposal \
-p $PROPOSAL_ID \
-r $REGISTRY_ADDRESS \
--wait false \
--l1-rpc-urls $L1_RPC_URL \
-c $L1_CHAIN_ID \
--private-key $PRIVATE_KEYConfirm the new rollup is now the canonical rollup:
# Check the canonical rollup address (should match NEW_ROLLUP_ADDRESS)
cast call $REGISTRY_ADDRESS "getCanonicalRollup()(address)" --rpc-url $L1_RPC_URL
# Check the number of rollup versions (should be 2)
cast call $REGISTRY_ADDRESS "numberOfVersions()(uint256)" --rpc-url $L1_RPC_URLIf time advancement isn't working as expected, set the timestamp explicitly:
# Get the target timestamp (current + desired seconds)
cast rpc anvil_setNextBlockTimestamp <UNIX_TIMESTAMP> --rpc-url $L1_RPC_URL
cast rpc anvil_mine 1 --rpc-url $L1_RPC_URL# States: 0=Pending, 1=Active, 2=Queued, 3=Executable, 4=Rejected, 5=Executed, 6=Dropped, 7=Expired
cast call <GOVERNANCE_ADDRESS> "getProposalState(uint256)(uint8)" $PROPOSAL_ID --rpc-url $L1_RPC_URLcast block latest --rpc-url $L1_RPC_URL | grep timestampaztec get-l1-addresses \
-r $REGISTRY_ADDRESS \
-v canonical \
--l1-rpc-urls $L1_RPC_URL \
-c $L1_CHAIN_ID \
--jsonaztec debug-rollup \
--rollup $NEW_ROLLUP_ADDRESS \
--l1-rpc-urls $L1_RPC_URL \
-c $L1_CHAIN_IDThe --rollup flag is required; without it the command may fail trying to resolve the default rollup address.
If you just want to test the governance flow without deploying a real rollup:
cd l1-contracts
# Deploy empty payload (no constructor args needed)
aztec-forge create \
--rpc-url $L1_RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast \
test/governance/governance/TestPayloads.sol:EmptyPayload
# Use the deployed address as PAYLOAD_ADDRESS and continue from Step 6- You need more tokens. The minimum to propose is 1,000,000 tokens (1e24 wei).
- Deposit more tokens in Step 6.
- Tokens were deposited at or after the proposal creation time.
- Advance Anvil's time and mine a block before creating the proposal (Step 7).
- The voting delay hasn't passed yet.
- Advance time past the votingDelay (60 seconds for local networks).
- Either voting period is not complete, or execution delay hasn't passed.
- Advance time past votingDuration (1 hour) + executionDelay (60 seconds).
- Constructor args may be parsing incorrectly. Place
--constructor-argsat the end of the command, after the contract path.
- Anvil may have auto-mined blocks that reset the accumulated time.
- Use
anvil_setNextBlockTimestampto set an explicit timestamp instead ofanvil_increaseTime.
- If you see
NotInPasterrors during voting, the CLI may have a bug determining voting power. - Workaround: specify
--vote-amountexplicitly with your deposited token amount.