diff --git a/CLAUDE.md b/CLAUDE.md index b67a857..13a2c0b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,8 +82,13 @@ These are referenced in `foundry.toml` under `[rpc_endpoints]`. returns the deployed address - `supportedNetworks()` — returns the list of Rain-supported network names (used as foundry RPC config aliases) -- `isStartBlock(...)` / `findDeployBlock(...)` — binary search a fork's history - for the block a contract first appears at +- `isStartBlock(...)` — reads two adjacent blocks: true when the target has the + expected code hash at a block and does not have it at the block before +- `findDeployBlock(...)` — binary searches a fork's history for a block where + the target has the expected code hash and did not have it at the block before. + Either one is "the block a contract first appears at" only where that code + hash is monotone; against a target that held the hash, lost it and holds it + again both answer about an appearance neither can identify - `checkResolvedAddresses(...)` — asserts an already-deployed contract holds the addresses the deployment expected, on the currently selected fork, via consumer-supplied static reads @@ -92,8 +97,10 @@ These are referenced in `foundry.toml` under `[rpc_endpoints]`. is the only point at which such a check means anything: registry bindings are mutable, so a pre-deploy check would read a source that can change before the constructor that consumes it -- `deployToNetworks(...)` — forks each network, verifies the factory and - dependencies, deploys via Zoltu, verifies address and code hash +- `deployToNetworks(...)` — forks each network; where the expected address is + still empty it verifies the factory and dependencies on that fork, deploys via + Zoltu and checks the address; the code hash is checked on every network, + deployed here or already there - `deployAndBroadcast(...)` — the main entry point: derives the deployer from a private key, then `deployToNetworks` @@ -292,8 +299,13 @@ expected addresses, expected code hashes, and dependency lists. - **Code hash verification**: Post-deploy bytecode integrity is verified against `expectedCodeHash`. The address registry is verified the same way before it is read. -- **Dependency checking**: Before deploying to any network, all dependencies - (contract addresses) are verified to have code on-chain. +- **Dependency checking**: per network, and only where the deploy actually runs. + On a network with no code at `expectedAddress`, the Zoltu factory must have + code and match `ZOLTU_FACTORY_CODEHASH` and every dependency must have code, + all read on that network's own fork immediately before broadcasting. A network + that already has the code skips the deploy and the dependency check with it: a + contract that is already deployed does not need its dependencies present to + stay deployed. There is no all-network pre-flight. - **Idempotent deploys**: If code already exists at the expected address, deployment is skipped for that network. - **Resolve once, verify after**: registry bindings are mutable, so the diff --git a/src/lib/LibRainDeploy.sol b/src/lib/LibRainDeploy.sol index 85ab1f9..616ec9f 100644 --- a/src/lib/LibRainDeploy.sol +++ b/src/lib/LibRainDeploy.sol @@ -83,6 +83,15 @@ library LibRainDeploy { /// hash at `blockNumber` and does NOT have it at `blockNumber - 1`. At /// block 0, only the first condition is checked. The fork is restored to /// its original block number after checking. + /// + /// "First" is only true of a target whose code hash is MONOTONE: once it + /// equals `expectedCodeHash` at some block it equals it at every later + /// block. This reads two adjacent blocks and nothing else, so on a target + /// that held `expectedCodeHash`, lost it and holds it again — a pre-Cancun + /// `SELFDESTRUCT` followed by a `CREATE2` redeploy of the same code at the + /// same address, say — it answers true at EVERY block where the hash + /// reappears, not only the earliest. Monotonicity is the caller's to know: + /// two blocks cannot show it. /// @param vm The Vm instance for fork manipulation. /// @param target The contract address to check. /// @param expectedCodeHash The code hash to look for. @@ -106,8 +115,27 @@ library LibRainDeploy { /// searching the fork history. Requires an active fork with archive access /// back to `startBlock`. The fork is restored to its original block /// number before returning. The target's code hash is verified against the - /// expected value before searching. The result is validated via - /// `isStartBlock`. + /// expected value before searching. + /// + /// The search REQUIRES the target's code hash to be monotone over + /// `[startBlock, block.number]`, in the sense `isStartBlock` describes. + /// Against a target that held `expectedCodeHash`, lost it and holds it + /// again, the search converges on one of those appearances with no way to + /// say which, and the result is meaningless as the subgraph start block it + /// is typically used as. + /// + /// That is the caller's precondition because nothing here can check it, and + /// `isStartBlock` least of all. `high` is only ever a block where the code + /// hash matches: it starts at the current block, checked above, and + /// otherwise takes a `mid` the loop has just read as matching. `low` is + /// either `startBlock`, checked above as NOT matching, or one past a `mid` + /// the loop has just read as not matching. It cannot still be `startBlock` + /// when the loop ends, because that needs `high` down at `startBlock` and + /// `high` is only ever a matching block, so where they meet the hash + /// matches and did not match at the block before — `isStartBlock`'s exact + /// condition, satisfied by construction and satisfied on a non-monotone + /// history just the same. Running it on the result would read two more + /// archive blocks to agree with itself. /// @param vm The Vm instance for fork manipulation. /// @param target The contract address to search for. /// @param expectedCodeHash The expected code hash of the target contract. diff --git a/test/src/lib/LibRainDeploy.t.sol b/test/src/lib/LibRainDeploy.t.sol index b1f8bdb..ef5a42d 100644 --- a/test/src/lib/LibRainDeploy.t.sol +++ b/test/src/lib/LibRainDeploy.t.sol @@ -359,7 +359,8 @@ contract LibRainDeployTest is Test { } /// `deployZoltu` MUST deploy a contract via the Zoltu factory and return - /// the deterministic address predicted by the factory's nonce. + /// the deterministic address the factory derives with `CREATE2` over the + /// creation code under a zero salt. function testDeployZoltu() external { vm.createSelectFork(LibRainDeploy.ARBITRUM_ONE); address deployed = this.externalDeployZoltu(type(MockDeployable).creationCode);