From 8fcd8d3a3a4ffea6991a33fde866a9fa809aebe8 Mon Sep 17 00:00:00 2001 From: Amie Corso Date: Tue, 19 Aug 2025 08:52:35 -0700 Subject: [PATCH 01/12] tests in working state --- test/Fork/BaseSepoliaForkBase.t.sol | 104 ++++++++++++++++++++++++ test/Fork/ENSIP19DataMigrations.sol | 107 +++++++++++++++++++++++++ test/Fork/ENSIP19LegacyFlows.t.sol | 104 ++++++++++++++++++++++++ test/Fork/ENSIP19NewFlows.sol | 119 ++++++++++++++++++++++++++++ 4 files changed, 434 insertions(+) create mode 100644 test/Fork/BaseSepoliaForkBase.t.sol create mode 100644 test/Fork/ENSIP19DataMigrations.sol create mode 100644 test/Fork/ENSIP19LegacyFlows.t.sol create mode 100644 test/Fork/ENSIP19NewFlows.sol diff --git a/test/Fork/BaseSepoliaForkBase.t.sol b/test/Fork/BaseSepoliaForkBase.t.sol new file mode 100644 index 0000000..09293e6 --- /dev/null +++ b/test/Fork/BaseSepoliaForkBase.t.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {Test} from "forge-std/Test.sol"; +import {RegistrarController} from "src/L2/RegistrarController.sol"; +import {UpgradeableRegistrarController} from "src/L2/UpgradeableRegistrarController.sol"; +import {ENS} from "ens-contracts/registry/ENS.sol"; +import {NameResolver} from "ens-contracts/resolvers/profiles/NameResolver.sol"; +import {IL2ReverseRegistrar} from "src/L2/interface/IL2ReverseRegistrar.sol"; + +import {BASE_ETH_NODE} from "src/util/Constants.sol"; + +contract BaseSepoliaForkBase is Test { + // RPC alias must be configured in foundry.toml as `base-sepolia`. + string internal constant FORK_ALIAS = "base-sepolia"; + + // Addresses from provided Terraform output + address internal constant ENS_REGISTRY = 0x1493b2567056c2181630115660963E13A8E32735; + address internal constant BASE_REGISTRAR = 0xA0c70ec36c010B55E3C434D6c6EbEEC50c705794; + address internal constant LEGACY_GA_CONTROLLER = 0x49aE3cC2e3AA768B1e5654f5D3C6002144A59581; + address internal constant LEGACY_L2_RESOLVER = 0x6533C94869D28fAA8dF77cc63f9e2b2D6Cf77eBA; + address internal constant LEGACY_REVERSE_REGISTRAR = 0xa0A8401ECF248a9375a0a71C4dedc263dA18dCd7; + + address internal constant UPGRADEABLE_CONTROLLER_PROXY = 0x82c858CDF64b3D893Fe54962680edFDDC37e94C8; + address internal constant UPGRADEABLE_L2_RESOLVER_PROXY = 0x85C87e548091f204C2d0350b39ce1874f02197c6; + + // ENS L2 Reverse Registrar (Base Sepolia) per ENS docs + address internal constant ENS_L2_REVERSE_REGISTRAR = 0x00000BeEF055f7934784D6d81b6BC86665630dbA; + + // Useful constants + uint256 internal constant BASE_COINTYPE = (0x80000000 | 0x00002105); + + // Actors + uint256 internal userPk; + address internal user; + + // Interfaces + RegistrarController internal legacyController; + UpgradeableRegistrarController internal upgradeableController; + NameResolver internal legacyResolver; + + function setUp() public virtual { + vm.createSelectFork(FORK_ALIAS); + + // Create a deterministic EOA we control for signing + userPk = uint256(keccak256("basenames.fork.user")); + user = vm.addr(userPk); + + legacyController = RegistrarController(LEGACY_GA_CONTROLLER); + upgradeableController = UpgradeableRegistrarController(UPGRADEABLE_CONTROLLER_PROXY); + legacyResolver = NameResolver(LEGACY_L2_RESOLVER); + } + + function _labelFor(string memory name) internal pure returns (bytes32) { + return keccak256(bytes(name)); + } + + function _nodeFor(string memory name) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(BASE_ETH_NODE, _labelFor(name))); + } + + function _fullName(string memory name) internal pure returns (string memory) { + return string.concat(name, ".base.eth"); + } + + function _baseReverseNode(address addr, bytes32 baseReverseParentNode) internal pure returns (bytes32) { + return keccak256(abi.encodePacked(baseReverseParentNode, _sha3HexAddress(addr))); + } + + function _sha3HexAddress(address addr) internal pure returns (bytes32 ret) { + bytes16 lookup = 0x30313233343536373839616263646566; + assembly { + let i := 40 + let n := addr + let ptr := mload(0x40) + mstore(0x40, add(ptr, 64)) + for {} gt(i, 0) {} { + i := sub(i, 1) + mstore8(add(ptr, i), byte(and(n, 0x0f), lookup)) + n := shr(4, n) + i := sub(i, 1) + mstore8(add(ptr, i), byte(and(n, 0x0f), lookup)) + n := shr(4, n) + if iszero(i) { break } + } + ret := keccak256(ptr, 40) + } + } + + // Build a signature for ENS L2 Reverse Registrar setNameForAddrWithSignature, EIP-191 style + function _buildL2ReverseSignature(string memory fullName, uint256[] memory coinTypes, uint256 expiry) + internal + view + returns (bytes memory) + { + // bytes32 message = keccak256(abi.encodePacked(address(this), selector, addr, expiry, name, coinTypes)).toEthSignedMessageHash(); + bytes4 selector = IL2ReverseRegistrar.setNameForAddrWithSignature.selector; + bytes32 inner = + keccak256(abi.encodePacked(ENS_L2_REVERSE_REGISTRAR, selector, user, expiry, fullName, coinTypes)); + bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", inner)); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(userPk, digest); + return abi.encodePacked(r, s, v); + } +} diff --git a/test/Fork/ENSIP19DataMigrations.sol b/test/Fork/ENSIP19DataMigrations.sol new file mode 100644 index 0000000..7cd7fde --- /dev/null +++ b/test/Fork/ENSIP19DataMigrations.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {BaseSepoliaForkBase} from "./BaseSepoliaForkBase.t.sol"; +import {MigrationController} from "src/L2/MigrationController.sol"; +import {ENS} from "ens-contracts/registry/ENS.sol"; +import {AddrResolver} from "ens-contracts/resolvers/profiles/AddrResolver.sol"; + +interface IL2ReverseRegistrarWithMigration { + function batchSetName(address[] calldata addresses) external; +} + +interface IOwnable { + function owner() external view returns (address); +} + +interface IL2ResolverApprove { + function approve(bytes32 node, address delegate, bool approved) external; +} + +contract ENSIP19DataMigrations is BaseSepoliaForkBase { + address internal constant MIGRATION_CONTROLLER = 0xE8A87034a06425476F2bD6fD14EA038332Cc5e10; + + function test_migration_controller_setBaseForwardAddr() public { + string memory name = "migratefwd"; + bytes32 root = legacyController.rootNode(); + bytes32 node = keccak256(abi.encodePacked(root, _labelFor(name))); + + // Register a name with legacy resolver + RegistrarControllerLike legacy = RegistrarControllerLike(LEGACY_GA_CONTROLLER); + uint256 price = legacy.registerPrice(name, 365 days); + vm.deal(user, price); + vm.prank(user); + legacy.register{value: price}( + RegistrarControllerLike.RegisterRequest({ + name: name, + owner: user, + duration: 365 days, + resolver: LEGACY_L2_RESOLVER, + data: new bytes[](0), + reverseRecord: false + }) + ); + + // Set a default EVM addr on the resolver so there is something to migrate + vm.prank(user); + AddrResolver(LEGACY_L2_RESOLVER).setAddr(node, user); + + // Authorize the MigrationController to write records for this node + vm.prank(user); + IL2ResolverApprove(LEGACY_L2_RESOLVER).approve(node, MIGRATION_CONTROLLER, true); + + uint256 coinType = MigrationController(MIGRATION_CONTROLLER).coinType(); + + // Pre: ENSIP-11 (coinType) record should be empty + bytes memory beforeBytes = AddrResolver(LEGACY_L2_RESOLVER).addr(node, coinType); + assertEq(beforeBytes.length, 0, "pre: ensip-11 addr already set"); + + // Call MigrationController as owner (l2_owner_address) + address ownerAddr = 0xdEC57186e5dB11CcFbb4C932b8f11bD86171CB9D; + bytes32[] memory nodes = new bytes32[](1); + nodes[0] = node; + vm.prank(ownerAddr); + MigrationController(MIGRATION_CONTROLLER).setBaseForwardAddr(nodes); + + // Post: ENSIP-11 (coinType) forward addr set + bytes memory afterBytes = AddrResolver(LEGACY_L2_RESOLVER).addr(node, coinType); + assertGt(afterBytes.length, 0, "post: ensip-11 addr not set"); + } + + function test_l2_reverse_registrar_with_migration_batchSetName() public { + string memory name = "migraterev"; + + // Claim/set old reverse name via legacy flow + vm.prank(user); + IReverseRegistrarLike(LEGACY_REVERSE_REGISTRAR).setNameForAddr(user, user, LEGACY_L2_RESOLVER, _fullName(name)); + + address l2rr = ENS_L2_REVERSE_REGISTRAR; + address rrOwner = IOwnable(l2rr).owner(); + + address[] memory addrs = new address[](1); + addrs[0] = user; + + vm.prank(rrOwner); + IL2ReverseRegistrarWithMigration(l2rr).batchSetName(addrs); + } +} + +interface RegistrarControllerLike { + struct RegisterRequest { + string name; + address owner; + uint256 duration; + address resolver; + bytes[] data; + bool reverseRecord; + } + + function registerPrice(string memory name, uint256 duration) external view returns (uint256); + function register(RegisterRequest calldata request) external payable; +} + +interface IReverseRegistrarLike { + function setNameForAddr(address addr, address owner, address resolver, string memory name) + external + returns (bytes32); +} diff --git a/test/Fork/ENSIP19LegacyFlows.t.sol b/test/Fork/ENSIP19LegacyFlows.t.sol new file mode 100644 index 0000000..b93316a --- /dev/null +++ b/test/Fork/ENSIP19LegacyFlows.t.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {BaseSepoliaForkBase} from "./BaseSepoliaForkBase.t.sol"; +import {RegistrarController} from "src/L2/RegistrarController.sol"; +import {IReverseRegistrar} from "src/L2/interface/IReverseRegistrar.sol"; +import {ENS} from "ens-contracts/registry/ENS.sol"; +import {NameResolver} from "ens-contracts/resolvers/profiles/NameResolver.sol"; +import {BASE_REVERSE_NODE} from "src/util/Constants.sol"; + +contract ENSIP19LegacyFlows is BaseSepoliaForkBase { + function test_register_name_on_legacy() public { + string memory name = "forkleg"; + bytes32 root = legacyController.rootNode(); + bytes32 node = keccak256(abi.encodePacked(root, _labelFor(name))); + + RegistrarController.RegisterRequest memory req = RegistrarController.RegisterRequest({ + name: name, + owner: user, + duration: 365 days, + resolver: LEGACY_L2_RESOLVER, + data: new bytes[](0), + reverseRecord: false + }); + + uint256 price = legacyController.registerPrice(name, req.duration); + + vm.deal(user, price); + vm.startPrank(user); + legacyController.register{value: price}(req); + vm.stopPrank(); + + // Assert resolver set on registry and owner assigned + ENS ens = ENS(ENS_REGISTRY); + address ownerNow = ens.owner(node); + address resolverNow = ens.resolver(node); + assertEq(ownerNow, user, "legacy owner"); + assertEq(resolverNow, LEGACY_L2_RESOLVER, "legacy resolver"); + } + + function test_set_primary_name_on_legacy() public { + string memory name = "forkprimary"; + bytes32 root = legacyController.rootNode(); + bytes32 node = keccak256(abi.encodePacked(root, _labelFor(name))); + + // First register the name with a resolver and no reverse + RegistrarController.RegisterRequest memory req = RegistrarController.RegisterRequest({ + name: name, + owner: user, + duration: 365 days, + resolver: LEGACY_L2_RESOLVER, + data: new bytes[](0), + reverseRecord: false + }); + uint256 price = legacyController.registerPrice(name, req.duration); + vm.deal(user, price); + vm.prank(user); + legacyController.register{value: price}(req); + + // Set primary via legacy ReverseRegistrar directly + vm.prank(user); + IReverseRegistrar(LEGACY_REVERSE_REGISTRAR).setNameForAddr(user, user, LEGACY_L2_RESOLVER, _fullName(name)); + + // Validate reverse record was set on the legacy resolver + bytes32 baseRevNode = _baseReverseNode(user, BASE_REVERSE_NODE); + string memory storedName = NameResolver(LEGACY_L2_RESOLVER).name(baseRevNode); + assertEq(keccak256(bytes(storedName)), keccak256(bytes(_fullName(name))), "reverse name not set"); + + // Forward resolver unchanged + ENS ens = ENS(ENS_REGISTRY); + assertEq(ens.resolver(node), LEGACY_L2_RESOLVER, "resolver unchanged"); + } + + function test_register_with_reverse_sets_primary_via_controller() public { + string memory name = "forklegrev"; + bytes32 root = legacyController.rootNode(); + bytes32 node = keccak256(abi.encodePacked(root, _labelFor(name))); + + RegistrarController.RegisterRequest memory req = RegistrarController.RegisterRequest({ + name: name, + owner: user, + duration: 365 days, + resolver: LEGACY_L2_RESOLVER, + data: new bytes[](0), + reverseRecord: true + }); + + uint256 price = legacyController.registerPrice(name, req.duration); + vm.deal(user, price); + vm.prank(user); + legacyController.register{value: price}(req); + + // Assert reverse was set by the controller calling the ReverseRegistrar + bytes32 baseRevNode = _baseReverseNode(user, BASE_REVERSE_NODE); + string memory storedName = NameResolver(LEGACY_L2_RESOLVER).name(baseRevNode); + string memory expectedFull = string.concat(name, legacyController.rootName()); + assertEq(keccak256(bytes(storedName)), keccak256(bytes(expectedFull)), "reverse name not set by controller"); + + // Also verify forward resolver/owner as a sanity check + ENS ens = ENS(ENS_REGISTRY); + assertEq(ens.owner(node), user); + assertEq(ens.resolver(node), LEGACY_L2_RESOLVER); + } +} diff --git a/test/Fork/ENSIP19NewFlows.sol b/test/Fork/ENSIP19NewFlows.sol new file mode 100644 index 0000000..ac0dc8c --- /dev/null +++ b/test/Fork/ENSIP19NewFlows.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +import {BaseSepoliaForkBase} from "./BaseSepoliaForkBase.t.sol"; +import {UpgradeableRegistrarController} from "src/L2/UpgradeableRegistrarController.sol"; +import {ENS} from "ens-contracts/registry/ENS.sol"; +import {AddrResolver} from "ens-contracts/resolvers/profiles/AddrResolver.sol"; +import {NameResolver} from "ens-contracts/resolvers/profiles/NameResolver.sol"; +import {BASE_REVERSE_NODE} from "src/util/Constants.sol"; + +contract ENSIP19NewFlows is BaseSepoliaForkBase { + uint256 internal constant BASE_SEPOLIA_COINTYPE = 2147568180; + + function test_register_on_new_sets_forward_records_ensip11() public { + string memory name = "forknewfwd"; + bytes32 root = legacyController.rootNode(); + bytes32 node = keccak256(abi.encodePacked(root, keccak256(bytes(name)))); + + bytes[] memory data = new bytes[](2); + // setAddr(bytes32,address) + bytes4 setAddrDefaultSel = bytes4(keccak256("setAddr(bytes32,address)")); + data[0] = abi.encodeWithSelector(setAddrDefaultSel, node, user); + // setAddr(bytes32,uint256,bytes) + bytes4 setAddrCointypeSel = bytes4(keccak256("setAddr(bytes32,uint256,bytes)")); + data[1] = abi.encodeWithSelector(setAddrCointypeSel, node, BASE_SEPOLIA_COINTYPE, _addressToBytes(user)); + + UpgradeableRegistrarController.RegisterRequest memory req = UpgradeableRegistrarController.RegisterRequest({ + name: name, + owner: user, + duration: 365 days, + resolver: UPGRADEABLE_L2_RESOLVER_PROXY, + data: data, + reverseRecord: false, + coinTypes: new uint256[](0), + signatureExpiry: 0, + signature: bytes("") + }); + + uint256 price = upgradeableController.registerPrice(name, req.duration); + vm.deal(user, price); + vm.prank(user); + upgradeableController.register{value: price}(req); + + ENS ens = ENS(ENS_REGISTRY); + address resolverNow = ens.resolver(node); + address ownerNow = ens.owner(node); + assertEq(resolverNow, UPGRADEABLE_L2_RESOLVER_PROXY, "resolver should be upgradeable L2 resolver"); + assertEq(ownerNow, user, "owner should be user"); + + bytes memory coinAddr = AddrResolver(UPGRADEABLE_L2_RESOLVER_PROXY).addr(node, BASE_SEPOLIA_COINTYPE); + assertEq(coinAddr.length, 20, "ensip-11 addr length"); + assertEq(address(bytes20(coinAddr)), user, "ensip-11 addr matches user"); + assertEq(AddrResolver(UPGRADEABLE_L2_RESOLVER_PROXY).addr(node), user, "default addr matches user"); + } + + function test_register_with_reverse_on_new_sets_legacy_reverse() public { + string memory name = "forknewrev"; + bytes32 root = legacyController.rootNode(); + bytes32 node = keccak256(abi.encodePacked(root, keccak256(bytes(name)))); + + UpgradeableRegistrarController.RegisterRequest memory req = UpgradeableRegistrarController.RegisterRequest({ + name: name, + owner: user, + duration: 365 days, + resolver: UPGRADEABLE_L2_RESOLVER_PROXY, + data: new bytes[](0), + reverseRecord: true, + coinTypes: new uint256[](0), + signatureExpiry: 0, + signature: bytes("") + }); + + uint256 price = upgradeableController.registerPrice(name, req.duration); + vm.deal(user, price); + vm.prank(user); + upgradeableController.register{value: price}(req); + + bytes32 baseRevNode = _baseReverseNode(user, BASE_REVERSE_NODE); + string memory storedName = NameResolver(LEGACY_L2_RESOLVER).name(baseRevNode); + string memory expectedFull = string.concat(name, legacyController.rootName()); + assertEq(keccak256(bytes(storedName)), keccak256(bytes(expectedFull)), "legacy reverse name not set"); + + ENS ens = ENS(ENS_REGISTRY); + assertEq(ens.resolver(node), UPGRADEABLE_L2_RESOLVER_PROXY); + assertEq(ens.owner(node), user); + } + + function test_set_primary_on_new_writes_both_paths_no_mock() public { + string memory name = "forknewprim"; + string memory fullName = string.concat(name, legacyController.rootName()); + uint256[] memory coinTypes = new uint256[](1); + coinTypes[0] = BASE_SEPOLIA_COINTYPE; + uint256 expiry = block.timestamp + 30 minutes; + bytes memory signature = _buildL2ReverseSignature(fullName, coinTypes, expiry); + + vm.prank(user); + upgradeableController.setReverseRecord(name, expiry, coinTypes, signature); + + bytes32 baseRevNode = _baseReverseNode(user, BASE_REVERSE_NODE); + string memory storedLegacy = NameResolver(LEGACY_L2_RESOLVER).name(baseRevNode); + assertEq(keccak256(bytes(storedLegacy)), keccak256(bytes(fullName)), "legacy reverse not set"); + + (bool ok, bytes memory ret) = + ENS_L2_REVERSE_REGISTRAR.staticcall(abi.encodeWithSignature("nameForAddr(address)", user)); + if (ok) { + string memory l2Name = abi.decode(ret, (string)); + assertEq(keccak256(bytes(l2Name)), keccak256(bytes(fullName)), "l2 reverse not set"); + } else { + assertTrue(ok || true); + } + } + + function _addressToBytes(address a) internal pure returns (bytes memory b) { + b = new bytes(20); + assembly { + mstore(add(b, 32), mul(a, exp(256, 12))) + } + } +} From 21a1fad7e77fa314f9d51b214755a454e916aecf Mon Sep 17 00:00:00 2001 From: Amie Corso Date: Tue, 19 Aug 2025 11:23:39 -0700 Subject: [PATCH 02/12] fork tests, failing L2RR migration --- src/L2/interface/IL2ReverseRegistrar.sol | 12 +++++ test/Fork/BaseSepoliaForkBase.t.sol | 7 ++- test/Fork/ENSIP19DataMigrations.sol | 65 ++++++++---------------- test/Fork/ENSIP19NewFlows.sol | 18 +++---- 4 files changed, 44 insertions(+), 58 deletions(-) diff --git a/src/L2/interface/IL2ReverseRegistrar.sol b/src/L2/interface/IL2ReverseRegistrar.sol index 18211ed..e913f99 100644 --- a/src/L2/interface/IL2ReverseRegistrar.sol +++ b/src/L2/interface/IL2ReverseRegistrar.sol @@ -15,6 +15,12 @@ interface IL2ReverseRegistrar { /// @param name The name to set. function setNameForAddr(address addr, string memory name) external; + /// @notice Returns the name for an address. + /// + /// @param addr The address to get the name for. + /// @return The name for the address. + function nameForAddr(address addr) external view returns (string memory); + /// @notice Sets the `nameForAddr()` record for the addr provided account using a signature. /// /// @param addr The address to set the name for. @@ -46,4 +52,10 @@ interface IL2ReverseRegistrar { uint256[] memory coinTypes, bytes memory signature ) external; + + /// @notice Migrates the names from the old reverse resolver to the new one. + /// Only callable by the owner. + /// + /// @param addresses The addresses to migrate. + function batchSetName(address[] calldata addresses) external; } diff --git a/test/Fork/BaseSepoliaForkBase.t.sol b/test/Fork/BaseSepoliaForkBase.t.sol index 09293e6..546f65e 100644 --- a/test/Fork/BaseSepoliaForkBase.t.sol +++ b/test/Fork/BaseSepoliaForkBase.t.sol @@ -14,7 +14,7 @@ contract BaseSepoliaForkBase is Test { // RPC alias must be configured in foundry.toml as `base-sepolia`. string internal constant FORK_ALIAS = "base-sepolia"; - // Addresses from provided Terraform output + // Addresses from Terraform output address internal constant ENS_REGISTRY = 0x1493b2567056c2181630115660963E13A8E32735; address internal constant BASE_REGISTRAR = 0xA0c70ec36c010B55E3C434D6c6EbEEC50c705794; address internal constant LEGACY_GA_CONTROLLER = 0x49aE3cC2e3AA768B1e5654f5D3C6002144A59581; @@ -27,9 +27,6 @@ contract BaseSepoliaForkBase is Test { // ENS L2 Reverse Registrar (Base Sepolia) per ENS docs address internal constant ENS_L2_REVERSE_REGISTRAR = 0x00000BeEF055f7934784D6d81b6BC86665630dbA; - // Useful constants - uint256 internal constant BASE_COINTYPE = (0x80000000 | 0x00002105); - // Actors uint256 internal userPk; address internal user; @@ -38,6 +35,7 @@ contract BaseSepoliaForkBase is Test { RegistrarController internal legacyController; UpgradeableRegistrarController internal upgradeableController; NameResolver internal legacyResolver; + IL2ReverseRegistrar internal l2ReverseRegistrar; function setUp() public virtual { vm.createSelectFork(FORK_ALIAS); @@ -49,6 +47,7 @@ contract BaseSepoliaForkBase is Test { legacyController = RegistrarController(LEGACY_GA_CONTROLLER); upgradeableController = UpgradeableRegistrarController(UPGRADEABLE_CONTROLLER_PROXY); legacyResolver = NameResolver(LEGACY_L2_RESOLVER); + l2ReverseRegistrar = IL2ReverseRegistrar(ENS_L2_REVERSE_REGISTRAR); } function _labelFor(string memory name) internal pure returns (bytes32) { diff --git a/test/Fork/ENSIP19DataMigrations.sol b/test/Fork/ENSIP19DataMigrations.sol index 7cd7fde..3e8728a 100644 --- a/test/Fork/ENSIP19DataMigrations.sol +++ b/test/Fork/ENSIP19DataMigrations.sol @@ -5,21 +5,16 @@ import {BaseSepoliaForkBase} from "./BaseSepoliaForkBase.t.sol"; import {MigrationController} from "src/L2/MigrationController.sol"; import {ENS} from "ens-contracts/registry/ENS.sol"; import {AddrResolver} from "ens-contracts/resolvers/profiles/AddrResolver.sol"; - -interface IL2ReverseRegistrarWithMigration { - function batchSetName(address[] calldata addresses) external; -} - -interface IOwnable { - function owner() external view returns (address); -} - -interface IL2ResolverApprove { - function approve(bytes32 node, address delegate, bool approved) external; -} +import {RegistrarController} from "src/L2/RegistrarController.sol"; +import {ReverseRegistrar} from "src/L2/ReverseRegistrar.sol"; +import {L2Resolver} from "src/L2/L2Resolver.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {BASE_REVERSE_NODE} from "src/util/Constants.sol"; +import {NameResolver} from "ens-contracts/resolvers/profiles/NameResolver.sol"; contract ENSIP19DataMigrations is BaseSepoliaForkBase { address internal constant MIGRATION_CONTROLLER = 0xE8A87034a06425476F2bD6fD14EA038332Cc5e10; + address internal constant L2_OWNER = 0xdEC57186e5dB11CcFbb4C932b8f11bD86171CB9D; function test_migration_controller_setBaseForwardAddr() public { string memory name = "migratefwd"; @@ -27,12 +22,12 @@ contract ENSIP19DataMigrations is BaseSepoliaForkBase { bytes32 node = keccak256(abi.encodePacked(root, _labelFor(name))); // Register a name with legacy resolver - RegistrarControllerLike legacy = RegistrarControllerLike(LEGACY_GA_CONTROLLER); - uint256 price = legacy.registerPrice(name, 365 days); + RegistrarController legacyRC = RegistrarController(LEGACY_GA_CONTROLLER); + uint256 price = legacyRC.registerPrice(name, 365 days); vm.deal(user, price); vm.prank(user); - legacy.register{value: price}( - RegistrarControllerLike.RegisterRequest({ + legacyRC.register{value: price}( + RegistrarController.RegisterRequest({ name: name, owner: user, duration: 365 days, @@ -46,9 +41,9 @@ contract ENSIP19DataMigrations is BaseSepoliaForkBase { vm.prank(user); AddrResolver(LEGACY_L2_RESOLVER).setAddr(node, user); - // Authorize the MigrationController to write records for this node - vm.prank(user); - IL2ResolverApprove(LEGACY_L2_RESOLVER).approve(node, MIGRATION_CONTROLLER, true); + // Configure MigrationController as registrar controller on the resolver (as L2 owner) + vm.prank(L2_OWNER); + L2Resolver(LEGACY_L2_RESOLVER).setRegistrarController(MIGRATION_CONTROLLER); uint256 coinType = MigrationController(MIGRATION_CONTROLLER).coinType(); @@ -57,10 +52,9 @@ contract ENSIP19DataMigrations is BaseSepoliaForkBase { assertEq(beforeBytes.length, 0, "pre: ensip-11 addr already set"); // Call MigrationController as owner (l2_owner_address) - address ownerAddr = 0xdEC57186e5dB11CcFbb4C932b8f11bD86171CB9D; bytes32[] memory nodes = new bytes32[](1); nodes[0] = node; - vm.prank(ownerAddr); + vm.prank(L2_OWNER); MigrationController(MIGRATION_CONTROLLER).setBaseForwardAddr(nodes); // Post: ENSIP-11 (coinType) forward addr set @@ -73,35 +67,18 @@ contract ENSIP19DataMigrations is BaseSepoliaForkBase { // Claim/set old reverse name via legacy flow vm.prank(user); - IReverseRegistrarLike(LEGACY_REVERSE_REGISTRAR).setNameForAddr(user, user, LEGACY_L2_RESOLVER, _fullName(name)); + ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).setNameForAddr(user, user, LEGACY_L2_RESOLVER, _fullName(name)); - address l2rr = ENS_L2_REVERSE_REGISTRAR; - address rrOwner = IOwnable(l2rr).owner(); + address rrOwner = Ownable(ENS_L2_REVERSE_REGISTRAR).owner(); address[] memory addrs = new address[](1); addrs[0] = user; vm.prank(rrOwner); - IL2ReverseRegistrarWithMigration(l2rr).batchSetName(addrs); - } -} + l2ReverseRegistrar.batchSetName(addrs); -interface RegistrarControllerLike { - struct RegisterRequest { - string name; - address owner; - uint256 duration; - address resolver; - bytes[] data; - bool reverseRecord; + // Assert L2 reverse registrar stored the migrated name + string memory l2Name = l2ReverseRegistrar.nameForAddr(user); + assertEq(keccak256(bytes(l2Name)), keccak256(bytes(_fullName(name))), "l2 reverse name not migrated"); } - - function registerPrice(string memory name, uint256 duration) external view returns (uint256); - function register(RegisterRequest calldata request) external payable; -} - -interface IReverseRegistrarLike { - function setNameForAddr(address addr, address owner, address resolver, string memory name) - external - returns (bytes32); } diff --git a/test/Fork/ENSIP19NewFlows.sol b/test/Fork/ENSIP19NewFlows.sol index ac0dc8c..874ca58 100644 --- a/test/Fork/ENSIP19NewFlows.sol +++ b/test/Fork/ENSIP19NewFlows.sol @@ -53,7 +53,7 @@ contract ENSIP19NewFlows is BaseSepoliaForkBase { assertEq(AddrResolver(UPGRADEABLE_L2_RESOLVER_PROXY).addr(node), user, "default addr matches user"); } - function test_register_with_reverse_on_new_sets_legacy_reverse() public { + function test_register_with_reverse_on_new_sets_only_legacy_reverse_no_signature() public { string memory name = "forknewrev"; bytes32 root = legacyController.rootNode(); bytes32 node = keccak256(abi.encodePacked(root, keccak256(bytes(name)))); @@ -83,9 +83,13 @@ contract ENSIP19NewFlows is BaseSepoliaForkBase { ENS ens = ENS(ENS_REGISTRY); assertEq(ens.resolver(node), UPGRADEABLE_L2_RESOLVER_PROXY); assertEq(ens.owner(node), user); + + // L2 reverse should NOT be set without signature + string memory l2Name = l2ReverseRegistrar.nameForAddr(user); + assertTrue(keccak256(bytes(l2Name)) != keccak256(bytes(expectedFull)), "l2 reverse should not be set"); } - function test_set_primary_on_new_writes_both_paths_no_mock() public { + function test_set_primary_on_new_writes_both_paths_with_signature() public { string memory name = "forknewprim"; string memory fullName = string.concat(name, legacyController.rootName()); uint256[] memory coinTypes = new uint256[](1); @@ -100,14 +104,8 @@ contract ENSIP19NewFlows is BaseSepoliaForkBase { string memory storedLegacy = NameResolver(LEGACY_L2_RESOLVER).name(baseRevNode); assertEq(keccak256(bytes(storedLegacy)), keccak256(bytes(fullName)), "legacy reverse not set"); - (bool ok, bytes memory ret) = - ENS_L2_REVERSE_REGISTRAR.staticcall(abi.encodeWithSignature("nameForAddr(address)", user)); - if (ok) { - string memory l2Name = abi.decode(ret, (string)); - assertEq(keccak256(bytes(l2Name)), keccak256(bytes(fullName)), "l2 reverse not set"); - } else { - assertTrue(ok || true); - } + string memory l2Name = l2ReverseRegistrar.nameForAddr(user); + assertEq(keccak256(bytes(l2Name)), keccak256(bytes(fullName)), "l2 reverse not set"); } function _addressToBytes(address a) internal pure returns (bytes memory b) { From b4ec0b8a139d5f9e8354a3dab72da3e4837a4340 Mon Sep 17 00:00:00 2001 From: Amie Corso Date: Tue, 19 Aug 2025 16:54:19 -0700 Subject: [PATCH 03/12] checkpoint -- legacy flows working --- test/Fork/BaseSepoliaConstants.sol | 30 ++++++++++ test/Fork/BaseSepoliaForkBase.t.sol | 90 ++++++++++++++++++----------- test/Fork/ENSIP19DataMigrations.sol | 46 +++++++++------ test/Fork/ENSIP19LegacyFlows.t.sol | 20 ++++--- test/Fork/ENSIP19NewFlows.sol | 10 ++-- 5 files changed, 131 insertions(+), 65 deletions(-) create mode 100644 test/Fork/BaseSepoliaConstants.sol diff --git a/test/Fork/BaseSepoliaConstants.sol b/test/Fork/BaseSepoliaConstants.sol new file mode 100644 index 0000000..6fcc454 --- /dev/null +++ b/test/Fork/BaseSepoliaConstants.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.23; + +library BaseSepolia { + // ENS / Basenames addresses on Base Sepolia + address constant REGISTRY = 0x1493b2567056c2181630115660963E13A8E32735; + address constant BASE_REGISTRAR = 0xA0c70ec36c010B55E3C434D6c6EbEEC50c705794; + address constant LEGACY_GA_CONTROLLER = 0x49aE3cC2e3AA768B1e5654f5D3C6002144A59581; + address constant LEGACY_L2_RESOLVER = 0x6533C94869D28fAA8dF77cc63f9e2b2D6Cf77eBA; + // ReverseRegistrar with correct reverse node configured for Base Sepolia + address constant LEGACY_REVERSE_REGISTRAR = 0x876eF94ce0773052a2f81921E70FF25a5e76841f; + // Old reverse registrar with incorrect reverse node configured for Base Sepolia + // address constant LEGACY_REVERSE_REGISTRAR = 0xa0A8401ECF248a9375a0a71C4dedc263dA18dCd7; + + address constant UPGRADEABLE_CONTROLLER_PROXY = 0x82c858CDF64b3D893Fe54962680edFDDC37e94C8; + address constant UPGRADEABLE_L2_RESOLVER_PROXY = 0x85C87e548091f204C2d0350b39ce1874f02197c6; + + // ENS L2 Reverse Registrar (ENS-managed) on Base Sepolia + address constant ENS_L2_REVERSE_REGISTRAR = 0x00000BeEF055f7934784D6d81b6BC86665630dbA; + + // Ops / controllers + address constant L2_OWNER = 0xdEC57186e5dB11CcFbb4C932b8f11bD86171CB9D; + address constant MIGRATION_CONTROLLER = 0xE8A87034a06425476F2bD6fD14EA038332Cc5e10; + + // ENSIP-11 Base Sepolia cointype + uint256 constant BASE_SEPOLIA_COINTYPE = 2147568180; + + // ENSIP-19 Base Sepolia reverse parent node: namehash("80014a34.reverse") + bytes32 constant BASE_SEPOLIA_REVERSE_NODE = 0x9831acb91a733dba6ffe6c6e872dd546b8c24e2dbd225f3616a8c670cbbd8b8a; +} diff --git a/test/Fork/BaseSepoliaForkBase.t.sol b/test/Fork/BaseSepoliaForkBase.t.sol index 546f65e..e473235 100644 --- a/test/Fork/BaseSepoliaForkBase.t.sol +++ b/test/Fork/BaseSepoliaForkBase.t.sol @@ -2,30 +2,39 @@ pragma solidity ^0.8.23; import {Test} from "forge-std/Test.sol"; -import {RegistrarController} from "src/L2/RegistrarController.sol"; -import {UpgradeableRegistrarController} from "src/L2/UpgradeableRegistrarController.sol"; import {ENS} from "ens-contracts/registry/ENS.sol"; import {NameResolver} from "ens-contracts/resolvers/profiles/NameResolver.sol"; -import {IL2ReverseRegistrar} from "src/L2/interface/IL2ReverseRegistrar.sol"; +import {RegistrarController} from "src/L2/RegistrarController.sol"; +import {UpgradeableRegistrarController} from "src/L2/UpgradeableRegistrarController.sol"; +import {IL2ReverseRegistrar} from "src/L2/interface/IL2ReverseRegistrar.sol"; +import {IReverseRegistrar} from "src/L2/interface/IReverseRegistrar.sol"; +import {Sha3} from "src/lib/Sha3.sol"; import {BASE_ETH_NODE} from "src/util/Constants.sol"; +import {BaseSepolia as BaseSepoliaConstants} from "test/Fork/BaseSepoliaConstants.sol"; +import {L2Resolver} from "src/L2/L2Resolver.sol"; +import {ReverseRegistrar} from "src/L2/ReverseRegistrar.sol"; + contract BaseSepoliaForkBase is Test { // RPC alias must be configured in foundry.toml as `base-sepolia`. string internal constant FORK_ALIAS = "base-sepolia"; - // Addresses from Terraform output - address internal constant ENS_REGISTRY = 0x1493b2567056c2181630115660963E13A8E32735; - address internal constant BASE_REGISTRAR = 0xA0c70ec36c010B55E3C434D6c6EbEEC50c705794; - address internal constant LEGACY_GA_CONTROLLER = 0x49aE3cC2e3AA768B1e5654f5D3C6002144A59581; - address internal constant LEGACY_L2_RESOLVER = 0x6533C94869D28fAA8dF77cc63f9e2b2D6Cf77eBA; - address internal constant LEGACY_REVERSE_REGISTRAR = 0xa0A8401ECF248a9375a0a71C4dedc263dA18dCd7; + // Addresses from constants + address internal constant REGISTRY = BaseSepoliaConstants.REGISTRY; + address internal constant BASE_REGISTRAR = BaseSepoliaConstants.BASE_REGISTRAR; + address internal constant LEGACY_GA_CONTROLLER = BaseSepoliaConstants.LEGACY_GA_CONTROLLER; + address internal constant LEGACY_L2_RESOLVER = BaseSepoliaConstants.LEGACY_L2_RESOLVER; + address internal constant LEGACY_REVERSE_REGISTRAR = BaseSepoliaConstants.LEGACY_REVERSE_REGISTRAR; - address internal constant UPGRADEABLE_CONTROLLER_PROXY = 0x82c858CDF64b3D893Fe54962680edFDDC37e94C8; - address internal constant UPGRADEABLE_L2_RESOLVER_PROXY = 0x85C87e548091f204C2d0350b39ce1874f02197c6; + address internal constant UPGRADEABLE_CONTROLLER_PROXY = BaseSepoliaConstants.UPGRADEABLE_CONTROLLER_PROXY; + address internal constant UPGRADEABLE_L2_RESOLVER_PROXY = BaseSepoliaConstants.UPGRADEABLE_L2_RESOLVER_PROXY; - // ENS L2 Reverse Registrar (Base Sepolia) per ENS docs - address internal constant ENS_L2_REVERSE_REGISTRAR = 0x00000BeEF055f7934784D6d81b6BC86665630dbA; + // ENS L2 Reverse Registrar (Base Sepolia) + address internal constant ENS_L2_REVERSE_REGISTRAR = BaseSepoliaConstants.ENS_L2_REVERSE_REGISTRAR; + + // Owners / ops + address internal constant L2_OWNER = BaseSepoliaConstants.L2_OWNER; // Actors uint256 internal userPk; @@ -48,6 +57,38 @@ contract BaseSepoliaForkBase is Test { upgradeableController = UpgradeableRegistrarController(UPGRADEABLE_CONTROLLER_PROXY); legacyResolver = NameResolver(LEGACY_L2_RESOLVER); l2ReverseRegistrar = IL2ReverseRegistrar(ENS_L2_REVERSE_REGISTRAR); + + // Ensure legacy resolver authorizes the configured legacy reverse registrar + // and ensure the reverse parent node is owned by that registrar so claims succeed. + // 1) Align resolver.reverseRegistrar + try L2Resolver(LEGACY_L2_RESOLVER).reverseRegistrar() returns (address currentRR) { + if (currentRR != LEGACY_REVERSE_REGISTRAR) { + vm.prank(L2_OWNER); + L2Resolver(LEGACY_L2_RESOLVER).setReverseRegistrar(LEGACY_REVERSE_REGISTRAR); + } + } catch {} + // 2) Ensure ENS owner(BASE_SEPOLIA_REVERSE_NODE) == LEGACY_REVERSE_REGISTRAR + bytes32 parentNode = BaseSepoliaConstants.BASE_SEPOLIA_REVERSE_NODE; + address currentOwner = ENS(REGISTRY).owner(parentNode); + if (currentOwner != LEGACY_REVERSE_REGISTRAR) { + vm.prank(currentOwner); + ENS(REGISTRY).setOwner(parentNode, LEGACY_REVERSE_REGISTRAR); + } + // 3) Ensure RegistrarController uses the configured legacy reverse registrar + try legacyController.reverseRegistrar() returns (IReverseRegistrar currentLegacyRR) { + if (address(currentLegacyRR) != LEGACY_REVERSE_REGISTRAR) { + address rcOwner = legacyController.owner(); + vm.prank(rcOwner); + legacyController.setReverseRegistrar(IReverseRegistrar(LEGACY_REVERSE_REGISTRAR)); + } + } catch {} + // 4) Approve RegistrarController as a controller on the ReverseRegistrar so it can set names on behalf of users + try ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).setControllerApproval(LEGACY_GA_CONTROLLER, true) {} + catch { + address rrOwner = ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).owner(); + vm.prank(rrOwner); + ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).setControllerApproval(LEGACY_GA_CONTROLLER, true); + } } function _labelFor(string memory name) internal pure returns (bytes32) { @@ -63,27 +104,7 @@ contract BaseSepoliaForkBase is Test { } function _baseReverseNode(address addr, bytes32 baseReverseParentNode) internal pure returns (bytes32) { - return keccak256(abi.encodePacked(baseReverseParentNode, _sha3HexAddress(addr))); - } - - function _sha3HexAddress(address addr) internal pure returns (bytes32 ret) { - bytes16 lookup = 0x30313233343536373839616263646566; - assembly { - let i := 40 - let n := addr - let ptr := mload(0x40) - mstore(0x40, add(ptr, 64)) - for {} gt(i, 0) {} { - i := sub(i, 1) - mstore8(add(ptr, i), byte(and(n, 0x0f), lookup)) - n := shr(4, n) - i := sub(i, 1) - mstore8(add(ptr, i), byte(and(n, 0x0f), lookup)) - n := shr(4, n) - if iszero(i) { break } - } - ret := keccak256(ptr, 40) - } + return keccak256(abi.encodePacked(baseReverseParentNode, Sha3.hexAddress(addr))); } // Build a signature for ENS L2 Reverse Registrar setNameForAddrWithSignature, EIP-191 style @@ -92,7 +113,6 @@ contract BaseSepoliaForkBase is Test { view returns (bytes memory) { - // bytes32 message = keccak256(abi.encodePacked(address(this), selector, addr, expiry, name, coinTypes)).toEthSignedMessageHash(); bytes4 selector = IL2ReverseRegistrar.setNameForAddrWithSignature.selector; bytes32 inner = keccak256(abi.encodePacked(ENS_L2_REVERSE_REGISTRAR, selector, user, expiry, fullName, coinTypes)); diff --git a/test/Fork/ENSIP19DataMigrations.sol b/test/Fork/ENSIP19DataMigrations.sol index 3e8728a..7831902 100644 --- a/test/Fork/ENSIP19DataMigrations.sol +++ b/test/Fork/ENSIP19DataMigrations.sol @@ -1,28 +1,31 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import {BaseSepoliaForkBase} from "./BaseSepoliaForkBase.t.sol"; -import {MigrationController} from "src/L2/MigrationController.sol"; +import {NameResolver} from "ens-contracts/resolvers/profiles/NameResolver.sol"; +import {NameEncoder} from "ens-contracts/utils/NameEncoder.sol"; import {ENS} from "ens-contracts/registry/ENS.sol"; import {AddrResolver} from "ens-contracts/resolvers/profiles/AddrResolver.sol"; +import {console2} from "forge-std/console2.sol"; +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; + import {RegistrarController} from "src/L2/RegistrarController.sol"; import {ReverseRegistrar} from "src/L2/ReverseRegistrar.sol"; import {L2Resolver} from "src/L2/L2Resolver.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {BASE_REVERSE_NODE} from "src/util/Constants.sol"; -import {NameResolver} from "ens-contracts/resolvers/profiles/NameResolver.sol"; +import {MigrationController} from "src/L2/MigrationController.sol"; +import {Sha3} from "src/lib/Sha3.sol"; -contract ENSIP19DataMigrations is BaseSepoliaForkBase { - address internal constant MIGRATION_CONTROLLER = 0xE8A87034a06425476F2bD6fD14EA038332Cc5e10; - address internal constant L2_OWNER = 0xdEC57186e5dB11CcFbb4C932b8f11bD86171CB9D; +import {BaseSepoliaForkBase} from "./BaseSepoliaForkBase.t.sol"; +import {BaseSepolia as BaseSepoliaConstants} from "test/Fork/BaseSepoliaConstants.sol"; +contract ENSIP19DataMigrations is BaseSepoliaForkBase { function test_migration_controller_setBaseForwardAddr() public { string memory name = "migratefwd"; bytes32 root = legacyController.rootNode(); bytes32 node = keccak256(abi.encodePacked(root, _labelFor(name))); // Register a name with legacy resolver - RegistrarController legacyRC = RegistrarController(LEGACY_GA_CONTROLLER); + RegistrarController legacyRC = RegistrarController(BaseSepoliaConstants.LEGACY_GA_CONTROLLER); uint256 price = legacyRC.registerPrice(name, 365 days); vm.deal(user, price); vm.prank(user); @@ -31,34 +34,36 @@ contract ENSIP19DataMigrations is BaseSepoliaForkBase { name: name, owner: user, duration: 365 days, - resolver: LEGACY_L2_RESOLVER, + resolver: BaseSepoliaConstants.LEGACY_L2_RESOLVER, data: new bytes[](0), reverseRecord: false }) ); - // Set a default EVM addr on the resolver so there is something to migrate + // Set a legacy EVM addr record (ETH_COINTYPE) on the resolver so there is something to migrate vm.prank(user); - AddrResolver(LEGACY_L2_RESOLVER).setAddr(node, user); + AddrResolver(BaseSepoliaConstants.LEGACY_L2_RESOLVER).setAddr(node, user); // Configure MigrationController as registrar controller on the resolver (as L2 owner) vm.prank(L2_OWNER); - L2Resolver(LEGACY_L2_RESOLVER).setRegistrarController(MIGRATION_CONTROLLER); + L2Resolver(BaseSepoliaConstants.LEGACY_L2_RESOLVER).setRegistrarController( + BaseSepoliaConstants.MIGRATION_CONTROLLER + ); - uint256 coinType = MigrationController(MIGRATION_CONTROLLER).coinType(); + uint256 coinType = MigrationController(BaseSepoliaConstants.MIGRATION_CONTROLLER).coinType(); // Pre: ENSIP-11 (coinType) record should be empty - bytes memory beforeBytes = AddrResolver(LEGACY_L2_RESOLVER).addr(node, coinType); + bytes memory beforeBytes = AddrResolver(BaseSepoliaConstants.LEGACY_L2_RESOLVER).addr(node, coinType); assertEq(beforeBytes.length, 0, "pre: ensip-11 addr already set"); // Call MigrationController as owner (l2_owner_address) bytes32[] memory nodes = new bytes32[](1); nodes[0] = node; vm.prank(L2_OWNER); - MigrationController(MIGRATION_CONTROLLER).setBaseForwardAddr(nodes); + MigrationController(BaseSepoliaConstants.MIGRATION_CONTROLLER).setBaseForwardAddr(nodes); // Post: ENSIP-11 (coinType) forward addr set - bytes memory afterBytes = AddrResolver(LEGACY_L2_RESOLVER).addr(node, coinType); + bytes memory afterBytes = AddrResolver(BaseSepoliaConstants.LEGACY_L2_RESOLVER).addr(node, coinType); assertGt(afterBytes.length, 0, "post: ensip-11 addr not set"); } @@ -67,13 +72,20 @@ contract ENSIP19DataMigrations is BaseSepoliaForkBase { // Claim/set old reverse name via legacy flow vm.prank(user); - ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).setNameForAddr(user, user, LEGACY_L2_RESOLVER, _fullName(name)); + ReverseRegistrar(BaseSepoliaConstants.LEGACY_REVERSE_REGISTRAR).setNameForAddr( + user, user, BaseSepoliaConstants.LEGACY_L2_RESOLVER, _fullName(name) + ); address rrOwner = Ownable(ENS_L2_REVERSE_REGISTRAR).owner(); address[] memory addrs = new address[](1); addrs[0] = user; + (, bytes32 calculatedBaseReverseNode) = NameEncoder.dnsEncodeName("80014a34.reverse"); + console2.logBytes32(calculatedBaseReverseNode); + bytes32 node = keccak256(abi.encodePacked(calculatedBaseReverseNode, Sha3.hexAddress(user))); + console2.logBytes32(node); + vm.prank(rrOwner); l2ReverseRegistrar.batchSetName(addrs); diff --git a/test/Fork/ENSIP19LegacyFlows.t.sol b/test/Fork/ENSIP19LegacyFlows.t.sol index b93316a..47ed377 100644 --- a/test/Fork/ENSIP19LegacyFlows.t.sol +++ b/test/Fork/ENSIP19LegacyFlows.t.sol @@ -1,12 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import {BaseSepoliaForkBase} from "./BaseSepoliaForkBase.t.sol"; -import {RegistrarController} from "src/L2/RegistrarController.sol"; -import {IReverseRegistrar} from "src/L2/interface/IReverseRegistrar.sol"; import {ENS} from "ens-contracts/registry/ENS.sol"; import {NameResolver} from "ens-contracts/resolvers/profiles/NameResolver.sol"; -import {BASE_REVERSE_NODE} from "src/util/Constants.sol"; + +import {IReverseRegistrar} from "src/L2/interface/IReverseRegistrar.sol"; +import {RegistrarController} from "src/L2/RegistrarController.sol"; + +import {BaseSepoliaForkBase} from "./BaseSepoliaForkBase.t.sol"; +import {BaseSepolia as BaseSepoliaConstants} from "./BaseSepoliaConstants.sol"; contract ENSIP19LegacyFlows is BaseSepoliaForkBase { function test_register_name_on_legacy() public { @@ -31,7 +33,7 @@ contract ENSIP19LegacyFlows is BaseSepoliaForkBase { vm.stopPrank(); // Assert resolver set on registry and owner assigned - ENS ens = ENS(ENS_REGISTRY); + ENS ens = ENS(REGISTRY); address ownerNow = ens.owner(node); address resolverNow = ens.resolver(node); assertEq(ownerNow, user, "legacy owner"); @@ -62,12 +64,12 @@ contract ENSIP19LegacyFlows is BaseSepoliaForkBase { IReverseRegistrar(LEGACY_REVERSE_REGISTRAR).setNameForAddr(user, user, LEGACY_L2_RESOLVER, _fullName(name)); // Validate reverse record was set on the legacy resolver - bytes32 baseRevNode = _baseReverseNode(user, BASE_REVERSE_NODE); + bytes32 baseRevNode = _baseReverseNode(user, BaseSepoliaConstants.BASE_SEPOLIA_REVERSE_NODE); string memory storedName = NameResolver(LEGACY_L2_RESOLVER).name(baseRevNode); assertEq(keccak256(bytes(storedName)), keccak256(bytes(_fullName(name))), "reverse name not set"); // Forward resolver unchanged - ENS ens = ENS(ENS_REGISTRY); + ENS ens = ENS(REGISTRY); assertEq(ens.resolver(node), LEGACY_L2_RESOLVER, "resolver unchanged"); } @@ -91,13 +93,13 @@ contract ENSIP19LegacyFlows is BaseSepoliaForkBase { legacyController.register{value: price}(req); // Assert reverse was set by the controller calling the ReverseRegistrar - bytes32 baseRevNode = _baseReverseNode(user, BASE_REVERSE_NODE); + bytes32 baseRevNode = _baseReverseNode(user, BaseSepoliaConstants.BASE_SEPOLIA_REVERSE_NODE); string memory storedName = NameResolver(LEGACY_L2_RESOLVER).name(baseRevNode); string memory expectedFull = string.concat(name, legacyController.rootName()); assertEq(keccak256(bytes(storedName)), keccak256(bytes(expectedFull)), "reverse name not set by controller"); // Also verify forward resolver/owner as a sanity check - ENS ens = ENS(ENS_REGISTRY); + ENS ens = ENS(REGISTRY); assertEq(ens.owner(node), user); assertEq(ens.resolver(node), LEGACY_L2_RESOLVER); } diff --git a/test/Fork/ENSIP19NewFlows.sol b/test/Fork/ENSIP19NewFlows.sol index 874ca58..695a611 100644 --- a/test/Fork/ENSIP19NewFlows.sol +++ b/test/Fork/ENSIP19NewFlows.sol @@ -1,12 +1,14 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import {BaseSepoliaForkBase} from "./BaseSepoliaForkBase.t.sol"; -import {UpgradeableRegistrarController} from "src/L2/UpgradeableRegistrarController.sol"; import {ENS} from "ens-contracts/registry/ENS.sol"; import {AddrResolver} from "ens-contracts/resolvers/profiles/AddrResolver.sol"; import {NameResolver} from "ens-contracts/resolvers/profiles/NameResolver.sol"; + import {BASE_REVERSE_NODE} from "src/util/Constants.sol"; +import {UpgradeableRegistrarController} from "src/L2/UpgradeableRegistrarController.sol"; + +import {BaseSepoliaForkBase} from "./BaseSepoliaForkBase.t.sol"; contract ENSIP19NewFlows is BaseSepoliaForkBase { uint256 internal constant BASE_SEPOLIA_COINTYPE = 2147568180; @@ -41,7 +43,7 @@ contract ENSIP19NewFlows is BaseSepoliaForkBase { vm.prank(user); upgradeableController.register{value: price}(req); - ENS ens = ENS(ENS_REGISTRY); + ENS ens = ENS(REGISTRY); address resolverNow = ens.resolver(node); address ownerNow = ens.owner(node); assertEq(resolverNow, UPGRADEABLE_L2_RESOLVER_PROXY, "resolver should be upgradeable L2 resolver"); @@ -80,7 +82,7 @@ contract ENSIP19NewFlows is BaseSepoliaForkBase { string memory expectedFull = string.concat(name, legacyController.rootName()); assertEq(keccak256(bytes(storedName)), keccak256(bytes(expectedFull)), "legacy reverse name not set"); - ENS ens = ENS(ENS_REGISTRY); + ENS ens = ENS(REGISTRY); assertEq(ens.resolver(node), UPGRADEABLE_L2_RESOLVER_PROXY); assertEq(ens.owner(node), user); From 9acc0b658a2d7c8b51469c93e0dc2454f79e7e9f Mon Sep 17 00:00:00 2001 From: Amie Corso Date: Tue, 19 Aug 2025 17:15:11 -0700 Subject: [PATCH 04/12] tests passing --- test/Fork/BaseSepoliaForkBase.t.sol | 20 ++++++++++---------- test/Fork/ENSIP19NewFlows.sol | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/test/Fork/BaseSepoliaForkBase.t.sol b/test/Fork/BaseSepoliaForkBase.t.sol index e473235..83e7a44 100644 --- a/test/Fork/BaseSepoliaForkBase.t.sol +++ b/test/Fork/BaseSepoliaForkBase.t.sol @@ -60,21 +60,14 @@ contract BaseSepoliaForkBase is Test { // Ensure legacy resolver authorizes the configured legacy reverse registrar // and ensure the reverse parent node is owned by that registrar so claims succeed. - // 1) Align resolver.reverseRegistrar - try L2Resolver(LEGACY_L2_RESOLVER).reverseRegistrar() returns (address currentRR) { - if (currentRR != LEGACY_REVERSE_REGISTRAR) { - vm.prank(L2_OWNER); - L2Resolver(LEGACY_L2_RESOLVER).setReverseRegistrar(LEGACY_REVERSE_REGISTRAR); - } - } catch {} - // 2) Ensure ENS owner(BASE_SEPOLIA_REVERSE_NODE) == LEGACY_REVERSE_REGISTRAR + // 1) Ensure ENS owner(BASE_SEPOLIA_REVERSE_NODE) == LEGACY_REVERSE_REGISTRAR bytes32 parentNode = BaseSepoliaConstants.BASE_SEPOLIA_REVERSE_NODE; address currentOwner = ENS(REGISTRY).owner(parentNode); if (currentOwner != LEGACY_REVERSE_REGISTRAR) { vm.prank(currentOwner); ENS(REGISTRY).setOwner(parentNode, LEGACY_REVERSE_REGISTRAR); } - // 3) Ensure RegistrarController uses the configured legacy reverse registrar + // 2) Ensure RegistrarController uses the configured legacy reverse registrar try legacyController.reverseRegistrar() returns (IReverseRegistrar currentLegacyRR) { if (address(currentLegacyRR) != LEGACY_REVERSE_REGISTRAR) { address rcOwner = legacyController.owner(); @@ -82,13 +75,20 @@ contract BaseSepoliaForkBase is Test { legacyController.setReverseRegistrar(IReverseRegistrar(LEGACY_REVERSE_REGISTRAR)); } } catch {} - // 4) Approve RegistrarController as a controller on the ReverseRegistrar so it can set names on behalf of users + // 3) Approve RegistrarController as a controller on the ReverseRegistrar so it can set names on behalf of users try ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).setControllerApproval(LEGACY_GA_CONTROLLER, true) {} catch { address rrOwner = ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).owner(); vm.prank(rrOwner); ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).setControllerApproval(LEGACY_GA_CONTROLLER, true); } + // 4) Approve upgradeable controller proxy + try ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).setControllerApproval(UPGRADEABLE_CONTROLLER_PROXY, true) {} + catch { + address rrOwner2 = ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).owner(); + vm.prank(rrOwner2); + ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).setControllerApproval(UPGRADEABLE_CONTROLLER_PROXY, true); + } } function _labelFor(string memory name) internal pure returns (bytes32) { diff --git a/test/Fork/ENSIP19NewFlows.sol b/test/Fork/ENSIP19NewFlows.sol index 695a611..21433c1 100644 --- a/test/Fork/ENSIP19NewFlows.sol +++ b/test/Fork/ENSIP19NewFlows.sol @@ -5,10 +5,10 @@ import {ENS} from "ens-contracts/registry/ENS.sol"; import {AddrResolver} from "ens-contracts/resolvers/profiles/AddrResolver.sol"; import {NameResolver} from "ens-contracts/resolvers/profiles/NameResolver.sol"; -import {BASE_REVERSE_NODE} from "src/util/Constants.sol"; import {UpgradeableRegistrarController} from "src/L2/UpgradeableRegistrarController.sol"; import {BaseSepoliaForkBase} from "./BaseSepoliaForkBase.t.sol"; +import {BaseSepolia as BaseSepoliaConstants} from "./BaseSepoliaConstants.sol"; contract ENSIP19NewFlows is BaseSepoliaForkBase { uint256 internal constant BASE_SEPOLIA_COINTYPE = 2147568180; @@ -77,7 +77,7 @@ contract ENSIP19NewFlows is BaseSepoliaForkBase { vm.prank(user); upgradeableController.register{value: price}(req); - bytes32 baseRevNode = _baseReverseNode(user, BASE_REVERSE_NODE); + bytes32 baseRevNode = _baseReverseNode(user, BaseSepoliaConstants.BASE_SEPOLIA_REVERSE_NODE); string memory storedName = NameResolver(LEGACY_L2_RESOLVER).name(baseRevNode); string memory expectedFull = string.concat(name, legacyController.rootName()); assertEq(keccak256(bytes(storedName)), keccak256(bytes(expectedFull)), "legacy reverse name not set"); @@ -102,7 +102,7 @@ contract ENSIP19NewFlows is BaseSepoliaForkBase { vm.prank(user); upgradeableController.setReverseRecord(name, expiry, coinTypes, signature); - bytes32 baseRevNode = _baseReverseNode(user, BASE_REVERSE_NODE); + bytes32 baseRevNode = _baseReverseNode(user, BaseSepoliaConstants.BASE_SEPOLIA_REVERSE_NODE); string memory storedLegacy = NameResolver(LEGACY_L2_RESOLVER).name(baseRevNode); assertEq(keccak256(bytes(storedLegacy)), keccak256(bytes(fullName)), "legacy reverse not set"); From 35da29f88ad7a9b903bb12beb4851ee51e3583cf Mon Sep 17 00:00:00 2001 From: Amie Corso Date: Wed, 20 Aug 2025 10:03:24 -0700 Subject: [PATCH 05/12] remove setup --- test/Fork/BaseSepoliaForkBase.t.sol | 32 ----------------------------- 1 file changed, 32 deletions(-) diff --git a/test/Fork/BaseSepoliaForkBase.t.sol b/test/Fork/BaseSepoliaForkBase.t.sol index 83e7a44..b9d3223 100644 --- a/test/Fork/BaseSepoliaForkBase.t.sol +++ b/test/Fork/BaseSepoliaForkBase.t.sol @@ -57,38 +57,6 @@ contract BaseSepoliaForkBase is Test { upgradeableController = UpgradeableRegistrarController(UPGRADEABLE_CONTROLLER_PROXY); legacyResolver = NameResolver(LEGACY_L2_RESOLVER); l2ReverseRegistrar = IL2ReverseRegistrar(ENS_L2_REVERSE_REGISTRAR); - - // Ensure legacy resolver authorizes the configured legacy reverse registrar - // and ensure the reverse parent node is owned by that registrar so claims succeed. - // 1) Ensure ENS owner(BASE_SEPOLIA_REVERSE_NODE) == LEGACY_REVERSE_REGISTRAR - bytes32 parentNode = BaseSepoliaConstants.BASE_SEPOLIA_REVERSE_NODE; - address currentOwner = ENS(REGISTRY).owner(parentNode); - if (currentOwner != LEGACY_REVERSE_REGISTRAR) { - vm.prank(currentOwner); - ENS(REGISTRY).setOwner(parentNode, LEGACY_REVERSE_REGISTRAR); - } - // 2) Ensure RegistrarController uses the configured legacy reverse registrar - try legacyController.reverseRegistrar() returns (IReverseRegistrar currentLegacyRR) { - if (address(currentLegacyRR) != LEGACY_REVERSE_REGISTRAR) { - address rcOwner = legacyController.owner(); - vm.prank(rcOwner); - legacyController.setReverseRegistrar(IReverseRegistrar(LEGACY_REVERSE_REGISTRAR)); - } - } catch {} - // 3) Approve RegistrarController as a controller on the ReverseRegistrar so it can set names on behalf of users - try ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).setControllerApproval(LEGACY_GA_CONTROLLER, true) {} - catch { - address rrOwner = ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).owner(); - vm.prank(rrOwner); - ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).setControllerApproval(LEGACY_GA_CONTROLLER, true); - } - // 4) Approve upgradeable controller proxy - try ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).setControllerApproval(UPGRADEABLE_CONTROLLER_PROXY, true) {} - catch { - address rrOwner2 = ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).owner(); - vm.prank(rrOwner2); - ReverseRegistrar(LEGACY_REVERSE_REGISTRAR).setControllerApproval(UPGRADEABLE_CONTROLLER_PROXY, true); - } } function _labelFor(string memory name) internal pure returns (bytes32) { From f6c05ec53880503e6162d3646af74cf7fb8aedf8 Mon Sep 17 00:00:00 2001 From: Amie Corso Date: Wed, 20 Aug 2025 13:22:46 -0700 Subject: [PATCH 06/12] add ci.yml file --- .github/workflows/ci.yml | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..80c689a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: ci +permissions: + contents: read + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + env: + BASE_SEPOLIA_RPC_URL: "https://sepolia.base.org" + steps: + - uses: actions/checkout@v4 + + - uses: foundry-rs/foundry-toolchain@v1 + with: + version: stable + + - name: Cache lib + uses: actions/cache@v4 + with: + path: lib + key: lib-${{ runner.os }}-${{ hashFiles('**/foundry.toml', '**/.gitmodules') }} + + - name: Install deps + run: forge install --no-git --no-commit + + - name: Build + run: forge build --sizes + + - name: Test + run: forge test -vvv + + From 22e04d115fda22358b4b1c57ebb18a2a1ddc10f1 Mon Sep 17 00:00:00 2001 From: Amie Corso Date: Wed, 20 Aug 2025 13:25:15 -0700 Subject: [PATCH 07/12] try again --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 80c689a..8c9f199 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: key: lib-${{ runner.os }}-${{ hashFiles('**/foundry.toml', '**/.gitmodules') }} - name: Install deps - run: forge install --no-git --no-commit + run: forge install --no-git - name: Build run: forge build --sizes From 7bce44c2d2666c2bfeea570cdca8a306c8126b0d Mon Sep 17 00:00:00 2001 From: Amie Corso Date: Wed, 20 Aug 2025 13:30:47 -0700 Subject: [PATCH 08/12] remove the --sizes flag --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c9f199..937c2a3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: run: forge install --no-git - name: Build - run: forge build --sizes + run: forge build - name: Test run: forge test -vvv From 21166af04a5ce413d0d05d596ffa6124fe7f3dab Mon Sep 17 00:00:00 2001 From: Amie Corso Date: Wed, 20 Aug 2025 13:32:47 -0700 Subject: [PATCH 09/12] add BASE_SEPOLIA_RPC_URL to build and test environments --- .github/workflows/ci.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 937c2a3..135b669 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,9 +28,15 @@ jobs: run: forge install --no-git - name: Build - run: forge build + env: + BASE_SEPOLIA_RPC_URL: ${{ env.BASE_SEPOLIA_RPC_URL }} + run: | + echo "BASE_SEPOLIA_RPC_URL=$BASE_SEPOLIA_RPC_URL" + forge build - name: Test + env: + BASE_SEPOLIA_RPC_URL: ${{ env.BASE_SEPOLIA_RPC_URL }} run: forge test -vvv From bcfbfc57e1417ab4b535551a68089ff2de68b23e Mon Sep 17 00:00:00 2001 From: Amie Corso Date: Wed, 20 Aug 2025 13:35:05 -0700 Subject: [PATCH 10/12] add --ffi flag --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 135b669..e5a63e2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,6 @@ jobs: - name: Test env: BASE_SEPOLIA_RPC_URL: ${{ env.BASE_SEPOLIA_RPC_URL }} - run: forge test -vvv + run: forge test -vvv --ffi From 0dfc2f0be5b47f7f6e38b17edfaf11a2e0c9319b Mon Sep 17 00:00:00 2001 From: Amie Corso Date: Wed, 20 Aug 2025 13:38:58 -0700 Subject: [PATCH 11/12] try adding python setup --- .github/workflows/ci.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e5a63e2..0795d3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,16 @@ jobs: with: version: stable + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install Python deps + run: | + python3 -V + pip install --disable-pip-version-check --no-cache-dir eth-abi + - name: Cache lib uses: actions/cache@v4 with: From 1455fdd7cd158de5f8933a2dbbe4e9e6bf083db5 Mon Sep 17 00:00:00 2001 From: Amie Corso Date: Wed, 20 Aug 2025 13:44:54 -0700 Subject: [PATCH 12/12] ugh we already had a test.yml --- .github/workflows/ci.yml | 52 -------------------------------------- .github/workflows/test.yml | 1 + 2 files changed, 1 insertion(+), 52 deletions(-) delete mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 0795d3f..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: ci -permissions: - contents: read - -on: - push: - pull_request: - -jobs: - test: - runs-on: ubuntu-latest - env: - BASE_SEPOLIA_RPC_URL: "https://sepolia.base.org" - steps: - - uses: actions/checkout@v4 - - - uses: foundry-rs/foundry-toolchain@v1 - with: - version: stable - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - - - name: Install Python deps - run: | - python3 -V - pip install --disable-pip-version-check --no-cache-dir eth-abi - - - name: Cache lib - uses: actions/cache@v4 - with: - path: lib - key: lib-${{ runner.os }}-${{ hashFiles('**/foundry.toml', '**/.gitmodules') }} - - - name: Install deps - run: forge install --no-git - - - name: Build - env: - BASE_SEPOLIA_RPC_URL: ${{ env.BASE_SEPOLIA_RPC_URL }} - run: | - echo "BASE_SEPOLIA_RPC_URL=$BASE_SEPOLIA_RPC_URL" - forge build - - - name: Test - env: - BASE_SEPOLIA_RPC_URL: ${{ env.BASE_SEPOLIA_RPC_URL }} - run: forge test -vvv --ffi - - diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 297aabc..286ea33 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,6 +7,7 @@ on: env: FOUNDRY_PROFILE: ci + BASE_SEPOLIA_RPC_URL: "https://sepolia.base.org" jobs: forge-test: