Skip to content
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

Finish ENSIP-19 integration #95

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
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
27 changes: 27 additions & 0 deletions src/L2/ReverseRegistrarShim.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {IReverseRegistrar} from "./interface/IReverseRegistrar.sol";
import {IL2ReverseResolver} from "./interface/IL2ReverseResolver.sol";

contract ReverseRegistrarShim {
address public immutable reverseRegistrar;
address public immutable reverseResolver;
address public immutable l2Resolver;

constructor(address reverseRegistrar_, address reverseResolver_, address l2Resolver_) {
reverseRegistrar = reverseRegistrar_;
reverseResolver = reverseResolver_;
l2Resolver = l2Resolver_;
}

function setNameForAddrWithSignature(
address addr,
string calldata name,
uint256 signatureExpiry,
bytes memory signature
) external returns (bytes32) {
IReverseRegistrar(reverseRegistrar).setNameForAddr(addr, msg.sender, l2Resolver, name);
return IL2ReverseResolver(reverseResolver).setNameForAddrWithSignature(addr, name, signatureExpiry, signature);
}
}
675 changes: 675 additions & 0 deletions src/L2/UpgradeableRegistrarController.sol

Large diffs are not rendered by default.

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

interface IL2ReverseResolver {
/**
* @dev Sets the name for an addr using a signature that can be verified with ERC1271.
* @param addr The reverse record to set
* @param name The name of the reverse record
* @param signatureExpiry Date when the signature expires
* @param signature The resolver of the reverse node
* @return The ENS node hash of the reverse record.
*/
function setNameForAddrWithSignature(
address addr,
string calldata name,
uint256 signatureExpiry,
bytes memory signature
) external returns (bytes32);
}
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
34 changes: 34 additions & 0 deletions test/ReverseRegistrarShim/ReverseRegistrarShimBase.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {Test} from "forge-std/Test.sol";
import {ReverseRegistrarShim} from "src/L2/ReverseRegistrarShim.sol";
import {MockReverseRegistrar} from "test/mocks/MockReverseRegistrar.sol";
import {MockReverseResolver} from "test/mocks/MockReverseResolver.sol";
import {MockPublicResolver} from "test/mocks/MockPublicResolver.sol";

contract ReverseRegistrarShimBase is Test {
MockReverseResolver revRes;
MockReverseRegistrar revReg;
MockPublicResolver resolver;

ReverseRegistrarShim public shim;

address userA;
address userB;
string nameA = "userAName";
string nameB = "userBName";

uint256 signatureExpiry = 0;
bytes signature;

function setUp() external {
revRes = new MockReverseResolver();
revReg = new MockReverseRegistrar();
resolver = new MockPublicResolver();
shim = new ReverseRegistrarShim(address(revReg), address(revRes), address(resolver));

userA = makeAddr("userA");
userB = makeAddr("userB");
}
}
28 changes: 28 additions & 0 deletions test/ReverseRegistrarShim/SetNameForAddrWithSignature.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {ReverseRegistrarShimBase} from "./ReverseRegistrarShimBase.t.sol";
import {MockReverseRegistrar} from "test/mocks/MockReverseRegistrar.sol";
import {MockReverseResolver} from "test/mocks/MockReverseResolver.sol";

contract SetNameForAddrWithSignature is ReverseRegistrarShimBase {
function test_setsNameForAddr_onReverseRegistrar() public {
vm.prank(userA);
vm.expectCall(
address(revReg),
abi.encodeWithSelector(MockReverseRegistrar.setNameForAddr.selector, userA, userA, address(resolver), nameA)
);
shim.setNameForAddrWithSignature(userA, nameA, signatureExpiry, signature);
}

function test_setsNameForAddr_onReverseResolver() public {
vm.prank(userA);
vm.expectCall(
address(revRes),
abi.encodeWithSelector(
MockReverseResolver.setNameForAddrWithSignature.selector, userA, nameA, signatureExpiry, signature
)
);
shim.setNameForAddrWithSignature(userA, nameA, signatureExpiry, signature);
}
}
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);
}
}
Loading
Loading