@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 +9 -0
- package/ARCHITECTURE.md +13 -1
- package/AUDIT_INSTRUCTIONS.md +25 -0
- package/CHANGE_LOG.md +8 -2
- package/README.md +12 -1
- package/RISKS.md +11 -4
- package/SKILLS.md +10 -2
- package/STYLE_GUIDE.md +1 -1
- package/USER_JOURNEYS.md +50 -97
- package/package.json +1 -1
- package/script/helpers/AddressRegistryDeploymentLib.sol +1 -1
- package/src/JBAddressRegistry.sol +7 -1
- package/test/JBAddressRegistryEdge.t.sol +25 -12
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
|
-
|
|
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)
|
package/AUDIT_INSTRUCTIONS.md
CHANGED
|
@@ -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` →
|
|
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
|
-
-
|
|
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
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **
|
|
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/
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9
|
+
**Entry point**: `JBAddressRegistry.registerAddress(address deployer, uint256 nonce)`
|
|
29
10
|
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
##
|
|
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
|
-
|
|
58
|
+
**Entry point**: `JBAddressRegistry.deployerOf(address addr)` (public mapping getter -- `view`)
|
|
96
59
|
|
|
97
|
-
|
|
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
|
-
|
|
62
|
+
**Parameters**:
|
|
63
|
+
- `addr` -- The address of the contract to look up
|
|
101
64
|
|
|
102
|
-
|
|
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
|
-
|
|
67
|
+
**Events**: None. No state mutation occurs.
|
|
107
68
|
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
83
|
+
**Entry point**: `JBAddressRegistry.registerAddress(address deployer, uint256 nonce)` or `JBAddressRegistry.registerAddress(address deployer, bytes32 salt, bytes calldata bytecode)`
|
|
132
84
|
|
|
133
|
-
|
|
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
|
-
|
|
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
|
-
|
|
89
|
+
**State changes**:
|
|
90
|
+
1. `JBAddressRegistry.deployerOf[addr] = deployer` -- unconditional `SSTORE`, overwrites any existing value without checking the previous deployer
|
|
139
91
|
|
|
140
|
-
|
|
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,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
|
|
161
|
+
// Duplicate registration - second registration reverts
|
|
162
162
|
// =========================================================================
|
|
163
163
|
|
|
164
|
-
/// @notice Registering the same computed address twice
|
|
165
|
-
function
|
|
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
|
-
//
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
// =========================================================================
|