Prerequisites
Before deploying to L2, ensure you have:- L1Unwrapper address from L1 deployment
- Hardhat environment configured for L2 network
- Private key with sufficient L2 tokens for gas
- Environment variables set:
MINIMUM_WITHDRAWAL_AMOUNTMAXIMUM_DEPOSIT_AMOUNT
Configuration
Update theconfig.js file with L2 network parameters:
config.js
Deployment Process
Compile Poseidon Hasher
The Poseidon hasher requires special compilation:This generates the Hasher contract from the Poseidon hash circuit.
Deploy Verifiers
Deploy the zero-knowledge proof verifiers:
- Verifier2: Validates proofs with 2 input notes
- Verifier16: Validates proofs with 16 input notes (for consolidation)
Deploy TornadoPool Implementation
Deploy the main TornadoPool logic contract:Constructor parameters:
verifier2,verifier16: Proof verifier addressesMERKLE_TREE_HEIGHT: Merkle tree depth (23 = 8M leaves)hasher: Poseidon hasher addresstoken: Wrapped token on L2 (e.g., WETH on Gnosis)omniBridge: OmniBridge address on L2l1Unwrapper: L1Unwrapper address from L1 deploymentgovAddress: L1 governance contract addressl1ChainId: Chain ID of L1 network (1 for mainnet)multisig: L2 multisig for emergency operations
Deploy CrossChainUpgradeableProxy
Deploy the upgradeable proxy with cross-chain governance:Parameters:
tornadoImpl.address: Implementation contract addressgovAddress: L1 governance address (admin)[]: Empty initialization data (will initialize separately)amb: AMB bridge address on L2l1ChainId: L1 chain ID for cross-chain verification
Deployment Script
Run the complete deployment:Save all deployed addresses for future reference and verification.
TornadoPool Architecture
UTXO Model
TornadoPool uses a UTXO (Unspent Transaction Output) model similar to Bitcoin:- Each deposit creates new UTXOs (commitments)
- Transactions consume input UTXOs and create output UTXOs
- Zero-knowledge proofs hide the relationship between inputs and outputs
Merkle Tree
All commitments are stored in a Merkle tree:- Height: 23 levels (supports ~8.4 million notes)
- Hasher: Poseidon hash function (ZK-friendly)
- Root history: Recent roots stored for proof validation
Cross-Chain Governance
The CrossChainUpgradeableProxy enables L1 governance to control the L2 contract:contracts/CrossChainUpgradeableProxy.sol
isCalledByOwner() check verifies:
contracts/bridge/CrossChainGuard.sol
Upgrading with CREATE2
To deploy an upgraded implementation with deterministic address:- Generates deterministic address using CREATE2
- Checks if already deployed
- Deploys via SingletonFactory with salt
- Uses 5M gas limit for complex contracts
Network Configuration
Configure Hardhat for L2 networks:hardhat.config.js
Verification
Verify all deployed contracts:Troubleshooting
Hasher Compilation Failed
- Run
npx hardhat hasherbefore deployment - Check that
scripts/compileHasher.jsexists - Ensure circomlibjs dependencies are installed
Initialization Failed
- Verify
MINIMUM_WITHDRAWAL_AMOUNT≥ 0.5 ETH (MIN_EXT_AMOUNT_LIMIT) - Check that proxy was deployed successfully
- Ensure no other transaction initialized the proxy
Upgrade Deployment Failed
- Confirm salt and constructor args match original deployment
- Check SingletonFactory has sufficient permissions
- Verify gas limit is sufficient (5M recommended)