@bananapus/address-registry-v6 0.0.8 → 0.0.10

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.
@@ -0,0 +1,122 @@
1
+ # Audit Instructions -- nana-address-registry-v6
2
+
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
+
5
+ ## Scope
6
+
7
+ **In scope -- all Solidity in `src/`:**
8
+ ```
9
+ src/JBAddressRegistry.sol # Registry implementation (~127 lines)
10
+ src/interfaces/IJBAddressRegistry.sol # Interface (~27 lines)
11
+ ```
12
+
13
+ **Out of scope:** Test files, deployment scripts, forge-std.
14
+
15
+ ## Architecture
16
+
17
+ ### JBAddressRegistry
18
+
19
+ A standalone, stateless-logic contract with a single storage mapping:
20
+
21
+ ```
22
+ mapping(address addr => address deployer) public deployerOf;
23
+ ```
24
+
25
+ Two public `registerAddress` overloads accept deployer parameters, compute the deterministic address, and store the mapping. No validation is performed beyond address computation and a nonce range check.
26
+
27
+ ### Registration (create)
28
+
29
+ `registerAddress(address deployer, uint256 nonce)`:
30
+ 1. Calls `_addressFrom(deployer, nonce)` to compute the `create` address via RLP encoding
31
+ 2. Stores `deployerOf[computedAddress] = deployer`
32
+ 3. Emits `AddressRegistered(computedAddress, deployer, msg.sender)`
33
+
34
+ ### Registration (create2)
35
+
36
+ `registerAddress(address deployer, bytes32 salt, bytes calldata bytecode)`:
37
+ 1. Computes `address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, keccak256(bytecode))))))`
38
+ 2. Stores `deployerOf[computedAddress] = deployer`
39
+ 3. Emits `AddressRegistered(computedAddress, deployer, msg.sender)`
40
+
41
+ ### RLP Encoding (_addressFrom)
42
+
43
+ The internal `_addressFrom(address origin, uint256 nonce)` function implements RLP encoding of `[origin, nonce]` for `create` address computation. It handles 10 nonce ranges covering `0` through `type(uint64).max`:
44
+
45
+ | Nonce Range | RLP Prefix Byte | Nonce Length Prefix |
46
+ |-------------|----------------|---------------------|
47
+ | `0` | `0xd6` | `0x80` (empty byte) |
48
+ | `1 - 0x7f` | `0xd6` | (none, raw byte) |
49
+ | `0x80 - 0xff` | `0xd7` | `0x81` |
50
+ | `0x100 - 0xffff` | `0xd8` | `0x82` |
51
+ | `0x10000 - 0xffffff` | `0xd9` | `0x83` |
52
+ | `0x1000000 - 0xffffffff` | `0xda` | `0x84` |
53
+ | `0x100000000 - 0xffffffffff` | `0xdb` | `0x85` |
54
+ | `0x10000000000 - 0xffffffffffff` | `0xdc` | `0x86` |
55
+ | `0x1000000000000 - 0xffffffffffffff` | `0xdd` | `0x87` |
56
+ | `0x100000000000000 - 0xffffffffffffffff` | `0xde` | `0x88` |
57
+
58
+ The final address is extracted from `keccak256(rlp_data)` via inline assembly: `mstore(0, hash); addr := mload(0)`.
59
+
60
+ Nonces above `type(uint64).max` revert with `JBAddressRegistry_NonceTooLarge`.
61
+
62
+ ## Priority Audit Areas
63
+
64
+ ### 1. RLP Encoding Correctness (Highest Priority)
65
+
66
+ The `_addressFrom` function is the only non-trivial logic in the contract. Verify:
67
+
68
+ - **Every nonce range boundary is correct.** The if/else chain must produce correct RLP for every boundary value: `0`, `1`, `0x7f`, `0x80`, `0xff`, `0x100`, `0xffff`, `0x10000`, etc., up to `type(uint64).max`. An off-by-one at any boundary would silently produce wrong addresses.
69
+ - **RLP prefix bytes are correct.** The first byte (`0xd6`-`0xde`) encodes the total length of the list. Verify each prefix matches the actual encoded data length (20-byte address + nonce encoding + overhead).
70
+ - **Nonce encoding is correct.** For nonce `0`, the RLP encoding is `0x80` (empty byte string), not `0x00`. For nonces `1-0x7f`, the nonce IS the RLP encoding (single byte). For `0x80+`, a length prefix is prepended.
71
+ - **Assembly address extraction.** The final `mstore(0, hash); addr := mload(0)` extracts the low 160 bits of the keccak256 hash. Verify this is equivalent to `address(uint160(uint256(hash)))`.
72
+ - **Comparison with reference implementations.** Cross-check against the Ethereum Yellow Paper, OpenZeppelin's `Create2` library, and the linked StackExchange reference (https://ethereum.stackexchange.com/a/87840/68134).
73
+
74
+ ### 2. create2 Address Computation
75
+
76
+ The `create2` overload uses `abi.encodePacked(bytes1(0xff), deployer, salt, keccak256(bytecode))`. Verify:
77
+ - This matches the EIP-1014 specification exactly.
78
+ - The `bytes calldata bytecode` parameter is hashed correctly (constructor arguments must be included by the caller; the contract does not append them).
79
+ - No length ambiguity in the packed encoding (each component has fixed size: 1 + 20 + 32 + 32 = 85 bytes).
80
+
81
+ ### 3. Overwrite Behavior
82
+
83
+ `_registerAddress` unconditionally overwrites `deployerOf[addr]`. Verify:
84
+ - A re-registration with the same parameters produces the same result (idempotent).
85
+ - A re-registration with different parameters that happen to compute the same address (collision) would overwrite. Since address collisions require a keccak256 collision, this is cryptographically infeasible -- but confirm there is no cheaper attack vector.
86
+ - The `caller` field in the `AddressRegistered` event correctly reflects `msg.sender`, not the `deployer` parameter.
87
+
88
+ ### 4. Gas and DoS
89
+
90
+ The contract has no loops, no arrays, and no unbounded storage growth beyond the mapping. Verify:
91
+ - `registerAddress` has bounded gas cost regardless of inputs.
92
+ - The `bytecode` parameter in the `create2` overload is hashed in memory. For very large bytecode, this could consume significant memory gas but cannot cause an OOG in the registry itself (the caller pays).
93
+
94
+ ## Invariants to Verify
95
+
96
+ 1. **Determinism**: For any `(deployer, nonce)` pair, `_addressFrom` always returns the same address, and that address matches what the EVM would produce for a `create` deployment from `deployer` at `nonce`.
97
+ 2. **create2 correctness**: For any `(deployer, salt, bytecode)` triple, the computed address matches what the EVM would produce for a `create2` deployment.
98
+ 3. **No side effects**: `registerAddress` only modifies `deployerOf[computedAddress]` and emits one event. No other state is touched.
99
+ 4. **Nonce boundary completeness**: Every valid nonce (0 through `type(uint64).max`) produces a correct RLP encoding. No nonce in this range falls through without being encoded.
100
+
101
+ ## Testing Setup
102
+
103
+ ```bash
104
+ cd nana-address-registry-v6
105
+ npm install
106
+ forge build
107
+ forge test
108
+
109
+ # Run edge case tests
110
+ forge test --match-contract JBAddressRegistryEdge -vvv
111
+
112
+ # Run nonce truncation regression test
113
+ forge test --match-path test/regression/NonceTruncation.t.sol -vvv
114
+
115
+ # Run fork tests
116
+ forge test --match-contract Fork -vvv
117
+
118
+ # Write a PoC
119
+ forge test --match-path test/audit/ExploitPoC.t.sol -vvv
120
+ ```
121
+
122
+ Go break it.
package/CHANGE_LOG.md ADDED
@@ -0,0 +1,88 @@
1
+ # nana-address-registry-v6 Changelog (v5 → v6)
2
+
3
+ This document describes all changes between `nana-address-registry` (v5) and `nana-address-registry-v6` (v6).
4
+
5
+ ---
6
+
7
+ ## 1. Breaking Changes
8
+
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.
10
+ - **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
+
12
+ ## 2. New Features
13
+
14
+ - **Extended nonce support (uint40 through uint64)**: Four new RLP encoding branches handle nonces in the ranges `uint40`, `uint48`, `uint56`, and `uint64`, covering any realistic Ethereum account nonce.
15
+
16
+ ## 3. Event Changes
17
+
18
+ None. The `AddressRegistered` event signature is identical between v5 and v6:
19
+
20
+ ```solidity
21
+ event AddressRegistered(address indexed addr, address indexed deployer, address caller);
22
+ ```
23
+
24
+ ## 4. Error Changes
25
+
26
+ | Error | v5 | v6 |
27
+ |---|---|---|
28
+ | `JBAddressRegistry_NonceTooLarge(uint256 nonce)` | Does not exist | **Added** — reverts when `nonce > type(uint64).max` |
29
+
30
+ In v5, passing a nonce larger than `uint32` fell through to the `else` branch, which cast the nonce to `uint32`, silently truncating it and producing an incorrect address. v6 replaces this silent truncation with an explicit revert.
31
+
32
+ ## 5. Implementation Changes (Non-Interface)
33
+
34
+ ### `_addressFrom` — RLP nonce encoding
35
+
36
+ | Aspect | v5 | v6 |
37
+ |---|---|---|
38
+ | Maximum supported nonce | `uint32` (implicit, no guard) | `uint64` (explicit revert above) |
39
+ | Nonce > `uint32` behavior | Silent truncation to `uint32` — incorrect address computed | Correctly encodes `uint40`–`uint64`; reverts above `uint64` |
40
+ | RLP branches | 6 (`0x00`, `≤0x7f`, `≤0xff`, `≤0xffff`, `≤0xffffff`, `else→uint32`) | 10 (`0x00`, `≤0x7f`, `≤0xff`, `≤0xffff`, `≤0xffffff`, `≤0xffffffff`, `≤0xffffffffff`, `≤0xffffffffffff`, `≤0xffffffffffffff`, `else→uint64`) |
41
+ | Lint annotations | None | `forge-lint: disable-next-line(unsafe-typecast)` on each narrowing cast; `forge-lint: disable-next-line(asm-keccak256)` on inline assembly `keccak256` |
42
+
43
+ ### Internal function call style
44
+
45
+ All internal function calls now use **named arguments** for clarity:
46
+
47
+ ```solidity
48
+ // v5
49
+ address hook = _addressFrom(deployer, nonce);
50
+ _registerAddress(hook, deployer);
51
+
52
+ // v6
53
+ address hook = _addressFrom({origin: deployer, nonce: nonce});
54
+ _registerAddress({addr: hook, deployer: deployer});
55
+ ```
56
+
57
+ ### Section header naming
58
+
59
+ | v5 | v6 |
60
+ |---|---|
61
+ | `// ---------------------- internal transactions ---------------------- //` | `// -------------------------- internal views ------------------------- //` (for `_addressFrom`) |
62
+ | (none) | `// ------------------------ internal functions ----------------------- //` (new section for `_registerAddress`) |
63
+ | (none) | `// --------------------------- custom errors ------------------------- //` (new section) |
64
+
65
+ ### NatDoc comment improvements
66
+
67
+ - **Contract-level**: Fixed typo `reponsible` → `responsible`; reformatted line wrapping.
68
+ - **`_addressFrom`**: Replaced informal note (`"this won't work for nonces > 2**32. If you reach that nonce please: 1) ping us, because wow 2) use another deployer"`) with a precise description (`"RLP encoding of [origin, nonce]. Supports nonces up to uint64 max"`). Attribution changed from `"Taken from"` to `"Adapted from"`.
69
+
70
+ ### Interface NatDoc
71
+
72
+ The v6 interface (`IJBAddressRegistry`) adds full NatDoc documentation that was absent in v5:
73
+
74
+ - `@notice` on the interface itself.
75
+ - `@notice`, `@param`, and `@return` tags on `AddressRegistered`, `deployerOf`, and both `registerAddress` overloads.
76
+
77
+ No function signatures, parameter types, or return types changed.
78
+
79
+ ## 6. Migration Table
80
+
81
+ | v5 | v6 | Action Required |
82
+ |---|---|---|
83
+ | `IJBAddressRegistry` | `IJBAddressRegistry` | **None** — ABI-identical. Update import path only. |
84
+ | `JBAddressRegistry` | `JBAddressRegistry` | **None** — ABI-compatible. Deploy new instance compiled with Solidity 0.8.26. |
85
+ | `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
+ | `registerAddress(address, bytes32, bytes)` | `registerAddress(address, bytes32, bytes)` | **None** — signature unchanged. |
87
+ | `deployerOf(address)` | `deployerOf(address)` | **None** — signature unchanged. |
88
+ | (no error) | `JBAddressRegistry_NonceTooLarge(uint256)` | **New** — callers passing nonces > `uint64.max` must handle this revert. |
package/README.md CHANGED
@@ -60,7 +60,7 @@ Deployers can be exploited or act maliciously. Clients should still communicate
60
60
 
61
61
  ### Limitations
62
62
 
63
- - The `_addressFrom` function for `create` addresses uses RLP encoding that only supports nonces up to `2^32` (4,294,967,295). Higher nonces are silently truncated via `uint32` cast, producing incorrect addresses. In practice, this limit is unreachable.
63
+ - 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
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.
65
65
  - Registration is permissionless -- anyone can call `registerAddress`, not just the deployer. Security relies entirely on deterministic address computation.
66
66
 
package/RISKS.md CHANGED
@@ -1,20 +1,18 @@
1
- # nana-address-registry-v6 — Risks
1
+ # RISKS.md -- nana-address-registry-v6
2
2
 
3
- ## Trust Assumptions
3
+ ## 1. Trust Assumptions
4
4
 
5
- 1. **Self-Registration** Anyone can register any address. The registry does NOT verify that the caller actually deployed the contract. It only verifies that the computed address matches the provided parameters.
6
- 2. **Address Computation** Relies on correct RLP encoding (create) and keccak256 (create2) for address derivation. Standard Ethereum address computation.
7
- 3. **Frontend Trust** — Frontends must maintain their own list of trusted deployers. The registry only provides the mapping, not a trust judgment.
5
+ - **Self-registration.** Anyone can register any address. The registry does NOT verify that the caller actually deployed the contract. It only verifies that the computed address matches the provided deployer/nonce or deployer/salt/bytecode parameters.
6
+ - **Frontend trust.** Frontends must maintain their own list of trusted deployers. The registry provides the mapping, not a trust judgment.
8
7
 
9
- ## Known Risks
8
+ ## 2. Known Risks
10
9
 
11
- | Risk | Description | Mitigation |
12
- |------|-------------|------------|
13
- | False registration | Anyone can call registerAddress the computed address must match, but the "deployer" parameter is not verified as the actual deployer | Address computation ensures the deployer/nonce pair produced the address |
14
- | Overwrite | A second registration for the same address overwrites the first | By design; last writer wins |
15
- | No unregister | Once registered, a deployer mapping cannot be removed | Intentional; provides permanent provenance |
16
- | Nonce range | Supports nonces up to uint64 max | Covers any realistic Ethereum nonce |
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.
17
14
 
18
- ## Privileged Roles
15
+ ## 3. Invariants to Verify
19
16
 
20
- None the contract is fully permissionless. Anyone can register, anyone can query.
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`.
package/STYLE_GUIDE.md CHANGED
@@ -197,7 +197,7 @@ interface IJBExample is IJBBase {
197
197
  | Public/external function | `camelCase` | `cashOutTokensOf` |
198
198
  | Internal/private function | `_camelCase` | `_processFee` |
199
199
  | Internal storage | `_camelCase` | `_accountingContextForTokenOf` |
200
- | Function parameter | `camelCase` | `projectId`, `cashOutCount` |
200
+ | Function parameter | `camelCase` (no underscores) | `projectId`, `cashOutCount` |
201
201
 
202
202
  ## NatSpec
203
203
 
@@ -253,9 +253,12 @@ uint256 public constant MAX_RESERVED_PERCENT = 10_000;
253
253
 
254
254
  ## Function Calls
255
255
 
256
- Use named parameters for readability when calling functions with 3+ arguments:
256
+ Use named arguments for all function calls with 2 or more arguments — in both `src/` and `script/`:
257
257
 
258
258
  ```solidity
259
+ // Good — named arguments
260
+ token.mint({account: beneficiary, amount: count});
261
+ _transferOwnership({newOwner: address(0), projectId: 0});
259
262
  PERMISSIONS.hasPermission({
260
263
  operator: sender,
261
264
  account: account,
@@ -264,8 +267,18 @@ PERMISSIONS.hasPermission({
264
267
  includeRoot: true,
265
268
  includeWildcardProjectId: true
266
269
  });
270
+
271
+ // Bad — positional arguments with 2+ args
272
+ token.mint(beneficiary, count);
273
+ _transferOwnership(address(0), 0);
267
274
  ```
268
275
 
276
+ Single-argument calls use positional style: `_burn(amount)`.
277
+
278
+ This also applies to constructor calls, struct literals, and inherited/library calls (e.g., OZ `_mint`, `_safeMint`, `safeTransfer`, `allowance`, `Clones.cloneDeterministic`).
279
+
280
+ Named argument keys must use **camelCase** — never underscores. If a function's parameter names use underscores, rename them to camelCase first.
281
+
269
282
  ## Multiline Signatures
270
283
 
271
284
  ```solidity
@@ -553,6 +566,7 @@ CI checks formatting via `forge fmt --check`.
553
566
 
554
567
  CI runs `forge build --sizes` to catch contracts approaching the 24KB limit. When the repo's default `optimizer_runs` differs from what you want for size checking, use `FOUNDRY_PROFILE=ci_sizes forge build --sizes` with a `[profile.ci_sizes]` section in `foundry.toml`.
555
568
 
569
+
556
570
  ## Repo-Specific Deviations
557
571
 
558
572
  None. This repo follows the standard configuration exactly.
@@ -0,0 +1,144 @@
1
+ # User Journeys -- nana-address-registry-v6
2
+
3
+ Concrete end-to-end flows through the address registry. Each journey traces the exact function calls, state changes, and external interactions.
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**
22
+
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
27
+
28
+ 3. **`_registerAddress` stores the mapping and emits the event**
29
+
30
+ - Sets `deployerOf[computedAddress] = deployer`
31
+ - Emits `AddressRegistered(addr: computedAddress, deployer: deployer, caller: msg.sender)`
32
+
33
+ ### Result
34
+
35
+ `deployerOf[computedAddress]` now returns `deployer`. Anyone querying the registry for that address can verify who deployed it.
36
+
37
+ ### What to verify
38
+
39
+ - The computed address matches the actual on-chain contract address for the given `(deployer, nonce)` pair.
40
+ - 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).
42
+ - The `caller` in the event is `msg.sender`, which may differ from `deployer`.
43
+
44
+ ---
45
+
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)`**
58
+
59
+ - No parameter validation beyond the address computation itself
60
+ - `bytecode` must include ABI-encoded constructor arguments appended to the creation code
61
+
62
+ 2. **The contract computes the create2 address inline**
63
+
64
+ - `address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, keccak256(bytecode))))))`
65
+ - This matches the EIP-1014 specification
66
+
67
+ 3. **`_registerAddress` stores the mapping and emits the event**
68
+
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
77
+
78
+ - 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.
80
+ - 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).
81
+
82
+ ---
83
+
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
94
+
95
+ 1. **Frontend reads `deployerOf(hookAddress)`**
96
+
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
99
+
100
+ 2. **Frontend evaluates the result**
101
+
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.
105
+
106
+ ### Result
107
+
108
+ The frontend can make a trust decision about the hook without needing to trace the deployment transaction on-chain.
109
+
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.
113
+ - 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
+ - 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
+
116
+ ---
117
+
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`**
130
+
131
+ 2. **`_registerAddress` overwrites the existing value**
132
+
133
+ - `deployerOf[addr] = deployer` (unconditional write, no check for existing value)
134
+ - Emits a new `AddressRegistered` event
135
+
136
+ ### Result
137
+
138
+ The previous deployer mapping is overwritten. The event log contains both the old and new registrations.
139
+
140
+ ### What to verify
141
+
142
+ - 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
+ - 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
+ - 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.8",
3
+ "version": "0.0.10",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,10 +1,10 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.26;
3
3
 
4
- import "@sphinx-labs/contracts/contracts/foundry/SphinxPlugin.sol";
5
- import {Script, stdJson, VmSafe} from "forge-std/Script.sol";
4
+ import {Sphinx} from "@sphinx-labs/contracts/contracts/foundry/SphinxPlugin.sol";
5
+ import {Script} from "forge-std/Script.sol";
6
6
 
7
- import "src/JBAddressRegistry.sol";
7
+ import {JBAddressRegistry} from "src/JBAddressRegistry.sol";
8
8
 
9
9
  contract Deploy is Script, Sphinx {
10
10
  bytes32 constant ADDRESS_REGISTRY_SALT = "_JBAddressRegistryV6_";
@@ -18,7 +18,9 @@ contract Deploy is Script, Sphinx {
18
18
 
19
19
  function run() public sphinx {
20
20
  // Only deploy if this bytecode is not already deployed.
21
- if (!_isDeployed(ADDRESS_REGISTRY_SALT, type(JBAddressRegistry).creationCode, "")) {
21
+ if (!_isDeployed({
22
+ salt: ADDRESS_REGISTRY_SALT, creationCode: type(JBAddressRegistry).creationCode, arguments: ""
23
+ })) {
22
24
  new JBAddressRegistry{salt: ADDRESS_REGISTRY_SALT}();
23
25
  }
24
26
  }
@@ -13,7 +13,9 @@ struct AddressRegistryDeployment {
13
13
 
14
14
  library AddressRegistryDeploymentLib {
15
15
  // Cheat code address, 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D.
16
+ // forge-lint: disable-next-line(screaming-snake-case-const)
16
17
  address internal constant VM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code"))));
18
+ // forge-lint: disable-next-line(screaming-snake-case-const)
17
19
  Vm internal constant vm = Vm(VM_ADDRESS);
18
20
 
19
21
  function getDeployment(string memory path) internal returns (AddressRegistryDeployment memory deployment) {
@@ -27,7 +29,7 @@ library AddressRegistryDeploymentLib {
27
29
 
28
30
  for (uint256 _i; _i < networks.length; _i++) {
29
31
  if (networks[_i].chainId == chainId) {
30
- return getDeployment(path, networks[_i].name);
32
+ return getDeployment({path: path, networkName: networks[_i].name});
31
33
  }
32
34
  }
33
35
 
@@ -36,14 +38,20 @@ library AddressRegistryDeploymentLib {
36
38
 
37
39
  function getDeployment(
38
40
  string memory path,
39
- string memory network_name
41
+ string memory networkName
40
42
  )
41
43
  internal
42
44
  view
43
45
  returns (AddressRegistryDeployment memory deployment)
44
46
  {
45
- deployment.registry =
46
- IJBAddressRegistry(_getDeploymentAddress(path, "nana-address-registry", network_name, "JBAddressRegistry"));
47
+ deployment.registry = IJBAddressRegistry(
48
+ _getDeploymentAddress({
49
+ path: path,
50
+ projectName: "nana-address-registry-v6",
51
+ networkName: networkName,
52
+ contractName: "JBAddressRegistry"
53
+ })
54
+ );
47
55
  }
48
56
 
49
57
  /// @notice Get the address of a contract that was deployed by the Deploy script.
@@ -53,8 +61,8 @@ library AddressRegistryDeploymentLib {
53
61
  /// @return The address of the contract.
54
62
  function _getDeploymentAddress(
55
63
  string memory path,
56
- string memory project_name,
57
- string memory network_name,
64
+ string memory projectName,
65
+ string memory networkName,
58
66
  string memory contractName
59
67
  )
60
68
  internal
@@ -62,7 +70,8 @@ library AddressRegistryDeploymentLib {
62
70
  returns (address)
63
71
  {
64
72
  string memory deploymentJson =
65
- vm.readFile(string.concat(path, project_name, "/", network_name, "/", contractName, ".json"));
66
- return stdJson.readAddress(deploymentJson, ".address");
73
+ // forge-lint: disable-next-line(unsafe-cheatcode)
74
+ vm.readFile(string.concat(path, projectName, "/", networkName, "/", contractName, ".json"));
75
+ return stdJson.readAddress({json: deploymentJson, key: ".address"});
67
76
  }
68
77
  }
@@ -104,6 +104,7 @@ contract JBAddressRegistry is IJBAddressRegistry {
104
104
  // forge-lint: disable-next-line(unsafe-typecast)
105
105
  data = abi.encodePacked(bytes1(0xde), bytes1(0x94), origin, bytes1(0x88), uint64(nonce));
106
106
  }
107
+ // forge-lint: disable-next-line(asm-keccak256)
107
108
  bytes32 hash = keccak256(data);
108
109
  assembly {
109
110
  mstore(0, hash)
@@ -1,8 +1,8 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.26;
3
3
 
4
- import "forge-std/Test.sol";
5
- import "../src/JBAddressRegistry.sol";
4
+ import {Test} from "forge-std/Test.sol";
5
+ import {JBAddressRegistry} from "../src/JBAddressRegistry.sol";
6
6
 
7
7
  contract JBAddressRegistryTest is Test {
8
8
  event AddressRegistered(address indexed addr, address indexed deployer, address caller);
@@ -1,8 +1,8 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.26;
3
3
 
4
- import "forge-std/Test.sol";
5
- import "../src/JBAddressRegistry.sol";
4
+ import {Test} from "forge-std/Test.sol";
5
+ import {JBAddressRegistry} from "../src/JBAddressRegistry.sol";
6
6
 
7
7
  contract JBAddressRegistryTest_Fork is Test {
8
8
  address owner = makeAddr("_owner");