diff --git a/src/interfaces/IENSRegistry.sol b/src/interfaces/IENSRegistry.sol index 44bf554..ee31650 100644 --- a/src/interfaces/IENSRegistry.sol +++ b/src/interfaces/IENSRegistry.sol @@ -5,6 +5,10 @@ pragma solidity ^0.8.19; interface IENSRegistry { function setSubnodeOwner(bytes32 node, bytes32 label, address owner) external returns (bytes32); + function setSubnodeRecord(bytes32 node, bytes32 label, address owner, address resolver, uint64 ttl) external; + + function resolver(bytes32 node) external view returns (address); + function setOwner(bytes32 node, address owner) external; function owner(bytes32 node) external view returns (address); diff --git a/test/ENSNamehash.sol b/test/ENSNamehash.sol new file mode 100644 index 0000000..a7c7f06 --- /dev/null +++ b/test/ENSNamehash.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.19; + +/* + * @dev Solidity implementation of the ENS namehash algorithm. + * + * Warning! Does not normalize or validate names before hashing. + */ +library ENSNamehash { + function namehash(bytes memory domain) internal pure returns (bytes32) { + return namehash(domain, 0); + } + + function namehash(bytes memory domain, uint256 i) internal pure returns (bytes32) { + if (domain.length <= i) { + return 0x0000000000000000000000000000000000000000000000000000000000000000; + } + + uint256 len = LabelLength(domain, i); + + return keccak256(abi.encodePacked(namehash(domain, i + len + 1), keccak(domain, i, len))); + } + + function LabelLength(bytes memory domain, uint256 i) private pure returns (uint256) { + uint256 len; + while (i + len != domain.length && domain[i + len] != 0x2e) { + len++; + } + return len; + } + + function keccak(bytes memory data, uint256 offset, uint256 len) private pure returns (bytes32 ret) { + require(offset + len <= data.length); + assembly { + ret := keccak256(add(add(data, 32), offset), len) + } + } +} diff --git a/test/GoerliENSTestProposal.sol b/test/GoerliENSTestProposal.sol index f6d937a..610e075 100644 --- a/test/GoerliENSTestProposal.sol +++ b/test/GoerliENSTestProposal.sol @@ -4,25 +4,72 @@ pragma solidity ^0.8.19; import { IENSResolver } from "@interfaces/IENSResolver.sol"; import { IENSRegistry } from "@interfaces/IENSRegistry.sol"; +import { ENSNamehash } from "./ENSNamehash.sol"; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { TornadoAddresses } from "@proprietary/TornadoAddresses.sol"; contract GoerliTestProposal is Ownable, TornadoAddresses { address ensResolverAddress = 0xd7a4F6473f32aC2Af804B3686AE8F1932bC35750; // goerli ENS resolver address ensRegistryAddress = 0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e; - bytes32 testNode = 0xa8f2228d0331c20e8d36b0bf33b102b5b8d8d416db25502b04e5b854d7a5c556; // tornadotest.eth on Goerli + string testDomain = "tornadotest.eth"; + bytes32 testNode = calculateDomainNode(testDomain); IENSResolver ensResolver = IENSResolver(ensResolverAddress); IENSRegistry ensRegistry = IENSRegistry(ensRegistryAddress); - function changeNode(bytes32 newNode) public onlyOwner { + function transferDomainOwnership(address to) public onlyOwner { + _transferDomainOwnership(testNode, to); + } + + function transferSubdomainOwnership(string memory domain, string memory subdomainLabel, address to) public onlyOwner { + _transferSubdomainOwnership(domain, subdomainLabel, to); + } + + function registerSubname(string memory domain, string memory subdomainLabel) public onlyOwner { + bytes32 rootNode = calculateDomainNode(domain); + address rootResolver = ensRegistry.resolver(rootNode); + bytes32 subdomainLabelhash = calculateSubdomainLabelhash(subdomainLabel); + + ensRegistry.setSubnodeRecord(rootNode, subdomainLabelhash, address(this), rootResolver, 0); + } + + function _transferDomainOwnership(bytes32 domainNode, address to) private { + require(ensRegistry.owner(domainNode) == address(this), "This test contract must be an owner of domain"); + ensRegistry.setOwner(domainNode, to); + } + + function _transferSubdomainOwnership(string memory domain, string memory subdomainLabel, address to) private { + bytes32 rootNode = calculateDomainNode(domain); + bytes32 subdomainLabelhash = calculateSubdomainLabelhash(subdomainLabel); + bytes32 subdomainNode = keccak256(abi.encodePacked(rootNode, subdomainLabelhash)); + require(ensRegistry.recordExists(subdomainNode), "Subdomain not registered"); + require(ensRegistry.owner(rootNode) == address(this), "This test contract must be an owner of domain"); + + ensRegistry.setSubnodeOwner(rootNode, subdomainLabelhash, to); + } + + function setNewDomain(string memory newDomain) public onlyOwner { + bytes32 newNode = ENSNamehash.namehash(bytes(newDomain)); require(ensRegistry.recordExists(newNode), "Node doesn't exist"); require(ensRegistry.owner(newNode) == address(this), "Contract is not an owner of new ENS name"); transferDomainOwnership(owner()); + testDomain = newDomain; testNode = newNode; } + function calculateDomainNode(string memory domain) internal pure returns (bytes32) { + return ENSNamehash.namehash(bytes(domain)); + } + + function calculateSubdomainLabelhash(string memory subdomainLabel) internal pure returns (bytes32) { + return keccak256(bytes(subdomainLabel)); + } + + function ownSubdomain(string memory domain, string memory subdomainLabel) public { + _transferSubdomainOwnership(domain, subdomainLabel, address(this)); + } + function setClassicUiIpfs() public { bytes memory classicUiIPFSContenthash = hex"e301017012208124caa06a8419371b1d2eab9180191727d1ce0c0832975362f77a679ce614b6"; @@ -47,11 +94,7 @@ contract GoerliTestProposal is Ownable, TornadoAddresses { ensResolver.setContenthash(testNode, docsIPFSContenthash); } - function setStakingAddress() public { - ensResolver.setAddr(testNode, stakingAddress); - } - - function transferDomainOwnership(address to) public onlyOwner { - ensRegistry.setOwner(testNode, to); + function setStakingAddress(string memory domain) public { + ensResolver.setAddr(calculateDomainNode(domain), stakingAddress); } }