Skip to content

Commit 78a63a7

Browse files
authored
feat(BA-2783): Fork tests for ENSIP-19 migration on Base Sepolia (#140)
1 parent fc5caaa commit 78a63a7

File tree

7 files changed

+455
-0
lines changed

7 files changed

+455
-0
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ on:
77

88
env:
99
FOUNDRY_PROFILE: ci
10+
BASE_SEPOLIA_RPC_URL: "https://sepolia.base.org"
1011

1112
jobs:
1213
forge-test:

src/L2/interface/IL2ReverseRegistrar.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ interface IL2ReverseRegistrar {
1515
/// @param name The name to set.
1616
function setNameForAddr(address addr, string memory name) external;
1717

18+
/// @notice Returns the name for an address.
19+
///
20+
/// @param addr The address to get the name for.
21+
/// @return The name for the address.
22+
function nameForAddr(address addr) external view returns (string memory);
23+
1824
/// @notice Sets the `nameForAddr()` record for the addr provided account using a signature.
1925
///
2026
/// @param addr The address to set the name for.
@@ -46,4 +52,10 @@ interface IL2ReverseRegistrar {
4652
uint256[] memory coinTypes,
4753
bytes memory signature
4854
) external;
55+
56+
/// @notice Migrates the names from the old reverse resolver to the new one.
57+
/// Only callable by the owner.
58+
///
59+
/// @param addresses The addresses to migrate.
60+
function batchSetName(address[] calldata addresses) external;
4961
}

test/Fork/BaseSepoliaConstants.sol

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.23;
3+
4+
library BaseSepolia {
5+
// ENS / Basenames addresses on Base Sepolia
6+
address constant REGISTRY = 0x1493b2567056c2181630115660963E13A8E32735;
7+
address constant BASE_REGISTRAR = 0xA0c70ec36c010B55E3C434D6c6EbEEC50c705794;
8+
address constant LEGACY_GA_CONTROLLER = 0x49aE3cC2e3AA768B1e5654f5D3C6002144A59581;
9+
address constant LEGACY_L2_RESOLVER = 0x6533C94869D28fAA8dF77cc63f9e2b2D6Cf77eBA;
10+
// ReverseRegistrar with correct reverse node configured for Base Sepolia
11+
address constant LEGACY_REVERSE_REGISTRAR = 0x876eF94ce0773052a2f81921E70FF25a5e76841f;
12+
// Old reverse registrar with incorrect reverse node configured for Base Sepolia
13+
// address constant LEGACY_REVERSE_REGISTRAR = 0xa0A8401ECF248a9375a0a71C4dedc263dA18dCd7;
14+
15+
address constant UPGRADEABLE_CONTROLLER_PROXY = 0x82c858CDF64b3D893Fe54962680edFDDC37e94C8;
16+
address constant UPGRADEABLE_L2_RESOLVER_PROXY = 0x85C87e548091f204C2d0350b39ce1874f02197c6;
17+
18+
// ENS L2 Reverse Registrar (ENS-managed) on Base Sepolia
19+
address constant ENS_L2_REVERSE_REGISTRAR = 0x00000BeEF055f7934784D6d81b6BC86665630dbA;
20+
21+
// Ops / controllers
22+
address constant L2_OWNER = 0xdEC57186e5dB11CcFbb4C932b8f11bD86171CB9D;
23+
address constant MIGRATION_CONTROLLER = 0xE8A87034a06425476F2bD6fD14EA038332Cc5e10;
24+
25+
// ENSIP-11 Base Sepolia cointype
26+
uint256 constant BASE_SEPOLIA_COINTYPE = 2147568180;
27+
28+
// ENSIP-19 Base Sepolia reverse parent node: namehash("80014a34.reverse")
29+
bytes32 constant BASE_SEPOLIA_REVERSE_NODE = 0x9831acb91a733dba6ffe6c6e872dd546b8c24e2dbd225f3616a8c670cbbd8b8a;
30+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.23;
3+
4+
import {Test} from "forge-std/Test.sol";
5+
import {ENS} from "ens-contracts/registry/ENS.sol";
6+
import {NameResolver} from "ens-contracts/resolvers/profiles/NameResolver.sol";
7+
8+
import {RegistrarController} from "src/L2/RegistrarController.sol";
9+
import {UpgradeableRegistrarController} from "src/L2/UpgradeableRegistrarController.sol";
10+
import {IL2ReverseRegistrar} from "src/L2/interface/IL2ReverseRegistrar.sol";
11+
import {IReverseRegistrar} from "src/L2/interface/IReverseRegistrar.sol";
12+
import {Sha3} from "src/lib/Sha3.sol";
13+
import {BASE_ETH_NODE} from "src/util/Constants.sol";
14+
15+
import {BaseSepolia as BaseSepoliaConstants} from "test/Fork/BaseSepoliaConstants.sol";
16+
import {L2Resolver} from "src/L2/L2Resolver.sol";
17+
import {ReverseRegistrar} from "src/L2/ReverseRegistrar.sol";
18+
19+
contract BaseSepoliaForkBase is Test {
20+
// RPC alias must be configured in foundry.toml as `base-sepolia`.
21+
string internal constant FORK_ALIAS = "base-sepolia";
22+
23+
// Addresses from constants
24+
address internal constant REGISTRY = BaseSepoliaConstants.REGISTRY;
25+
address internal constant BASE_REGISTRAR = BaseSepoliaConstants.BASE_REGISTRAR;
26+
address internal constant LEGACY_GA_CONTROLLER = BaseSepoliaConstants.LEGACY_GA_CONTROLLER;
27+
address internal constant LEGACY_L2_RESOLVER = BaseSepoliaConstants.LEGACY_L2_RESOLVER;
28+
address internal constant LEGACY_REVERSE_REGISTRAR = BaseSepoliaConstants.LEGACY_REVERSE_REGISTRAR;
29+
30+
address internal constant UPGRADEABLE_CONTROLLER_PROXY = BaseSepoliaConstants.UPGRADEABLE_CONTROLLER_PROXY;
31+
address internal constant UPGRADEABLE_L2_RESOLVER_PROXY = BaseSepoliaConstants.UPGRADEABLE_L2_RESOLVER_PROXY;
32+
33+
// ENS L2 Reverse Registrar (Base Sepolia)
34+
address internal constant ENS_L2_REVERSE_REGISTRAR = BaseSepoliaConstants.ENS_L2_REVERSE_REGISTRAR;
35+
36+
// Owners / ops
37+
address internal constant L2_OWNER = BaseSepoliaConstants.L2_OWNER;
38+
39+
// Actors
40+
uint256 internal userPk;
41+
address internal user;
42+
43+
// Interfaces
44+
RegistrarController internal legacyController;
45+
UpgradeableRegistrarController internal upgradeableController;
46+
NameResolver internal legacyResolver;
47+
IL2ReverseRegistrar internal l2ReverseRegistrar;
48+
49+
function setUp() public virtual {
50+
vm.createSelectFork(FORK_ALIAS);
51+
52+
// Create a deterministic EOA we control for signing
53+
userPk = uint256(keccak256("basenames.fork.user"));
54+
user = vm.addr(userPk);
55+
56+
legacyController = RegistrarController(LEGACY_GA_CONTROLLER);
57+
upgradeableController = UpgradeableRegistrarController(UPGRADEABLE_CONTROLLER_PROXY);
58+
legacyResolver = NameResolver(LEGACY_L2_RESOLVER);
59+
l2ReverseRegistrar = IL2ReverseRegistrar(ENS_L2_REVERSE_REGISTRAR);
60+
}
61+
62+
function _labelFor(string memory name) internal pure returns (bytes32) {
63+
return keccak256(bytes(name));
64+
}
65+
66+
function _nodeFor(string memory name) internal pure returns (bytes32) {
67+
return keccak256(abi.encodePacked(BASE_ETH_NODE, _labelFor(name)));
68+
}
69+
70+
function _fullName(string memory name) internal pure returns (string memory) {
71+
return string.concat(name, ".base.eth");
72+
}
73+
74+
function _baseReverseNode(address addr, bytes32 baseReverseParentNode) internal pure returns (bytes32) {
75+
return keccak256(abi.encodePacked(baseReverseParentNode, Sha3.hexAddress(addr)));
76+
}
77+
78+
// Build a signature for ENS L2 Reverse Registrar setNameForAddrWithSignature, EIP-191 style
79+
function _buildL2ReverseSignature(string memory fullName, uint256[] memory coinTypes, uint256 expiry)
80+
internal
81+
view
82+
returns (bytes memory)
83+
{
84+
bytes4 selector = IL2ReverseRegistrar.setNameForAddrWithSignature.selector;
85+
bytes32 inner =
86+
keccak256(abi.encodePacked(ENS_L2_REVERSE_REGISTRAR, selector, user, expiry, fullName, coinTypes));
87+
bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", inner));
88+
(uint8 v, bytes32 r, bytes32 s) = vm.sign(userPk, digest);
89+
return abi.encodePacked(r, s, v);
90+
}
91+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.23;
3+
4+
import {NameResolver} from "ens-contracts/resolvers/profiles/NameResolver.sol";
5+
import {NameEncoder} from "ens-contracts/utils/NameEncoder.sol";
6+
import {ENS} from "ens-contracts/registry/ENS.sol";
7+
import {AddrResolver} from "ens-contracts/resolvers/profiles/AddrResolver.sol";
8+
import {console2} from "forge-std/console2.sol";
9+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
10+
11+
import {RegistrarController} from "src/L2/RegistrarController.sol";
12+
import {ReverseRegistrar} from "src/L2/ReverseRegistrar.sol";
13+
import {L2Resolver} from "src/L2/L2Resolver.sol";
14+
import {BASE_REVERSE_NODE} from "src/util/Constants.sol";
15+
import {MigrationController} from "src/L2/MigrationController.sol";
16+
import {Sha3} from "src/lib/Sha3.sol";
17+
18+
import {BaseSepoliaForkBase} from "./BaseSepoliaForkBase.t.sol";
19+
import {BaseSepolia as BaseSepoliaConstants} from "test/Fork/BaseSepoliaConstants.sol";
20+
21+
contract ENSIP19DataMigrations is BaseSepoliaForkBase {
22+
function test_migration_controller_setBaseForwardAddr() public {
23+
string memory name = "migratefwd";
24+
bytes32 root = legacyController.rootNode();
25+
bytes32 node = keccak256(abi.encodePacked(root, _labelFor(name)));
26+
27+
// Register a name with legacy resolver
28+
RegistrarController legacyRC = RegistrarController(BaseSepoliaConstants.LEGACY_GA_CONTROLLER);
29+
uint256 price = legacyRC.registerPrice(name, 365 days);
30+
vm.deal(user, price);
31+
vm.prank(user);
32+
legacyRC.register{value: price}(
33+
RegistrarController.RegisterRequest({
34+
name: name,
35+
owner: user,
36+
duration: 365 days,
37+
resolver: BaseSepoliaConstants.LEGACY_L2_RESOLVER,
38+
data: new bytes[](0),
39+
reverseRecord: false
40+
})
41+
);
42+
43+
// Set a legacy EVM addr record (ETH_COINTYPE) on the resolver so there is something to migrate
44+
vm.prank(user);
45+
AddrResolver(BaseSepoliaConstants.LEGACY_L2_RESOLVER).setAddr(node, user);
46+
47+
// Configure MigrationController as registrar controller on the resolver (as L2 owner)
48+
vm.prank(L2_OWNER);
49+
L2Resolver(BaseSepoliaConstants.LEGACY_L2_RESOLVER).setRegistrarController(
50+
BaseSepoliaConstants.MIGRATION_CONTROLLER
51+
);
52+
53+
uint256 coinType = MigrationController(BaseSepoliaConstants.MIGRATION_CONTROLLER).coinType();
54+
55+
// Pre: ENSIP-11 (coinType) record should be empty
56+
bytes memory beforeBytes = AddrResolver(BaseSepoliaConstants.LEGACY_L2_RESOLVER).addr(node, coinType);
57+
assertEq(beforeBytes.length, 0, "pre: ensip-11 addr already set");
58+
59+
// Call MigrationController as owner (l2_owner_address)
60+
bytes32[] memory nodes = new bytes32[](1);
61+
nodes[0] = node;
62+
vm.prank(L2_OWNER);
63+
MigrationController(BaseSepoliaConstants.MIGRATION_CONTROLLER).setBaseForwardAddr(nodes);
64+
65+
// Post: ENSIP-11 (coinType) forward addr set
66+
bytes memory afterBytes = AddrResolver(BaseSepoliaConstants.LEGACY_L2_RESOLVER).addr(node, coinType);
67+
assertGt(afterBytes.length, 0, "post: ensip-11 addr not set");
68+
}
69+
70+
function test_l2_reverse_registrar_with_migration_batchSetName() public {
71+
string memory name = "migraterev";
72+
73+
// Claim/set old reverse name via legacy flow
74+
vm.prank(user);
75+
ReverseRegistrar(BaseSepoliaConstants.LEGACY_REVERSE_REGISTRAR).setNameForAddr(
76+
user, user, BaseSepoliaConstants.LEGACY_L2_RESOLVER, _fullName(name)
77+
);
78+
79+
address rrOwner = Ownable(ENS_L2_REVERSE_REGISTRAR).owner();
80+
81+
address[] memory addrs = new address[](1);
82+
addrs[0] = user;
83+
84+
(, bytes32 calculatedBaseReverseNode) = NameEncoder.dnsEncodeName("80014a34.reverse");
85+
console2.logBytes32(calculatedBaseReverseNode);
86+
bytes32 node = keccak256(abi.encodePacked(calculatedBaseReverseNode, Sha3.hexAddress(user)));
87+
console2.logBytes32(node);
88+
89+
vm.prank(rrOwner);
90+
l2ReverseRegistrar.batchSetName(addrs);
91+
92+
// Assert L2 reverse registrar stored the migrated name
93+
string memory l2Name = l2ReverseRegistrar.nameForAddr(user);
94+
assertEq(keccak256(bytes(l2Name)), keccak256(bytes(_fullName(name))), "l2 reverse name not migrated");
95+
}
96+
}

test/Fork/ENSIP19LegacyFlows.t.sol

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.23;
3+
4+
import {ENS} from "ens-contracts/registry/ENS.sol";
5+
import {NameResolver} from "ens-contracts/resolvers/profiles/NameResolver.sol";
6+
7+
import {IReverseRegistrar} from "src/L2/interface/IReverseRegistrar.sol";
8+
import {RegistrarController} from "src/L2/RegistrarController.sol";
9+
10+
import {BaseSepoliaForkBase} from "./BaseSepoliaForkBase.t.sol";
11+
import {BaseSepolia as BaseSepoliaConstants} from "./BaseSepoliaConstants.sol";
12+
13+
contract ENSIP19LegacyFlows is BaseSepoliaForkBase {
14+
function test_register_name_on_legacy() public {
15+
string memory name = "forkleg";
16+
bytes32 root = legacyController.rootNode();
17+
bytes32 node = keccak256(abi.encodePacked(root, _labelFor(name)));
18+
19+
RegistrarController.RegisterRequest memory req = RegistrarController.RegisterRequest({
20+
name: name,
21+
owner: user,
22+
duration: 365 days,
23+
resolver: LEGACY_L2_RESOLVER,
24+
data: new bytes[](0),
25+
reverseRecord: false
26+
});
27+
28+
uint256 price = legacyController.registerPrice(name, req.duration);
29+
30+
vm.deal(user, price);
31+
vm.startPrank(user);
32+
legacyController.register{value: price}(req);
33+
vm.stopPrank();
34+
35+
// Assert resolver set on registry and owner assigned
36+
ENS ens = ENS(REGISTRY);
37+
address ownerNow = ens.owner(node);
38+
address resolverNow = ens.resolver(node);
39+
assertEq(ownerNow, user, "legacy owner");
40+
assertEq(resolverNow, LEGACY_L2_RESOLVER, "legacy resolver");
41+
}
42+
43+
function test_set_primary_name_on_legacy() public {
44+
string memory name = "forkprimary";
45+
bytes32 root = legacyController.rootNode();
46+
bytes32 node = keccak256(abi.encodePacked(root, _labelFor(name)));
47+
48+
// First register the name with a resolver and no reverse
49+
RegistrarController.RegisterRequest memory req = RegistrarController.RegisterRequest({
50+
name: name,
51+
owner: user,
52+
duration: 365 days,
53+
resolver: LEGACY_L2_RESOLVER,
54+
data: new bytes[](0),
55+
reverseRecord: false
56+
});
57+
uint256 price = legacyController.registerPrice(name, req.duration);
58+
vm.deal(user, price);
59+
vm.prank(user);
60+
legacyController.register{value: price}(req);
61+
62+
// Set primary via legacy ReverseRegistrar directly
63+
vm.prank(user);
64+
IReverseRegistrar(LEGACY_REVERSE_REGISTRAR).setNameForAddr(user, user, LEGACY_L2_RESOLVER, _fullName(name));
65+
66+
// Validate reverse record was set on the legacy resolver
67+
bytes32 baseRevNode = _baseReverseNode(user, BaseSepoliaConstants.BASE_SEPOLIA_REVERSE_NODE);
68+
string memory storedName = NameResolver(LEGACY_L2_RESOLVER).name(baseRevNode);
69+
assertEq(keccak256(bytes(storedName)), keccak256(bytes(_fullName(name))), "reverse name not set");
70+
71+
// Forward resolver unchanged
72+
ENS ens = ENS(REGISTRY);
73+
assertEq(ens.resolver(node), LEGACY_L2_RESOLVER, "resolver unchanged");
74+
}
75+
76+
function test_register_with_reverse_sets_primary_via_controller() public {
77+
string memory name = "forklegrev";
78+
bytes32 root = legacyController.rootNode();
79+
bytes32 node = keccak256(abi.encodePacked(root, _labelFor(name)));
80+
81+
RegistrarController.RegisterRequest memory req = RegistrarController.RegisterRequest({
82+
name: name,
83+
owner: user,
84+
duration: 365 days,
85+
resolver: LEGACY_L2_RESOLVER,
86+
data: new bytes[](0),
87+
reverseRecord: true
88+
});
89+
90+
uint256 price = legacyController.registerPrice(name, req.duration);
91+
vm.deal(user, price);
92+
vm.prank(user);
93+
legacyController.register{value: price}(req);
94+
95+
// Assert reverse was set by the controller calling the ReverseRegistrar
96+
bytes32 baseRevNode = _baseReverseNode(user, BaseSepoliaConstants.BASE_SEPOLIA_REVERSE_NODE);
97+
string memory storedName = NameResolver(LEGACY_L2_RESOLVER).name(baseRevNode);
98+
string memory expectedFull = string.concat(name, legacyController.rootName());
99+
assertEq(keccak256(bytes(storedName)), keccak256(bytes(expectedFull)), "reverse name not set by controller");
100+
101+
// Also verify forward resolver/owner as a sanity check
102+
ENS ens = ENS(REGISTRY);
103+
assertEq(ens.owner(node), user);
104+
assertEq(ens.resolver(node), LEGACY_L2_RESOLVER);
105+
}
106+
}

0 commit comments

Comments
 (0)