@bananapus/address-registry-v6 0.0.11 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/ADMINISTRATION.md CHANGED
@@ -26,6 +26,15 @@ Every function is callable by any address. There are no restricted operations.
26
26
  - **Permanent storage, no removal.** There is no `unregister` or `removeAddress` function. Entries can be overwritten but never deleted.
27
27
  - **No approval or queue.** Registrations take effect immediately in the same transaction.
28
28
 
29
+ ## Client-Side Trust
30
+
31
+ The registry stores deployer mappings without making trust judgments. All trust decisions are delegated to clients:
32
+
33
+ - **No on-chain filtering.** The contract does not distinguish "trusted" from "untrusted" deployers. Clients must maintain their own allowlist of known deployer addresses and cross-reference against `deployerOf()`.
34
+ - **Event-based discovery.** The contract emits an `AddressRegistered(address indexed addr, address indexed deployer, address caller)` event on every registration. Clients can monitor this event to discover new registrations in real time.
35
+ - **Overwrite risk.** Since registrations are overwritable, a client that caches `deployerOf(addr)` at time T may see a different result at time T+1 if someone re-registers the same address with a different deployer/nonce or deployer/salt/bytecode combination. Clients should re-check at time of use rather than caching indefinitely.
36
+ - **No validation of deployment.** The registry computes the expected address from the inputs but does not verify that a contract actually exists at that address. A registration can be created for an address that has not yet been deployed (or will never be deployed).
37
+
29
38
  ## Admin Boundaries
30
39
 
31
40
  There are no admins. Specifically:
package/ARCHITECTURE.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  ## Purpose
4
4
 
5
- Deployer verification registry for Juicebox V6. Allows contracts deployed via `create` or `create2` to publicly register their deployer's address. Frontend clients use this to verify that hooks and other contracts were deployed by trusted deployers.
5
+ Juicebox projects can attach arbitrary hook contracts (pay hooks, cashout hooks, 721 tier hooks, etc.) that execute during payments, cashouts, and payouts. A malicious hook could steal funds or mislead users. Frontends need a way to answer the question: "Was this hook deployed by a trusted deployer like `JB721TiersHookDeployer`?"
6
+
7
+ `JBAddressRegistry` solves this by letting contracts deployed via `create` or `create2` publicly register their deployer's address. A frontend can then call `deployerOf(hookAddress)` and check the result against its own list of trusted deployers — displaying warnings or blocking interactions for hooks with unknown origins.
6
8
 
7
9
  ## Contract Map
8
10
 
@@ -38,6 +40,16 @@ Frontend → JBAddressRegistry.deployerOf(hookAddress)
38
40
  → Frontend checks deployer against trusted deployer list
39
41
  ```
40
42
 
43
+ ## Design Decisions
44
+
45
+ **Deployer verification, not a whitelist.** The registry records *who* deployed a contract, not *whether* a contract is approved. This keeps the registry permissionless and neutral — any deployer can register, and trust decisions are made by each frontend independently. There is no governance or admin role.
46
+
47
+ **Both `create` and `create2` support.** Deployers that use `create` (nonce-based) and `create2` (salt + bytecode) both exist in the Juicebox ecosystem. Supporting both ensures any deployer can register its contracts regardless of deployment strategy.
48
+
49
+ **No validation beyond hash match.** The registry does not check that registered addresses contain code, implement a particular interface, or were recently deployed. It only verifies that the provided deployer + nonce/salt/bytecode deterministically produce the claimed address. This keeps the contract simple and gas-efficient — frontends already perform their own trust checks on the deployer address.
50
+
51
+ **Anyone can call `registerAddress`.** Registration is not restricted to the deployer itself. Any account that knows the deployer address and nonce (or salt + bytecode) can register a contract. This is safe because the mapping is deterministic — providing incorrect inputs simply computes a different address, not a false registration for the target contract.
52
+
41
53
  ## Dependencies
42
54
 
43
55
  - `@sphinx-labs/plugins` — Deployment tooling (devDependency only)
@@ -2,6 +2,31 @@
2
2
 
3
3
  You are auditing a permissionless address registry for Juicebox V6. The contract stores a mapping from deployed contract addresses to their deployers, computed deterministically from `create` or `create2` parameters. It has no owner, no access control, no constructor arguments, and no external dependencies. Read [RISKS.md](./RISKS.md) first -- it documents all known risks and trust assumptions. Then come back here.
4
4
 
5
+ ## Compiler and Version Info
6
+
7
+ | Setting | Value |
8
+ |---------|-------|
9
+ | Solidity version | ^0.8.26 |
10
+ | EVM target | cancun |
11
+ | Optimizer | enabled, 200 runs |
12
+ | via-IR | not enabled |
13
+ | Fuzz runs | 4,096 |
14
+ | Invariant runs | 1,024 (depth 100) |
15
+
16
+ Source: [`foundry.toml`](./foundry.toml)
17
+
18
+ ## Previous Audit Findings
19
+
20
+ A Nemesis automated audit was conducted on 2026-03-17. Results are in [`.audit/findings/nemesis-verified.md`](./.audit/findings/nemesis-verified.md). Summary:
21
+
22
+ | ID | Severity | Title | Status |
23
+ |----|----------|-------|--------|
24
+ | NM-001 | LOW | Deployment library project name mismatch (`"nana-address-registry"` vs `"nana-address-registry-v6"`) | Open (deployment script only, no runtime impact) |
25
+
26
+ The core contract (`JBAddressRegistry`) was verified sound -- RLP encoding is correct across all 10 nonce ranges, CREATE2 computation matches EIP-1014. No CRITICAL, HIGH, or MEDIUM findings were identified.
27
+
28
+ No prior formal audit with finding IDs from an external security firm has been conducted.
29
+
5
30
  ## Scope
6
31
 
7
32
  **In scope -- all Solidity in `src/`:**
package/CHANGE_LOG.md CHANGED
@@ -2,11 +2,17 @@
2
2
 
3
3
  This document describes all changes between `nana-address-registry` (v5) and `nana-address-registry-v6` (v6).
4
4
 
5
+ ## Summary
6
+
7
+ - **Nonce range extended from `uint32` to `uint64`**: Fixes silent address miscalculation for large nonces — previously truncated without error, now correctly RLP-encodes up to `uint64` and reverts above.
8
+ - **New `NonceTooLarge` error**: Explicit revert replaces silent truncation for nonces exceeding `uint64.max`.
9
+ - **No ABI-breaking changes**: Interface and function signatures are identical — only the internal nonce encoding logic changed.
10
+
5
11
  ---
6
12
 
7
13
  ## 1. Breaking Changes
8
14
 
9
- - **Solidity version bump**: `0.8.23` → `0.8.26`. Contracts compiled against v5 ABIs will still be compatible (no ABI-level breaking changes), but the compiler version requirement has changed.
15
+ - **Solidity version bump**: `0.8.23` → `^0.8.26`. Contracts compiled against v5 ABIs will still be compatible (no ABI-level breaking changes), but the compiler version requirement has changed.
10
16
  - **Nonce range extended from `uint32` to `uint64`**: In v5, the `_addressFrom` function silently produced incorrect addresses for nonces at or above `2^32`. In v6, nonces up to `uint64.max` are correctly RLP-encoded, and nonces above `uint64.max` revert with `JBAddressRegistry_NonceTooLarge`. Any off-chain tooling that assumed the `uint32` ceiling must be updated.
11
17
 
12
18
  ## 2. New Features
@@ -81,7 +87,7 @@ No function signatures, parameter types, or return types changed.
81
87
  | v5 | v6 | Action Required |
82
88
  |---|---|---|
83
89
  | `IJBAddressRegistry` | `IJBAddressRegistry` | **None** — ABI-identical. Update import path only. |
84
- | `JBAddressRegistry` | `JBAddressRegistry` | **None** — ABI-compatible. Deploy new instance compiled with Solidity 0.8.26. |
90
+ | `JBAddressRegistry` | `JBAddressRegistry` | **None** — ABI-compatible. Deploy new instance compiled with Solidity ^0.8.26. |
85
91
  | `registerAddress(address, uint256)` | `registerAddress(address, uint256)` | **None** — signature unchanged. Nonces > `uint32` now work correctly; nonces > `uint64` now revert instead of silently producing wrong addresses. |
86
92
  | `registerAddress(address, bytes32, bytes)` | `registerAddress(address, bytes32, bytes)` | **None** — signature unchanged. |
87
93
  | `deployerOf(address)` | `deployerOf(address)` | **None** — signature unchanged. |
package/README.md CHANGED
@@ -46,6 +46,17 @@ In both cases, the registry stores the mapping `deployerOf[computedAddress] = de
46
46
 
47
47
  No access control is needed -- only the correct deployer + parameters can produce a given address, so registrations cannot be faked.
48
48
 
49
+ ```solidity
50
+ // Register a contract deployed via create.
51
+ registry.registerAddress(deployer, nonce);
52
+
53
+ // Register a contract deployed via create2.
54
+ registry.registerAddress(deployer, salt, bytecode);
55
+
56
+ // Look up who deployed a contract.
57
+ address deployer = registry.deployerOf(contractAddress);
58
+ ```
59
+
49
60
  ### Events
50
61
 
51
62
  | Event | Fields | Emitted When |
@@ -61,7 +72,7 @@ Deployers can be exploited or act maliciously. Clients should still communicate
61
72
  ### Limitations
62
73
 
63
74
  - The `_addressFrom` function for `create` addresses uses RLP encoding that only supports nonces up to `uint64` max (18,446,744,073,709,551,615). Higher nonces revert with `JBAddressRegistry_NonceTooLarge`. In practice, this limit is unreachable.
64
- - No overwrite protection: registering the same computed address again overwrites the previous deployer. This is safe because only the correct deployer + parameters produce a given address -- a second call with the same inputs just re-registers the same deployer.
75
+ - Re-registration is prevented: registering the same computed address again reverts with `JBAddressRegistry_AlreadyRegistered`. Only the first registration is accepted.
65
76
  - Registration is permissionless -- anyone can call `registerAddress`, not just the deployer. Security relies entirely on deterministic address computation.
66
77
 
67
78
  ## Deployment
package/RISKS.md CHANGED
@@ -8,11 +8,18 @@
8
8
  ## 2. Known Risks
9
9
 
10
10
  - **False registration.** Anyone can call `registerAddress` with arbitrary deployer/nonce values, registering a computed address with a deployer that did not actually deploy it. Consumers must verify the deployer is trusted, not just that a mapping exists.
11
- - **Overwrite of existing entries.** There is no check preventing re-registration. A second call with different parameters that computes the same address will overwrite the previous `deployerOf` entry.
12
- - **No removal mechanism.** Once registered, an entry cannot be removed, only overwritten.
13
- - **RLP encoding correctness.** The `_addressFrom` function manually implements RLP encoding for nonces up to uint64. Well-tested pattern; nonce capped at uint64 max with explicit revert.
11
+ - **No removal mechanism.** Once registered, an entry cannot be removed. Re-registration of the same address reverts with `JBAddressRegistry_AlreadyRegistered`.
12
+ - **CREATE2 vs CREATE1 trust model.** CREATE1 registrations (`registerAddress(deployer, nonce)`) can be spoofed: anyone who knows the deployer address and nonce can register the computed address without being the deployer. CREATE2 registrations (`registerAddress(deployer, salt, bytecode)`) have the same trust model — knowing the parameters is sufficient to register. The function accepts raw deployment bytecode and hashes it internally (`keccak256(bytecode)`) for the CREATE2 address computation. In both cases, the registry only proves "this address COULD have been deployed by this deployer with these parameters", not "this deployer DID deploy this address". Consumers must verify the deployer is trusted independently.
13
+ - **Frontend trust example.** A malicious actor could register `deployerOf[uniswapRouter] = attackerDeployer` if they find a `(deployer, nonce)` or `(deployer, salt, bytecode)` combination that computes to the Uniswap router address and register it before the legitimate deployer does. Only the first registration is accepted; subsequent attempts revert with `JBAddressRegistry_AlreadyRegistered`. Frontends that display "deployed by X" based on `deployerOf` should cross-reference against a curated allowlist of trusted deployers.
14
14
 
15
15
  ## 3. Invariants to Verify
16
16
 
17
17
  - `deployerOf[addr]` always corresponds to a valid deployer/nonce pair that produces `addr` (if registered via `registerAddress`).
18
- - `create2` registrations: `deployerOf[addr]` corresponds to a valid deployer/salt/bytecodeHash that produces `addr`.
18
+ - `create2` registrations: `deployerOf[addr]` corresponds to a valid deployer/salt/bytecode combination whose `keccak256(bytecode)` produces `addr` via the CREATE2 formula.
19
+ - Registration idempotency: `deployerOf[addr]` reflects the first and only registration. Subsequent attempts to register the same address revert with `JBAddressRegistry_AlreadyRegistered`.
20
+
21
+ ## 4. Accepted Behaviors
22
+
23
+ ### 4.1 RLP encoding correctness is well-tested and bounded
24
+
25
+ The `_addressFrom` function manually implements RLP encoding for nonces up to uint64. The nonce is capped at uint64 max with an explicit revert, and the encoding logic is a well-tested pattern. This is not an open risk.
package/SKILLS.md CHANGED
@@ -41,6 +41,14 @@ Deployed on: Ethereum, Optimism, Arbitrum, Base, and their Sepolia testnets.
41
41
  | `_addressFrom(address origin, uint256 nonce) returns (address)` | Computes `create` address using RLP encoding. Handles 10 nonce ranges: `0`, `1-0x7f`, `0x80-0xff`, `0x100-0xffff`, `0x10000-0xffffff`, `0x1000000-0xffffffff`, `0x100000000-0xffffffffff`, `0x10000000000-0xffffffffffff`, `0x1000000000000-0xffffffffffffff`, `0x100000000000000-0xffffffffffffffff`. Reverts with `JBAddressRegistry_NonceTooLarge` for nonces above `uint64` max. Uses `keccak256` of the RLP-encoded `[origin, nonce]` and extracts the low 160 bits via assembly. |
42
42
  | `_registerAddress(address addr, address deployer)` | Writes `deployerOf[addr] = deployer` and emits `AddressRegistered`. Shared by both public `registerAddress` overloads. |
43
43
 
44
+ ## Errors
45
+
46
+ | Error | Defined In | Trigger Condition |
47
+ |-------|-----------|-------------------|
48
+ | `JBAddressRegistry_NonceTooLarge(uint256 nonce)` | `JBAddressRegistry` | `registerAddress(deployer, nonce)` is called with a `nonce` greater than `type(uint64).max` (18,446,744,073,709,551,615). Reverts inside `_addressFrom` before any state change. In practice unreachable since no EOA or contract can reach this nonce. |
49
+
50
+ This is the only custom error in the contract. The `create2` overload of `registerAddress` has no revert paths (invalid parameters silently compute the wrong address).
51
+
44
52
  ## Integration Points
45
53
 
46
54
  | Dependency | Import | Used For |
@@ -83,11 +91,11 @@ No arrays, no structs, no linked lists. One mapping, that is all.
83
91
  - **Nonce limit**: `_addressFrom` supports nonces up to `uint64` max (18,446,744,073,709,551,615). Nonces above this revert with `JBAddressRegistry_NonceTooLarge`. In practice this limit is unreachable.
84
92
  - **No overwrite protection**: Calling `registerAddress` with parameters that compute to an already-registered address will overwrite the `deployerOf` entry. This is safe because only the correct deployer + parameters produce a given address. But be aware that the same address can be "re-registered" by anyone at any time (with the same result).
85
93
  - **Permissionless**: Anyone can call `registerAddress`, not just the deployer. `msg.sender` is recorded in the event as `caller` but is NOT stored in the mapping. Only the computed deployer is stored.
86
- - **No validation**: The registry does not check that `addr` has code deployed, or that the deployer is a real deployer. It purely does math and stores the result. If you pass wrong parameters, it silently registers a mapping for the wrong address.
94
+ - **No validation**: The registry does not check that `addr` has code deployed, or that the deployer is a real deployer. It purely does math and stores the result. If you pass wrong parameters, it silently registers a mapping for the wrong address. Frontends MUST verify `addr.code.length > 0` (or `extcodesize(addr) > 0` in assembly) before trusting a registry entry.
87
95
  - **`deployerOf` returns `address(0)` for unregistered addresses**: This is the default mapping value, not a sentinel. There is no way to distinguish "never registered" from "registered with deployer = address(0)" (though the latter would require someone to call `registerAddress(address(0), ...)` deliberately).
88
96
  - **`create2` bytecode must include constructor args**: When registering a `create2` deployment, the `bytecode` parameter must be the full creation bytecode including ABI-encoded constructor arguments: `abi.encodePacked(type(Contract).creationCode, abi.encode(arg1, arg2, ...))`. Omitting constructor args will compute the wrong address.
89
97
  - **Contract nonces start at 1**: When using the `create` overload to register a contract deployed by another contract, remember that contract nonces start at 1 (not 0 like EOAs). The first contract deployed by a factory is at nonce 1.
90
- - **Solidity version**: The implementation uses `pragma solidity 0.8.26` (exact). The interface uses `pragma solidity ^0.8.0` (flexible) so it can be imported by any 0.8.x consumer.
98
+ - **Solidity version**: The implementation uses `pragma solidity ^0.8.26`. The interface uses `pragma solidity ^0.8.0` (flexible) so it can be imported by any 0.8.x consumer.
91
99
 
92
100
  ## Example: Register a `create` Deployment
93
101
 
package/STYLE_GUIDE.md CHANGED
@@ -21,7 +21,7 @@ One contract/interface/struct/enum per file. Name the file after the type it con
21
21
 
22
22
  ```solidity
23
23
  // Contracts — pin to exact version
24
- pragma solidity 0.8.26;
24
+ pragma solidity ^0.8.26;
25
25
 
26
26
  // Interfaces, structs, enums — caret for forward compatibility
27
27
  pragma solidity ^0.8.0;
package/USER_JOURNEYS.md CHANGED
@@ -1,144 +1,97 @@
1
1
  # User Journeys -- nana-address-registry-v6
2
2
 
3
- Concrete end-to-end flows through the address registry. Each journey traces the exact function calls, state changes, and external interactions.
3
+ All user paths through the Juicebox V6 address registry. For each journey: entry point, key parameters, state changes, events, and edge cases.
4
4
 
5
- ## Journey 1: Register a Contract Deployed via create
6
-
7
- **Actor:** Anyone (the deployer, the deployed contract itself, or a third party).
8
- **Goal:** Record the deployer of a contract that was deployed using the `create` opcode (standard deployment).
9
-
10
- ### Precondition
11
-
12
- A contract exists on-chain at the address that `create` would produce from `(deployer, nonce)`. The caller knows the deployer's address and the nonce used during deployment.
13
-
14
- ### Steps
15
-
16
- 1. **Caller invokes `registerAddress(address deployer, uint256 nonce)`**
17
-
18
- - The `nonce` must be `<= type(uint64).max` or the call reverts with `JBAddressRegistry_NonceTooLarge(nonce)`
19
- - The contract calls `_addressFrom(deployer, nonce)` internally
20
-
21
- 2. **`_addressFrom` computes the create address via RLP encoding**
5
+ ---
22
6
 
23
- - Selects the correct RLP encoding branch based on the nonce range (10 branches: `0`, `1-0x7f`, `0x80-0xff`, ..., up to `uint64` max)
24
- - Builds the RLP-encoded byte sequence: `[list_prefix, 0x94, deployer_20_bytes, nonce_encoding]`
25
- - Hashes the sequence with `keccak256`
26
- - Extracts the low 160 bits as the computed address via inline assembly
7
+ ## 1. Register a Contract Deployed via `create`
27
8
 
28
- 3. **`_registerAddress` stores the mapping and emits the event**
9
+ **Entry point**: `JBAddressRegistry.registerAddress(address deployer, uint256 nonce)`
29
10
 
30
- - Sets `deployerOf[computedAddress] = deployer`
31
- - Emits `AddressRegistered(addr: computedAddress, deployer: deployer, caller: msg.sender)`
11
+ **Who can call**: Anyone. No access control -- any account can register any `(deployer, nonce)` pair. The caller does not need to be the deployer.
32
12
 
33
- ### Result
13
+ **Parameters**:
14
+ - `deployer` -- Address of the account that deployed the contract via `create`
15
+ - `nonce` -- The nonce the deployer's account had when deploying the contract (must be `<= type(uint64).max`)
34
16
 
35
- `deployerOf[computedAddress]` now returns `deployer`. Anyone querying the registry for that address can verify who deployed it.
17
+ **State changes**:
18
+ 1. Computes the deterministic `create` address via RLP encoding of `[deployer, nonce]` -- selects one of 10 encoding branches based on nonce range (`0`, `1..0x7f`, `0x80..0xff`, ..., up to `uint64` max)
19
+ 2. `JBAddressRegistry.deployerOf[computedAddress] = deployer` -- stores the deployer mapping
36
20
 
37
- ### What to verify
21
+ **Events**: `AddressRegistered(addr, deployer, caller)` -- where `addr` is the computed `create` address, `deployer` is the registered deployer, and `caller` is `msg.sender`. Both `addr` and `deployer` are `indexed`.
38
22
 
39
- - The computed address matches the actual on-chain contract address for the given `(deployer, nonce)` pair.
23
+ **Edge cases**:
24
+ - `nonce > type(uint64).max` reverts with `JBAddressRegistry_NonceTooLarge(nonce)`
40
25
  - If the caller provides incorrect parameters (wrong nonce, wrong deployer), a mapping is still created -- but for a different (likely nonexistent) address. The registry does not verify that code exists at the computed address.
41
- - Calling `registerAddress` again with the same parameters is idempotent (same result, no revert).
26
+ - Calling `registerAddress` again with the same parameters is idempotent (same mapping value, same event, no revert).
42
27
  - The `caller` in the event is `msg.sender`, which may differ from `deployer`.
43
28
 
44
29
  ---
45
30
 
46
- ## Journey 2: Register a Contract Deployed via create2
47
-
48
- **Actor:** Anyone (the deployer, the deployed contract itself, or a third party).
49
- **Goal:** Record the deployer of a contract that was deployed using the `create2` opcode (deterministic deployment).
50
-
51
- ### Precondition
52
-
53
- A contract exists on-chain at the address that `create2` would produce from `(deployer, salt, bytecode)`. The caller knows the deployer's address, the salt, and the full creation bytecode (including constructor arguments).
54
-
55
- ### Steps
56
-
57
- 1. **Caller invokes `registerAddress(address deployer, bytes32 salt, bytes calldata bytecode)`**
31
+ ## 2. Register a Contract Deployed via `create2`
58
32
 
59
- - No parameter validation beyond the address computation itself
60
- - `bytecode` must include ABI-encoded constructor arguments appended to the creation code
33
+ **Entry point**: `JBAddressRegistry.registerAddress(address deployer, bytes32 salt, bytes calldata bytecode)`
61
34
 
62
- 2. **The contract computes the create2 address inline**
35
+ **Who can call**: Anyone. No access control -- any account can register any `(deployer, salt, bytecode)` tuple. The caller does not need to be the deployer.
63
36
 
64
- - `address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, keccak256(bytecode))))))`
65
- - This matches the EIP-1014 specification
37
+ **Parameters**:
38
+ - `deployer` -- Address of the account or factory contract that deployed the contract via `create2`
39
+ - `salt` -- The `create2` salt used during deployment
40
+ - `bytecode` -- The full creation bytecode including ABI-encoded constructor arguments (not runtime bytecode)
66
41
 
67
- 3. **`_registerAddress` stores the mapping and emits the event**
42
+ **State changes**:
43
+ 1. Computes the deterministic `create2` address: `address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, keccak256(bytecode))))))`
44
+ 2. `JBAddressRegistry.deployerOf[computedAddress] = deployer` -- stores the deployer mapping
68
45
 
69
- - Sets `deployerOf[computedAddress] = deployer`
70
- - Emits `AddressRegistered(addr: computedAddress, deployer: deployer, caller: msg.sender)`
71
-
72
- ### Result
73
-
74
- `deployerOf[computedAddress]` now returns `deployer`. Frontends and other contracts can look up the deployer of any `create2`-deployed contract.
75
-
76
- ### What to verify
46
+ **Events**: `AddressRegistered(addr, deployer, caller)` -- where `addr` is the computed `create2` address, `deployer` is the registered deployer, and `caller` is `msg.sender`. Both `addr` and `deployer` are `indexed`.
77
47
 
48
+ **Edge cases**:
78
49
  - The `bytecode` parameter must be the full creation bytecode, not the deployed (runtime) bytecode. Omitting constructor arguments produces a different hash and therefore a different address.
79
- - For large bytecode payloads, `keccak256(bytecode)` is computed in memory. The gas cost scales with bytecode size, but the registry itself has no gas limit issues -- the caller pays.
50
+ - For large bytecode payloads, `keccak256(bytecode)` is computed in memory. Gas cost scales with bytecode size, but the registry itself has no gas limit issues -- the caller pays.
80
51
  - The salt is deployer-specific. Different deployers with the same salt and bytecode produce different addresses (because the deployer address is part of the hash input).
52
+ - No parameter validation beyond the address computation itself. No revert conditions exist for this overload.
81
53
 
82
54
  ---
83
55
 
84
- ## Journey 3: Frontend Verifies a Hook's Deployer
85
-
86
- **Actor:** Frontend application or off-chain service.
87
- **Goal:** Determine whether a Juicebox hook was deployed by a trusted factory.
88
-
89
- ### Precondition
90
-
91
- The hook contract has been registered in the registry (via Journey 1 or 2). The frontend maintains a list of trusted deployer addresses.
92
-
93
- ### Steps
56
+ ## 3. Frontend Verifies a Hook's Deployer
94
57
 
95
- 1. **Frontend reads `deployerOf(hookAddress)`**
58
+ **Entry point**: `JBAddressRegistry.deployerOf(address addr)` (public mapping getter -- `view`)
96
59
 
97
- - This is a simple public mapping getter -- a `staticcall` with no state changes
98
- - Returns `address(0)` if the hook has never been registered
60
+ **Who can call**: Anyone. This is a `view` function (auto-generated mapping getter) with no access control or state changes.
99
61
 
100
- 2. **Frontend evaluates the result**
62
+ **Parameters**:
63
+ - `addr` -- The address of the contract to look up
101
64
 
102
- - If `deployer == address(0)`: the hook is unregistered. The frontend should treat it as untrusted.
103
- - If `deployer` is in the trusted deployer set: the hook was deployed by a known, audited factory. The frontend can display it with confidence.
104
- - If `deployer` is a non-zero address not in the trusted set: the hook is registered but was deployed by an unknown deployer. The frontend should flag it as unverified.
65
+ **State changes**: None. Read-only query.
105
66
 
106
- ### Result
67
+ **Events**: None. No state mutation occurs.
107
68
 
108
- The frontend can make a trust decision about the hook without needing to trace the deployment transaction on-chain.
69
+ **Result**: Returns the registered deployer address, or `address(0)` if the address has never been registered. The frontend evaluates:
70
+ - `deployer == address(0)` -- the hook is unregistered; treat as untrusted
71
+ - `deployer` is in the trusted deployer set -- the hook was deployed by a known, audited factory; display with confidence
72
+ - `deployer` is a non-zero address not in the trusted set -- registered but deployed by an unknown deployer; flag as unverified
109
73
 
110
- ### What to verify
111
-
112
- - `deployerOf` returns `address(0)` for any address that has never been registered. There is no way to distinguish "never registered" from "registered with `deployer = address(0)`", though the latter requires deliberately passing `address(0)` as the deployer.
74
+ **Edge cases**:
75
+ - `deployerOf` returns `address(0)` for any address never registered. There is no way to distinguish "never registered" from "registered with `deployer = address(0)`", though the latter requires deliberately passing `address(0)` as the deployer.
113
76
  - The registry provides no guarantee that code exists at the registered address. A deployer could register an address for a contract that has been self-destructed or was never deployed.
114
77
  - The registry provides no guarantee that the registered deployer is the *actual* deployer. It only guarantees that the deterministic computation of `(deployer, nonce)` or `(deployer, salt, bytecode)` produces that address. Since the EVM's address derivation is deterministic, this is equivalent -- but the registry itself does not verify the deployment transaction.
115
78
 
116
79
  ---
117
80
 
118
- ## Journey 4: Overwrite a Previous Registration
119
-
120
- **Actor:** Anyone.
121
- **Goal:** Re-register an address with the same or different parameters.
122
-
123
- ### Precondition
124
-
125
- `deployerOf[addr]` already has a non-zero value from a previous registration.
126
-
127
- ### Steps
128
-
129
- 1. **Caller invokes either `registerAddress` overload with parameters that compute to `addr`**
81
+ ## 4. Overwrite a Previous Registration
130
82
 
131
- 2. **`_registerAddress` overwrites the existing value**
83
+ **Entry point**: `JBAddressRegistry.registerAddress(address deployer, uint256 nonce)` or `JBAddressRegistry.registerAddress(address deployer, bytes32 salt, bytes calldata bytecode)`
132
84
 
133
- - `deployerOf[addr] = deployer` (unconditional write, no check for existing value)
134
- - Emits a new `AddressRegistered` event
85
+ **Who can call**: Anyone. There is no access control on overwrites. No owner check or deployer verification is performed.
135
86
 
136
- ### Result
87
+ **Parameters**: Same as Journey 1 or Journey 2, depending on which `registerAddress` overload is called. The provided parameters must compute to the same target address as the previous registration.
137
88
 
138
- The previous deployer mapping is overwritten. The event log contains both the old and new registrations.
89
+ **State changes**:
90
+ 1. `JBAddressRegistry.deployerOf[addr] = deployer` -- unconditional `SSTORE`, overwrites any existing value without checking the previous deployer
139
91
 
140
- ### What to verify
92
+ **Events**: `AddressRegistered(addr, deployer, caller)` -- emitted on every call, even if the stored value is unchanged.
141
93
 
94
+ **Edge cases**:
142
95
  - Overwriting is safe because the same address can only be computed from the same `(deployer, nonce)` or `(deployer, salt, bytecode)` parameters (assuming no keccak256 collision). Re-registering with the same parameters produces the same result.
143
96
  - There is no way to "unregister" an address. Setting `deployerOf[addr] = address(0)` would require computing a `(deployer, nonce)` pair that produces `addr` with `deployer = address(0)`, which is theoretically possible but practically useless.
144
97
  - The event log preserves the full history of registrations. Off-chain indexers can detect overwrites by tracking multiple `AddressRegistered` events for the same `addr`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bananapus/address-registry-v6",
3
- "version": "0.0.11",
3
+ "version": "0.0.14",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  import {stdJson} from "forge-std/Script.sol";
5
5
  import {Vm} from "forge-std/Vm.sol";
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: MIT
2
- pragma solidity 0.8.26;
2
+ pragma solidity ^0.8.26;
3
3
 
4
4
  import {IJBAddressRegistry} from "./interfaces/IJBAddressRegistry.sol";
5
5
 
@@ -16,6 +16,10 @@ contract JBAddressRegistry is IJBAddressRegistry {
16
16
  // --------------------------- custom errors ------------------------- //
17
17
  //*********************************************************************//
18
18
 
19
+ /// @notice Thrown when attempting to register an address that has already been registered.
20
+ /// @param addr The address that is already registered.
21
+ error JBAddressRegistry_AlreadyRegistered(address addr);
22
+
19
23
  /// @notice Thrown when a nonce exceeds the maximum value supported by the RLP encoding (uint64 max).
20
24
  error JBAddressRegistry_NonceTooLarge(uint256 nonce);
21
25
 
@@ -120,6 +124,8 @@ contract JBAddressRegistry is IJBAddressRegistry {
120
124
  /// @param addr The deployed contract's address.
121
125
  /// @param deployer The deployer's address.
122
126
  function _registerAddress(address addr, address deployer) internal {
127
+ if (deployerOf[addr] != address(0)) revert JBAddressRegistry_AlreadyRegistered(addr);
128
+
123
129
  deployerOf[addr] = deployer;
124
130
 
125
131
  emit AddressRegistered({addr: addr, deployer: deployer, caller: msg.sender});
@@ -158,30 +158,43 @@ contract JBAddressRegistryEdge is Test {
158
158
  }
159
159
 
160
160
  // =========================================================================
161
- // Duplicate registration - second registration overwrites first
161
+ // Duplicate registration - second registration reverts
162
162
  // =========================================================================
163
163
 
164
- /// @notice Registering the same computed address twice overwrites the deployer.
165
- function test_duplicateRegistration_overwrites() public {
164
+ /// @notice Registering the same computed address twice reverts with AlreadyRegistered.
165
+ function test_duplicateRegistration_reverts() public {
166
166
  uint64 nonce = 1;
167
167
 
168
168
  vm.setNonce(deployer, nonce);
169
169
  vm.prank(deployer);
170
170
  address deployed = address(new MockDeployment());
171
171
 
172
- // First registration.
172
+ // First registration succeeds.
173
173
  registry.registerAddress(deployer, nonce);
174
174
  assertEq(registry.deployerOf(deployed), deployer);
175
175
 
176
- // Register again with a different deployer (same computed address via different params).
177
- // Actually, same deployer + nonce = same address. Let's just call register again.
178
- address fakeDeployer = makeAddr("fake");
179
- registry.registerAddress(fakeDeployer, nonce);
176
+ // Second registration of the same address reverts.
177
+ vm.expectRevert(
178
+ abi.encodeWithSelector(JBAddressRegistry.JBAddressRegistry_AlreadyRegistered.selector, deployed)
179
+ );
180
+ registry.registerAddress(deployer, nonce);
181
+ }
180
182
 
181
- // The real deployed address still has the correct deployer.
182
- // But the address computed from (fakeDeployer, nonce) is a DIFFERENT address.
183
- // So deployed's mapping is unchanged.
184
- assertEq(registry.deployerOf(deployed), deployer, "Original registration should be unchanged");
183
+ /// @notice Registering the same create2 address twice reverts with AlreadyRegistered.
184
+ function test_duplicateRegistration_create2_reverts() public {
185
+ Factory factory = new Factory();
186
+ bytes32 salt = bytes32(uint256(42));
187
+ address deployed = factory.deploy(salt);
188
+
189
+ // First registration succeeds.
190
+ registry.registerAddress(address(factory), salt, type(MockDeployment).creationCode);
191
+ assertEq(registry.deployerOf(deployed), address(factory));
192
+
193
+ // Second registration of the same address reverts.
194
+ vm.expectRevert(
195
+ abi.encodeWithSelector(JBAddressRegistry.JBAddressRegistry_AlreadyRegistered.selector, deployed)
196
+ );
197
+ registry.registerAddress(address(factory), salt, type(MockDeployment).creationCode);
185
198
  }
186
199
 
187
200
  // =========================================================================