@bananapus/address-registry-v6 0.0.2 → 0.0.4
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/README.md +54 -10
- package/SKILLS.md +118 -19
- package/foundry.toml +1 -1
- package/package.json +1 -1
- package/script/Deploy.s.sol +1 -1
- package/script/helpers/AddressRegistryDeploymentLib.sol +1 -1
- package/slither-ci.config.json +1 -1
- package/src/JBAddressRegistry.sol +21 -4
- package/test/JBAddressRegistry.t.sol +1 -1
- package/test/JBAddressRegistryEdge.t.sol +349 -0
- package/test/JBAddressRegistry_Fork.t.sol +1 -1
- package/test/regression/L67_NonceTruncation.t.sol +66 -0
package/README.md
CHANGED
|
@@ -1,30 +1,74 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Juicebox Address Registry
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Knowing who deployed a contract is the first step toward trusting it. This registry lets anyone prove a contract's deployer on-chain -- no access control, no admin keys, just math.
|
|
4
|
+
|
|
5
|
+
`JBAddressRegistry` maps contract addresses to their deployers using deterministic address computation. Provide the deployer address and deployment parameters, and the registry verifies the relationship and stores it permanently. Frontend clients can then look up any registered contract to see who deployed it, which is especially useful for vetting Juicebox pay and cash-out hooks before interacting with them.
|
|
6
|
+
|
|
7
|
+
## Deployed Address
|
|
8
|
+
|
|
9
|
+
`JBAddressRegistry` is deployed at the same address on all supported networks via deterministic `create2` deployment:
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
0x2d9b78cb37ca724cfb9b32cd8e9a5dc1c88bc7bb
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
| Network | Chain ID |
|
|
16
|
+
|---------|----------|
|
|
17
|
+
| Ethereum | 1 |
|
|
18
|
+
| Optimism | 10 |
|
|
19
|
+
| Arbitrum | 42161 |
|
|
20
|
+
| Base | 8453 |
|
|
21
|
+
| Ethereum Sepolia | 11155111 |
|
|
22
|
+
| Optimism Sepolia | 11155420 |
|
|
23
|
+
| Arbitrum Sepolia | 421614 |
|
|
24
|
+
| Base Sepolia | 84532 |
|
|
4
25
|
|
|
5
26
|
## Architecture
|
|
6
27
|
|
|
7
28
|
| Contract | Description |
|
|
8
29
|
|----------|-------------|
|
|
9
|
-
| `JBAddressRegistry` | Standalone registry. Computes deployed addresses deterministically from deployer parameters and stores the deployer in `deployerOf`. |
|
|
30
|
+
| `JBAddressRegistry` | Standalone registry. Computes deployed addresses deterministically from deployer parameters and stores the deployer in `deployerOf`. No constructor arguments, no access control, no external dependencies. |
|
|
10
31
|
|
|
11
|
-
###
|
|
32
|
+
### Interface
|
|
12
33
|
|
|
13
34
|
| Type | Description |
|
|
14
35
|
|------|-------------|
|
|
15
|
-
| `IJBAddressRegistry` | Interface exposing `deployerOf`,
|
|
36
|
+
| `IJBAddressRegistry` | Interface exposing `deployerOf`, both `registerAddress` overloads, and the `AddressRegistered` event. |
|
|
16
37
|
|
|
17
38
|
### How It Works
|
|
18
39
|
|
|
19
|
-
Anyone can register a contract by providing its deployer and deployment parameters
|
|
40
|
+
Anyone can register a contract by providing its deployer and deployment parameters:
|
|
41
|
+
|
|
42
|
+
- **`create` deployments**: Provide the deployer address and the nonce at the time of deployment. The registry reconstructs the address using RLP encoding (the same encoding the EVM uses internally to compute `create` addresses).
|
|
43
|
+
- **`create2` deployments**: Provide the deployer address, the `create2` salt, and the full deployment bytecode (creation code + encoded constructor arguments). The registry computes `keccak256(0xff ++ deployer ++ salt ++ keccak256(bytecode))`.
|
|
20
44
|
|
|
21
|
-
|
|
45
|
+
In both cases, the registry stores the mapping `deployerOf[computedAddress] = deployer` and emits an `AddressRegistered` event. Frontend clients can then look up any contract's deployer to verify trust.
|
|
46
|
+
|
|
47
|
+
No access control is needed -- only the correct deployer + parameters can produce a given address, so registrations cannot be faked.
|
|
48
|
+
|
|
49
|
+
### Events
|
|
50
|
+
|
|
51
|
+
| Event | Fields | Emitted When |
|
|
52
|
+
|-------|--------|-------------|
|
|
53
|
+
| `AddressRegistered` | `address indexed addr`, `address indexed deployer`, `address caller` | A contract address is registered via either `registerAddress` overload. `caller` is `msg.sender`, which may differ from the deployer. |
|
|
22
54
|
|
|
23
55
|
### Risks
|
|
24
56
|
|
|
25
|
-
Hooks have token minting access, making malicious hooks dangerous. Clients should warn project owners and users about any potential for unintended or adversarial behavior, especially for unknown hooks.
|
|
57
|
+
Hooks have token minting access, making malicious hooks dangerous. A registered deployer does not guarantee a hook is safe -- it only tells you who deployed it. Clients should warn project owners and users about any potential for unintended or adversarial behavior, especially for unknown hooks.
|
|
58
|
+
|
|
59
|
+
Deployers can be exploited or act maliciously. Clients should still communicate risk to users even when the deployer is a known entity.
|
|
60
|
+
|
|
61
|
+
### Limitations
|
|
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.
|
|
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
|
+
- Registration is permissionless -- anyone can call `registerAddress`, not just the deployer. Security relies entirely on deterministic address computation.
|
|
66
|
+
|
|
67
|
+
## Deployment
|
|
68
|
+
|
|
69
|
+
The deploy script uses [Sphinx](https://github.com/sphinx-labs/sphinx) for deterministic multi-chain deployment. The registry is deployed via `create2` with the salt `_JBAddressRegistryV6_`, ensuring the same address on every chain. The script skips deployment if the bytecode is already present at the computed address.
|
|
26
70
|
|
|
27
|
-
|
|
71
|
+
A helper library `AddressRegistryDeploymentLib` is provided in `script/helpers/` to resolve deployed addresses from Sphinx deployment artifacts at runtime.
|
|
28
72
|
|
|
29
73
|
## Install
|
|
30
74
|
|
|
@@ -38,7 +82,7 @@ npm install
|
|
|
38
82
|
|---------|-------------|
|
|
39
83
|
| `forge build` | Compile contracts |
|
|
40
84
|
| `forge test` | Run unit tests |
|
|
41
|
-
| `FOUNDRY_PROFILE=CI forge test` | Run fork tests |
|
|
85
|
+
| `FOUNDRY_PROFILE=CI forge test` | Run fork tests (requires `RPC_ETHEREUM_MAINNET` env var) |
|
|
42
86
|
| `forge coverage --match-path "./src/*.sol" --report lcov --report summary` | Generate coverage report |
|
|
43
87
|
| `npm run deploy:mainnets` | Propose mainnet deployment via Sphinx |
|
|
44
88
|
| `npm run deploy:testnets` | Propose testnet deployment via Sphinx |
|
package/SKILLS.md
CHANGED
|
@@ -1,22 +1,45 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Juicebox Address Registry
|
|
2
2
|
|
|
3
3
|
## Purpose
|
|
4
4
|
|
|
5
|
-
Allows anyone to register the deployer of a contract so that frontend clients can verify trust -- primarily for Juicebox pay/cash-out hooks, but works for any contract deployed via `create` or `create2`.
|
|
5
|
+
Allows anyone to register the deployer of a contract so that frontend clients can verify trust -- primarily for Juicebox pay/cash-out hooks, but works for any contract deployed via `create` or `create2`. The contract has no constructor arguments, no access control, no owner, and no external dependencies. Security relies entirely on the mathematical properties of deterministic address computation.
|
|
6
|
+
|
|
7
|
+
## Deployed Address
|
|
8
|
+
|
|
9
|
+
Same address on all networks (deterministic `create2` via Sphinx with salt `_JBAddressRegistryV6_`):
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
0x2d9b78cb37ca724cfb9b32cd8e9a5dc1c88bc7bb
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Deployed on: Ethereum, Optimism, Arbitrum, Base, and their Sepolia testnets.
|
|
6
16
|
|
|
7
17
|
## Contracts
|
|
8
18
|
|
|
9
19
|
| Contract | Role |
|
|
10
20
|
|----------|------|
|
|
11
|
-
| `JBAddressRegistry` | Standalone registry
|
|
21
|
+
| `JBAddressRegistry` | Standalone registry. Stores `mapping(address => address) deployerOf`. Computes addresses deterministically from deployer + deployment params, then writes the mapping. |
|
|
12
22
|
|
|
13
23
|
## Key Functions
|
|
14
24
|
|
|
15
25
|
| Function | Contract | What it does |
|
|
16
26
|
|----------|----------|--------------|
|
|
17
|
-
| `registerAddress(address deployer, uint256 nonce)` | `JBAddressRegistry` | Registers a contract deployed via `create`. Computes address from deployer+nonce using RLP encoding. |
|
|
18
|
-
| `registerAddress(address deployer, bytes32 salt, bytes calldata bytecode)` | `JBAddressRegistry` | Registers a contract deployed via `create2`. Computes `keccak256(0xff
|
|
19
|
-
| `deployerOf(address)` | `JBAddressRegistry` | Returns the registered deployer of
|
|
27
|
+
| `registerAddress(address deployer, uint256 nonce)` | `JBAddressRegistry` | Registers a contract deployed via `create`. Computes address from deployer + nonce using RLP encoding (`_addressFrom`). Stores `deployerOf[computedAddress] = deployer`. Emits `AddressRegistered`. |
|
|
28
|
+
| `registerAddress(address deployer, bytes32 salt, bytes calldata bytecode)` | `JBAddressRegistry` | Registers a contract deployed via `create2`. Computes `address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, keccak256(bytecode))))))`. Stores `deployerOf[computedAddress] = deployer`. Emits `AddressRegistered`. |
|
|
29
|
+
| `deployerOf(address addr)` | `JBAddressRegistry` | Returns the registered deployer of `addr`. Returns `address(0)` if not registered. This is a public mapping getter. |
|
|
30
|
+
|
|
31
|
+
## Events
|
|
32
|
+
|
|
33
|
+
| Event | Fields | Emitted By |
|
|
34
|
+
|-------|--------|------------|
|
|
35
|
+
| `AddressRegistered(address indexed addr, address indexed deployer, address caller)` | `addr`: the computed contract address. `deployer`: the deployer stored in the mapping. `caller`: `msg.sender` who called `registerAddress` (may differ from deployer). | Both `registerAddress` overloads, via internal `_registerAddress`. |
|
|
36
|
+
|
|
37
|
+
## Internal Functions
|
|
38
|
+
|
|
39
|
+
| Function | What it does |
|
|
40
|
+
|----------|--------------|
|
|
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
|
+
| `_registerAddress(address addr, address deployer)` | Writes `deployerOf[addr] = deployer` and emits `AddressRegistered`. Shared by both public `registerAddress` overloads. |
|
|
20
43
|
|
|
21
44
|
## Integration Points
|
|
22
45
|
|
|
@@ -24,28 +47,104 @@ Allows anyone to register the deployer of a contract so that frontend clients ca
|
|
|
24
47
|
|------------|--------|----------|
|
|
25
48
|
| None | -- | This contract is fully standalone with no external dependencies. |
|
|
26
49
|
|
|
50
|
+
The npm package name is `@bananapus/address-registry-v6`. Import the interface with:
|
|
51
|
+
|
|
52
|
+
```solidity
|
|
53
|
+
import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
|
|
54
|
+
```
|
|
55
|
+
|
|
27
56
|
## Key Types
|
|
28
57
|
|
|
29
|
-
|
|
|
30
|
-
|
|
31
|
-
|
|
|
58
|
+
| Type | Description |
|
|
59
|
+
|------|-------------|
|
|
60
|
+
| `IJBAddressRegistry` | Interface declaring `deployerOf`, both `registerAddress` overloads, and the `AddressRegistered` event. Pragma `^0.8.0`. |
|
|
61
|
+
| `AddressRegistryDeployment` | Struct in `script/helpers/AddressRegistryDeploymentLib.sol` containing `IJBAddressRegistry registry`. Used by deploy scripts to pass around the deployed instance. |
|
|
62
|
+
|
|
63
|
+
## Storage Layout
|
|
64
|
+
|
|
65
|
+
Single slot category:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
mapping(address addr => address deployer) public deployerOf;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
No arrays, no structs, no linked lists. One mapping, that is all.
|
|
72
|
+
|
|
73
|
+
## Deployment Details
|
|
74
|
+
|
|
75
|
+
- Deploy script: `script/Deploy.s.sol`
|
|
76
|
+
- Salt: `bytes32("_JBAddressRegistryV6_")`
|
|
77
|
+
- Deployer proxy: `0x4e59b44847b379578588920cA78FbF26c0B4956C` (Arachnid deterministic deployment proxy)
|
|
78
|
+
- The script checks if code already exists at the computed address and skips deployment if so
|
|
79
|
+
- `AddressRegistryDeploymentLib` (in `script/helpers/`) resolves deployed addresses from Sphinx JSON artifacts by chain ID
|
|
32
80
|
|
|
33
81
|
## Gotchas
|
|
34
82
|
|
|
35
|
-
-
|
|
36
|
-
- No overwrite protection
|
|
37
|
-
-
|
|
83
|
+
- **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
|
+
- **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
|
+
- **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.
|
|
87
|
+
- **`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
|
+
- **`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
|
+
- **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.
|
|
91
|
+
|
|
92
|
+
## Example: Register a `create` Deployment
|
|
93
|
+
|
|
94
|
+
```solidity
|
|
95
|
+
import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
|
|
96
|
+
|
|
97
|
+
// After deploying a hook from a factory contract:
|
|
98
|
+
address hook = address(new MyPayHook(projectId));
|
|
99
|
+
// Factory's nonce was N when it deployed the hook.
|
|
100
|
+
// If this is the factory's first deployment, nonce = 1.
|
|
101
|
+
registry.registerAddress(address(this), 1);
|
|
102
|
+
```
|
|
38
103
|
|
|
39
|
-
## Example
|
|
104
|
+
## Example: Register a `create2` Deployment
|
|
40
105
|
|
|
41
106
|
```solidity
|
|
42
107
|
import {IJBAddressRegistry} from "@bananapus/address-registry-v6/src/interfaces/IJBAddressRegistry.sol";
|
|
43
108
|
|
|
44
|
-
|
|
45
|
-
address hook = new MyPayHook();
|
|
46
|
-
|
|
109
|
+
bytes32 salt = keccak256(abi.encode(projectId));
|
|
110
|
+
address hook = address(new MyPayHook{salt: salt}(projectId));
|
|
111
|
+
|
|
112
|
+
// Bytecode must include constructor arguments.
|
|
113
|
+
bytes memory bytecode = abi.encodePacked(
|
|
114
|
+
type(MyPayHook).creationCode,
|
|
115
|
+
abi.encode(projectId)
|
|
116
|
+
);
|
|
117
|
+
registry.registerAddress(address(this), salt, bytecode);
|
|
118
|
+
```
|
|
47
119
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
120
|
+
## Example: Frontend Lookup
|
|
121
|
+
|
|
122
|
+
```solidity
|
|
123
|
+
address deployer = registry.deployerOf(hookAddress);
|
|
124
|
+
if (deployer == address(0)) {
|
|
125
|
+
// Not registered -- unknown deployer.
|
|
126
|
+
} else if (deployer == TRUSTED_DEPLOYER) {
|
|
127
|
+
// Deployed by a known, trusted factory.
|
|
128
|
+
} else {
|
|
129
|
+
// Registered but deployer is not in the trusted set.
|
|
130
|
+
}
|
|
51
131
|
```
|
|
132
|
+
|
|
133
|
+
## RLP Encoding Reference (for `create` address computation)
|
|
134
|
+
|
|
135
|
+
The `_addressFrom` function implements RLP encoding of `[deployer, nonce]` to match how the EVM computes `create` addresses. The encoding varies by nonce size:
|
|
136
|
+
|
|
137
|
+
| Nonce Range | RLP Prefix | Nonce Encoding |
|
|
138
|
+
|-------------|-----------|----------------|
|
|
139
|
+
| `0` | `0xd6 0x94` | `0x80` (RLP empty byte) |
|
|
140
|
+
| `1 - 0x7f` | `0xd6 0x94` | `uint8(nonce)` |
|
|
141
|
+
| `0x80 - 0xff` | `0xd7 0x94` | `0x81` + `uint8(nonce)` |
|
|
142
|
+
| `0x100 - 0xffff` | `0xd8 0x94` | `0x82` + `uint16(nonce)` |
|
|
143
|
+
| `0x10000 - 0xffffff` | `0xd9 0x94` | `0x83` + `uint24(nonce)` |
|
|
144
|
+
| `0x1000000 - 0xffffffff` | `0xda 0x94` | `0x84` + `uint32(nonce)` |
|
|
145
|
+
| `0x100000000 - 0xffffffffff` | `0xdb 0x94` | `0x85` + `uint40(nonce)` |
|
|
146
|
+
| `0x10000000000 - 0xffffffffffff` | `0xdc 0x94` | `0x86` + `uint48(nonce)` |
|
|
147
|
+
| `0x1000000000000 - 0xffffffffffffff` | `0xdd 0x94` | `0x87` + `uint56(nonce)` |
|
|
148
|
+
| `0x100000000000000 - 0xffffffffffffffff` | `0xde 0x94` | `0x88` + `uint64(nonce)` |
|
|
149
|
+
|
|
150
|
+
The final address is `keccak256(rlp_encoded_data)` with the low 160 bits extracted.
|
package/foundry.toml
CHANGED
package/package.json
CHANGED
package/script/Deploy.s.sol
CHANGED
package/slither-ci.config.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.
|
|
2
|
+
pragma solidity 0.8.26;
|
|
3
3
|
|
|
4
4
|
import {IJBAddressRegistry} from "./interfaces/IJBAddressRegistry.sol";
|
|
5
5
|
|
|
@@ -12,6 +12,13 @@ import {IJBAddressRegistry} from "./interfaces/IJBAddressRegistry.sol";
|
|
|
12
12
|
/// @dev The addresses of the deployed contracts are computed deterministically based on the deployer's address, and a
|
|
13
13
|
/// nonce (for `create`) or `create2` salt and deployment bytecode (for `create2`).
|
|
14
14
|
contract JBAddressRegistry is IJBAddressRegistry {
|
|
15
|
+
//*********************************************************************//
|
|
16
|
+
// -------------------------------- errors --------------------------- //
|
|
17
|
+
//*********************************************************************//
|
|
18
|
+
|
|
19
|
+
/// @notice Thrown when a nonce exceeds the maximum value supported by the RLP encoding (uint64 max).
|
|
20
|
+
error JBAddressRegistry_NonceTooLarge(uint256 nonce);
|
|
21
|
+
|
|
15
22
|
//*********************************************************************//
|
|
16
23
|
// --------------------- public stored properties -------------------- //
|
|
17
24
|
//*********************************************************************//
|
|
@@ -59,11 +66,13 @@ contract JBAddressRegistry is IJBAddressRegistry {
|
|
|
59
66
|
//*********************************************************************//
|
|
60
67
|
|
|
61
68
|
/// @notice Compute the address of a contract deployed using `create` based on the deployer's address and nonce.
|
|
62
|
-
/// @dev
|
|
63
|
-
///
|
|
69
|
+
/// @dev RLP encoding of [origin, nonce]. Supports nonces up to uint64 max (covers any realistic Ethereum nonce).
|
|
70
|
+
/// @dev Adapted from https://ethereum.stackexchange.com/a/87840/68134
|
|
64
71
|
/// @param origin The deployer's address.
|
|
65
72
|
/// @param nonce The nonce used to deploy the contract.
|
|
66
73
|
function _addressFrom(address origin, uint256 nonce) internal pure returns (address addr) {
|
|
74
|
+
if (nonce > type(uint64).max) revert JBAddressRegistry_NonceTooLarge(nonce);
|
|
75
|
+
|
|
67
76
|
bytes memory data;
|
|
68
77
|
if (nonce == 0x00) {
|
|
69
78
|
data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), origin, bytes1(0x80));
|
|
@@ -75,8 +84,16 @@ contract JBAddressRegistry is IJBAddressRegistry {
|
|
|
75
84
|
data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), origin, bytes1(0x82), uint16(nonce));
|
|
76
85
|
} else if (nonce <= 0xffffff) {
|
|
77
86
|
data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), origin, bytes1(0x83), uint24(nonce));
|
|
78
|
-
} else {
|
|
87
|
+
} else if (nonce <= 0xffffffff) {
|
|
79
88
|
data = abi.encodePacked(bytes1(0xda), bytes1(0x94), origin, bytes1(0x84), uint32(nonce));
|
|
89
|
+
} else if (nonce <= 0xffffffffff) {
|
|
90
|
+
data = abi.encodePacked(bytes1(0xdb), bytes1(0x94), origin, bytes1(0x85), uint40(nonce));
|
|
91
|
+
} else if (nonce <= 0xffffffffffff) {
|
|
92
|
+
data = abi.encodePacked(bytes1(0xdc), bytes1(0x94), origin, bytes1(0x86), uint48(nonce));
|
|
93
|
+
} else if (nonce <= 0xffffffffffffff) {
|
|
94
|
+
data = abi.encodePacked(bytes1(0xdd), bytes1(0x94), origin, bytes1(0x87), uint56(nonce));
|
|
95
|
+
} else {
|
|
96
|
+
data = abi.encodePacked(bytes1(0xde), bytes1(0x94), origin, bytes1(0x88), uint64(nonce));
|
|
80
97
|
}
|
|
81
98
|
bytes32 hash = keccak256(data);
|
|
82
99
|
assembly {
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.26;
|
|
3
|
+
|
|
4
|
+
import {Test} from "forge-std/Test.sol";
|
|
5
|
+
import {JBAddressRegistry} from "../src/JBAddressRegistry.sol";
|
|
6
|
+
|
|
7
|
+
/// @title JBAddressRegistryEdge
|
|
8
|
+
/// @notice Edge case and negative tests for JBAddressRegistry: RLP encoding boundaries,
|
|
9
|
+
/// mismatched parameters, duplicate registrations, and address(0) inputs.
|
|
10
|
+
contract JBAddressRegistryEdge is Test {
|
|
11
|
+
event AddressRegistered(address indexed addr, address indexed deployer, address caller);
|
|
12
|
+
|
|
13
|
+
JBAddressRegistry registry;
|
|
14
|
+
address deployer = makeAddr("deployer");
|
|
15
|
+
|
|
16
|
+
function setUp() public {
|
|
17
|
+
registry = new JBAddressRegistry();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// =========================================================================
|
|
21
|
+
// RLP encoding boundary tests - verify _addressFrom at each nonce boundary
|
|
22
|
+
// =========================================================================
|
|
23
|
+
|
|
24
|
+
/// @notice Nonce 0: uses 0x80 byte suffix.
|
|
25
|
+
function test_nonceBoundary_zero() public {
|
|
26
|
+
_verifyCreateRegistration(deployer, 0);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// @notice Nonce 1: single-byte RLP (nonce <= 0x7f).
|
|
30
|
+
function test_nonceBoundary_one() public {
|
|
31
|
+
_verifyCreateRegistration(deployer, 1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// @notice Nonce 0x7f: upper boundary of single-byte RLP.
|
|
35
|
+
function test_nonceBoundary_0x7f() public {
|
|
36
|
+
_verifyCreateRegistration(deployer, 0x7f);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// @notice Nonce 0x80: switches to 2-byte RLP (0x81 prefix).
|
|
40
|
+
function test_nonceBoundary_0x80() public {
|
|
41
|
+
_verifyCreateRegistration(deployer, 0x80);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// @notice Nonce 0xff: upper boundary of uint8 nonce.
|
|
45
|
+
function test_nonceBoundary_0xff() public {
|
|
46
|
+
_verifyCreateRegistration(deployer, 0xff);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/// @notice Nonce 0x100: switches to uint16 encoding.
|
|
50
|
+
function test_nonceBoundary_0x100() public {
|
|
51
|
+
_verifyCreateRegistration(deployer, 0x100);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// @notice Nonce 0xffff: upper boundary of uint16 nonce.
|
|
55
|
+
function test_nonceBoundary_0xffff() public {
|
|
56
|
+
_verifyCreateRegistration(deployer, 0xffff);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// @notice Nonce 0x10000: switches to uint24 encoding.
|
|
60
|
+
function test_nonceBoundary_0x10000() public {
|
|
61
|
+
_verifyCreateRegistration(deployer, 0x10000);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// @notice Nonce 0xffffff: upper boundary of uint24 nonce.
|
|
65
|
+
function test_nonceBoundary_0xffffff() public {
|
|
66
|
+
_verifyCreateRegistration(deployer, 0xffffff);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// @notice Nonce 0x1000000: switches to uint32 encoding.
|
|
70
|
+
function test_nonceBoundary_0x1000000() public {
|
|
71
|
+
_verifyCreateRegistration(deployer, 0x1000000);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// @notice Nonce at max uint32.
|
|
75
|
+
function test_nonceBoundary_maxUint32() public {
|
|
76
|
+
_verifyCreateRegistration(deployer, type(uint32).max);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/// @notice Nonce 0x100000000: switches to uint40 encoding.
|
|
80
|
+
function test_nonceBoundary_0x100000000() public {
|
|
81
|
+
_verifyCreateRegistration(deployer, 0x100000000);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// @notice Nonce at max uint40.
|
|
85
|
+
function test_nonceBoundary_maxUint40() public {
|
|
86
|
+
_verifyCreateRegistration(deployer, type(uint40).max);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// @notice Nonce 0x10000000000: switches to uint48 encoding.
|
|
90
|
+
function test_nonceBoundary_0x10000000000() public {
|
|
91
|
+
_verifyCreateRegistration(deployer, 0x10000000000);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// @notice Nonce at max uint48.
|
|
95
|
+
function test_nonceBoundary_maxUint48() public {
|
|
96
|
+
_verifyCreateRegistration(deployer, type(uint48).max);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/// @notice Nonce 0x1000000000000: switches to uint56 encoding.
|
|
100
|
+
function test_nonceBoundary_0x1000000000000() public {
|
|
101
|
+
_verifyCreateRegistration(deployer, 0x1000000000000);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// @notice Nonce at max uint56.
|
|
105
|
+
function test_nonceBoundary_maxUint56() public {
|
|
106
|
+
_verifyCreateRegistration(deployer, type(uint56).max);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// @notice Nonce 0x100000000000000: switches to uint64 encoding.
|
|
110
|
+
function test_nonceBoundary_0x100000000000000() public {
|
|
111
|
+
_verifyCreateRegistration(deployer, 0x100000000000000);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// @notice Nonce at max uint64 - highest supported nonce. Does not deploy (VM limitation).
|
|
115
|
+
function test_nonceBoundary_maxUint64() public {
|
|
116
|
+
// Cannot use _verifyCreateRegistration because vm.setNonce cannot handle uint64.max.
|
|
117
|
+
// Just verify registerAddress does not revert at the boundary.
|
|
118
|
+
registry.registerAddress(deployer, type(uint64).max);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// =========================================================================
|
|
122
|
+
// Mismatched parameters - wrong deployer/nonce registers wrong address
|
|
123
|
+
// =========================================================================
|
|
124
|
+
|
|
125
|
+
/// @notice Registering with wrong nonce maps to a different (non-deployed) address.
|
|
126
|
+
function test_mismatchedNonce_registersDifferentAddress() public {
|
|
127
|
+
uint64 correctNonce = 5;
|
|
128
|
+
|
|
129
|
+
// Set nonce and deploy.
|
|
130
|
+
vm.setNonce(deployer, correctNonce);
|
|
131
|
+
vm.prank(deployer);
|
|
132
|
+
address deployed = address(new MockDeployment());
|
|
133
|
+
|
|
134
|
+
// Register with wrong nonce - should map to a different address.
|
|
135
|
+
uint256 wrongNonce = 6;
|
|
136
|
+
registry.registerAddress(deployer, wrongNonce);
|
|
137
|
+
|
|
138
|
+
// The deployed contract should NOT be mapped (wrong nonce was used).
|
|
139
|
+
assertEq(
|
|
140
|
+
registry.deployerOf(deployed), address(0), "Deployed address should not be registered with wrong nonce"
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// @notice Registering with wrong deployer maps to a different address.
|
|
145
|
+
function test_mismatchedDeployer_registersDifferentAddress() public {
|
|
146
|
+
address wrongDeployer = makeAddr("wrongDeployer");
|
|
147
|
+
uint64 nonce = 1;
|
|
148
|
+
|
|
149
|
+
vm.setNonce(deployer, nonce);
|
|
150
|
+
vm.prank(deployer);
|
|
151
|
+
address deployed = address(new MockDeployment());
|
|
152
|
+
|
|
153
|
+
// Register with wrong deployer.
|
|
154
|
+
registry.registerAddress(wrongDeployer, nonce);
|
|
155
|
+
|
|
156
|
+
// The deployed address should not be registered.
|
|
157
|
+
assertEq(registry.deployerOf(deployed), address(0), "Should not be registered with wrong deployer");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// =========================================================================
|
|
161
|
+
// Duplicate registration - second registration overwrites first
|
|
162
|
+
// =========================================================================
|
|
163
|
+
|
|
164
|
+
/// @notice Registering the same computed address twice overwrites the deployer.
|
|
165
|
+
function test_duplicateRegistration_overwrites() public {
|
|
166
|
+
uint64 nonce = 1;
|
|
167
|
+
|
|
168
|
+
vm.setNonce(deployer, nonce);
|
|
169
|
+
vm.prank(deployer);
|
|
170
|
+
address deployed = address(new MockDeployment());
|
|
171
|
+
|
|
172
|
+
// First registration.
|
|
173
|
+
registry.registerAddress(deployer, nonce);
|
|
174
|
+
assertEq(registry.deployerOf(deployed), deployer);
|
|
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);
|
|
180
|
+
|
|
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");
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// =========================================================================
|
|
188
|
+
// Unregistered address returns address(0)
|
|
189
|
+
// =========================================================================
|
|
190
|
+
|
|
191
|
+
/// @notice deployerOf returns address(0) for unregistered addresses.
|
|
192
|
+
function test_unregisteredAddress_returnsZero() public {
|
|
193
|
+
assertEq(registry.deployerOf(address(0xdead)), address(0));
|
|
194
|
+
assertEq(registry.deployerOf(address(0)), address(0));
|
|
195
|
+
assertEq(registry.deployerOf(address(this)), address(0));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// =========================================================================
|
|
199
|
+
// Create2 - wrong bytecode maps to wrong address
|
|
200
|
+
// =========================================================================
|
|
201
|
+
|
|
202
|
+
/// @notice Create2 registration with wrong bytecode produces different address.
|
|
203
|
+
function test_create2_wrongBytecode_registersDifferentAddress() public {
|
|
204
|
+
Factory factory = new Factory();
|
|
205
|
+
bytes32 salt = bytes32(uint256(42));
|
|
206
|
+
|
|
207
|
+
address deployed = factory.deploy(salt);
|
|
208
|
+
|
|
209
|
+
// Register with correct params.
|
|
210
|
+
registry.registerAddress(address(factory), salt, type(MockDeployment).creationCode);
|
|
211
|
+
assertEq(registry.deployerOf(deployed), address(factory), "Correct create2 registration should work");
|
|
212
|
+
|
|
213
|
+
// Now try registering with wrong bytecode - produces different computed address.
|
|
214
|
+
bytes memory wrongBytecode = hex"deadbeef";
|
|
215
|
+
registry.registerAddress(address(factory), salt, wrongBytecode);
|
|
216
|
+
|
|
217
|
+
// The deployed address's mapping should be unchanged (wrong bytecode = different address).
|
|
218
|
+
assertEq(registry.deployerOf(deployed), address(factory), "Original mapping unchanged by wrong bytecode");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// =========================================================================
|
|
222
|
+
// address(0) deployer
|
|
223
|
+
// =========================================================================
|
|
224
|
+
|
|
225
|
+
/// @notice Registration with address(0) as deployer succeeds (permissionless).
|
|
226
|
+
function test_zeroAddressDeployer_succeeds() public {
|
|
227
|
+
// This should not revert - the registry is permissionless and doesn't validate.
|
|
228
|
+
registry.registerAddress(address(0), 1);
|
|
229
|
+
|
|
230
|
+
// The computed address for (address(0), nonce=1) gets mapped to address(0).
|
|
231
|
+
// We just verify no revert and the mapping is set.
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// =========================================================================
|
|
235
|
+
// Event emission
|
|
236
|
+
// =========================================================================
|
|
237
|
+
|
|
238
|
+
/// @notice Verify AddressRegistered event is emitted with correct fields for create.
|
|
239
|
+
function test_eventEmission_create() public {
|
|
240
|
+
uint64 nonce = 1;
|
|
241
|
+
vm.setNonce(deployer, nonce);
|
|
242
|
+
vm.prank(deployer);
|
|
243
|
+
address deployed = address(new MockDeployment());
|
|
244
|
+
|
|
245
|
+
vm.expectEmit(true, true, true, true);
|
|
246
|
+
emit AddressRegistered(deployed, deployer, address(this));
|
|
247
|
+
|
|
248
|
+
registry.registerAddress(deployer, nonce);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/// @notice Verify AddressRegistered event is emitted with correct fields for create2.
|
|
252
|
+
function test_eventEmission_create2() public {
|
|
253
|
+
Factory factory = new Factory();
|
|
254
|
+
bytes32 salt = bytes32(uint256(99));
|
|
255
|
+
address deployed = factory.deploy(salt);
|
|
256
|
+
|
|
257
|
+
vm.expectEmit(true, true, true, true);
|
|
258
|
+
emit AddressRegistered(deployed, address(factory), address(this));
|
|
259
|
+
|
|
260
|
+
registry.registerAddress(address(factory), salt, type(MockDeployment).creationCode);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// =========================================================================
|
|
264
|
+
// Fuzz: any valid nonce produces correct registration
|
|
265
|
+
// =========================================================================
|
|
266
|
+
|
|
267
|
+
/// @notice Fuzz across the uint32 nonce range with full deploy-and-verify.
|
|
268
|
+
function testFuzz_createRegistration_anyNonce(uint32 nonce) public {
|
|
269
|
+
_verifyCreateRegistration(deployer, uint256(nonce));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/// @notice Fuzz: any nonce within uint64 range should not revert on registerAddress.
|
|
273
|
+
function testFuzz_registerAddress_anyUint64Nonce(uint64 nonce) public {
|
|
274
|
+
// Just verify registerAddress does not revert for any uint64 nonce.
|
|
275
|
+
registry.registerAddress(deployer, uint256(nonce));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// =========================================================================
|
|
279
|
+
// Nonce > uint64 - reverts with NonceTooLarge (L-67 fix extended to uint64)
|
|
280
|
+
// =========================================================================
|
|
281
|
+
|
|
282
|
+
/// @notice Nonces above uint64 max revert instead of silently truncating.
|
|
283
|
+
function test_nonceAboveUint64_reverts() public {
|
|
284
|
+
address deployer1 = makeAddr("deployer_overflow");
|
|
285
|
+
uint256 tooLargeNonce = uint256(type(uint64).max) + 1;
|
|
286
|
+
|
|
287
|
+
vm.expectRevert(
|
|
288
|
+
abi.encodeWithSelector(JBAddressRegistry.JBAddressRegistry_NonceTooLarge.selector, tooLargeNonce)
|
|
289
|
+
);
|
|
290
|
+
registry.registerAddress(deployer1, tooLargeNonce);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/// @notice Nonces in the uint32-uint64 range now succeed (L-67 fix extended support).
|
|
294
|
+
function test_nonceInUint40Range_succeeds() public {
|
|
295
|
+
registry.registerAddress(deployer, uint256(type(uint40).max));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// =========================================================================
|
|
299
|
+
// Helpers
|
|
300
|
+
// =========================================================================
|
|
301
|
+
|
|
302
|
+
/// @dev Deploys a contract at the given nonce and verifies registration.
|
|
303
|
+
function _verifyCreateRegistration(address _deployer, uint256 nonce) internal {
|
|
304
|
+
// Set nonce (must be >= current nonce).
|
|
305
|
+
uint64 currentNonce = vm.getNonce(_deployer);
|
|
306
|
+
if (nonce < currentNonce) {
|
|
307
|
+
// Can't set nonce lower than current, use a fresh deployer.
|
|
308
|
+
_deployer = makeAddr(string.concat("deployer_", vm.toString(nonce)));
|
|
309
|
+
}
|
|
310
|
+
if (vm.getNonce(_deployer) != nonce) {
|
|
311
|
+
// forge-lint: disable-next-line(unsafe-typecast)
|
|
312
|
+
vm.setNonce(_deployer, uint64(nonce));
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Deploy.
|
|
316
|
+
vm.prank(_deployer);
|
|
317
|
+
address deployed = address(new MockDeployment());
|
|
318
|
+
|
|
319
|
+
// Register.
|
|
320
|
+
registry.registerAddress(_deployer, nonce);
|
|
321
|
+
|
|
322
|
+
// Verify.
|
|
323
|
+
assertEq(
|
|
324
|
+
registry.deployerOf(deployed),
|
|
325
|
+
_deployer,
|
|
326
|
+
string.concat("Registration failed for nonce ", vm.toString(nonce))
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
contract MockDeployment {
|
|
332
|
+
string stored = "Hello, world!";
|
|
333
|
+
|
|
334
|
+
constructor() {}
|
|
335
|
+
|
|
336
|
+
function getFancyData() external view returns (string memory) {
|
|
337
|
+
return stored;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
contract Factory {
|
|
342
|
+
function deploy() public returns (address) {
|
|
343
|
+
return address(new MockDeployment());
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function deploy(bytes32 _salt) public returns (address) {
|
|
347
|
+
return address(new MockDeployment{salt: _salt}());
|
|
348
|
+
}
|
|
349
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.26;
|
|
3
|
+
|
|
4
|
+
import {Test} from "forge-std/Test.sol";
|
|
5
|
+
import {JBAddressRegistry} from "../../src/JBAddressRegistry.sol";
|
|
6
|
+
|
|
7
|
+
/// @title L67_NonceTruncation
|
|
8
|
+
/// @notice Regression test for L-67: _addressFrom originally only handled nonces up to uint32 max,
|
|
9
|
+
/// silently truncating larger values. The fix extends RLP encoding to uint64 max and adds
|
|
10
|
+
/// an explicit revert for nonces beyond that range.
|
|
11
|
+
contract L67_NonceTruncation is Test {
|
|
12
|
+
JBAddressRegistry registry;
|
|
13
|
+
address deployer = makeAddr("deployer");
|
|
14
|
+
|
|
15
|
+
function setUp() public {
|
|
16
|
+
registry = new JBAddressRegistry();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/// @notice Nonce exactly at uint32 max should succeed.
|
|
20
|
+
function test_nonceAtUint32Max_succeeds() public {
|
|
21
|
+
registry.registerAddress(deployer, type(uint32).max);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/// @notice Nonce one above uint32 max should now succeed (uint64 support added in L-67 fix).
|
|
25
|
+
function test_nonceAboveUint32Max_succeeds() public {
|
|
26
|
+
uint256 nonce = uint256(type(uint32).max) + 1;
|
|
27
|
+
// Should not revert -- the fix extended support to uint64.
|
|
28
|
+
registry.registerAddress(deployer, nonce);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// @notice Nonce at uint64 max should succeed (upper boundary of support).
|
|
32
|
+
function test_nonceAtUint64Max_succeeds() public {
|
|
33
|
+
registry.registerAddress(deployer, type(uint64).max);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// @notice Nonce one above uint64 max should revert with NonceTooLarge.
|
|
37
|
+
function test_nonceAboveUint64Max_reverts() public {
|
|
38
|
+
uint256 tooLargeNonce = uint256(type(uint64).max) + 1;
|
|
39
|
+
|
|
40
|
+
vm.expectRevert(
|
|
41
|
+
abi.encodeWithSelector(JBAddressRegistry.JBAddressRegistry_NonceTooLarge.selector, tooLargeNonce)
|
|
42
|
+
);
|
|
43
|
+
registry.registerAddress(deployer, tooLargeNonce);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// @notice A nonce at uint256 max should revert with NonceTooLarge.
|
|
47
|
+
function test_nonceUint256Max_reverts() public {
|
|
48
|
+
uint256 maxNonce = type(uint256).max;
|
|
49
|
+
|
|
50
|
+
vm.expectRevert(abi.encodeWithSelector(JBAddressRegistry.JBAddressRegistry_NonceTooLarge.selector, maxNonce));
|
|
51
|
+
registry.registerAddress(deployer, maxNonce);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// @notice Fuzz: any nonce above uint64 max should revert.
|
|
55
|
+
function testFuzz_nonceAboveUint64Max_reverts(uint256 nonce) public {
|
|
56
|
+
vm.assume(nonce > type(uint64).max);
|
|
57
|
+
|
|
58
|
+
vm.expectRevert(abi.encodeWithSelector(JBAddressRegistry.JBAddressRegistry_NonceTooLarge.selector, nonce));
|
|
59
|
+
registry.registerAddress(deployer, nonce);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// @notice Fuzz: any nonce within uint64 range should succeed (no revert).
|
|
63
|
+
function testFuzz_nonceWithinUint64Range_succeeds(uint64 nonce) public {
|
|
64
|
+
registry.registerAddress(deployer, uint256(nonce));
|
|
65
|
+
}
|
|
66
|
+
}
|