Skip to content

Commit c48170a

Browse files
feat(registry): AddressRegistry, the write-once IAddressRegistryV1 concrete
An immutable root authority binds an opaque `bytes32` name to an address, once; nothing, root included, can change one after; and reading an unbound name reverts rather than answering with the zero address, so no caller has to remember to check. That is the whole contract. No rotation, no removal, no upgrade, no admin surface and no second reader — each of those turns a binding from a constant back into a value that can move, which is the one property the registry exists to provide, and the property that makes a deploy-time check of a binding worth anything. `ADDRESS_REGISTRY_ROOT` is a PLACEHOLDER. The root is a constant in the creation code, so it is part of the contract's identity: changing it moves the deterministic deploy address and code hash on every network. No deploy-pin snapshot, deploy suite or pin lib is generated here for that reason, and `AddressRegistryDeployPinsTest` fails if this source and `rain-deploy`'s `LibAddressRegistry` pins ever disagree. The interface comes from `rain-deploy`, which the bump from 0.1.3 to 0.1.6 brings in. That bump also drops `deployAndBroadcast`'s trailing dependency code-hash mapping, which no longer exists upstream.
1 parent 6173d3e commit c48170a

13 files changed

Lines changed: 416 additions & 29 deletions

‎.gas-snapshot‎

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,24 @@
1-
CloneFactoryCloneDeterministicTest:testCloneDeterministicEvent(bytes32,bytes) (runs: 2048, μ: 421457, ~: 371564)
1+
AddressRegistryDeployPinsTest:testAddressRegistryDeploysToPinnedAddress() (gas: 185870)
2+
AddressRegistryDeployPinsTest:testAddressRegistryPinsDeriveFromThisSource() (gas: 1430)
3+
AddressRegistryGetTest:testGetNoGeneratedMappingGetter(bytes32) (runs: 2048, μ: 5460, ~: 5460)
4+
AddressRegistryGetTest:testGetNoOtherEntryPoint(bytes4,bytes32) (runs: 2048, μ: 8746, ~: 8746)
5+
AddressRegistryGetTest:testGetOpaqueNames(address) (runs: 2048, μ: 624044, ~: 624044)
6+
AddressRegistryGetTest:testGetReturnsRegistered(bytes32,address) (runs: 2048, μ: 34917, ~: 34917)
7+
AddressRegistryGetTest:testGetUnsetReverts(bytes32) (runs: 2048, μ: 10868, ~: 10868)
8+
AddressRegistryRegisterTest:testRegisterDistinctNames(bytes32,bytes32,address,address) (runs: 2048, μ: 65290, ~: 65290)
9+
AddressRegistryRegisterTest:testRegisterEvent(bytes32,address) (runs: 2048, μ: 36420, ~: 36420)
10+
AddressRegistryRegisterTest:testRegisterNoEventOnRevert(address,bytes32,address) (runs: 2048, μ: 10763, ~: 10763)
11+
AddressRegistryRegisterTest:testRegisterOnlyRoot(address,bytes32,address) (runs: 2048, μ: 9672, ~: 9672)
12+
AddressRegistryRegisterTest:testRegisterOnlyRootWhenAlreadyBound(address,bytes32,address,address) (runs: 2048, μ: 36210, ~: 36210)
13+
AddressRegistryRegisterTest:testRegisterWriteOnce(bytes32,address,address) (runs: 2048, μ: 36439, ~: 36439)
14+
AddressRegistryRegisterTest:testRegisterWriteOnceSameAccount(bytes32,address) (runs: 2048, μ: 35973, ~: 35973)
15+
AddressRegistryRegisterTest:testRegisterZeroAccount(bytes32) (runs: 2048, μ: 12750, ~: 12750)
16+
CloneFactoryCloneDeterministicTest:testCloneDeterministicEvent(bytes32,bytes) (runs: 2048, μ: 420846, ~: 349101)
217
CloneFactoryCloneDeterministicTest:testCloneDeterministicInitializeFailureFails(bytes32,bytes32) (runs: 2048, μ: 161266, ~: 161266)
3-
CloneFactoryCloneDeterministicTest:testCloneDeterministicManyClonesPerImpl(bytes32,bytes32,bytes) (runs: 2048, μ: 569724, ~: 482275)
4-
CloneFactoryCloneDeterministicTest:testCloneDeterministicMatchesPredict(bytes32,bytes) (runs: 2048, μ: 423089, ~: 372826)
18+
CloneFactoryCloneDeterministicTest:testCloneDeterministicManyClonesPerImpl(bytes32,bytes32,bytes) (runs: 2048, μ: 571796, ~: 437419)
19+
CloneFactoryCloneDeterministicTest:testCloneDeterministicMatchesPredict(bytes32,bytes) (runs: 2048, μ: 422475, ~: 350218)
520
CloneFactoryCloneDeterministicTest:testCloneDeterministicSaltIsAbiEncodeHash(address,bytes32,address) (runs: 2048, μ: 4686, ~: 4686)
6-
CloneFactoryCloneDeterministicTest:testCloneDeterministicSenderScoped(bytes32,bytes,address,address) (runs: 2048, μ: 585442, ~: 485815)
21+
CloneFactoryCloneDeterministicTest:testCloneDeterministicSenderScoped(bytes32,bytes,address,address) (runs: 2048, μ: 590582, ~: 440959)
722
CloneFactoryCloneDeterministicTest:testCloneDeterministicZeroImplementationCodeSize(address,bytes,bytes32) (runs: 2048, μ: 10699, ~: 10678)
823
LibCloneFactoryDeployTaggedConstantsTest:testCloneFactory_0_1_3_CreationDeploysToPinnedAddress() (gas: 240145)
924
LibCloneFactoryDeployTaggedConstantsTest:testCloneFactory_0_1_3_RuntimeHashesToBytecodeHash() (gas: 755)

‎CLAUDE.md‎

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,20 @@ code in this repository.
55

66
## Project Overview
77

8-
rain.factory.deploy is the **deployment** half of `rain.factory`: the concrete
9-
`CloneFactory` contract plus its deployed address + codehash pins. The core
10-
contract `CloneFactory` clones any contract implementing `ICloneableV2` (an
11-
interface it imports from the `rain-factory` Soldeer package) and atomically
12-
initializes it.
8+
rain.factory.deploy holds Rain's Zoltu-deployed concrete contracts and their
9+
deploy pins. Two unrelated contracts live here, sharing only that deploy model:
10+
11+
- `CloneFactory` — the **deployment** half of `rain.factory`: the concrete
12+
contract plus its deployed address + codehash pins. It clones any contract
13+
implementing `ICloneableV2` (an interface it imports from the `rain-factory`
14+
Soldeer package) and atomically initializes it.
15+
- `AddressRegistry` — the implementation of `IAddressRegistryV1`, which arrives
16+
from the `rain-deploy` Soldeer package alongside `LibAddressRegistry` (the
17+
reader) and the cross-network deploy gate. An immutable root authority binds a
18+
`bytes32` name to an address once and forever; reading an unbound name
19+
reverts. It has no deploy pins yet — `ADDRESS_REGISTRY_ROOT` is a placeholder
20+
and the root is part of the creation code, so no snapshot can exist until a
21+
human supplies the real value.
1322

1423
License: LicenseRef-DCL-1.0 (DecentraLicense). All source files must include
1524
SPDX headers.
@@ -68,6 +77,14 @@ as the `rain-factory` Soldeer dependency, so they are read under
6877
- `src/concrete/CloneFactory.sol` — The single concrete implementation of
6978
`ICloneableFactoryV3`. Uses OpenZeppelin `Clones.cloneDeterministic()`; there
7079
is no plain `clone()`.
80+
- `src/concrete/AddressRegistry.sol` — The single concrete implementation of
81+
`IAddressRegistryV1` (from the `rain-deploy` Soldeer package). Two functions,
82+
`register` and `get`, and nothing else: adding rotation, removal, an admin
83+
surface or a non-reverting reader would destroy the write-once property the
84+
contract exists for. `ADDRESS_REGISTRY_ROOT` is a placeholder until a human
85+
supplies the real root; because it is a constant in the creation code, it is
86+
part of the contract's identity, and `rain-deploy`'s `LibAddressRegistry` pins
87+
must be re-derived whenever it changes.
7188
- `src/lib/LibCloneFactoryDeploy.sol` — Deterministic deployment address and
7289
codehash constants (generated; aliases the current tag's
7390
`src/generated/<tag>/` snapshot).
@@ -103,6 +120,12 @@ A deploy is a human-dispatched run of the `Manual sol artifacts` workflow
103120
(`workflow_dispatch` → `rainix-manual-sol-artifacts`), never a merge and never
104121
part of the release workflow.
105122

123+
`AddressRegistry` is not deployable yet and has no suite in `script/Deploy.sol`,
124+
no `src/generated/<tag>/` snapshot and no pin lib. Its root authority is a
125+
placeholder, and the root is part of the creation code, so any snapshot
126+
generated now would pin an address nobody can use. Supplying the real root is
127+
what unblocks generating them.
128+
106129
## Releases and versioning
107130

108131
This is a **deploy repo**, not a library repo, so nothing publishes on merge:

‎README.md‎

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,39 @@
11
# rain.factory.deploy
22

3-
The **deployment** half of `rain.factory`: the concrete `CloneFactory` contract,
4-
its deployed address + codehash pins (`LibCloneFactoryDeploy`), the frozen
5-
per-tag deploy-pin snapshots under `src/generated/<tag>/`, and the deploy
6-
script.
7-
8-
The **library** half — the `ICloneable*` interfaces — lives in
9-
[`rain.factory`](https://github.com/rainlanguage/rain.factory) and is imported
10-
here as the `rain-factory` Soldeer package. Consumers that need only the
11-
interfaces depend on `rain-factory`; consumers that need the deployed
12-
address/codehash pins depend on `rain-factory-deploy`.
3+
Rain's Zoltu-deployed concrete contracts and their deploy pins. Two unrelated
4+
contracts live here, sharing only that deploy model:
5+
6+
- **`CloneFactory`** — the **deployment** half of `rain.factory`: the concrete
7+
contract, its deployed address + codehash pins (`LibCloneFactoryDeploy`), the
8+
frozen per-tag deploy-pin snapshots under `src/generated/<tag>/`, and the
9+
deploy script. The **library** half — the `ICloneable*` interfaces — lives in
10+
[`rain.factory`](https://github.com/rainlanguage/rain.factory) and is imported
11+
here as the `rain-factory` Soldeer package. Consumers that need only the
12+
interfaces depend on `rain-factory`; consumers that need the deployed
13+
address/codehash pins depend on `rain-factory-deploy`.
14+
- **`AddressRegistry`** — the implementation of `IAddressRegistryV1`, which is
15+
the same split: the interface, the library that reads a registered address,
16+
and the cross-network deploy gate all live in
17+
[`rain.deploy`](https://github.com/rainlanguage/rain.deploy) and arrive here
18+
as the `rain-deploy` Soldeer package.
19+
20+
## `AddressRegistry`
21+
22+
An immutable root authority binds a `bytes32` name to an address, once, forever;
23+
anyone reads a bound name; reading an unbound name reverts. There is no
24+
rotation, no removal, no upgrade and no admin surface, because a binding that
25+
can move is not worth checking at deploy time.
26+
27+
Names are opaque. Nothing here says how one is derived, and nothing here should.
28+
29+
`ADDRESS_REGISTRY_ROOT` in `src/concrete/AddressRegistry.sol` is currently a
30+
**placeholder**. The root is a constant in the creation code, so it is part of
31+
the contract's identity: changing it changes the deterministic deploy address
32+
and code hash on every network. A human must supply the real root before this
33+
contract is deployed anywhere or given a deploy-pin snapshot, and
34+
`rain-deploy`'s `LibAddressRegistry` pins must be re-derived from the resulting
35+
creation code at the same time. `AddressRegistryDeployPinsTest` fails if those
36+
two ever disagree.
1337

1438
## Releases
1539

‎foundry.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ runs = 2048
3434
forge-std = "1.16.1"
3535
"@openzeppelin-contracts" = "5.6.1"
3636
"rain-extrospection" = "0.1.1"
37-
"rain-deploy" = "0.1.3"
37+
"rain-deploy" = "0.1.6"
3838
"rain-sol-codegen" = "0.1.0"
3939
"rain-factory" = "0.1.5"
4040

‎script/BuildPointers.sol‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pragma solidity =0.8.25;
55
import {Script} from "forge-std-1.16.1/src/Script.sol";
66
import {LibCodeGen} from "rain-sol-codegen-0.1.0/src/lib/LibCodeGen.sol";
77
import {LibFs} from "rain-sol-codegen-0.1.0/src/lib/LibFs.sol";
8-
import {LibRainDeploy} from "rain-deploy-0.1.3/src/lib/LibRainDeploy.sol";
8+
import {LibRainDeploy} from "rain-deploy-0.1.6/src/lib/LibRainDeploy.sol";
99
import {CloneFactory} from "../src/concrete/CloneFactory.sol";
1010

1111
/// @title BuildPointers

‎script/Deploy.sol‎

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ pragma solidity =0.8.25;
44

55
import {Script} from "forge-std-1.16.1/src/Script.sol";
66
import {CloneFactory} from "../src/concrete/CloneFactory.sol";
7-
import {LibRainDeploy} from "rain-deploy-0.1.3/src/lib/LibRainDeploy.sol";
7+
import {LibRainDeploy} from "rain-deploy-0.1.6/src/lib/LibRainDeploy.sol";
88
import {LibCloneFactoryDeploy} from "../src/lib/LibCloneFactoryDeploy.sol";
99

1010
/// @dev Hash of the "clone-factory" deployment suite string.
@@ -13,8 +13,6 @@ bytes32 constant DEPLOYMENT_SUITE_CLONE_FACTORY = keccak256("clone-factory");
1313
/// @title Deploy
1414
/// @notice A script that deploys a CloneFactory.
1515
contract Deploy is Script {
16-
mapping(string => mapping(address => bytes32)) internal sDepCodeHashes;
17-
1816
function run() external {
1917
uint256 deployerPrivateKey = vm.envUint("DEPLOYMENT_KEY");
2018

@@ -28,8 +26,7 @@ contract Deploy is Script {
2826
"src/concrete/CloneFactory.sol:CloneFactory",
2927
LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_ADDRESS,
3028
LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_CODEHASH,
31-
new address[](0),
32-
sDepCodeHashes
29+
new address[](0)
3330
);
3431
} else {
3532
revert(

‎src/concrete/AddressRegistry.sol‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// SPDX-License-Identifier: LicenseRef-DCL-1.0
2+
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
3+
pragma solidity =0.8.25;
4+
5+
import {IAddressRegistryV1} from "rain-deploy-0.1.6/src/interface/IAddressRegistryV1.sol";
6+
7+
/// @dev PLACEHOLDER ROOT AUTHORITY. THIS IS NOT A REAL ROOT.
8+
///
9+
/// The only account that may bind a name. It is a compile-time constant, not
10+
/// storage, so it can never be rotated, and it is part of the creation code, so
11+
/// changing it changes the deterministic deploy address and code hash of
12+
/// `AddressRegistry` on every network.
13+
///
14+
/// A human MUST replace this value with the intended root before any deploy-pin
15+
/// snapshot is generated for this contract, and the pins in `rain-deploy`'s
16+
/// `LibAddressRegistry` MUST be regenerated from the resulting creation code.
17+
address constant ADDRESS_REGISTRY_ROOT = address(0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD);
18+
19+
/// @title AddressRegistry
20+
/// @notice The whole of `IAddressRegistryV1`: an immutable root authority binds
21+
/// a `bytes32` name that is unbound, anyone reads a name that is bound, and a
22+
/// read of an unbound name reverts.
23+
///
24+
/// There is deliberately nothing else. No rotation, no removal, no upgrade, no
25+
/// pause, no admin surface, and no reader that returns the zero address for an
26+
/// unbound name. Every one of those would turn a binding from a constant back
27+
/// into a value that can move, which is the single property the registry
28+
/// exists to provide.
29+
///
30+
/// The storage mapping is `internal` rather than `public` for that reason: a
31+
/// public mapping's generated getter answers an unbound name with the zero
32+
/// address, which is exactly the silent failure `get` reverts to prevent.
33+
contract AddressRegistry is IAddressRegistryV1 {
34+
/// The bindings. Not `public`: the only reader is `get`, which reverts on an
35+
/// unbound name. A name maps to the zero address if and only if it is
36+
/// unbound, which is why `register` rejects the zero address.
37+
mapping(bytes32 name => address account) internal sAddresses;
38+
39+
/// @inheritdoc IAddressRegistryV1
40+
function register(bytes32 name, address account) external {
41+
if (msg.sender != ADDRESS_REGISTRY_ROOT) {
42+
revert NotRoot(msg.sender);
43+
}
44+
if (account == address(0)) {
45+
revert ZeroAccount(name);
46+
}
47+
address registered = sAddresses[name];
48+
// Write-once. Root has no more authority here than anyone else: an
49+
// existing binding is never overwritten, not even with the same
50+
// address.
51+
if (registered != address(0)) {
52+
revert NameAlreadyRegistered(name, registered);
53+
}
54+
sAddresses[name] = account;
55+
emit Register(name, account);
56+
}
57+
58+
/// @inheritdoc IAddressRegistryV1
59+
function get(bytes32 name) external view returns (address account) {
60+
account = sAddresses[name];
61+
if (account == address(0)) {
62+
revert NameNotRegistered(name);
63+
}
64+
}
65+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// SPDX-License-Identifier: LicenseRef-DCL-1.0
2+
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
3+
pragma solidity =0.8.25;
4+
5+
import {Test} from "forge-std-1.16.1/src/Test.sol";
6+
7+
import {LibRainDeploy} from "rain-deploy-0.1.6/src/lib/LibRainDeploy.sol";
8+
import {LibAddressRegistry} from "rain-deploy-0.1.6/src/lib/LibAddressRegistry.sol";
9+
import {AddressRegistry} from "../../../src/concrete/AddressRegistry.sol";
10+
11+
/// @title AddressRegistryDeployPinsTest
12+
/// @notice `rain-deploy`'s `LibAddressRegistry` pins the deterministic address
13+
/// and code hash of the `AddressRegistry` in this repo, and consumers resolve
14+
/// names through those pins. The root authority is a constant in this contract's
15+
/// creation code, so changing it moves both pins; this suite is what makes that
16+
/// loud instead of silent.
17+
contract AddressRegistryDeployPinsTest is Test {
18+
/// The pins MUST be derivable from this source without deploying anything:
19+
/// the Zoltu factory is `CREATE2` over its calldata with a zero salt, so the
20+
/// address is a pure function of the creation code, and the code hash is
21+
/// `keccak256` of the runtime code that creation code leaves behind.
22+
function testAddressRegistryPinsDeriveFromThisSource() external pure {
23+
assertEq(LibRainDeploy.zoltuAddress(type(AddressRegistry).creationCode), LibAddressRegistry.ADDRESS_REGISTRY);
24+
assertEq(keccak256(type(AddressRegistry).runtimeCode), LibAddressRegistry.ADDRESS_REGISTRY_CODEHASH);
25+
}
26+
27+
/// Actually deploying this contract's creation code through the Zoltu
28+
/// factory MUST land at the pinned address with the pinned code hash, so the
29+
/// derivation is checked against the factory rather than only against
30+
/// itself.
31+
function testAddressRegistryDeploysToPinnedAddress() external {
32+
LibRainDeploy.etchZoltuFactory(vm);
33+
34+
address deployed = LibRainDeploy.deployZoltu(type(AddressRegistry).creationCode);
35+
36+
assertEq(deployed, LibAddressRegistry.ADDRESS_REGISTRY);
37+
assertEq(deployed.codehash, LibAddressRegistry.ADDRESS_REGISTRY_CODEHASH);
38+
assertEq(keccak256(deployed.code), LibAddressRegistry.ADDRESS_REGISTRY_CODEHASH);
39+
}
40+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// SPDX-License-Identifier: LicenseRef-DCL-1.0
2+
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
3+
pragma solidity =0.8.25;
4+
5+
import {Test} from "forge-std-1.16.1/src/Test.sol";
6+
7+
import {IAddressRegistryV1} from "rain-deploy-0.1.6/src/interface/IAddressRegistryV1.sol";
8+
import {AddressRegistry, ADDRESS_REGISTRY_ROOT} from "../../../src/concrete/AddressRegistry.sol";
9+
10+
/// @title AddressRegistryGetTest
11+
/// @notice A test suite for `AddressRegistry.get`: it answers a bound name with
12+
/// its address and an unbound name with a revert, and it is the only reader.
13+
contract AddressRegistryGetTest is Test {
14+
/// The registry under test. Stateful, so a fresh one per test.
15+
AddressRegistry internal sRegistry;
16+
17+
function setUp() external {
18+
sRegistry = new AddressRegistry();
19+
}
20+
21+
/// A read of an unbound name reverts rather than returning the zero
22+
/// address, so a caller cannot proceed on a name nobody bound by forgetting
23+
/// to check.
24+
function testGetUnsetReverts(bytes32 name) external {
25+
vm.expectRevert(abi.encodeWithSelector(IAddressRegistryV1.NameNotRegistered.selector, name));
26+
sRegistry.get(name);
27+
}
28+
29+
/// A read of a bound name returns exactly what was bound, and reading does
30+
/// not consume or alter the binding.
31+
function testGetReturnsRegistered(bytes32 name, address account) external {
32+
vm.assume(account != address(0));
33+
34+
vm.prank(ADDRESS_REGISTRY_ROOT);
35+
sRegistry.register(name, account);
36+
37+
assertEq(sRegistry.get(name), account);
38+
assertEq(sRegistry.get(name), account);
39+
}
40+
41+
/// Names are opaque: nothing about a name's bytes changes how it is stored
42+
/// or read, including names a string-hashing convention would never
43+
/// produce.
44+
function testGetOpaqueNames(address account) external {
45+
vm.assume(account != address(0));
46+
47+
bytes32[3] memory names = [bytes32(0), bytes32(uint256(1)), bytes32(type(uint256).max)];
48+
for (uint256 i = 0; i < names.length; i++) {
49+
AddressRegistry registry = new AddressRegistry();
50+
vm.prank(ADDRESS_REGISTRY_ROOT);
51+
registry.register(names[i], account);
52+
assertEq(registry.get(names[i]), account);
53+
}
54+
}
55+
56+
/// `get` is the only reader. The bindings mapping is not `public`, so the
57+
/// getter a `public` mapping would generate — which answers an unbound name
58+
/// with the zero address, the exact silent failure `get` reverts to prevent
59+
/// — does not exist.
60+
function testGetNoGeneratedMappingGetter(bytes32 name) external {
61+
(bool success,) = address(sRegistry).call(abi.encodeWithSignature("sAddresses(bytes32)", name));
62+
assertFalse(success);
63+
}
64+
65+
/// There is no other entry point at all: no fallback, no receive, and
66+
/// nothing beyond the two `IAddressRegistryV1` functions, so an unknown
67+
/// selector reverts instead of being silently absorbed.
68+
function testGetNoOtherEntryPoint(bytes4 selector, bytes32 name) external {
69+
vm.assume(selector != IAddressRegistryV1.get.selector);
70+
vm.assume(selector != IAddressRegistryV1.register.selector);
71+
72+
(bool success,) = address(sRegistry).call(abi.encodeWithSelector(selector, name, address(this)));
73+
assertFalse(success);
74+
}
75+
}

0 commit comments

Comments
 (0)