Merge 20-add-mainnet-deployment-docs into main
ea27707 parent: 2d802ed modified
CHANGELOG.md +5 -0 | @@ -89,6 +89,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | ||
| 89 | 89 | |
| 90 | 90 | ### Added |
| 91 | 91 | |
| 92 | +- A "Mainnet deployment" section in `docs/ZK.md`: finish and freeze the | |
| 93 | + ceremony before choosing an address, since the verifier is part of `Love`'s | |
| 94 | + creation code and so its CREATE2 address depends on the zkey; rehearse, | |
| 95 | + deploy and check the result on Ethereum with `script/Love.s.sol`; and what | |
| 96 | + cannot be changed once live. | |
| 92 | 97 | - `zk/scripts/e2e.mjs`, run with `pnpm e2e` in `zk/`: brings up a local anvil, |
| 93 | 98 | deploys LOVE with `script/Love.s.sol`, deposits three notes, rebuilds the tree |
| 94 | 99 | from the `Deposit` events, proves with `zk/lib.mjs` and claims from an |
| @@ -89,6 +89,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 89 | 89 | ||
| 90 | ### Added | 90 | ### Added |
| 91 | 91 | ||
| 92 | +- A "Mainnet deployment" section in `docs/ZK.md`: finish and freeze the | ||
| 93 | + ceremony before choosing an address, since the verifier is part of `Love`'s | ||
| 94 | + creation code and so its CREATE2 address depends on the zkey; rehearse, | ||
| 95 | + deploy and check the result on Ethereum with `script/Love.s.sol`; and what | ||
| 96 | + cannot be changed once live. | ||
| 92 | - `zk/scripts/e2e.mjs`, run with `pnpm e2e` in `zk/`: brings up a local anvil, | 97 | - `zk/scripts/e2e.mjs`, run with `pnpm e2e` in `zk/`: brings up a local anvil, |
| 93 | deploys LOVE with `script/Love.s.sol`, deposits three notes, rebuilds the tree | 98 | deploys LOVE with `script/Love.s.sol`, deposits three notes, rebuilds the tree |
| 94 | from the `Deposit` events, proves with `zk/lib.mjs` and claims from an | 99 | from the `Deposit` events, proves with `zk/lib.mjs` and claims from an |
modified
docs/ZK.md +41 -0 | @@ -118,6 +118,8 @@ This is fine for a testnet. Before it holds real value: | ||
| 118 | 118 | - Run phase 2 with several independent contributors. |
| 119 | 119 | - Have the circuit and the vendored Poseidon audited. |
| 120 | 120 | |
| 121 | +The order to do this in, and what else goes into a mainnet launch, is in [Mainnet deployment](#mainnet-deployment). | |
| 122 | + | |
| 121 | 123 | ## Testing |
| 122 | 124 | |
| 123 | 125 | `forge test` covers the contracts against the proofs in `test/fixtures/proofs.json`, which are replayed, so it never runs the prover. `pnpm e2e` does: it needs [anvil](https://book.getfoundry.sh/anvil/) and `forge` on your `PATH`, and runs the whole cycle on a local chain. |
| @@ -140,6 +142,45 @@ It starts anvil as Base (chain 8453), puts Base's wETH at the OP Stack predeploy | ||
| 140 | 142 | - **Note handling.** The secret must never exist on a server: generate and prove locally, and share the claim code over a channel with no public log. Anything that lets a server preview a claim, or an indexer map notes to recipients, undoes the design. |
| 141 | 143 | - **Gas.** A deposit costs roughly 0.4 to 0.7 million gas, most of it the ten hashes, and a claim about 0.29 million. That is fine on an L2 and heavy on Ethereum mainnet. |
| 142 | 144 | |
| 145 | +## Mainnet deployment | |
| 146 | + | |
| 147 | +The steps are the same on any allowlisted chain. Ethereum is the case spelled out here, since its gas makes the [limits](#limits) bite hardest. | |
| 148 | + | |
| 149 | +**1. Finish the ceremony, and freeze it.** Do the three things under [Trusted setup](#trusted-setup): a public phase 1, a phase 2 with more than one contributor, and an audit of the circuit and the vendored Poseidon. Then regenerate everything in one go and commit it as a set: `withdraw.zkey`, `withdraw.wasm`, `verification_key.json`, `Groth16Verifier.sol` and `proofs.json`. Run `forge test` and `pnpm e2e` on the result. | |
| 150 | + | |
| 151 | +Do this before choosing an address. [`Love`](../src/Love.sol) builds its `Groth16Verifier` in its constructor, so the verifier's code is part of `Love`'s creation code, and its CREATE2 address depends on the zkey. A new setup means a new address. Deploying the same zkey on every chain is what keeps that address the same everywhere. The registry is unaffected, since it knows nothing about the verifier. | |
| 152 | + | |
| 153 | +**2. Rehearse.** Run the deploy against a fork, or a testnet, with the frozen artifacts. Without `--broadcast`, `forge script` only simulates. | |
| 154 | + | |
| 155 | +```shell | |
| 156 | +forge script script/Love.s.sol:LoveScript --sig 'predict()' # the address, before anything is sent | |
| 157 | + | |
| 158 | +WETH=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 \ | |
| 159 | +forge script script/Love.s.sol:LoveScript \ | |
| 160 | + --rpc-url "$MAINNET_RPC_URL" \ | |
| 161 | + --account "$DEPLOYER_ACCOUNT" | |
| 162 | +``` | |
| 163 | + | |
| 164 | +**3. Deploy.** The same command with `--broadcast --verify --etherscan-api-key "$ETHERSCAN_API_KEY"`. `WETH` must be set on Ethereum: the default is the OP Stack predeploy, which doesn't exist there. The script deploys the registry, registers wETH, which the registry checks against its reviewed hash for chain 1, and deploys `Love`, skipping any of those already done. Keep the deployer account for this and nothing else: `Love` has no owner, so it needs no key afterwards. | |
| 165 | + | |
| 166 | +**4. Check what landed.** Against the chain, not the script's log: | |
| 167 | + | |
| 168 | +```shell | |
| 169 | +cast call "$LOVE" "WETH()(address)" # canonical WETH9, 0xC02a…6Cc2 | |
| 170 | +cast call "$LOVE" "REGISTRY()(address)" # the registry's CREATE2 address | |
| 171 | +cast call "$LOVE" "DENOMINATION()(uint256)" | |
| 172 | +cast call "$LOVE" "VERIFIER()(address)" # then compare its code with src/Groth16Verifier.sol on Etherscan | |
| 173 | +``` | |
| 174 | + | |
| 175 | +Then make one deposit and one claim with your own note, the claim to an address with no history. It costs 0.01 wETH and tells you the circuit, verifier and tree agree on the real chain. | |
| 176 | + | |
| 177 | +**5. Know what you launched.** Nothing below can be changed once it is live: | |
| 178 | + | |
| 179 | +- No pause, no upgrade and no admin. A bug in the circuit or the ceremony is fixed by a new deployment and a migration, not a patch. | |
| 180 | +- The tree takes 1,024 deposits, ever. | |
| 181 | +- Gas is roughly 0.4 to 0.7 million per deposit and 0.29 million per claim. | |
| 182 | +- The anonymity set is one until others deposit. Say so wherever the pool is announced. | |
| 183 | + | |
| 143 | 184 | ## Reference |
| 144 | 185 | |
| 145 | 186 | | Function | Effect | |
| @@ -118,6 +118,8 @@ This is fine for a testnet. Before it holds real value: | |||
| 118 | - Run phase 2 with several independent contributors. | 118 | - Run phase 2 with several independent contributors. |
| 119 | - Have the circuit and the vendored Poseidon audited. | 119 | - Have the circuit and the vendored Poseidon audited. |
| 120 | 120 | ||
| 121 | +The order to do this in, and what else goes into a mainnet launch, is in [Mainnet deployment](#mainnet-deployment). | ||
| 122 | + | ||
| 121 | ## Testing | 123 | ## Testing |
| 122 | 124 | ||
| 123 | `forge test` covers the contracts against the proofs in `test/fixtures/proofs.json`, which are replayed, so it never runs the prover. `pnpm e2e` does: it needs [anvil](https://book.getfoundry.sh/anvil/) and `forge` on your `PATH`, and runs the whole cycle on a local chain. | 125 | `forge test` covers the contracts against the proofs in `test/fixtures/proofs.json`, which are replayed, so it never runs the prover. `pnpm e2e` does: it needs [anvil](https://book.getfoundry.sh/anvil/) and `forge` on your `PATH`, and runs the whole cycle on a local chain. |
| @@ -140,6 +142,45 @@ It starts anvil as Base (chain 8453), puts Base's wETH at the OP Stack predeploy | |||
| 140 | - **Note handling.** The secret must never exist on a server: generate and prove locally, and share the claim code over a channel with no public log. Anything that lets a server preview a claim, or an indexer map notes to recipients, undoes the design. | 142 | - **Note handling.** The secret must never exist on a server: generate and prove locally, and share the claim code over a channel with no public log. Anything that lets a server preview a claim, or an indexer map notes to recipients, undoes the design. |
| 141 | - **Gas.** A deposit costs roughly 0.4 to 0.7 million gas, most of it the ten hashes, and a claim about 0.29 million. That is fine on an L2 and heavy on Ethereum mainnet. | 143 | - **Gas.** A deposit costs roughly 0.4 to 0.7 million gas, most of it the ten hashes, and a claim about 0.29 million. That is fine on an L2 and heavy on Ethereum mainnet. |
| 142 | 144 | ||
| 145 | +## Mainnet deployment | ||
| 146 | + | ||
| 147 | +The steps are the same on any allowlisted chain. Ethereum is the case spelled out here, since its gas makes the [limits](#limits) bite hardest. | ||
| 148 | + | ||
| 149 | +**1. Finish the ceremony, and freeze it.** Do the three things under [Trusted setup](#trusted-setup): a public phase 1, a phase 2 with more than one contributor, and an audit of the circuit and the vendored Poseidon. Then regenerate everything in one go and commit it as a set: `withdraw.zkey`, `withdraw.wasm`, `verification_key.json`, `Groth16Verifier.sol` and `proofs.json`. Run `forge test` and `pnpm e2e` on the result. | ||
| 150 | + | ||
| 151 | +Do this before choosing an address. [`Love`](../src/Love.sol) builds its `Groth16Verifier` in its constructor, so the verifier's code is part of `Love`'s creation code, and its CREATE2 address depends on the zkey. A new setup means a new address. Deploying the same zkey on every chain is what keeps that address the same everywhere. The registry is unaffected, since it knows nothing about the verifier. | ||
| 152 | + | ||
| 153 | +**2. Rehearse.** Run the deploy against a fork, or a testnet, with the frozen artifacts. Without `--broadcast`, `forge script` only simulates. | ||
| 154 | + | ||
| 155 | +```shell | ||
| 156 | +forge script script/Love.s.sol:LoveScript --sig 'predict()' # the address, before anything is sent | ||
| 157 | + | ||
| 158 | +WETH=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 \ | ||
| 159 | +forge script script/Love.s.sol:LoveScript \ | ||
| 160 | + --rpc-url "$MAINNET_RPC_URL" \ | ||
| 161 | + --account "$DEPLOYER_ACCOUNT" | ||
| 162 | +``` | ||
| 163 | + | ||
| 164 | +**3. Deploy.** The same command with `--broadcast --verify --etherscan-api-key "$ETHERSCAN_API_KEY"`. `WETH` must be set on Ethereum: the default is the OP Stack predeploy, which doesn't exist there. The script deploys the registry, registers wETH, which the registry checks against its reviewed hash for chain 1, and deploys `Love`, skipping any of those already done. Keep the deployer account for this and nothing else: `Love` has no owner, so it needs no key afterwards. | ||
| 165 | + | ||
| 166 | +**4. Check what landed.** Against the chain, not the script's log: | ||
| 167 | + | ||
| 168 | +```shell | ||
| 169 | +cast call "$LOVE" "WETH()(address)" # canonical WETH9, 0xC02a…6Cc2 | ||
| 170 | +cast call "$LOVE" "REGISTRY()(address)" # the registry's CREATE2 address | ||
| 171 | +cast call "$LOVE" "DENOMINATION()(uint256)" | ||
| 172 | +cast call "$LOVE" "VERIFIER()(address)" # then compare its code with src/Groth16Verifier.sol on Etherscan | ||
| 173 | +``` | ||
| 174 | + | ||
| 175 | +Then make one deposit and one claim with your own note, the claim to an address with no history. It costs 0.01 wETH and tells you the circuit, verifier and tree agree on the real chain. | ||
| 176 | + | ||
| 177 | +**5. Know what you launched.** Nothing below can be changed once it is live: | ||
| 178 | + | ||
| 179 | +- No pause, no upgrade and no admin. A bug in the circuit or the ceremony is fixed by a new deployment and a migration, not a patch. | ||
| 180 | +- The tree takes 1,024 deposits, ever. | ||
| 181 | +- Gas is roughly 0.4 to 0.7 million per deposit and 0.29 million per claim. | ||
| 182 | +- The anonymity set is one until others deposit. Say so wherever the pool is announced. | ||
| 183 | + | ||
| 143 | ## Reference | 184 | ## Reference |
| 144 | 185 | ||
| 145 | | Function | Effect | | 186 | | Function | Effect | |