Skip to content

Add UpgradeableRegistrarController #123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 48 commits into from
May 28, 2025
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
c307e78
Add upgradeable RegistrarController, make it EIP-7201 compliant'
stevieraykatz Oct 1, 2024
a578e14
Add interface to new ENS contract, add support for setting primary to…
stevieraykatz Oct 1, 2024
5dfed4a
Add initializer for reverse resolver contract
stevieraykatz Oct 1, 2024
43a53a7
Add shim for migration period
stevieraykatz Oct 1, 2024
fc735bf
Add tests for Shim
stevieraykatz Oct 2, 2024
7a0d049
lint
stevieraykatz Oct 2, 2024
8151302
Testing start
stevieraykatz Oct 10, 2024
1fc8be7
chore: forge fmt
abdulla-cb Oct 31, 2024
15ebd14
chore: typos
abdulla-cb Oct 31, 2024
d256095
forge install: openzeppelin-contracts-upgradeable
abdulla-cb Oct 31, 2024
5214bc2
feat: use v5.0.0 of openzeppelin-contracts-upgradeable
abdulla-cb Oct 31, 2024
34a313a
feat: update tests to use OpenZeppelin Upgradeable
abdulla-cb Nov 1, 2024
78e3f28
chore: fix typo
abdulla-cb Nov 1, 2024
5e64d7d
Update interface to ENS reverse resolver
stevieraykatz Apr 16, 2025
5f78d9f
Remove launch logic
stevieraykatz Apr 16, 2025
d747ca0
remove shim from this branch
stevieraykatz Apr 16, 2025
c2bc49f
Add support for new cointypes array in reverse set
stevieraykatz Apr 16, 2025
48feaf1
Add cointypes to test helper
stevieraykatz Apr 16, 2025
6211b51
Remove test for removed method
stevieraykatz Apr 16, 2025
0e17adf
Merge branch 'main' into upgradeable-registrar
stevieraykatz Apr 16, 2025
148cb2c
attempt to fix ci
stevieraykatz Apr 17, 2025
eb85c2e
Cleanup test mocks
stevieraykatz Apr 17, 2025
50a2c3f
Remove launch logic, cleanup test base
stevieraykatz Apr 17, 2025
438d626
Fix missing refs from rename
stevieraykatz Apr 17, 2025
91ce6d5
Remove reference to unused test var
stevieraykatz Apr 17, 2025
2b85da9
fix _getExpiry
stevieraykatz Apr 17, 2025
e35b156
Fix Register test to use a duration
stevieraykatz Apr 17, 2025
752aeb3
lint
stevieraykatz Apr 17, 2025
3de1f8f
fix natspec
stevieraykatz Apr 17, 2025
cadfc45
Fixes to natspec and typos per comments on PR #95
stevieraykatz Apr 18, 2025
cdd4326
Add registrar controller switch-over integration test
stevieraykatz Apr 22, 2025
4317ccc
chore: cleanup logging, formatting
stevieraykatz Apr 22, 2025
6b124db
feat: add grace period renewal test
stevieraykatz Apr 23, 2025
c1a274a
chore: typos and gas
stevieraykatz Apr 23, 2025
44be9dc
Merge branch 'main' into upgradeable-registrar
stevieraykatz May 7, 2025
9d91fa3
Fix ext link ref to branch with updated code
stevieraykatz May 7, 2025
3e0a849
add interface for new reverse registrar
stevieraykatz May 7, 2025
465e843
lint interface
stevieraykatz May 7, 2025
0102186
refactor reverse registration to leverage updated reverse registrar v2
stevieraykatz May 7, 2025
e48a19d
finish refactor to IReverseRegistrarV2
stevieraykatz May 7, 2025
b5a815c
Fix tests to account for new reverse registrar v2
stevieraykatz May 9, 2025
cce8e36
lint
stevieraykatz May 9, 2025
51dc6e9
Nits from PR comments
stevieraykatz May 27, 2025
d8cb250
cleanup tests based on renamings
stevieraykatz May 27, 2025
5a956e9
update submodules, pin solady version
stevieraykatz May 27, 2025
a3eb0c8
Revert "update submodules, pin solady version"
stevieraykatz May 27, 2025
73afd8b
Natspec and cleanup from PR
stevieraykatz May 28, 2025
ad21ff6
Reorder modifiers
stevieraykatz May 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@
[submodule "lib/eas-contracts"]
path = lib/eas-contracts
url = https://github.com/ethereum-attestation-service/eas-contracts
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/openzeppelin/openzeppelin-contracts-upgradeable
14 changes: 12 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,19 @@
src = "src"
out = "out"
libs = ["lib"]
remappings = ["@ensdomains/buffer/=lib/buffer"]
remappings = [
"@ensdomains/buffer/=lib/buffer",
"solady/=lib/solady/src/",
"forge-std/=lib/forge-std/src/",
"ens-contracts/=lib/ens-contracts/contracts/",
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"openzeppelin-contracts/=lib/openzeppelin-contracts",
"eas-contracts/=lib/eas-contracts/contracts/",
"verifications/=lib/verifications/src",
"openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/"
]
fs_permissions = [{access = "read", path = "./script/premint/"}]
auto_detect_remappings = false

[rpc_endpoints]
sepolia="${SEPOLIA_RPC_URL}"
Expand All @@ -12,4 +23,3 @@ base-sepolia="${BASE_SEPOLIA_RPC_URL}"
[etherscan]
sepolia={url = "https://api-sepolia.etherscan.io/api", key = "${ETHERSCAN_API_KEY}"}
base-sepolia={url = "https://api-sepolia.basescan.org/api", key = "${BASE_ETHERSCAN_API_KEY}"}

1 change: 1 addition & 0 deletions lib/openzeppelin-contracts-upgradeable
661 changes: 661 additions & 0 deletions src/L2/UpgradeableRegistrarController.sol

Large diffs are not rendered by default.

48 changes: 48 additions & 0 deletions src/L2/interface/IL2ReverseRegistrar.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Interface for the L2 Reverse Registrar.
interface IL2ReverseRegistrar {
/// @notice Sets the `nameForAddr()` record for the calling account.
///
/// @param name The name to set.
function setName(string memory name) external;

/// @notice Sets the `nameForAddr()` record for the addr provided account.
///
/// @param addr The address to set the name for.
/// @param name The name to set.
function setNameForAddr(address addr, string memory name) external;

/// @notice Sets the `nameForAddr()` record for the addr provided account using a signature.
///
/// @param addr The address to set the name for.
/// @param name The name to set.
/// @param coinTypes The coin types to set. Must be inclusive of the coin type for the contract.
/// @param signatureExpiry Date when the signature expires.
/// @param signature The signature from the addr.
function setNameForAddrWithSignature(
address addr,
uint256 signatureExpiry,
string memory name,
uint256[] memory coinTypes,
bytes memory signature
) external;

/// @notice Sets the `nameForAddr()` record for the contract provided that is owned with `Ownable`.
///
/// @param contractAddr The address of the contract to set the name for (implementing Ownable).
/// @param owner The owner of the contract (via Ownable).
/// @param signatureExpiry The expiry of the signature.
/// @param name The name to set.
/// @param coinTypes The coin types to set. Must be inclusive of the coin type for the contract.
/// @param signature The signature of an address that will return true on isValidSignature for the owner.
function setNameForOwnableWithSignature(
address contractAddr,
address owner,
uint256 signatureExpiry,
string memory name,
uint256[] memory coinTypes,
bytes memory signature
) external;
}
2 changes: 1 addition & 1 deletion test/RegistrarController/RegisterPrice.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {IPriceOracle} from "src/L2/interface/IPriceOracle.sol";
contract RegisterPrice is RegistrarControllerBase {
function test_returnsRegisterPrice_fromPricingOracle() public view {
uint256 retPrice = controller.registerPrice(name, 0);
assertEq(retPrice, prices.DEFAULT_BASE_WEI() + prices.DEFAULT_PERMIUM_WEI());
assertEq(retPrice, prices.DEFAULT_BASE_WEI() + prices.DEFAULT_PREMIUM_WEI());
}

function test_fuzz_returnsRegisterPrice_fromPricingOracle(uint256 fuzzBase, uint256 fuzzPremium) public {
Expand Down
2 changes: 1 addition & 1 deletion test/RegistrarController/RentPrice.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ contract RentPrice is RegistrarControllerBase {
function test_returnsPrice_fromPricingOracle() public view {
IPriceOracle.Price memory retPrices = controller.rentPrice(name, 0);
assertEq(retPrices.base, prices.DEFAULT_BASE_WEI());
assertEq(retPrices.premium, prices.DEFAULT_PERMIUM_WEI());
assertEq(retPrices.premium, prices.DEFAULT_PREMIUM_WEI());
}

function test_returnsPremium_ifTimeIsNearLaunchTime() public {
Expand Down
21 changes: 21 additions & 0 deletions test/UpgradeableRegistrarController/Available.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {UpgradeableRegistrarControllerBase} from "./UpgradeableRegistrarControllerBase.t.sol";

contract Available is UpgradeableRegistrarControllerBase {
function test_returnsFalse_whenNotAvailableOnBase() public {
base.setAvailable(uint256(nameLabel), false);
assertFalse(controller.available(name));
}

function test_returnsFalse_whenInvalidLength() public {
base.setAvailable(uint256(shortNameLabel), true);
assertFalse(controller.available(shortName));
}

function test_returnsTrue_whenValidAndAvailable() public {
base.setAvailable(uint256(nameLabel), true);
assertTrue(controller.available(name));
}
}
148 changes: 148 additions & 0 deletions test/UpgradeableRegistrarController/DiscountedRegister.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {UpgradeableRegistrarControllerBase} from "./UpgradeableRegistrarControllerBase.t.sol";
import {UpgradeableRegistrarController} from "src/L2/UpgradeableRegistrarController.sol";
import {IPriceOracle} from "src/L2/interface/IPriceOracle.sol";

contract DiscountedRegister is UpgradeableRegistrarControllerBase {
function test_reverts_ifTheDiscountIsInactive() public {
UpgradeableRegistrarController.DiscountDetails memory inactiveDiscount = _getDefaultDiscount();
vm.deal(user, 1 ether);

inactiveDiscount.active = false;
vm.prank(owner);
controller.setDiscountDetails(inactiveDiscount);
uint256 price = controller.discountedRegisterPrice(name, duration, discountKey);

vm.expectRevert(abi.encodeWithSelector(UpgradeableRegistrarController.InactiveDiscount.selector, discountKey));
vm.prank(user);
controller.discountedRegister{value: price}(_getDefaultRegisterRequest(), discountKey, "");
}

function test_reverts_whenInvalidDiscountRegistration() public {
vm.deal(user, 1 ether);
vm.prank(owner);
controller.setDiscountDetails(_getDefaultDiscount());
validator.setReturnValue(false);
uint256 price = controller.discountedRegisterPrice(name, duration, discountKey);

vm.expectRevert(
abi.encodeWithSelector(UpgradeableRegistrarController.InvalidDiscount.selector, discountKey, "")
);
vm.prank(user);
controller.discountedRegister{value: price}(_getDefaultRegisterRequest(), discountKey, "");
}

function test_reverts_whenNameNotAvailble() public {
vm.deal(user, 1 ether);
vm.prank(owner);
controller.setDiscountDetails(_getDefaultDiscount());
uint256 price = controller.discountedRegisterPrice(name, duration, discountKey);
validator.setReturnValue(true);
base.setAvailable(uint256(nameLabel), false);

vm.expectRevert(abi.encodeWithSelector(UpgradeableRegistrarController.NameNotAvailable.selector, name));
vm.prank(user);
controller.discountedRegister{value: price}(_getDefaultRegisterRequest(), discountKey, "");
}

function test_reverts_whenDurationTooShort() public {
vm.deal(user, 1 ether);
vm.prank(owner);
controller.setDiscountDetails(_getDefaultDiscount());
uint256 price = controller.discountedRegisterPrice(name, duration, discountKey);
validator.setReturnValue(true);
base.setAvailable(uint256(nameLabel), true);

UpgradeableRegistrarController.RegisterRequest memory shortDurationRequest = _getDefaultRegisterRequest();
uint256 shortDuration = controller.MIN_REGISTRATION_DURATION() - 1;
shortDurationRequest.duration = shortDuration;
vm.expectRevert(abi.encodeWithSelector(UpgradeableRegistrarController.DurationTooShort.selector, shortDuration));
vm.prank(user);
controller.discountedRegister{value: price}(shortDurationRequest, discountKey, "");
}

function test_reverts_whenValueTooSmall() public {
vm.deal(user, 1 ether);
vm.prank(owner);
controller.setDiscountDetails(_getDefaultDiscount());
prices.setPrice(name, IPriceOracle.Price({base: 1 ether, premium: 0}));
uint256 price = controller.discountedRegisterPrice(name, duration, discountKey);
validator.setReturnValue(true);
base.setAvailable(uint256(nameLabel), true);

vm.expectRevert(UpgradeableRegistrarController.InsufficientValue.selector);
vm.prank(user);
controller.discountedRegister{value: price - 1}(_getDefaultRegisterRequest(), discountKey, "");
}

function test_registersWithDiscountSuccessfully() public {
vm.deal(user, 1 ether);
vm.prank(owner);
controller.setDiscountDetails(_getDefaultDiscount());
validator.setReturnValue(true);
base.setAvailable(uint256(nameLabel), true);
UpgradeableRegistrarController.RegisterRequest memory request = _getDefaultRegisterRequest();
uint256 expires = block.timestamp + request.duration;
base.setNameExpires(uint256(nameLabel), expires);
uint256 price = controller.discountedRegisterPrice(name, duration, discountKey);

vm.expectEmit(address(controller));
emit UpgradeableRegistrarController.ETHPaymentProcessed(user, price);
vm.expectEmit(address(controller));
emit UpgradeableRegistrarController.NameRegistered(request.name, nameLabel, user, expires);
vm.expectEmit(address(controller));
emit UpgradeableRegistrarController.DiscountApplied(user, discountKey);

vm.prank(user);
controller.discountedRegister{value: price}(request, discountKey, "");

bytes memory retByte = resolver.firstBytes();
assertEq(keccak256(retByte), keccak256(request.data[0]));
assertTrue(reverse.hasClaimed(user));
address[] memory addrs = new address[](1);
addrs[0] = user;
assertTrue(controller.hasRegisteredWithDiscount(addrs));
}

function test_sendsARefund_ifUserOverpayed() public {
vm.deal(user, 1 ether);
vm.prank(owner);
controller.setDiscountDetails(_getDefaultDiscount());
validator.setReturnValue(true);
base.setAvailable(uint256(nameLabel), true);
UpgradeableRegistrarController.RegisterRequest memory request = _getDefaultRegisterRequest();
uint256 expires = block.timestamp + request.duration;
base.setNameExpires(uint256(nameLabel), expires);
uint256 price = controller.discountedRegisterPrice(name, duration, discountKey);

vm.prank(user);
controller.discountedRegister{value: price + 1}(request, discountKey, "");

uint256 expectedBalance = 1 ether - price;
assertEq(user.balance, expectedBalance);
}

function test_reverts_ifTheRegistrantHasAlreadyRegisteredWithDiscount() public {
vm.deal(user, 1 ether);
vm.prank(owner);
controller.setDiscountDetails(_getDefaultDiscount());
validator.setReturnValue(true);
base.setAvailable(uint256(nameLabel), true);
UpgradeableRegistrarController.RegisterRequest memory request = _getDefaultRegisterRequest();
uint256 expires = block.timestamp + request.duration;
base.setNameExpires(uint256(nameLabel), expires);
uint256 price = controller.discountedRegisterPrice(name, duration, discountKey);

vm.prank(user);
controller.discountedRegister{value: price}(request, discountKey, "");

vm.expectRevert(
abi.encodeWithSelector(UpgradeableRegistrarController.AlreadyRegisteredWithDiscount.selector, user)
);
request.name = "newname";
vm.prank(user);
controller.discountedRegister{value: price}(request, discountKey, "");
}
}
29 changes: 29 additions & 0 deletions test/UpgradeableRegistrarController/DiscountedRegisterPrice.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {UpgradeableRegistrarControllerBase} from "./UpgradeableRegistrarControllerBase.t.sol";
import {RegistrarController} from "src/L2/RegistrarController.sol";
import {IPriceOracle} from "src/L2/interface/IPriceOracle.sol";

contract DiscountedRegisterPrice is UpgradeableRegistrarControllerBase {
function test_returnsADiscountedPrice_whenThePriceIsGreaterThanTheDiscount(uint256 price) public {
vm.assume(price > discountAmount);
prices.setPrice(name, IPriceOracle.Price({base: price, premium: 0}));
vm.prank(owner);
controller.setDiscountDetails(_getDefaultDiscount());

uint256 expectedPrice = price - discountAmount;
uint256 retPrice = controller.discountedRegisterPrice(name, duration, discountKey);
assertEq(retPrice, expectedPrice);
}

function test_returnsZero_whenThePriceIsLessThanOrEqualToTheDiscount(uint256 price) public {
vm.assume(price > 0 && price <= discountAmount);
prices.setPrice(name, IPriceOracle.Price({base: price, premium: 0}));
vm.prank(owner);
controller.setDiscountDetails(_getDefaultDiscount());

uint256 retPrice = controller.discountedRegisterPrice(name, duration, discountKey);
assertEq(retPrice, 0);
}
}
86 changes: 86 additions & 0 deletions test/UpgradeableRegistrarController/Register.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {UpgradeableRegistrarControllerBase} from "./UpgradeableRegistrarControllerBase.t.sol";
import {UpgradeableRegistrarController} from "src/L2/UpgradeableRegistrarController.sol";
import {IPriceOracle} from "src/L2/interface/IPriceOracle.sol";

contract Register is UpgradeableRegistrarControllerBase {
function test_reverts_whenResolverRequiredAndNotSupplied() public {
vm.deal(user, 1 ether);
uint256 price = controller.registerPrice(name, duration);
vm.expectRevert(UpgradeableRegistrarController.ResolverRequiredWhenDataSupplied.selector);
vm.prank(user);
UpgradeableRegistrarController.RegisterRequest memory noResolverRequest = _getDefaultRegisterRequest();
noResolverRequest.resolver = address(0);
controller.register{value: price}(noResolverRequest);
}

function test_reverts_whenNameNotAvailble() public {
vm.deal(user, 1 ether);
uint256 price = controller.registerPrice(name, duration);
base.setAvailable(uint256(nameLabel), false);
vm.expectRevert(abi.encodeWithSelector(UpgradeableRegistrarController.NameNotAvailable.selector, name));
vm.prank(user);
controller.register{value: price}(_getDefaultRegisterRequest());
}

function test_reverts_whenDurationTooShort() public {
vm.deal(user, 1 ether);
uint256 price = controller.registerPrice(name, duration);
base.setAvailable(uint256(nameLabel), true);
UpgradeableRegistrarController.RegisterRequest memory shortDurationRequest = _getDefaultRegisterRequest();
uint256 shortDuration = controller.MIN_REGISTRATION_DURATION() - 1;
shortDurationRequest.duration = shortDuration;
vm.expectRevert(abi.encodeWithSelector(UpgradeableRegistrarController.DurationTooShort.selector, shortDuration));
vm.prank(user);
controller.register{value: price}(shortDurationRequest);
}

function test_reverts_whenValueTooSmall() public {
vm.deal(user, 1 ether);
uint256 price = controller.registerPrice(name, duration);
base.setAvailable(uint256(nameLabel), true);
vm.expectRevert(UpgradeableRegistrarController.InsufficientValue.selector);
vm.prank(user);
controller.register{value: price - 1}(_getDefaultRegisterRequest());
}

function test_registersSuccessfully() public {
vm.deal(user, 1 ether);
UpgradeableRegistrarController.RegisterRequest memory request = _getDefaultRegisterRequest();

base.setAvailable(uint256(nameLabel), true);
uint256 expires = block.timestamp + request.duration;
base.setNameExpires(uint256(nameLabel), expires);
uint256 price = controller.registerPrice(request.name, request.duration);

vm.expectEmit(address(controller));
emit UpgradeableRegistrarController.ETHPaymentProcessed(user, price);
vm.expectEmit(address(controller));
emit UpgradeableRegistrarController.NameRegistered(request.name, nameLabel, user, expires);

vm.prank(user);
controller.register{value: price}(request);

bytes memory retByte = resolver.firstBytes();
assertEq(keccak256(retByte), keccak256(request.data[0]));
assertTrue(reverse.hasClaimed(user));
}

function test_sendsARefund_ifUserOverpayed() public {
vm.deal(user, 1 ether);
UpgradeableRegistrarController.RegisterRequest memory request = _getDefaultRegisterRequest();

base.setAvailable(uint256(nameLabel), true);
uint256 expires = block.timestamp + request.duration;
base.setNameExpires(uint256(nameLabel), expires);
uint256 price = controller.registerPrice(request.name, request.duration);

vm.prank(user);
controller.register{value: price + 1}(request);

uint256 expectedBalance = 1 ether - price;
assertEq(user.balance, expectedBalance);
}
}
Loading
Loading