# Audit report

> DeploymentBatcher Phase 1 finalize. Look hardest at the owner check, setMinter only for the wrapper, setTrustedAdapter, the batcher-registry setRegistry call, and the ShareOFT salt rule. Do not change vault, wrapper, or ShareOFT creation. Lottery, FriendKey, Ajna, Permit2, and the app-registry rebind are out of scope.

| | |
|---|---|
| Repository | https://github.com/wenakita/imd-batcher-phase1.git |
| Commit | `1b70ee1cf41b189c24e15d91537c61ae11c4ef74` |
| Job | `c4579349-2671-450c-b6ba-3e65d8bb27b2` |
| Judged | 2026-09-30 10:09 UTC |
| Findings | 1 low |

Four agents audited the code as it is at `1b70ee1`, each in one area (math, permissions, economics, control flow),
and a judge reproduced, merged and ranked what they found, then read the code once more itself. Nothing in the repository was changed or deployed.

## Findings

### 1. Low: A replacement Phase 1 module can wire a different registry than the batcher registry

`imd-batcher/contracts/shared/deploy/batchers/DeploymentBatcher.sol:947`

```
        IShareOFT4626(out.shareOFT).setRegistry(address(registry));
```

finalizePhase1Split executes by delegatecall, but registry is the Phase 1 module's immutable constructor value (lines 771 and 798), not the shell's independent immutable registry. setPhase1Module checks the approved runtime codehash and batcher() only (lines 2763-2767). A replacement of the supplied module built with a different registry is accepted and subsequent finalizations pass that different registry to ShareOFT, violating the requested batcher-registry wiring. This is a treasury configuration consistency defect, not an unprivileged authorization bypass; deliberate malicious module upgrades remain an intended trust assumption. Minimal fix: read the shell registry through IDeploymentBatcherRegistryAccess(batcher).registry() at this call, or reject a module whose registry() differs from address(registry) in setPhase1Module. Neither fix changes vault, wrapper, or ShareOFT creation. The two specialists' registry-mismatch reports are merged here.

**Reproduction**

Source-level reproduction against the pinned implementation: on chain 8453 construct shell B with registry R1 = address(0x1001). Construct the supplied DeploymentBatcherPhase1Module M with registry R2 = address(0x1002), batcher = B, and the same valid store, CREATE2 deployer, helpers and vault modules as B. As B.protocolTreasury(), call approvePhaseModuleCodehash(M, M.codehash), then setPhase1Module(M). The nonzero, approved-codehash and batcher checks all pass; there is no registry equality check. As Alice = address(0xA11CE), use params {creatorToken: address(0xCAFE), owner: Alice, vaultName: Creator Vault, vaultSymbol: cvTOKEN, shareName: Creator Shares, shareSymbol: sTOK, version: v1, vaultKind: Creator}, valid approved codeIds and salt override zero, and call deployPhase1CoreWithSalt followed by finalizePhase1WithSalt. Use a content-addressed bootstrap codeId so the separate bootstrap-address assumption does not interfere. At line 947 the delegatecalled module calls setRegistry(address(0x1002)) on out.shareOFT while B.registry() remains address(0x1001). Expected: reject M during wiring or call setRegistry(R1). Actual by tracing the immutable assignment, all installation guards and delegatecall: M is installed and finalize calls setRegistry(R2). Foundry execution was attempted with forge test --offline --match-contract 'DeploymentBatcher(ThreeWaySplitTest|Phase1EndpointPoisoningTest|OVaultRuntimeConfigTest)' --summary, but exited 1 before compilation because solc 0.8.30 is absent; imd-batcher/node_modules and imd-batcher/lib are also absent. This reproduction is a source trace, not a claimed executed EVM test.

---

Judge's submission `b633fcf46e09e85fd3acc27b7c153ed6662e078c6716f5d95dbae855d0c9d81c`, accepted on the IdentityMD network. Acceptance means the report met the job's checks;
it is not a guarantee that the code has no other defects.
