From 23c4a727f3332b844371ef0a4519d98802ca6bfe Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Mon, 5 Jun 2023 13:01:46 +0800 Subject: [PATCH 01/37] Add L2 reverse registrar contract --- .vscode/settings.json | 3 - .../reverseRegistrar/L2ReverseRegistrar.sol | 250 ++++++++++++++++++ 2 files changed, 250 insertions(+), 3 deletions(-) delete mode 100644 .vscode/settings.json create mode 100644 contracts/reverseRegistrar/L2ReverseRegistrar.sol diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 25fa6215..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "typescript.tsdk": "node_modules/typescript/lib" -} diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol new file mode 100644 index 00000000..5eb18a61 --- /dev/null +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -0,0 +1,250 @@ +pragma solidity >=0.8.4; + +import "../registry/ENS.sol"; +import "./IReverseRegistrar.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "../root/Controllable.sol"; + +abstract contract NameResolver { + function setName(bytes32 node, string memory name) public virtual; +} + +bytes32 constant lookup = 0x3031323334353637383961626364656600000000000000000000000000000000; + +bytes32 constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2; + +error InvalidSignature(); + +// namehash('addr.reverse') + +contract ReverseRegistrar is Ownable, Controllable, IReverseRegistrar { + ENS public immutable ens; + NameResolver public defaultResolver; + using ECDSA for bytes32; + + event ReverseClaimed(address indexed addr, bytes32 indexed node); + event DefaultResolverChanged(NameResolver indexed resolver); + + /** + * @dev Constructor + * @param ensAddr The address of the ENS registry. + */ + constructor(ENS ensAddr) { + ens = ensAddr; + + // Assign ownership of the reverse record to our deployer + ReverseRegistrar oldRegistrar = ReverseRegistrar( + ensAddr.owner(ADDR_REVERSE_NODE) + ); + if (address(oldRegistrar) != address(0x0)) { + oldRegistrar.claim(msg.sender); + } + } + + modifier authorised(address addr) { + require( + addr == msg.sender || + controllers[msg.sender] || + ens.isApprovedForAll(addr, msg.sender) || + ownsContract(addr), + "ReverseRegistrar: Caller is not a controller or authorised by address or the address itself" + ); + _; + } + + function setDefaultResolver(address resolver) public override onlyOwner { + require( + address(resolver) != address(0), + "ReverseRegistrar: Resolver address must not be 0" + ); + defaultResolver = NameResolver(resolver); + emit DefaultResolverChanged(NameResolver(resolver)); + } + + /** + * @dev Transfers ownership of the reverse ENS record associated with the + * calling account. + * @param addr The reverse record to set + * @param owner The address to set as the owner of the reverse record in ENS. + * @param resolver The resolver of the reverse node + * @return The ENS node hash of the reverse record. + */ + function _claimForAddr( + address addr, + address owner, + address resolver + ) internal override authorised(addr) returns (bytes32) { + bytes32 labelHash = sha3HexAddress(addr); + bytes32 reverseNode = keccak256( + abi.encodePacked(ADDR_REVERSE_NODE, labelHash) + ); + emit ReverseClaimed(addr, reverseNode); + ens.setSubnodeRecord(ADDR_REVERSE_NODE, labelHash, owner, resolver, 0); + return reverseNode; + } + + /** + * @dev Transfers ownership of the reverse ENS record associated with the + * calling account. + * @param addr The reverse record to set + * @param owner The address to set as the owner of the reverse record in ENS. + * @param resolver The resolver of the reverse node + * @return The ENS node hash of the reverse record. + */ + function _claimForAddrWithSignature( + address addr, + address owner, + address resolver, + address relayer, + uint256 signatureExpiry, + bytes memory signature + ) internal override returns (bytes32) { + bytes32 labelHash = sha3HexAddress(addr); + bytes32 reverseNode = keccak256( + abi.encodePacked(ADDR_REVERSE_NODE, labelHash) + ); + + bytes32 hash = keccak256( + abi.encodePacked( + IReverseRegistrar.claimForAddrWithSignature.selector, + addr, + owner, + resolver, + relayer, + signatureExpiry + ) + ); + + bytes32 message = hash.toEthSignedMessageHash(); + + if ( + !SignatureChecker.isValidSignatureNow(addr, message, signature) || + relayer != msg.sender || + signatureExpiry < block.timestamp || + signatureExpiry > block.timestamp + 1 days + ) { + revert InvalidSignature(); + } + + emit ReverseClaimed(addr, reverseNode); + ens.setSubnodeRecord(ADDR_REVERSE_NODE, labelHash, owner, resolver, 0); + return reverseNode; + } + + /** + * @dev Sets the `name()` record for the reverse ENS record associated with + * the calling account. First updates the resolver to the default reverse + * resolver if necessary. + * @param name The name to set for this address. + * @return The ENS node hash of the reverse record. + */ + function setName(string memory name) public override returns (bytes32) { + return + setNameForAddr( + msg.sender, + msg.sender, + address(defaultResolver), + name + ); + } + + /** + * @dev Sets the `name()` record for the reverse ENS record associated with + * the account provided. Updates the resolver to a designated resolver + * Only callable by controllers and authorised users + * @param addr The reverse record to set + * @param owner The owner of the reverse node + * @param resolver The resolver of the reverse node + * @param name The name to set for this address. + * @return The ENS node hash of the reverse record. + */ + function setNameForAddr( + address addr, + address owner, + address resolver, + string memory name + ) public override returns (bytes32) { + bytes32 node = _claimForAddr(addr, owner, resolver); + NameResolver(resolver).setName(node, name); + return node; + } + + /** + * @dev Sets the `name()` record for the reverse ENS record associated with + * the account provided. Updates the resolver to a designated resolver + * Only callable by controllers and authorised users + * @param addr The reverse record to set + * @param owner The owner of the reverse node + * @param resolver The resolver of the reverse node + * @param name The name to set for this address. + * @return The ENS node hash of the reverse record. + */ + function setNameForAddrWithSignature( + address addr, + address owner, + address resolver, + address relayer, + uint256 signatureExpiry, + bytes memory signature, + string memory name + ) public override returns (bytes32) { + bytes32 node = _claimForAddrWithSignature( + addr, + owner, + resolver, + relayer, + signatureExpiry, + signature + ); + NameResolver(resolver).setName(node, name); + return node; + } + + /** + * @dev Returns the node hash for a given account's reverse records. + * @param addr The address to hash + * @return The ENS node hash. + */ + function node(address addr) public pure override returns (bytes32) { + return + keccak256( + abi.encodePacked(ADDR_REVERSE_NODE, sha3HexAddress(addr)) + ); + } + + /** + * @dev An optimised function to compute the sha3 of the lower-case + * hexadecimal representation of an Ethereum address. + * @param addr The address to hash + * @return ret The SHA3 hash of the lower-case hexadecimal encoding of the + * input address. + */ + function sha3HexAddress(address addr) private pure returns (bytes32 ret) { + assembly { + for { + let i := 40 + } gt(i, 0) { + + } { + i := sub(i, 1) + mstore8(i, byte(and(addr, 0xf), lookup)) + addr := div(addr, 0x10) + i := sub(i, 1) + mstore8(i, byte(and(addr, 0xf), lookup)) + addr := div(addr, 0x10) + } + + ret := keccak256(0, 40) + } + } + + function ownsContract(address addr) internal view returns (bool) { + try Ownable(addr).owner() returns (address owner) { + return owner == msg.sender; + } catch { + return false; + } + } +} From c763636c90d8cc6d1bf7c13c57fe0ec17bd2a9da Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Tue, 15 Aug 2023 11:15:13 +0800 Subject: [PATCH 02/37] WIP --- .../reverseRegistrar/IL2ReverseRegistrar.sol | 39 +++++++++++++++++++ .../reverseRegistrar/L2ReverseClaimer.sol | 14 +++++++ .../reverseRegistrar/L2ReverseRegistrar.sol | 24 ++++-------- contracts/reverseRegistrar/README.md | 9 +++++ 4 files changed, 69 insertions(+), 17 deletions(-) create mode 100644 contracts/reverseRegistrar/IL2ReverseRegistrar.sol create mode 100644 contracts/reverseRegistrar/L2ReverseClaimer.sol create mode 100644 contracts/reverseRegistrar/README.md diff --git a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol new file mode 100644 index 00000000..da136cde --- /dev/null +++ b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol @@ -0,0 +1,39 @@ +pragma solidity >=0.8.4; + +interface IReverseRegistrar { + function setDefaultResolver(address resolver) external; + + function claim(address owner) external returns (bytes32); + + function claimForAddr( + address addr, + address owner + ) external returns (bytes32); + + function claimForAddrWithSignature( + address addr, + address owner, + address relayer, + uint256 signatureExpiry, + bytes calldata signature + ) external returns (bytes32); + + function setName(string memory name) external returns (bytes32); + + function setNameForAddr( + address addr, + address owner, + string memory name + ) external returns (bytes32); + + function setNameForAddrWithSignature( + address addr, + address owner, + address relayer, + uint256 signatureExpiry, + bytes calldata signature, + string memory name + ) external returns (bytes32); + + function node(address addr) external pure returns (bytes32); +} diff --git a/contracts/reverseRegistrar/L2ReverseClaimer.sol b/contracts/reverseRegistrar/L2ReverseClaimer.sol new file mode 100644 index 00000000..508b9acd --- /dev/null +++ b/contracts/reverseRegistrar/L2ReverseClaimer.sol @@ -0,0 +1,14 @@ +//SPDX-License-Identifier: MIT +pragma solidity >=0.8.17 <0.9.0; + +import {ENS} from "../registry/ENS.sol"; +import {IReverseRegistrar} from "../reverseRegistrar/IL2ReverseRegistrar.sol"; + +contract L2ReverseClaimer { + constructor(address reverseNode, ENS ens, address claimant) { + IReverseRegistrar reverseRegistrar = IReverseRegistrar( + ens.owner(reverseNode) + ); + reverseRegistrar.claim(claimant); + } +} diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 5eb18a61..8e1a8e47 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -1,7 +1,7 @@ pragma solidity >=0.8.4; import "../registry/ENS.sol"; -import "./IReverseRegistrar.sol"; +import "./IL2ReverseRegistrar.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; @@ -19,7 +19,7 @@ error InvalidSignature(); // namehash('addr.reverse') -contract ReverseRegistrar is Ownable, Controllable, IReverseRegistrar { +contract L2ReverseRegistrar is Ownable, Controllable, IReverseRegistrar { ENS public immutable ens; NameResolver public defaultResolver; using ECDSA for bytes32; @@ -141,13 +141,7 @@ contract ReverseRegistrar is Ownable, Controllable, IReverseRegistrar { * @return The ENS node hash of the reverse record. */ function setName(string memory name) public override returns (bytes32) { - return - setNameForAddr( - msg.sender, - msg.sender, - address(defaultResolver), - name - ); + return setNameForAddr(msg.sender, msg.sender, name); } /** @@ -156,18 +150,16 @@ contract ReverseRegistrar is Ownable, Controllable, IReverseRegistrar { * Only callable by controllers and authorised users * @param addr The reverse record to set * @param owner The owner of the reverse node - * @param resolver The resolver of the reverse node * @param name The name to set for this address. * @return The ENS node hash of the reverse record. */ function setNameForAddr( address addr, address owner, - address resolver, string memory name ) public override returns (bytes32) { - bytes32 node = _claimForAddr(addr, owner, resolver); - NameResolver(resolver).setName(node, name); + bytes32 node = _claimForAddr(addr, owner, address(defaultResolver)); + NameResolver(address(defaultResolver)).setName(node, name); return node; } @@ -177,14 +169,12 @@ contract ReverseRegistrar is Ownable, Controllable, IReverseRegistrar { * Only callable by controllers and authorised users * @param addr The reverse record to set * @param owner The owner of the reverse node - * @param resolver The resolver of the reverse node * @param name The name to set for this address. * @return The ENS node hash of the reverse record. */ function setNameForAddrWithSignature( address addr, address owner, - address resolver, address relayer, uint256 signatureExpiry, bytes memory signature, @@ -193,12 +183,12 @@ contract ReverseRegistrar is Ownable, Controllable, IReverseRegistrar { bytes32 node = _claimForAddrWithSignature( addr, owner, - resolver, + address(defaultResolver), relayer, signatureExpiry, signature ); - NameResolver(resolver).setName(node, name); + NameResolver(defaultResolver).setName(node, name); return node; } diff --git a/contracts/reverseRegistrar/README.md b/contracts/reverseRegistrar/README.md new file mode 100644 index 00000000..d6164c85 --- /dev/null +++ b/contracts/reverseRegistrar/README.md @@ -0,0 +1,9 @@ +# L2 Reverse Registrar + +## Open questions + +- What should be th + +## Notes + +Resolver could change if the owner of the reverse registrar needs to upgrade or change it. The resolver on L1 could have an updateable list of resolver addresses that it can check the state on, or force all deprecated resolvers to reclaim their name. From 21c5d36b602a5fb5ed2832835df7e444a7be6335 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Sat, 7 Oct 2023 00:45:20 +0800 Subject: [PATCH 03/37] Refactor L2 Reverse registrar to incorporate the resolver within --- .../reverseRegistrar/IL2ReverseRegistrar.sol | 26 +-- .../reverseRegistrar/L2ReverseClaimer.sol | 17 +- .../reverseRegistrar/L2ReverseRegistrar.sol | 171 +++++------------- contracts/reverseRegistrar/README.md | 8 +- .../profiles/L2ReverseResolverBase.sol | 73 ++++++++ .../profiles/NameResolver.sol | 40 ++++ .../profiles/TextResolver.sol | 52 ++++++ 7 files changed, 226 insertions(+), 161 deletions(-) create mode 100644 contracts/reverseRegistrar/profiles/L2ReverseResolverBase.sol create mode 100644 contracts/reverseRegistrar/profiles/NameResolver.sol create mode 100644 contracts/reverseRegistrar/profiles/TextResolver.sol diff --git a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol index da136cde..ce60c53f 100644 --- a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol @@ -1,23 +1,6 @@ pragma solidity >=0.8.4; -interface IReverseRegistrar { - function setDefaultResolver(address resolver) external; - - function claim(address owner) external returns (bytes32); - - function claimForAddr( - address addr, - address owner - ) external returns (bytes32); - - function claimForAddrWithSignature( - address addr, - address owner, - address relayer, - uint256 signatureExpiry, - bytes calldata signature - ) external returns (bytes32); - +interface IL2ReverseRegistrar { function setName(string memory name) external returns (bytes32); function setNameForAddr( @@ -29,11 +12,12 @@ interface IReverseRegistrar { function setNameForAddrWithSignature( address addr, address owner, + string memory name, + address resolver, address relayer, uint256 signatureExpiry, - bytes calldata signature, - string memory name + bytes memory signature ) external returns (bytes32); - function node(address addr) external pure returns (bytes32); + function node(address addr) external view returns (bytes32); } diff --git a/contracts/reverseRegistrar/L2ReverseClaimer.sol b/contracts/reverseRegistrar/L2ReverseClaimer.sol index 508b9acd..58cb0d07 100644 --- a/contracts/reverseRegistrar/L2ReverseClaimer.sol +++ b/contracts/reverseRegistrar/L2ReverseClaimer.sol @@ -2,13 +2,20 @@ pragma solidity >=0.8.17 <0.9.0; import {ENS} from "../registry/ENS.sol"; -import {IReverseRegistrar} from "../reverseRegistrar/IL2ReverseRegistrar.sol"; +import {IL2ReverseRegistrar} from "../reverseRegistrar/IL2ReverseRegistrar.sol"; contract L2ReverseClaimer { - constructor(address reverseNode, ENS ens, address claimant) { - IReverseRegistrar reverseRegistrar = IReverseRegistrar( - ens.owner(reverseNode) + constructor( + address l2ReverseRegistrarAddr, + ENS reverseRegistrar, + address claimant + ) { + IL2ReverseRegistrar reverseRegistrar = IL2ReverseRegistrar( + l2ReverseRegistrarAddr ); - reverseRegistrar.claim(claimant); + //reverseRegistrar.setName(claimant); } } + +// TODO: do we need a way of claiming a reverse node +// so that contracts can delegate ownership to an EoA/Smartcontract? diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 8e1a8e47..86b0827c 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -6,22 +6,19 @@ import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "../root/Controllable.sol"; - -abstract contract NameResolver { - function setName(bytes32 node, string memory name) public virtual; -} - -bytes32 constant lookup = 0x3031323334353637383961626364656600000000000000000000000000000000; - -bytes32 constant ADDR_REVERSE_NODE = 0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2; +import "./profiles/NameResolver.sol"; +import "./profiles/TextResolver.sol"; +import "./profiles/L2ReverseResolverBase.sol"; error InvalidSignature(); -// namehash('addr.reverse') - -contract L2ReverseRegistrar is Ownable, Controllable, IReverseRegistrar { - ENS public immutable ens; - NameResolver public defaultResolver; +contract L2ReverseRegistrar is + Ownable, + IL2ReverseRegistrar, + L2ReverseResolverBase, + NameResolver, + TextResolver +{ using ECDSA for bytes32; event ReverseClaimed(address indexed addr, bytes32 indexed node); @@ -29,60 +26,19 @@ contract L2ReverseRegistrar is Ownable, Controllable, IReverseRegistrar { /** * @dev Constructor - * @param ensAddr The address of the ENS registry. */ - constructor(ENS ensAddr) { - ens = ensAddr; - - // Assign ownership of the reverse record to our deployer - ReverseRegistrar oldRegistrar = ReverseRegistrar( - ensAddr.owner(ADDR_REVERSE_NODE) - ); - if (address(oldRegistrar) != address(0x0)) { - oldRegistrar.claim(msg.sender); - } - } + constructor(bytes32 L2ReverseNode) L2ReverseResolverBase(L2ReverseNode) {} - modifier authorised(address addr) { - require( - addr == msg.sender || - controllers[msg.sender] || - ens.isApprovedForAll(addr, msg.sender) || - ownsContract(addr), - "ReverseRegistrar: Caller is not a controller or authorised by address or the address itself" - ); + modifier authorised(address addr) override(L2ReverseResolverBase) { + isAuthorised(addr); _; } - function setDefaultResolver(address resolver) public override onlyOwner { + function isAuthorised(address addr) internal view override returns (bool) { require( - address(resolver) != address(0), - "ReverseRegistrar: Resolver address must not be 0" - ); - defaultResolver = NameResolver(resolver); - emit DefaultResolverChanged(NameResolver(resolver)); - } - - /** - * @dev Transfers ownership of the reverse ENS record associated with the - * calling account. - * @param addr The reverse record to set - * @param owner The address to set as the owner of the reverse record in ENS. - * @param resolver The resolver of the reverse node - * @return The ENS node hash of the reverse record. - */ - function _claimForAddr( - address addr, - address owner, - address resolver - ) internal override authorised(addr) returns (bytes32) { - bytes32 labelHash = sha3HexAddress(addr); - bytes32 reverseNode = keccak256( - abi.encodePacked(ADDR_REVERSE_NODE, labelHash) + addr == msg.sender || ownsContract(addr), + "ReverseRegistrar: Caller is not a controller or authorised by address or the address itself" ); - emit ReverseClaimed(addr, reverseNode); - ens.setSubnodeRecord(ADDR_REVERSE_NODE, labelHash, owner, resolver, 0); - return reverseNode; } /** @@ -93,24 +49,26 @@ contract L2ReverseRegistrar is Ownable, Controllable, IReverseRegistrar { * @param resolver The resolver of the reverse node * @return The ENS node hash of the reverse record. */ - function _claimForAddrWithSignature( + function setNameForAddrWithSignature( address addr, address owner, + string memory name, address resolver, address relayer, uint256 signatureExpiry, bytes memory signature - ) internal override returns (bytes32) { + ) public override returns (bytes32) { bytes32 labelHash = sha3HexAddress(addr); bytes32 reverseNode = keccak256( - abi.encodePacked(ADDR_REVERSE_NODE, labelHash) + abi.encodePacked(L2_REVERSE_NODE, labelHash) ); bytes32 hash = keccak256( abi.encodePacked( - IReverseRegistrar.claimForAddrWithSignature.selector, + IL2ReverseRegistrar.setNameForAddrWithSignature.selector, addr, owner, + name, resolver, relayer, signatureExpiry @@ -128,15 +86,13 @@ contract L2ReverseRegistrar is Ownable, Controllable, IReverseRegistrar { revert InvalidSignature(); } - emit ReverseClaimed(addr, reverseNode); - ens.setSubnodeRecord(ADDR_REVERSE_NODE, labelHash, owner, resolver, 0); + _setName(reverseNode, name); return reverseNode; } /** * @dev Sets the `name()` record for the reverse ENS record associated with - * the calling account. First updates the resolver to the default reverse - * resolver if necessary. + * the calling account. * @param name The name to set for this address. * @return The ENS node hash of the reverse record. */ @@ -158,37 +114,9 @@ contract L2ReverseRegistrar is Ownable, Controllable, IReverseRegistrar { address owner, string memory name ) public override returns (bytes32) { - bytes32 node = _claimForAddr(addr, owner, address(defaultResolver)); - NameResolver(address(defaultResolver)).setName(node, name); - return node; - } - - /** - * @dev Sets the `name()` record for the reverse ENS record associated with - * the account provided. Updates the resolver to a designated resolver - * Only callable by controllers and authorised users - * @param addr The reverse record to set - * @param owner The owner of the reverse node - * @param name The name to set for this address. - * @return The ENS node hash of the reverse record. - */ - function setNameForAddrWithSignature( - address addr, - address owner, - address relayer, - uint256 signatureExpiry, - bytes memory signature, - string memory name - ) public override returns (bytes32) { - bytes32 node = _claimForAddrWithSignature( - addr, - owner, - address(defaultResolver), - relayer, - signatureExpiry, - signature - ); - NameResolver(defaultResolver).setName(node, name); + bytes32 labelHash = sha3HexAddress(addr); + bytes32 node = keccak256(abi.encodePacked(L2_REVERSE_NODE, labelHash)); + _setName(node, name); return node; } @@ -197,37 +125,9 @@ contract L2ReverseRegistrar is Ownable, Controllable, IReverseRegistrar { * @param addr The address to hash * @return The ENS node hash. */ - function node(address addr) public pure override returns (bytes32) { + function node(address addr) public view override returns (bytes32) { return - keccak256( - abi.encodePacked(ADDR_REVERSE_NODE, sha3HexAddress(addr)) - ); - } - - /** - * @dev An optimised function to compute the sha3 of the lower-case - * hexadecimal representation of an Ethereum address. - * @param addr The address to hash - * @return ret The SHA3 hash of the lower-case hexadecimal encoding of the - * input address. - */ - function sha3HexAddress(address addr) private pure returns (bytes32 ret) { - assembly { - for { - let i := 40 - } gt(i, 0) { - - } { - i := sub(i, 1) - mstore8(i, byte(and(addr, 0xf), lookup)) - addr := div(addr, 0x10) - i := sub(i, 1) - mstore8(i, byte(and(addr, 0xf), lookup)) - addr := div(addr, 0x10) - } - - ret := keccak256(0, 40) - } + keccak256(abi.encodePacked(L2_REVERSE_NODE, sha3HexAddress(addr))); } function ownsContract(address addr) internal view returns (bool) { @@ -237,4 +137,17 @@ contract L2ReverseRegistrar is Ownable, Controllable, IReverseRegistrar { return false; } } + + function supportsInterface( + bytes4 interfaceID + ) + public + pure + override(NameResolver, TextResolver, L2ReverseResolverBase) + returns (bool) + { + return + interfaceID == type(IL2ReverseRegistrar).interfaceId || + super.supportsInterface(interfaceID); + } } diff --git a/contracts/reverseRegistrar/README.md b/contracts/reverseRegistrar/README.md index d6164c85..95b8c8b1 100644 --- a/contracts/reverseRegistrar/README.md +++ b/contracts/reverseRegistrar/README.md @@ -1,9 +1,5 @@ # L2 Reverse Registrar -## Open questions +## Resolver Profiles -- What should be th - -## Notes - -Resolver could change if the owner of the reverse registrar needs to upgrade or change it. The resolver on L1 could have an updateable list of resolver addresses that it can check the state on, or force all deprecated resolvers to reclaim their name. +Profiles are separate from resolvers as the authorised modifier needs the address, rather than the node diff --git a/contracts/reverseRegistrar/profiles/L2ReverseResolverBase.sol b/contracts/reverseRegistrar/profiles/L2ReverseResolverBase.sol new file mode 100644 index 00000000..d1be8ce6 --- /dev/null +++ b/contracts/reverseRegistrar/profiles/L2ReverseResolverBase.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.4; + +import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import "../../resolvers/profiles/IVersionableResolver.sol"; + +abstract contract L2ReverseResolverBase is ERC165 { + mapping(bytes32 => uint64) internal recordVersions; + event VersionChanged(bytes32 indexed node, uint64 newVersion); + bytes32 public immutable L2_REVERSE_NODE; + + bytes32 constant lookup = + 0x3031323334353637383961626364656600000000000000000000000000000000; + + function isAuthorised(address addr) internal view virtual returns (bool); + + constructor(bytes32 l2ReverseNode) { + L2_REVERSE_NODE = l2ReverseNode; + } + + modifier authorised(address addr) virtual { + require(isAuthorised(addr)); + _; + } + + /** + * Increments the record version associated with an ENS node. + * May only be called by the owner of that node in the ENS registry. + * @param addr The node to update. + */ + function clearRecords(address addr) public virtual authorised(addr) { + bytes32 labelHash = sha3HexAddress(addr); + bytes32 reverseNode = keccak256( + abi.encodePacked(L2_REVERSE_NODE, labelHash) + ); + recordVersions[reverseNode]++; + emit VersionChanged(reverseNode, recordVersions[reverseNode]); + } + + function supportsInterface( + bytes4 interfaceID + ) public view virtual override returns (bool) { + return + interfaceID == type(IVersionableResolver).interfaceId || + super.supportsInterface(interfaceID); + } + + /** + * @dev An optimised function to compute the sha3 of the lower-case + * hexadecimal representation of an Ethereum address. + * @param addr The address to hash + * @return ret The SHA3 hash of the lower-case hexadecimal encoding of the + * input address. + */ + function sha3HexAddress(address addr) internal pure returns (bytes32 ret) { + assembly { + for { + let i := 40 + } gt(i, 0) { + + } { + i := sub(i, 1) + mstore8(i, byte(and(addr, 0xf), lookup)) + addr := div(addr, 0x10) + i := sub(i, 1) + mstore8(i, byte(and(addr, 0xf), lookup)) + addr := div(addr, 0x10) + } + + ret := keccak256(0, 40) + } + } +} diff --git a/contracts/reverseRegistrar/profiles/NameResolver.sol b/contracts/reverseRegistrar/profiles/NameResolver.sol new file mode 100644 index 00000000..0bff9eb3 --- /dev/null +++ b/contracts/reverseRegistrar/profiles/NameResolver.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.4; + +import "./L2ReverseResolverBase.sol"; +import "../../resolvers/profiles/INameResolver.sol"; + +abstract contract NameResolver is INameResolver, L2ReverseResolverBase { + mapping(uint64 => mapping(bytes32 => string)) versionable_names; + + /** + * Sets the name associated with an ENS node, for reverse records. + * May only be called by the owner of that node in the ENS registry. + * @param node The node to update. + * @param newName name record + */ + function _setName(bytes32 node, string memory newName) internal virtual { + versionable_names[recordVersions[node]][node] = newName; + emit NameChanged(node, newName); + } + + /** + * Returns the name associated with an ENS node, for reverse records. + * Defined in EIP181. + * @param node The ENS node to query. + * @return The associated name. + */ + function name( + bytes32 node + ) external view virtual override returns (string memory) { + return versionable_names[recordVersions[node]][node]; + } + + function supportsInterface( + bytes4 interfaceID + ) public view virtual override returns (bool) { + return + interfaceID == type(INameResolver).interfaceId || + super.supportsInterface(interfaceID); + } +} diff --git a/contracts/reverseRegistrar/profiles/TextResolver.sol b/contracts/reverseRegistrar/profiles/TextResolver.sol new file mode 100644 index 00000000..748d12af --- /dev/null +++ b/contracts/reverseRegistrar/profiles/TextResolver.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.4; + +import "./L2ReverseResolverBase.sol"; +import "../../resolvers/profiles/ITextResolver.sol"; + +abstract contract TextResolver is ITextResolver, L2ReverseResolverBase { + mapping(uint64 => mapping(bytes32 => mapping(string => string))) versionable_texts; + + /** + * Sets the text data associated with an ENS node and key. + * May only be called by the owner of that node in the ENS registry. + * @param addr The node to update. + * @param key The key to set. + * @param value The text data value to set. + */ + function setText( + address addr, + string calldata key, + string calldata value + ) external virtual authorised(addr) { + bytes32 labelHash = sha3HexAddress(addr); + bytes32 reverseNode = keccak256( + abi.encodePacked(L2_REVERSE_NODE, labelHash) + ); + versionable_texts[recordVersions[reverseNode]][reverseNode][ + key + ] = value; + emit TextChanged(reverseNode, key, key, value); + } + + /** + * Returns the text data associated with an ENS node and key. + * @param node The ENS node to query. + * @param key The text data key to query. + * @return The associated text data. + */ + function text( + bytes32 node, + string calldata key + ) external view virtual override returns (string memory) { + return versionable_texts[recordVersions[node]][node][key]; + } + + function supportsInterface( + bytes4 interfaceID + ) public view virtual override returns (bool) { + return + interfaceID == type(ITextResolver).interfaceId || + super.supportsInterface(interfaceID); + } +} From 29fb8d795feba377c4592a3f55157bba178468a8 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Sat, 7 Oct 2023 00:57:26 +0800 Subject: [PATCH 04/37] Add boilerplate for tests --- .../reverseRegistrar/L2ReverseRegistrar.sol | 14 +++++------ .../{NameResolver.sol => L2NameResolver.sol} | 2 +- .../{TextResolver.sol => L2TextResolver.sol} | 2 +- .../TestL2ReverseRegistrar.js | 23 +++++++++++++++++++ 4 files changed, 32 insertions(+), 9 deletions(-) rename contracts/reverseRegistrar/profiles/{NameResolver.sol => L2NameResolver.sol} (94%) rename contracts/reverseRegistrar/profiles/{TextResolver.sol => L2TextResolver.sol} (95%) create mode 100644 test/reverseRegistrar/TestL2ReverseRegistrar.js diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 86b0827c..4ad10295 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -6,8 +6,8 @@ import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "../root/Controllable.sol"; -import "./profiles/NameResolver.sol"; -import "./profiles/TextResolver.sol"; +import "./profiles/L2NameResolver.sol"; +import "./profiles/L2TextResolver.sol"; import "./profiles/L2ReverseResolverBase.sol"; error InvalidSignature(); @@ -16,13 +16,13 @@ contract L2ReverseRegistrar is Ownable, IL2ReverseRegistrar, L2ReverseResolverBase, - NameResolver, - TextResolver + L2NameResolver, + L2TextResolver { using ECDSA for bytes32; event ReverseClaimed(address indexed addr, bytes32 indexed node); - event DefaultResolverChanged(NameResolver indexed resolver); + event DefaultResolverChanged(L2NameResolver indexed resolver); /** * @dev Constructor @@ -142,8 +142,8 @@ contract L2ReverseRegistrar is bytes4 interfaceID ) public - pure - override(NameResolver, TextResolver, L2ReverseResolverBase) + view + override(L2NameResolver, L2TextResolver, L2ReverseResolverBase) returns (bool) { return diff --git a/contracts/reverseRegistrar/profiles/NameResolver.sol b/contracts/reverseRegistrar/profiles/L2NameResolver.sol similarity index 94% rename from contracts/reverseRegistrar/profiles/NameResolver.sol rename to contracts/reverseRegistrar/profiles/L2NameResolver.sol index 0bff9eb3..9d92d831 100644 --- a/contracts/reverseRegistrar/profiles/NameResolver.sol +++ b/contracts/reverseRegistrar/profiles/L2NameResolver.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.4; import "./L2ReverseResolverBase.sol"; import "../../resolvers/profiles/INameResolver.sol"; -abstract contract NameResolver is INameResolver, L2ReverseResolverBase { +abstract contract L2NameResolver is INameResolver, L2ReverseResolverBase { mapping(uint64 => mapping(bytes32 => string)) versionable_names; /** diff --git a/contracts/reverseRegistrar/profiles/TextResolver.sol b/contracts/reverseRegistrar/profiles/L2TextResolver.sol similarity index 95% rename from contracts/reverseRegistrar/profiles/TextResolver.sol rename to contracts/reverseRegistrar/profiles/L2TextResolver.sol index 748d12af..97213890 100644 --- a/contracts/reverseRegistrar/profiles/TextResolver.sol +++ b/contracts/reverseRegistrar/profiles/L2TextResolver.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.4; import "./L2ReverseResolverBase.sol"; import "../../resolvers/profiles/ITextResolver.sol"; -abstract contract TextResolver is ITextResolver, L2ReverseResolverBase { +abstract contract L2TextResolver is ITextResolver, L2ReverseResolverBase { mapping(uint64 => mapping(bytes32 => mapping(string => string))) versionable_texts; /** diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js new file mode 100644 index 00000000..6b8212e1 --- /dev/null +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -0,0 +1,23 @@ +const { expect } = require('chai') +const { ethers } = require('hardhat') +const { labelhash, namehash } = require('../test-utils/ens') + +describe('L2ReverseRegistrar', function () { + let l2ReverseRegistrar + + beforeEach(async function () { + const L2ReverseRegistrar = await ethers.getContractFactory( + 'L2ReverseRegistrar', + ) + l2ReverseRegistrar = await L2ReverseRegistrar.deploy( + namehash('optimsim.reverse'), + ) + await l2ReverseRegistrar.deployed() + }) + + it('should deploy the contract', async function () { + expect(l2ReverseRegistrar.address).to.not.equal(0) + }) + + // Add more tests here +}) From 7d632cbfe87ed831faf47d791ca13c2723603203 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Sat, 7 Oct 2023 01:07:36 +0800 Subject: [PATCH 05/37] Remove setNameForAddr() --- contracts/reverseRegistrar/IL2ReverseRegistrar.sol | 6 ------ contracts/reverseRegistrar/L2ReverseRegistrar.sol | 11 +---------- 2 files changed, 1 insertion(+), 16 deletions(-) diff --git a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol index ce60c53f..2bfc7878 100644 --- a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol @@ -3,12 +3,6 @@ pragma solidity >=0.8.4; interface IL2ReverseRegistrar { function setName(string memory name) external returns (bytes32); - function setNameForAddr( - address addr, - address owner, - string memory name - ) external returns (bytes32); - function setNameForAddrWithSignature( address addr, address owner, diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 4ad10295..67eb4a2d 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -100,20 +100,11 @@ contract L2ReverseRegistrar is return setNameForAddr(msg.sender, msg.sender, name); } - /** - * @dev Sets the `name()` record for the reverse ENS record associated with - * the account provided. Updates the resolver to a designated resolver - * Only callable by controllers and authorised users - * @param addr The reverse record to set - * @param owner The owner of the reverse node - * @param name The name to set for this address. - * @return The ENS node hash of the reverse record. - */ function setNameForAddr( address addr, address owner, string memory name - ) public override returns (bytes32) { + ) internal returns (bytes32) { bytes32 labelHash = sha3HexAddress(addr); bytes32 node = keccak256(abi.encodePacked(L2_REVERSE_NODE, labelHash)); _setName(node, name); From 0c67b45de72b1255259c9a04c1dbaef632214dd2 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Mon, 9 Oct 2023 19:33:10 +0800 Subject: [PATCH 06/37] WIP --- contracts/reverseRegistrar/L2ReverseRegistrar.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 67eb4a2d..d9a8b4df 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -77,6 +77,8 @@ contract L2ReverseRegistrar is bytes32 message = hash.toEthSignedMessageHash(); + // TODO - check if addr owns the contract + if ( !SignatureChecker.isValidSignatureNow(addr, message, signature) || relayer != msg.sender || From 1e464bd7a997db791b0df50efb935818faf99272 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Thu, 12 Oct 2023 13:06:46 +0800 Subject: [PATCH 07/37] Add setName test, remove setNameForAddr() --- contracts/reverseRegistrar/L2ReverseRegistrar.sol | 10 +--------- test/reverseRegistrar/TestL2ReverseRegistrar.js | 14 +++++++++++++- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index d9a8b4df..8cd8c435 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -99,15 +99,7 @@ contract L2ReverseRegistrar is * @return The ENS node hash of the reverse record. */ function setName(string memory name) public override returns (bytes32) { - return setNameForAddr(msg.sender, msg.sender, name); - } - - function setNameForAddr( - address addr, - address owner, - string memory name - ) internal returns (bytes32) { - bytes32 labelHash = sha3HexAddress(addr); + bytes32 labelHash = sha3HexAddress(msg.sender); bytes32 node = keccak256(abi.encodePacked(L2_REVERSE_NODE, labelHash)); _setName(node, name); return node; diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index 6b8212e1..5eab9478 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -19,5 +19,17 @@ describe('L2ReverseRegistrar', function () { expect(l2ReverseRegistrar.address).to.not.equal(0) }) - // Add more tests here + //write all my tests for me + it('should set the name record for the calling account', async function () { + const name = 'myname.eth' + const tx = await l2ReverseRegistrar.setName(name) + await tx.wait() + + const node = await l2ReverseRegistrar.node( + await ethers.provider.getSigner().getAddress(), + ) + const actualName = await l2ReverseRegistrar.name(node) + + expect(actualName).to.equal(name) + }) }) From 27854e44d6356819af3d748126eacf626dc36e18 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Thu, 12 Oct 2023 18:07:28 +0800 Subject: [PATCH 08/37] Update OZ library to 4.9.3, add setNameWithAddrWithSignature tests --- .../reverseRegistrar/IL2ReverseRegistrar.sol | 2 - .../reverseRegistrar/L2ReverseRegistrar.sol | 86 ++++++++++++++++--- package.json | 2 +- .../TestL2ReverseRegistrar.js | 78 ++++++++++++++++- yarn.lock | 8 +- 5 files changed, 152 insertions(+), 24 deletions(-) diff --git a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol index 2bfc7878..64f291d5 100644 --- a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol @@ -5,9 +5,7 @@ interface IL2ReverseRegistrar { function setNameForAddrWithSignature( address addr, - address owner, string memory name, - address resolver, address relayer, uint256 signatureExpiry, bytes memory signature diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 8cd8c435..27bc682f 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -24,6 +24,8 @@ contract L2ReverseRegistrar is event ReverseClaimed(address indexed addr, bytes32 indexed node); event DefaultResolverChanged(L2NameResolver indexed resolver); + uint256 constant EXPIRY = 1 days; + /** * @dev Constructor */ @@ -42,18 +44,17 @@ contract L2ReverseRegistrar is } /** - * @dev Transfers ownership of the reverse ENS record associated with the - * calling account. + * @dev sets the name for an addr using a signature * @param addr The reverse record to set - * @param owner The address to set as the owner of the reverse record in ENS. - * @param resolver The resolver of the reverse node + * @param name The name of the reverse record + * @param relayer The relayer of the transaction. Can be address(0) if the user does not want to restrict + * @param signatureExpiry The resolver of the reverse node + * @param signature The resolver of the reverse node * @return The ENS node hash of the reverse record. */ function setNameForAddrWithSignature( address addr, - address owner, string memory name, - address resolver, address relayer, uint256 signatureExpiry, bytes memory signature @@ -67,9 +68,7 @@ contract L2ReverseRegistrar is abi.encodePacked( IL2ReverseRegistrar.setNameForAddrWithSignature.selector, addr, - owner, name, - resolver, relayer, signatureExpiry ) @@ -77,13 +76,11 @@ contract L2ReverseRegistrar is bytes32 message = hash.toEthSignedMessageHash(); - // TODO - check if addr owns the contract - if ( !SignatureChecker.isValidSignatureNow(addr, message, signature) || - relayer != msg.sender || + (relayer != address(0) && relayer != msg.sender) || signatureExpiry < block.timestamp || - signatureExpiry > block.timestamp + 1 days + signatureExpiry > block.timestamp + EXPIRY ) { revert InvalidSignature(); } @@ -92,6 +89,55 @@ contract L2ReverseRegistrar is return reverseNode; } + /** + * @dev sets the name for a contract that is owned by a SCW using a signature + * @param addr The reverse record to set + * @param name The name of the reverse record + * @param relayer The relayer of the transaction. Can be address(0) if the user does not want to restrict + * @param signatureExpiry The resolver of the reverse node + * @param signature The resolver of the reverse node + * @return The ENS node hash of the reverse record. + */ + function setNameForAddrWithSignature( + address addr, + address smartContractWallet, + string memory name, + address relayer, + uint256 signatureExpiry, + bytes memory signature + ) public returns (bytes32) { + bytes32 labelHash = sha3HexAddress(addr); + bytes32 reverseNode = keccak256( + abi.encodePacked(L2_REVERSE_NODE, labelHash) + ); + + bytes32 hash = keccak256( + abi.encodePacked( + IL2ReverseRegistrar.setNameForAddrWithSignature.selector, + addr, + name, + relayer, + signatureExpiry + ) + ); + + bytes32 message = hash.toEthSignedMessageHash(); + + if ( + ownsContract(smartContractWallet) && + SignatureChecker.isValidERC1271SignatureNow( + smartContractWallet, + message, + signature + ) + ) { + _setName(reverseNode, name); + return reverseNode; + } + + revert InvalidSignature(); + } + /** * @dev Sets the `name()` record for the reverse ENS record associated with * the calling account. @@ -99,7 +145,21 @@ contract L2ReverseRegistrar is * @return The ENS node hash of the reverse record. */ function setName(string memory name) public override returns (bytes32) { - bytes32 labelHash = sha3HexAddress(msg.sender); + return setNameForAddr(msg.sender, name); + } + + /** + * @dev Sets the `name()` record for the reverse ENS record associated with + * the addr provided account. + * @param name The name to set for this address. + * @return The ENS node hash of the reverse record. + */ + + function setNameForAddr( + address addr, + string memory name + ) public authorised(addr) returns (bytes32) { + bytes32 labelHash = sha3HexAddress(addr); bytes32 node = keccak256(abi.encodePacked(L2_REVERSE_NODE, labelHash)); _setName(node, name); return node; diff --git a/package.json b/package.json index d72f21bb..9489c418 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "dependencies": { "@ensdomains/buffer": "^0.1.1", "@ensdomains/solsha1": "0.0.3", - "@openzeppelin/contracts": "^4.1.0", + "@openzeppelin/contracts": "4.9.3", "dns-packet": "^5.3.0" }, "directories": { diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index 5eab9478..46cc27fd 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -1,17 +1,31 @@ const { expect } = require('chai') const { ethers } = require('hardhat') -const { labelhash, namehash } = require('../test-utils/ens') +const { namehash } = require('../test-utils/ens') +const { EMPTY_ADDRESS } = require('../test-utils/constants') describe('L2ReverseRegistrar', function () { let l2ReverseRegistrar + let l2ReverseRegistrarWithAccount2 + let signers + let account + let account2 + let setNameForAddrWithSignatureFuncSig = + 'setNameForAddrWithSignature(address,string,address,uint256,bytes)' + + before(async function () { + signers = await ethers.getSigners() + account = await signers[0].getAddress() + account2 = await signers[1].getAddress() - beforeEach(async function () { const L2ReverseRegistrar = await ethers.getContractFactory( 'L2ReverseRegistrar', ) l2ReverseRegistrar = await L2ReverseRegistrar.deploy( - namehash('optimsim.reverse'), + namehash('optimism.reverse'), ) + + l2ReverseRegistrarWithAccount2 = l2ReverseRegistrar.connect(signers[1]) + await l2ReverseRegistrar.deployed() }) @@ -29,7 +43,63 @@ describe('L2ReverseRegistrar', function () { await ethers.provider.getSigner().getAddress(), ) const actualName = await l2ReverseRegistrar.name(node) - expect(actualName).to.equal(name) }) + + describe('setNameForAddrWithSignature', function () { + it('allows an account to sign a message to allow a relayer to claim the address', async () => { + const funcId = ethers.utils + .id(setNameForAddrWithSignatureFuncSig) + .substring(0, 10) + + const block = await ethers.provider.getBlock('latest') + const signatureExpiry = block.timestamp + 3600 + const signature = await signers[0].signMessage( + ethers.utils.arrayify( + ethers.utils.solidityKeccak256( + ['bytes4', 'address', 'string', 'address', 'uint256'], + [funcId, account, 'hello.eth', EMPTY_ADDRESS, signatureExpiry], + ), + ), + ) + + await l2ReverseRegistrarWithAccount2[setNameForAddrWithSignatureFuncSig]( + account, + 'hello.eth', + EMPTY_ADDRESS, + signatureExpiry, + signature, + ) + + const node = await l2ReverseRegistrar.node(account) + assert.equal(await l2ReverseRegistrar.name(node), 'hello.eth') + }) + + it('reverts if signature parameters do not match', async () => { + const funcId = ethers.utils + .id(setNameForAddrWithSignatureFuncSig) + .substring(0, 10) + + const block = await ethers.provider.getBlock('latest') + const signatureExpiry = block.timestamp + 3600 + const signature = await signers[0].signMessage( + ethers.utils.arrayify( + ethers.utils.solidityKeccak256( + ['bytes4', 'address', 'string', 'address', 'uint256'], + [funcId, account, 'hello.eth', EMPTY_ADDRESS, signatureExpiry], + ), + ), + ) + + await expect( + l2ReverseRegistrarWithAccount2[setNameForAddrWithSignatureFuncSig]( + account, + 'notthesamename.eth', + EMPTY_ADDRESS, + signatureExpiry, + signature, + ), + ).to.be.revertedWith(`InvalidSignature()`) + }) + }) }) diff --git a/yarn.lock b/yarn.lock index 91ac4da1..2c00c781 100644 --- a/yarn.lock +++ b/yarn.lock @@ -865,10 +865,10 @@ find-up "^4.1.0" fs-extra "^8.1.0" -"@openzeppelin/contracts@^4.1.0": - version "4.8.0" - resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.8.0.tgz#6854c37df205dd2c056bdfa1b853f5d732109109" - integrity sha512-AGuwhRRL+NaKx73WKRNzeCxOCOCxpaqF+kp8TJ89QzAipSwZy/NoflkWaL9bywXFRhIzXt8j38sfF7KBKCPWLw== +"@openzeppelin/contracts@4.9.3": + version "4.9.3" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-4.9.3.tgz#00d7a8cf35a475b160b3f0293a6403c511099364" + integrity sha512-He3LieZ1pP2TNt5JbkPA4PNT9WC3gOTOlDcFGJW4Le4QKqwmiNJCRt44APfxMxvq7OugU/cqYuPcSBzOw38DAg== "@openzeppelin/test-helpers@^0.5.11": version "0.5.16" From 669081c227689eb8554169e227c8b0bedf79c048 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Thu, 19 Oct 2023 13:33:17 +0800 Subject: [PATCH 09/37] Add mocks to test ownable with signature --- .../reverseRegistrar/L2ReverseRegistrar.sol | 28 ++++--- .../TestL2ReverseRegistrar.js | 83 +++++++++++++++---- test/reverseRegistrar/mocks/MockOwnable.sol | 10 +++ .../mocks/MockSmartContractWallet.sol | 22 +++++ 4 files changed, 117 insertions(+), 26 deletions(-) create mode 100644 test/reverseRegistrar/mocks/MockOwnable.sol create mode 100644 test/reverseRegistrar/mocks/MockSmartContractWallet.sol diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 27bc682f..55376fd3 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -38,7 +38,7 @@ contract L2ReverseRegistrar is function isAuthorised(address addr) internal view override returns (bool) { require( - addr == msg.sender || ownsContract(addr), + addr == msg.sender || ownsContract(addr, msg.sender), "ReverseRegistrar: Caller is not a controller or authorised by address or the address itself" ); } @@ -91,22 +91,22 @@ contract L2ReverseRegistrar is /** * @dev sets the name for a contract that is owned by a SCW using a signature - * @param addr The reverse record to set + * @param contractAddr The reverse record to set * @param name The name of the reverse record * @param relayer The relayer of the transaction. Can be address(0) if the user does not want to restrict * @param signatureExpiry The resolver of the reverse node * @param signature The resolver of the reverse node * @return The ENS node hash of the reverse record. */ - function setNameForAddrWithSignature( - address addr, - address smartContractWallet, + function setNameForAddrWithSignatureAndOwnable( + address contractAddr, + address owner, string memory name, address relayer, uint256 signatureExpiry, bytes memory signature ) public returns (bytes32) { - bytes32 labelHash = sha3HexAddress(addr); + bytes32 labelHash = sha3HexAddress(contractAddr); bytes32 reverseNode = keccak256( abi.encodePacked(L2_REVERSE_NODE, labelHash) ); @@ -114,7 +114,8 @@ contract L2ReverseRegistrar is bytes32 hash = keccak256( abi.encodePacked( IL2ReverseRegistrar.setNameForAddrWithSignature.selector, - addr, + contractAddr, + owner, name, relayer, signatureExpiry @@ -124,9 +125,9 @@ contract L2ReverseRegistrar is bytes32 message = hash.toEthSignedMessageHash(); if ( - ownsContract(smartContractWallet) && + ownsContract(contractAddr, owner) && SignatureChecker.isValidERC1271SignatureNow( - smartContractWallet, + owner, message, signature ) @@ -175,9 +176,12 @@ contract L2ReverseRegistrar is keccak256(abi.encodePacked(L2_REVERSE_NODE, sha3HexAddress(addr))); } - function ownsContract(address addr) internal view returns (bool) { - try Ownable(addr).owner() returns (address owner) { - return owner == msg.sender; + function ownsContract( + address contractAddr, + address addr + ) internal view returns (bool) { + try Ownable(contractAddr).owner() returns (address owner) { + return owner == addr; } catch { return false; } diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index 46cc27fd..cee85a84 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -4,8 +4,10 @@ const { namehash } = require('../test-utils/ens') const { EMPTY_ADDRESS } = require('../test-utils/constants') describe('L2ReverseRegistrar', function () { - let l2ReverseRegistrar - let l2ReverseRegistrarWithAccount2 + let L2ReverseRegistrar + let L2ReverseRegistrarWithAccount2 + let MockSmartContractWallet + let MockOwnable let signers let account let account2 @@ -17,32 +19,44 @@ describe('L2ReverseRegistrar', function () { account = await signers[0].getAddress() account2 = await signers[1].getAddress() - const L2ReverseRegistrar = await ethers.getContractFactory( + const L2ReverseRegistrarFactory = await ethers.getContractFactory( 'L2ReverseRegistrar', ) - l2ReverseRegistrar = await L2ReverseRegistrar.deploy( + L2ReverseRegistrar = await L2ReverseRegistrarFactory.deploy( namehash('optimism.reverse'), ) - l2ReverseRegistrarWithAccount2 = l2ReverseRegistrar.connect(signers[1]) + const MockSmartContractWalletFactory = await ethers.getContractFactory( + 'MockSmartContractWallet', + ) + MockSmartContractWallet = await MockSmartContractWalletFactory.deploy( + account, + ) + + const MockOwnableFactory = await ethers.getContractFactory('MockOwnable') + MockOwnable = await MockOwnableFactory.deploy( + MockSmartContractWallet.address, + ) - await l2ReverseRegistrar.deployed() + L2ReverseRegistrarWithAccount2 = L2ReverseRegistrar.connect(signers[1]) + + await L2ReverseRegistrar.deployed() }) it('should deploy the contract', async function () { - expect(l2ReverseRegistrar.address).to.not.equal(0) + expect(L2ReverseRegistrar.address).to.not.equal(0) }) //write all my tests for me it('should set the name record for the calling account', async function () { const name = 'myname.eth' - const tx = await l2ReverseRegistrar.setName(name) + const tx = await L2ReverseRegistrar.setName(name) await tx.wait() - const node = await l2ReverseRegistrar.node( + const node = await L2ReverseRegistrar.node( await ethers.provider.getSigner().getAddress(), ) - const actualName = await l2ReverseRegistrar.name(node) + const actualName = await L2ReverseRegistrar.name(node) expect(actualName).to.equal(name) }) @@ -63,7 +77,7 @@ describe('L2ReverseRegistrar', function () { ), ) - await l2ReverseRegistrarWithAccount2[setNameForAddrWithSignatureFuncSig]( + await L2ReverseRegistrarWithAccount2['setNameForAddrWithSignature']( account, 'hello.eth', EMPTY_ADDRESS, @@ -71,8 +85,8 @@ describe('L2ReverseRegistrar', function () { signature, ) - const node = await l2ReverseRegistrar.node(account) - assert.equal(await l2ReverseRegistrar.name(node), 'hello.eth') + const node = await L2ReverseRegistrar.node(account) + assert.equal(await L2ReverseRegistrar.name(node), 'hello.eth') }) it('reverts if signature parameters do not match', async () => { @@ -92,7 +106,7 @@ describe('L2ReverseRegistrar', function () { ) await expect( - l2ReverseRegistrarWithAccount2[setNameForAddrWithSignatureFuncSig]( + L2ReverseRegistrarWithAccount2[setNameForAddrWithSignatureFuncSig]( account, 'notthesamename.eth', EMPTY_ADDRESS, @@ -102,4 +116,45 @@ describe('L2ReverseRegistrar', function () { ).to.be.revertedWith(`InvalidSignature()`) }) }) + + describe('setNameForAddrWithSignatureAndOwnable', function () { + it('allows an account to sign a message to allow a relayer to claim the address of a contract that is owned by another contract that the account is a signer of', async () => { + const node = await L2ReverseRegistrar.node(MockOwnable.address) + assert.equal(await L2ReverseRegistrar.name(node), '') + const funcId = ethers.utils + .id(setNameForAddrWithSignatureFuncSig) + .substring(0, 10) + + const block = await ethers.provider.getBlock('latest') + const signatureExpiry = block.timestamp + 3600 + const signature = await signers[0].signMessage( + ethers.utils.arrayify( + ethers.utils.solidityKeccak256( + ['bytes4', 'address', 'address', 'string', 'address', 'uint256'], + [ + funcId, + MockOwnable.address, + MockSmartContractWallet.address, + 'ownable.eth', + EMPTY_ADDRESS, + signatureExpiry, + ], + ), + ), + ) + + await L2ReverseRegistrarWithAccount2[ + 'setNameForAddrWithSignatureAndOwnable' + ]( + MockOwnable.address, + MockSmartContractWallet.address, + 'ownable.eth', + EMPTY_ADDRESS, + signatureExpiry, + signature, + ) + + assert.equal(await L2ReverseRegistrar.name(node), 'ownable.eth') + }) + }) }) diff --git a/test/reverseRegistrar/mocks/MockOwnable.sol b/test/reverseRegistrar/mocks/MockOwnable.sol new file mode 100644 index 00000000..c0354666 --- /dev/null +++ b/test/reverseRegistrar/mocks/MockOwnable.sol @@ -0,0 +1,10 @@ +//SPDX-License-Identifier: MIT +pragma solidity >=0.8.17 <0.9.0; + +contract MockOwnable { + address public owner; + + constructor(address _owner) { + owner = _owner; + } +} diff --git a/test/reverseRegistrar/mocks/MockSmartContractWallet.sol b/test/reverseRegistrar/mocks/MockSmartContractWallet.sol new file mode 100644 index 00000000..53250984 --- /dev/null +++ b/test/reverseRegistrar/mocks/MockSmartContractWallet.sol @@ -0,0 +1,22 @@ +//SPDX-License-Identifier: MIT +pragma solidity >=0.8.17 <0.9.0; +// import signatureVerifier by openzepellin +import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; + +contract MockSmartContractWallet { + address public owner; + + constructor(address _owner) { + owner = _owner; + } + + function isValidSignature( + bytes32 hash, + bytes memory signature + ) public view returns (bytes4) { + if (SignatureChecker.isValidSignatureNow(owner, hash, signature)) { + return 0x1626ba7e; + } + return 0xffffffff; + } +} From fea6ecb39da2037580ac8f10025fccf253bbb4de Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Thu, 19 Oct 2023 13:40:27 +0800 Subject: [PATCH 10/37] Fix Natspec --- contracts/reverseRegistrar/IL2ReverseRegistrar.sol | 9 +++++++++ contracts/reverseRegistrar/L2ReverseRegistrar.sol | 9 +++++---- contracts/reverseRegistrar/README.md | 4 ++++ contracts/reverseRegistrar/profiles/L2TextResolver.sol | 2 +- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol index 64f291d5..d66c1491 100644 --- a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol @@ -11,5 +11,14 @@ interface IL2ReverseRegistrar { bytes memory signature ) external returns (bytes32); + function setNameForAddrWithSignatureAndOwnable( + address contractAddr, + address owner, + string memory name, + address relayer, + uint256 signatureExpiry, + bytes memory signature + ) external returns (bytes32); + function node(address addr) external view returns (bytes32); } diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 55376fd3..3c6b5657 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -44,7 +44,7 @@ contract L2ReverseRegistrar is } /** - * @dev sets the name for an addr using a signature + * @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 relayer The relayer of the transaction. Can be address(0) if the user does not want to restrict @@ -90,12 +90,13 @@ contract L2ReverseRegistrar is } /** - * @dev sets the name for a contract that is owned by a SCW using a signature - * @param contractAddr The reverse record to set + * @dev Sets the name for a contract that is owned by a SCW using a signature + * @param contractAddr The reverse node to set + * @param owner The owner of the contract (via Ownable) * @param name The name of the reverse record * @param relayer The relayer of the transaction. Can be address(0) if the user does not want to restrict * @param signatureExpiry The resolver of the reverse node - * @param signature The resolver of the reverse node + * @param signature The signature of an address that will return true on isValidSignature for the owner * @return The ENS node hash of the reverse record. */ function setNameForAddrWithSignatureAndOwnable( diff --git a/contracts/reverseRegistrar/README.md b/contracts/reverseRegistrar/README.md index 95b8c8b1..fdfff8de 100644 --- a/contracts/reverseRegistrar/README.md +++ b/contracts/reverseRegistrar/README.md @@ -1,5 +1,9 @@ # L2 Reverse Registrar +## Summary + +The L2 Reverse registrar is a combination of a resolver and a reverse registrar that allows the name to be set for a particular reverse node. + ## Resolver Profiles Profiles are separate from resolvers as the authorised modifier needs the address, rather than the node diff --git a/contracts/reverseRegistrar/profiles/L2TextResolver.sol b/contracts/reverseRegistrar/profiles/L2TextResolver.sol index 97213890..136f0e74 100644 --- a/contracts/reverseRegistrar/profiles/L2TextResolver.sol +++ b/contracts/reverseRegistrar/profiles/L2TextResolver.sol @@ -18,7 +18,7 @@ abstract contract L2TextResolver is ITextResolver, L2ReverseResolverBase { address addr, string calldata key, string calldata value - ) external virtual authorised(addr) { + ) external authorised(addr) { bytes32 labelHash = sha3HexAddress(addr); bytes32 reverseNode = keccak256( abi.encodePacked(L2_REVERSE_NODE, labelHash) From 4ef442fb254dbc4ee6d7e0cd3a0e070a1d9ebdc9 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Thu, 19 Oct 2023 17:09:17 +0800 Subject: [PATCH 11/37] Add signature setting for text records --- .../reverseRegistrar/IL2ReverseRegistrar.sol | 35 ++++ .../reverseRegistrar/L2ReverseRegistrar.sol | 159 ++++++++++++++++-- .../profiles/L2TextResolver.sol | 23 +-- .../TestL2ReverseRegistrar.js | 4 +- 4 files changed, 187 insertions(+), 34 deletions(-) diff --git a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol index d66c1491..fc9da686 100644 --- a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol @@ -3,6 +3,11 @@ pragma solidity >=0.8.4; interface IL2ReverseRegistrar { function setName(string memory name) external returns (bytes32); + function setNameForAddr( + address addr, + string memory name + ) external returns (bytes32); + function setNameForAddrWithSignature( address addr, string memory name, @@ -20,5 +25,35 @@ interface IL2ReverseRegistrar { bytes memory signature ) external returns (bytes32); + function setText( + string calldata key, + string calldata value + ) external returns (bytes32); + + function setTextForAddr( + address addr, + string calldata key, + string calldata value + ) external returns (bytes32); + + function setTextForAddrWithSignature( + address addr, + string calldata key, + string calldata value, + address relayer, + uint256 signatureExpiry, + bytes memory signature + ) external returns (bytes32); + + function setTextForAddrWithSignatureAndOwnable( + address contractAddr, + address owner, + string calldata key, + string calldata value, + address relayer, + uint256 signatureExpiry, + bytes memory signature + ) external returns (bytes32); + function node(address addr) external view returns (bytes32); } diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 3c6b5657..27804452 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -59,10 +59,7 @@ contract L2ReverseRegistrar is uint256 signatureExpiry, bytes memory signature ) public override returns (bytes32) { - bytes32 labelHash = sha3HexAddress(addr); - bytes32 reverseNode = keccak256( - abi.encodePacked(L2_REVERSE_NODE, labelHash) - ); + bytes32 node = _getNamehash(addr); bytes32 hash = keccak256( abi.encodePacked( @@ -85,8 +82,8 @@ contract L2ReverseRegistrar is revert InvalidSignature(); } - _setName(reverseNode, name); - return reverseNode; + _setName(node, name); + return node; } /** @@ -107,14 +104,13 @@ contract L2ReverseRegistrar is uint256 signatureExpiry, bytes memory signature ) public returns (bytes32) { - bytes32 labelHash = sha3HexAddress(contractAddr); - bytes32 reverseNode = keccak256( - abi.encodePacked(L2_REVERSE_NODE, labelHash) - ); + bytes32 node = _getNamehash(contractAddr); bytes32 hash = keccak256( abi.encodePacked( - IL2ReverseRegistrar.setNameForAddrWithSignature.selector, + IL2ReverseRegistrar + .setNameForAddrWithSignatureAndOwnable + .selector, contractAddr, owner, name, @@ -133,8 +129,8 @@ contract L2ReverseRegistrar is signature ) ) { - _setName(reverseNode, name); - return reverseNode; + _setName(node, name); + return node; } revert InvalidSignature(); @@ -161,12 +157,140 @@ contract L2ReverseRegistrar is address addr, string memory name ) public authorised(addr) returns (bytes32) { - bytes32 labelHash = sha3HexAddress(addr); - bytes32 node = keccak256(abi.encodePacked(L2_REVERSE_NODE, labelHash)); + bytes32 node = _getNamehash(addr); _setName(node, name); return node; } + /** + * @dev Sets the name for an addr using a signature that can be verified with ERC1271. + * @param addr The reverse record to set + * @param key The key of the text record + * @param value The value of the text record + * @param relayer The relayer of the transaction. Can be address(0) if the user does not want to restrict + * @param signatureExpiry The resolver of the reverse node + * @param signature The resolver of the reverse node + * @return The ENS node hash of the reverse record. + */ + function setTextForAddrWithSignature( + address addr, + string calldata key, + string calldata value, + address relayer, + uint256 signatureExpiry, + bytes memory signature + ) public override returns (bytes32) { + bytes32 node = _getNamehash(addr); + + bytes32 hash = keccak256( + abi.encodePacked( + IL2ReverseRegistrar.setTextForAddrWithSignature.selector, + addr, + key, + value, + relayer, + signatureExpiry + ) + ); + + bytes32 message = hash.toEthSignedMessageHash(); + + if ( + !SignatureChecker.isValidSignatureNow(addr, message, signature) || + (relayer != address(0) && relayer != msg.sender) || + signatureExpiry < block.timestamp || + signatureExpiry > block.timestamp + EXPIRY + ) { + revert InvalidSignature(); + } + + _setText(node, key, value); + return node; + } + + /** + * @dev Sets the name for a contract that is owned by a SCW using a signature + * @param contractAddr The reverse node to set + * @param owner The owner of the contract (via Ownable) + * @param key The name of the reverse record + * @param value The name of the reverse record + * @param relayer The relayer of the transaction. Can be address(0) if the user does not want to restrict + * @param signatureExpiry The resolver of the reverse node + * @param signature The signature of an address that will return true on isValidSignature for the owner + * @return The ENS node hash of the reverse record. + */ + function setTextForAddrWithSignatureAndOwnable( + address contractAddr, + address owner, + string calldata key, + string calldata value, + address relayer, + uint256 signatureExpiry, + bytes memory signature + ) public returns (bytes32) { + bytes32 node = _getNamehash(contractAddr); + + bytes32 hash = keccak256( + abi.encodePacked( + IL2ReverseRegistrar.setNameForAddrWithSignature.selector, + contractAddr, + owner, + key, + value, + relayer, + signatureExpiry + ) + ); + + bytes32 message = hash.toEthSignedMessageHash(); + + if ( + ownsContract(contractAddr, owner) && + SignatureChecker.isValidERC1271SignatureNow( + owner, + message, + signature + ) + ) { + _setText(node, key, value); + return node; + } + + revert InvalidSignature(); + } + + /** + * @dev Sets the `name()` record for the reverse ENS record associated with + * the calling account. + * @param key The key for this text record. + * @param value The value to set for this text record. + * @return The ENS node hash of the reverse record. + */ + function setText( + string calldata key, + string calldata value + ) public override returns (bytes32) { + return setTextForAddr(msg.sender, key, value); + } + + /** + * @dev Sets the `text(key)` record for the reverse ENS record associated with + * the addr provided account. + * @param key The key for this text record. + * @param value The value to set for this text record. + * @return The ENS node hash of the reverse record. + */ + + function setTextForAddr( + address addr, + string calldata key, + string calldata value + ) public override authorised(addr) returns (bytes32) { + bytes32 node = _getNamehash(addr); + _setText(node, key, value); + return node; + } + /** * @dev Returns the node hash for a given account's reverse records. * @param addr The address to hash @@ -188,6 +312,11 @@ contract L2ReverseRegistrar is } } + function _getNamehash(address addr) internal view returns (bytes32) { + bytes32 labelHash = sha3HexAddress(addr); + return keccak256(abi.encodePacked(L2_REVERSE_NODE, labelHash)); + } + function supportsInterface( bytes4 interfaceID ) diff --git a/contracts/reverseRegistrar/profiles/L2TextResolver.sol b/contracts/reverseRegistrar/profiles/L2TextResolver.sol index 136f0e74..72eb8cdc 100644 --- a/contracts/reverseRegistrar/profiles/L2TextResolver.sol +++ b/contracts/reverseRegistrar/profiles/L2TextResolver.sol @@ -7,26 +7,13 @@ import "../../resolvers/profiles/ITextResolver.sol"; abstract contract L2TextResolver is ITextResolver, L2ReverseResolverBase { mapping(uint64 => mapping(bytes32 => mapping(string => string))) versionable_texts; - /** - * Sets the text data associated with an ENS node and key. - * May only be called by the owner of that node in the ENS registry. - * @param addr The node to update. - * @param key The key to set. - * @param value The text data value to set. - */ - function setText( - address addr, + function _setText( + bytes32 node, string calldata key, string calldata value - ) external authorised(addr) { - bytes32 labelHash = sha3HexAddress(addr); - bytes32 reverseNode = keccak256( - abi.encodePacked(L2_REVERSE_NODE, labelHash) - ); - versionable_texts[recordVersions[reverseNode]][reverseNode][ - key - ] = value; - emit TextChanged(reverseNode, key, key, value); + ) internal { + versionable_texts[recordVersions[node]][node][key] = value; + emit TextChanged(node, key, key, value); } /** diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index cee85a84..dd52551a 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -13,6 +13,8 @@ describe('L2ReverseRegistrar', function () { let account2 let setNameForAddrWithSignatureFuncSig = 'setNameForAddrWithSignature(address,string,address,uint256,bytes)' + let setNameForAddrWithSignatureAndOwnableFuncSig = + 'setNameForAddrWithSignatureAndOwnable(address,address,string,address,uint256,bytes)' before(async function () { signers = await ethers.getSigners() @@ -122,7 +124,7 @@ describe('L2ReverseRegistrar', function () { const node = await L2ReverseRegistrar.node(MockOwnable.address) assert.equal(await L2ReverseRegistrar.name(node), '') const funcId = ethers.utils - .id(setNameForAddrWithSignatureFuncSig) + .id(setNameForAddrWithSignatureAndOwnableFuncSig) .substring(0, 10) const block = await ethers.provider.getBlock('latest') From c9274e3f7d08c1f449de76186e97f1b1962d5a6e Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Thu, 19 Oct 2023 17:45:01 +0800 Subject: [PATCH 12/37] Add tests for TextResolver --- .../reverseRegistrar/L2ReverseRegistrar.sol | 4 +- .../TestL2ReverseRegistrar.js | 181 ++++++++++++++++-- 2 files changed, 172 insertions(+), 13 deletions(-) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 27804452..4770ff89 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -232,7 +232,9 @@ contract L2ReverseRegistrar is bytes32 hash = keccak256( abi.encodePacked( - IL2ReverseRegistrar.setNameForAddrWithSignature.selector, + IL2ReverseRegistrar + .setTextForAddrWithSignatureAndOwnable + .selector, contractAddr, owner, key, diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index dd52551a..a1057777 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -15,6 +15,10 @@ describe('L2ReverseRegistrar', function () { 'setNameForAddrWithSignature(address,string,address,uint256,bytes)' let setNameForAddrWithSignatureAndOwnableFuncSig = 'setNameForAddrWithSignatureAndOwnable(address,address,string,address,uint256,bytes)' + let setTextForAddrWithSignatureFuncSig = + 'setTextForAddrWithSignature(address,string,string,address,uint256,bytes)' + let setTextForAddrWithSignatureAndOwnableFuncSig = + 'setTextForAddrWithSignatureAndOwnable(address,address,string,string,address,uint256,bytes)' before(async function () { signers = await ethers.getSigners() @@ -45,24 +49,32 @@ describe('L2ReverseRegistrar', function () { await L2ReverseRegistrar.deployed() }) + beforeEach(async () => { + result = await ethers.provider.send('evm_snapshot') + }) + afterEach(async () => { + await ethers.provider.send('evm_revert', [result]) + }) + it('should deploy the contract', async function () { expect(L2ReverseRegistrar.address).to.not.equal(0) }) - //write all my tests for me - it('should set the name record for the calling account', async function () { - const name = 'myname.eth' - const tx = await L2ReverseRegistrar.setName(name) - await tx.wait() + describe('setName', () => { + it('should set the name record for the calling account', async function () { + const name = 'myname.eth' + const tx = await L2ReverseRegistrar.setName(name) + await tx.wait() - const node = await L2ReverseRegistrar.node( - await ethers.provider.getSigner().getAddress(), - ) - const actualName = await L2ReverseRegistrar.name(node) - expect(actualName).to.equal(name) + const node = await L2ReverseRegistrar.node( + await ethers.provider.getSigner().getAddress(), + ) + const actualName = await L2ReverseRegistrar.name(node) + expect(actualName).to.equal(name) + }) }) - describe('setNameForAddrWithSignature', function () { + describe('setNameForAddrWithSignature', () => { it('allows an account to sign a message to allow a relayer to claim the address', async () => { const funcId = ethers.utils .id(setNameForAddrWithSignatureFuncSig) @@ -119,7 +131,7 @@ describe('L2ReverseRegistrar', function () { }) }) - describe('setNameForAddrWithSignatureAndOwnable', function () { + describe('setNameForAddrWithSignatureAndOwnable', () => { it('allows an account to sign a message to allow a relayer to claim the address of a contract that is owned by another contract that the account is a signer of', async () => { const node = await L2ReverseRegistrar.node(MockOwnable.address) assert.equal(await L2ReverseRegistrar.name(node), '') @@ -159,4 +171,149 @@ describe('L2ReverseRegistrar', function () { assert.equal(await L2ReverseRegistrar.name(node), 'ownable.eth') }) }) + + describe('setText', () => { + it('should set the text record for the calling account', async function () { + const key = 'url;' + const value = 'http://ens.domains' + const tx = await L2ReverseRegistrar.setText(key, value) + await tx.wait() + + const node = await L2ReverseRegistrar.node( + await ethers.provider.getSigner().getAddress(), + ) + const actualRecord = await L2ReverseRegistrar.text(node, key) + expect(actualRecord).to.equal(value) + }) + }) + + describe('setTextForAddrWithSignature', function () { + it('allows an account to sign a message to allow a relayer to claim the address', async () => { + const funcId = ethers.utils + .id(setTextForAddrWithSignatureFuncSig) + .substring(0, 10) + + const block = await ethers.provider.getBlock('latest') + const signatureExpiry = block.timestamp + 3600 + const signature = await signers[0].signMessage( + ethers.utils.arrayify( + ethers.utils.solidityKeccak256( + ['bytes4', 'address', 'string', 'string', 'address', 'uint256'], + [ + funcId, + account, + 'url', + 'http://ens.domains', + EMPTY_ADDRESS, + signatureExpiry, + ], + ), + ), + ) + + await L2ReverseRegistrarWithAccount2['setTextForAddrWithSignature']( + account, + 'url', + 'http://ens.domains', + EMPTY_ADDRESS, + signatureExpiry, + signature, + ) + + const node = await L2ReverseRegistrar.node(account) + assert.equal( + await L2ReverseRegistrar.text(node, 'url'), + 'http://ens.domains', + ) + }) + + it('reverts if signature parameters do not match', async () => { + const funcId = ethers.utils + .id(setNameForAddrWithSignatureFuncSig) + .substring(0, 10) + + const block = await ethers.provider.getBlock('latest') + const signatureExpiry = block.timestamp + 3600 + const signature = await signers[0].signMessage( + ethers.utils.arrayify( + ethers.utils.solidityKeccak256( + ['bytes4', 'address', 'string', 'string', 'address', 'uint256'], + [ + funcId, + account, + 'url', + 'http://ens.domains', + EMPTY_ADDRESS, + signatureExpiry, + ], + ), + ), + ) + + await expect( + L2ReverseRegistrarWithAccount2[setTextForAddrWithSignatureFuncSig]( + account, + 'url', + 'http://some.other.url.com', + EMPTY_ADDRESS, + signatureExpiry, + signature, + ), + ).to.be.revertedWith(`InvalidSignature()`) + }) + }) + + describe('setTextForAddrWithSignatureAndOwnable', function () { + it('allows an account to sign a message to allow a relayer to claim the address of a contract that is owned by another contract that the account is a signer of', async () => { + const node = await L2ReverseRegistrar.node(MockOwnable.address) + assert.equal(await L2ReverseRegistrar.text(node, 'url'), '') + const funcId = ethers.utils + .id(setTextForAddrWithSignatureAndOwnableFuncSig) + .substring(0, 10) + + const block = await ethers.provider.getBlock('latest') + const signatureExpiry = block.timestamp + 3600 + const signature = await signers[0].signMessage( + ethers.utils.arrayify( + ethers.utils.solidityKeccak256( + [ + 'bytes4', + 'address', + 'address', + 'string', + 'string', + 'address', + 'uint256', + ], + [ + funcId, + MockOwnable.address, + MockSmartContractWallet.address, + 'url', + 'http://ens.domains', + EMPTY_ADDRESS, + signatureExpiry, + ], + ), + ), + ) + + await L2ReverseRegistrarWithAccount2[ + 'setTextForAddrWithSignatureAndOwnable' + ]( + MockOwnable.address, + MockSmartContractWallet.address, + 'url', + 'http://ens.domains', + EMPTY_ADDRESS, + signatureExpiry, + signature, + ) + + assert.equal( + await L2ReverseRegistrar.text(node, 'url'), + 'http://ens.domains', + ) + }) + }) }) From de9959af1b4fdbf64f59d398b6ef5ae011fa3e19 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Tue, 24 Oct 2023 13:58:06 +0800 Subject: [PATCH 13/37] Remove relayer --- .../reverseRegistrar/IL2ReverseRegistrar.sol | 4 -- .../reverseRegistrar/L2ReverseRegistrar.sol | 14 ----- .../TestL2ReverseRegistrar.js | 60 +++++-------------- 3 files changed, 15 insertions(+), 63 deletions(-) diff --git a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol index fc9da686..c902377b 100644 --- a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol @@ -11,7 +11,6 @@ interface IL2ReverseRegistrar { function setNameForAddrWithSignature( address addr, string memory name, - address relayer, uint256 signatureExpiry, bytes memory signature ) external returns (bytes32); @@ -20,7 +19,6 @@ interface IL2ReverseRegistrar { address contractAddr, address owner, string memory name, - address relayer, uint256 signatureExpiry, bytes memory signature ) external returns (bytes32); @@ -40,7 +38,6 @@ interface IL2ReverseRegistrar { address addr, string calldata key, string calldata value, - address relayer, uint256 signatureExpiry, bytes memory signature ) external returns (bytes32); @@ -50,7 +47,6 @@ interface IL2ReverseRegistrar { address owner, string calldata key, string calldata value, - address relayer, uint256 signatureExpiry, bytes memory signature ) external returns (bytes32); diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 4770ff89..90bb50ee 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -47,7 +47,6 @@ contract L2ReverseRegistrar is * @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 relayer The relayer of the transaction. Can be address(0) if the user does not want to restrict * @param signatureExpiry The resolver of the reverse node * @param signature The resolver of the reverse node * @return The ENS node hash of the reverse record. @@ -55,7 +54,6 @@ contract L2ReverseRegistrar is function setNameForAddrWithSignature( address addr, string memory name, - address relayer, uint256 signatureExpiry, bytes memory signature ) public override returns (bytes32) { @@ -66,7 +64,6 @@ contract L2ReverseRegistrar is IL2ReverseRegistrar.setNameForAddrWithSignature.selector, addr, name, - relayer, signatureExpiry ) ); @@ -75,7 +72,6 @@ contract L2ReverseRegistrar is if ( !SignatureChecker.isValidSignatureNow(addr, message, signature) || - (relayer != address(0) && relayer != msg.sender) || signatureExpiry < block.timestamp || signatureExpiry > block.timestamp + EXPIRY ) { @@ -91,7 +87,6 @@ contract L2ReverseRegistrar is * @param contractAddr The reverse node to set * @param owner The owner of the contract (via Ownable) * @param name The name of the reverse record - * @param relayer The relayer of the transaction. Can be address(0) if the user does not want to restrict * @param signatureExpiry The resolver of the reverse node * @param signature The signature of an address that will return true on isValidSignature for the owner * @return The ENS node hash of the reverse record. @@ -100,7 +95,6 @@ contract L2ReverseRegistrar is address contractAddr, address owner, string memory name, - address relayer, uint256 signatureExpiry, bytes memory signature ) public returns (bytes32) { @@ -114,7 +108,6 @@ contract L2ReverseRegistrar is contractAddr, owner, name, - relayer, signatureExpiry ) ); @@ -167,7 +160,6 @@ contract L2ReverseRegistrar is * @param addr The reverse record to set * @param key The key of the text record * @param value The value of the text record - * @param relayer The relayer of the transaction. Can be address(0) if the user does not want to restrict * @param signatureExpiry The resolver of the reverse node * @param signature The resolver of the reverse node * @return The ENS node hash of the reverse record. @@ -176,7 +168,6 @@ contract L2ReverseRegistrar is address addr, string calldata key, string calldata value, - address relayer, uint256 signatureExpiry, bytes memory signature ) public override returns (bytes32) { @@ -188,7 +179,6 @@ contract L2ReverseRegistrar is addr, key, value, - relayer, signatureExpiry ) ); @@ -197,7 +187,6 @@ contract L2ReverseRegistrar is if ( !SignatureChecker.isValidSignatureNow(addr, message, signature) || - (relayer != address(0) && relayer != msg.sender) || signatureExpiry < block.timestamp || signatureExpiry > block.timestamp + EXPIRY ) { @@ -214,7 +203,6 @@ contract L2ReverseRegistrar is * @param owner The owner of the contract (via Ownable) * @param key The name of the reverse record * @param value The name of the reverse record - * @param relayer The relayer of the transaction. Can be address(0) if the user does not want to restrict * @param signatureExpiry The resolver of the reverse node * @param signature The signature of an address that will return true on isValidSignature for the owner * @return The ENS node hash of the reverse record. @@ -224,7 +212,6 @@ contract L2ReverseRegistrar is address owner, string calldata key, string calldata value, - address relayer, uint256 signatureExpiry, bytes memory signature ) public returns (bytes32) { @@ -239,7 +226,6 @@ contract L2ReverseRegistrar is owner, key, value, - relayer, signatureExpiry ) ); diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index a1057777..d0f95f33 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -12,13 +12,13 @@ describe('L2ReverseRegistrar', function () { let account let account2 let setNameForAddrWithSignatureFuncSig = - 'setNameForAddrWithSignature(address,string,address,uint256,bytes)' + 'setNameForAddrWithSignature(address,string,uint256,bytes)' let setNameForAddrWithSignatureAndOwnableFuncSig = - 'setNameForAddrWithSignatureAndOwnable(address,address,string,address,uint256,bytes)' + 'setNameForAddrWithSignatureAndOwnable(address,address,string,uint256,bytes)' let setTextForAddrWithSignatureFuncSig = - 'setTextForAddrWithSignature(address,string,string,address,uint256,bytes)' + 'setTextForAddrWithSignature(address,string,string,uint256,bytes)' let setTextForAddrWithSignatureAndOwnableFuncSig = - 'setTextForAddrWithSignatureAndOwnable(address,address,string,string,address,uint256,bytes)' + 'setTextForAddrWithSignatureAndOwnable(address,address,string,string,uint256,bytes)' before(async function () { signers = await ethers.getSigners() @@ -85,8 +85,8 @@ describe('L2ReverseRegistrar', function () { const signature = await signers[0].signMessage( ethers.utils.arrayify( ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'string', 'address', 'uint256'], - [funcId, account, 'hello.eth', EMPTY_ADDRESS, signatureExpiry], + ['bytes4', 'address', 'string', 'uint256'], + [funcId, account, 'hello.eth', signatureExpiry], ), ), ) @@ -94,7 +94,6 @@ describe('L2ReverseRegistrar', function () { await L2ReverseRegistrarWithAccount2['setNameForAddrWithSignature']( account, 'hello.eth', - EMPTY_ADDRESS, signatureExpiry, signature, ) @@ -113,8 +112,8 @@ describe('L2ReverseRegistrar', function () { const signature = await signers[0].signMessage( ethers.utils.arrayify( ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'string', 'address', 'uint256'], - [funcId, account, 'hello.eth', EMPTY_ADDRESS, signatureExpiry], + ['bytes4', 'address', 'string', 'uint256'], + [funcId, account, 'hello.eth', signatureExpiry], ), ), ) @@ -123,7 +122,6 @@ describe('L2ReverseRegistrar', function () { L2ReverseRegistrarWithAccount2[setNameForAddrWithSignatureFuncSig]( account, 'notthesamename.eth', - EMPTY_ADDRESS, signatureExpiry, signature, ), @@ -144,13 +142,12 @@ describe('L2ReverseRegistrar', function () { const signature = await signers[0].signMessage( ethers.utils.arrayify( ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'address', 'string', 'address', 'uint256'], + ['bytes4', 'address', 'address', 'string', 'uint256'], [ funcId, MockOwnable.address, MockSmartContractWallet.address, 'ownable.eth', - EMPTY_ADDRESS, signatureExpiry, ], ), @@ -163,7 +160,6 @@ describe('L2ReverseRegistrar', function () { MockOwnable.address, MockSmartContractWallet.address, 'ownable.eth', - EMPTY_ADDRESS, signatureExpiry, signature, ) @@ -198,15 +194,8 @@ describe('L2ReverseRegistrar', function () { const signature = await signers[0].signMessage( ethers.utils.arrayify( ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'string', 'string', 'address', 'uint256'], - [ - funcId, - account, - 'url', - 'http://ens.domains', - EMPTY_ADDRESS, - signatureExpiry, - ], + ['bytes4', 'address', 'string', 'string', 'uint256'], + [funcId, account, 'url', 'http://ens.domains', signatureExpiry], ), ), ) @@ -215,7 +204,6 @@ describe('L2ReverseRegistrar', function () { account, 'url', 'http://ens.domains', - EMPTY_ADDRESS, signatureExpiry, signature, ) @@ -229,7 +217,7 @@ describe('L2ReverseRegistrar', function () { it('reverts if signature parameters do not match', async () => { const funcId = ethers.utils - .id(setNameForAddrWithSignatureFuncSig) + .id(setTextForAddrWithSignatureFuncSig) .substring(0, 10) const block = await ethers.provider.getBlock('latest') @@ -237,15 +225,8 @@ describe('L2ReverseRegistrar', function () { const signature = await signers[0].signMessage( ethers.utils.arrayify( ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'string', 'string', 'address', 'uint256'], - [ - funcId, - account, - 'url', - 'http://ens.domains', - EMPTY_ADDRESS, - signatureExpiry, - ], + ['bytes4', 'address', 'string', 'string', 'uint256'], + [funcId, account, 'url', 'http://ens.domains', signatureExpiry], ), ), ) @@ -255,7 +236,6 @@ describe('L2ReverseRegistrar', function () { account, 'url', 'http://some.other.url.com', - EMPTY_ADDRESS, signatureExpiry, signature, ), @@ -276,22 +256,13 @@ describe('L2ReverseRegistrar', function () { const signature = await signers[0].signMessage( ethers.utils.arrayify( ethers.utils.solidityKeccak256( - [ - 'bytes4', - 'address', - 'address', - 'string', - 'string', - 'address', - 'uint256', - ], + ['bytes4', 'address', 'address', 'string', 'string', 'uint256'], [ funcId, MockOwnable.address, MockSmartContractWallet.address, 'url', 'http://ens.domains', - EMPTY_ADDRESS, signatureExpiry, ], ), @@ -305,7 +276,6 @@ describe('L2ReverseRegistrar', function () { MockSmartContractWallet.address, 'url', 'http://ens.domains', - EMPTY_ADDRESS, signatureExpiry, signature, ) From a104c835e7e7ede6edad7f4e5814d9bef61f9838 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Tue, 24 Oct 2023 17:49:52 +0800 Subject: [PATCH 14/37] Change expiry to inception date --- .../reverseRegistrar/IL2ReverseRegistrar.sol | 8 ++-- .../reverseRegistrar/L2ReverseRegistrar.sol | 45 ++++++++++--------- 2 files changed, 29 insertions(+), 24 deletions(-) diff --git a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol index c902377b..84fb0afc 100644 --- a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol @@ -11,7 +11,7 @@ interface IL2ReverseRegistrar { function setNameForAddrWithSignature( address addr, string memory name, - uint256 signatureExpiry, + uint256 inceptionDate, bytes memory signature ) external returns (bytes32); @@ -19,7 +19,7 @@ interface IL2ReverseRegistrar { address contractAddr, address owner, string memory name, - uint256 signatureExpiry, + uint256 inceptionDate, bytes memory signature ) external returns (bytes32); @@ -38,7 +38,7 @@ interface IL2ReverseRegistrar { address addr, string calldata key, string calldata value, - uint256 signatureExpiry, + uint256 inceptionDate, bytes memory signature ) external returns (bytes32); @@ -47,7 +47,7 @@ interface IL2ReverseRegistrar { address owner, string calldata key, string calldata value, - uint256 signatureExpiry, + uint256 inceptionDate, bytes memory signature ) external returns (bytes32); diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 90bb50ee..b0752347 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -20,12 +20,11 @@ contract L2ReverseRegistrar is L2TextResolver { using ECDSA for bytes32; + mapping(bytes32 => uint256) public lastUpdated; event ReverseClaimed(address indexed addr, bytes32 indexed node); event DefaultResolverChanged(L2NameResolver indexed resolver); - uint256 constant EXPIRY = 1 days; - /** * @dev Constructor */ @@ -47,14 +46,14 @@ contract L2ReverseRegistrar is * @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 The resolver of the reverse node + * @param inceptionDate Date from when this signature is valid from * @param signature The resolver of the reverse node * @return The ENS node hash of the reverse record. */ function setNameForAddrWithSignature( address addr, string memory name, - uint256 signatureExpiry, + uint256 inceptionDate, bytes memory signature ) public override returns (bytes32) { bytes32 node = _getNamehash(addr); @@ -64,7 +63,7 @@ contract L2ReverseRegistrar is IL2ReverseRegistrar.setNameForAddrWithSignature.selector, addr, name, - signatureExpiry + inceptionDate ) ); @@ -72,13 +71,13 @@ contract L2ReverseRegistrar is if ( !SignatureChecker.isValidSignatureNow(addr, message, signature) || - signatureExpiry < block.timestamp || - signatureExpiry > block.timestamp + EXPIRY + inceptionDate <= lastUpdated[node] ) { revert InvalidSignature(); } _setName(node, name); + _setLastUpdated(node, inceptionDate); return node; } @@ -87,7 +86,7 @@ contract L2ReverseRegistrar is * @param contractAddr The reverse node to set * @param owner The owner of the contract (via Ownable) * @param name The name of the reverse record - * @param signatureExpiry The resolver of the reverse node + * @param inceptionDate Date from when this signature is valid from * @param signature The signature of an address that will return true on isValidSignature for the owner * @return The ENS node hash of the reverse record. */ @@ -95,7 +94,7 @@ contract L2ReverseRegistrar is address contractAddr, address owner, string memory name, - uint256 signatureExpiry, + uint256 inceptionDate, bytes memory signature ) public returns (bytes32) { bytes32 node = _getNamehash(contractAddr); @@ -108,7 +107,7 @@ contract L2ReverseRegistrar is contractAddr, owner, name, - signatureExpiry + inceptionDate ) ); @@ -120,7 +119,8 @@ contract L2ReverseRegistrar is owner, message, signature - ) + ) && + inceptionDate > lastUpdated[node] ) { _setName(node, name); return node; @@ -160,7 +160,7 @@ contract L2ReverseRegistrar is * @param addr The reverse record to set * @param key The key of the text record * @param value The value of the text record - * @param signatureExpiry The resolver of the reverse node + * @param inceptionDate Date from when this signature is valid from * @param signature The resolver of the reverse node * @return The ENS node hash of the reverse record. */ @@ -168,7 +168,7 @@ contract L2ReverseRegistrar is address addr, string calldata key, string calldata value, - uint256 signatureExpiry, + uint256 inceptionDate, bytes memory signature ) public override returns (bytes32) { bytes32 node = _getNamehash(addr); @@ -179,7 +179,7 @@ contract L2ReverseRegistrar is addr, key, value, - signatureExpiry + inceptionDate ) ); @@ -187,8 +187,7 @@ contract L2ReverseRegistrar is if ( !SignatureChecker.isValidSignatureNow(addr, message, signature) || - signatureExpiry < block.timestamp || - signatureExpiry > block.timestamp + EXPIRY + inceptionDate <= lastUpdated[node] ) { revert InvalidSignature(); } @@ -203,7 +202,7 @@ contract L2ReverseRegistrar is * @param owner The owner of the contract (via Ownable) * @param key The name of the reverse record * @param value The name of the reverse record - * @param signatureExpiry The resolver of the reverse node + * @param inceptionDate Date from when this signature is valid from * @param signature The signature of an address that will return true on isValidSignature for the owner * @return The ENS node hash of the reverse record. */ @@ -212,7 +211,7 @@ contract L2ReverseRegistrar is address owner, string calldata key, string calldata value, - uint256 signatureExpiry, + uint256 inceptionDate, bytes memory signature ) public returns (bytes32) { bytes32 node = _getNamehash(contractAddr); @@ -226,7 +225,7 @@ contract L2ReverseRegistrar is owner, key, value, - signatureExpiry + inceptionDate ) ); @@ -238,7 +237,8 @@ contract L2ReverseRegistrar is owner, message, signature - ) + ) && + inceptionDate > lastUpdated[node] ) { _setText(node, key, value); return node; @@ -276,6 +276,7 @@ contract L2ReverseRegistrar is ) public override authorised(addr) returns (bytes32) { bytes32 node = _getNamehash(addr); _setText(node, key, value); + _setLastUpdated(node, block.timestamp); return node; } @@ -305,6 +306,10 @@ contract L2ReverseRegistrar is return keccak256(abi.encodePacked(L2_REVERSE_NODE, labelHash)); } + function _setLastUpdated(bytes32 node, uint256 inceptionDate) internal { + lastUpdated[node] = inceptionDate; + } + function supportsInterface( bytes4 interfaceID ) From 2b66f4f8182079b3ae2a08c89e2a7bdb34a46c9d Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Thu, 26 Oct 2023 12:11:48 +0800 Subject: [PATCH 15/37] Add block.timestamp if first time setting --- contracts/reverseRegistrar/L2ReverseRegistrar.sol | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index b0752347..9353b3ad 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -71,7 +71,8 @@ contract L2ReverseRegistrar is if ( !SignatureChecker.isValidSignatureNow(addr, message, signature) || - inceptionDate <= lastUpdated[node] + inceptionDate <= lastUpdated[node] || + inceptionDate < block.timestamp ) { revert InvalidSignature(); } @@ -120,7 +121,8 @@ contract L2ReverseRegistrar is message, signature ) && - inceptionDate > lastUpdated[node] + inceptionDate > lastUpdated[node] && + inceptionDate >= block.timestamp ) { _setName(node, name); return node; @@ -187,7 +189,8 @@ contract L2ReverseRegistrar is if ( !SignatureChecker.isValidSignatureNow(addr, message, signature) || - inceptionDate <= lastUpdated[node] + inceptionDate <= lastUpdated[node] || + inceptionDate < block.timestamp ) { revert InvalidSignature(); } @@ -238,7 +241,8 @@ contract L2ReverseRegistrar is message, signature ) && - inceptionDate > lastUpdated[node] + inceptionDate > lastUpdated[node] && + inceptionDate >= block.timestamp ) { _setText(node, key, value); return node; From 753042825811d7e7fc0225601e1e2b6a2a236a92 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Wed, 1 Nov 2023 13:45:10 +0800 Subject: [PATCH 16/37] Merge text/name resolver into main contract --- .../reverseRegistrar/L2ReverseRegistrar.sol | 71 +++++++++++++++---- .../{profiles => }/L2ReverseResolverBase.sol | 2 +- .../profiles/L2NameResolver.sol | 40 ----------- .../profiles/L2TextResolver.sol | 39 ---------- 4 files changed, 59 insertions(+), 93 deletions(-) rename contracts/reverseRegistrar/{profiles => }/L2ReverseResolverBase.sol (97%) delete mode 100644 contracts/reverseRegistrar/profiles/L2NameResolver.sol delete mode 100644 contracts/reverseRegistrar/profiles/L2TextResolver.sol diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 9353b3ad..1f7664f5 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -5,25 +5,24 @@ import "./IL2ReverseRegistrar.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "../resolvers/profiles/ITextResolver.sol"; +import "../resolvers/profiles/INameResolver.sol"; import "../root/Controllable.sol"; -import "./profiles/L2NameResolver.sol"; -import "./profiles/L2TextResolver.sol"; -import "./profiles/L2ReverseResolverBase.sol"; +import "./L2ReverseResolverBase.sol"; error InvalidSignature(); contract L2ReverseRegistrar is Ownable, + ITextResolver, + INameResolver, IL2ReverseRegistrar, - L2ReverseResolverBase, - L2NameResolver, - L2TextResolver + L2ReverseResolverBase { using ECDSA for bytes32; mapping(bytes32 => uint256) public lastUpdated; event ReverseClaimed(address indexed addr, bytes32 indexed node); - event DefaultResolverChanged(L2NameResolver indexed resolver); /** * @dev Constructor @@ -284,6 +283,55 @@ contract L2ReverseRegistrar is return node; } + mapping(uint64 => mapping(bytes32 => mapping(string => string))) versionable_texts; + + function _setText( + bytes32 node, + string calldata key, + string calldata value + ) internal { + versionable_texts[recordVersions[node]][node][key] = value; + emit TextChanged(node, key, key, value); + } + + /** + * Returns the text data associated with an ENS node and key. + * @param node The ENS node to query. + * @param key The text data key to query. + * @return The associated text data. + */ + function text( + bytes32 node, + string calldata key + ) external view virtual override returns (string memory) { + return versionable_texts[recordVersions[node]][node][key]; + } + + mapping(uint64 => mapping(bytes32 => string)) versionable_names; + + /** + * Sets the name associated with an ENS node, for reverse records. + * May only be called by the owner of that node in the ENS registry. + * @param node The node to update. + * @param newName name record + */ + function _setName(bytes32 node, string memory newName) internal virtual { + versionable_names[recordVersions[node]][node] = newName; + emit NameChanged(node, newName); + } + + /** + * Returns the name associated with an ENS node, for reverse records. + * Defined in EIP181. + * @param node The ENS node to query. + * @return The associated name. + */ + function name( + bytes32 node + ) external view virtual override returns (string memory) { + return versionable_names[recordVersions[node]][node]; + } + /** * @dev Returns the node hash for a given account's reverse records. * @param addr The address to hash @@ -316,14 +364,11 @@ contract L2ReverseRegistrar is function supportsInterface( bytes4 interfaceID - ) - public - view - override(L2NameResolver, L2TextResolver, L2ReverseResolverBase) - returns (bool) - { + ) public view override(L2ReverseResolverBase) returns (bool) { return interfaceID == type(IL2ReverseRegistrar).interfaceId || + interfaceID == type(ITextResolver).interfaceId || + interfaceID == type(INameResolver).interfaceId || super.supportsInterface(interfaceID); } } diff --git a/contracts/reverseRegistrar/profiles/L2ReverseResolverBase.sol b/contracts/reverseRegistrar/L2ReverseResolverBase.sol similarity index 97% rename from contracts/reverseRegistrar/profiles/L2ReverseResolverBase.sol rename to contracts/reverseRegistrar/L2ReverseResolverBase.sol index d1be8ce6..cd3c5f50 100644 --- a/contracts/reverseRegistrar/profiles/L2ReverseResolverBase.sol +++ b/contracts/reverseRegistrar/L2ReverseResolverBase.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.4; import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import "../../resolvers/profiles/IVersionableResolver.sol"; +import "../resolvers/profiles/IVersionableResolver.sol"; abstract contract L2ReverseResolverBase is ERC165 { mapping(bytes32 => uint64) internal recordVersions; diff --git a/contracts/reverseRegistrar/profiles/L2NameResolver.sol b/contracts/reverseRegistrar/profiles/L2NameResolver.sol deleted file mode 100644 index 9d92d831..00000000 --- a/contracts/reverseRegistrar/profiles/L2NameResolver.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.4; - -import "./L2ReverseResolverBase.sol"; -import "../../resolvers/profiles/INameResolver.sol"; - -abstract contract L2NameResolver is INameResolver, L2ReverseResolverBase { - mapping(uint64 => mapping(bytes32 => string)) versionable_names; - - /** - * Sets the name associated with an ENS node, for reverse records. - * May only be called by the owner of that node in the ENS registry. - * @param node The node to update. - * @param newName name record - */ - function _setName(bytes32 node, string memory newName) internal virtual { - versionable_names[recordVersions[node]][node] = newName; - emit NameChanged(node, newName); - } - - /** - * Returns the name associated with an ENS node, for reverse records. - * Defined in EIP181. - * @param node The ENS node to query. - * @return The associated name. - */ - function name( - bytes32 node - ) external view virtual override returns (string memory) { - return versionable_names[recordVersions[node]][node]; - } - - function supportsInterface( - bytes4 interfaceID - ) public view virtual override returns (bool) { - return - interfaceID == type(INameResolver).interfaceId || - super.supportsInterface(interfaceID); - } -} diff --git a/contracts/reverseRegistrar/profiles/L2TextResolver.sol b/contracts/reverseRegistrar/profiles/L2TextResolver.sol deleted file mode 100644 index 72eb8cdc..00000000 --- a/contracts/reverseRegistrar/profiles/L2TextResolver.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.4; - -import "./L2ReverseResolverBase.sol"; -import "../../resolvers/profiles/ITextResolver.sol"; - -abstract contract L2TextResolver is ITextResolver, L2ReverseResolverBase { - mapping(uint64 => mapping(bytes32 => mapping(string => string))) versionable_texts; - - function _setText( - bytes32 node, - string calldata key, - string calldata value - ) internal { - versionable_texts[recordVersions[node]][node][key] = value; - emit TextChanged(node, key, key, value); - } - - /** - * Returns the text data associated with an ENS node and key. - * @param node The ENS node to query. - * @param key The text data key to query. - * @return The associated text data. - */ - function text( - bytes32 node, - string calldata key - ) external view virtual override returns (string memory) { - return versionable_texts[recordVersions[node]][node][key]; - } - - function supportsInterface( - bytes4 interfaceID - ) public view virtual override returns (bool) { - return - interfaceID == type(ITextResolver).interfaceId || - super.supportsInterface(interfaceID); - } -} From 37eeec4a8dae842cff2564c8b5864fa9c4aa3b6f Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Mon, 6 Nov 2023 12:26:02 +0800 Subject: [PATCH 17/37] WIP multicall --- .../reverseRegistrar/L2ReverseRegistrar.sol | 15 ++++-- .../TestL2ReverseRegistrar.js | 49 +++++++++++++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 1f7664f5..70f5cd2b 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -9,10 +9,12 @@ import "../resolvers/profiles/ITextResolver.sol"; import "../resolvers/profiles/INameResolver.sol"; import "../root/Controllable.sol"; import "./L2ReverseResolverBase.sol"; +import "../resolvers/Multicallable.sol"; error InvalidSignature(); contract L2ReverseRegistrar is + Multicallable, Ownable, ITextResolver, INameResolver, @@ -21,6 +23,8 @@ contract L2ReverseRegistrar is { using ECDSA for bytes32; mapping(bytes32 => uint256) public lastUpdated; + mapping(uint64 => mapping(bytes32 => mapping(string => string))) versionable_texts; + mapping(uint64 => mapping(bytes32 => string)) versionable_names; event ReverseClaimed(address indexed addr, bytes32 indexed node); @@ -283,8 +287,6 @@ contract L2ReverseRegistrar is return node; } - mapping(uint64 => mapping(bytes32 => mapping(string => string))) versionable_texts; - function _setText( bytes32 node, string calldata key, @@ -307,8 +309,6 @@ contract L2ReverseRegistrar is return versionable_texts[recordVersions[node]][node][key]; } - mapping(uint64 => mapping(bytes32 => string)) versionable_names; - /** * Sets the name associated with an ENS node, for reverse records. * May only be called by the owner of that node in the ENS registry. @@ -364,7 +364,12 @@ contract L2ReverseRegistrar is function supportsInterface( bytes4 interfaceID - ) public view override(L2ReverseResolverBase) returns (bool) { + ) + public + view + override(L2ReverseResolverBase, Multicallable) + returns (bool) + { return interfaceID == type(IL2ReverseRegistrar).interfaceId || interfaceID == type(ITextResolver).interfaceId || diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index d0f95f33..85f9516c 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -286,4 +286,53 @@ describe('L2ReverseRegistrar', function () { ) }) }) + + describe('setTextForAddrWithSignatureAndOwnable', function () { + it('allows an account to sign a message to allow a relayer to claim the address of a contract that is owned by another contract that the account is a signer of', async () => { + const node = await L2ReverseRegistrar.node(MockOwnable.address) + assert.equal(await L2ReverseRegistrar.text(node, 'url'), '') + const funcId1 = ethers.utils + .id(setTextForAddrWithSignatureFuncSig) + .substring(0, 10) + + const funcId2 = ethers.utils + .id(setNameForAddrWithSignatureFuncSig) + .substring(0, 10) + + const block = await ethers.provider.getBlock('latest') + const inceptionDate = block.timestamp + const signature1 = await signers[0].signMessage( + ethers.utils.arrayify( + ethers.utils.solidityKeccak256( + ['bytes4', 'address', 'string', 'string', 'uint256'], + [funcId1, account, 'url', 'http://ens.domains', inceptionDate], + ), + ), + ) + const signature2 = await signers[0].signMessage( + ethers.utils.arrayify( + ethers.utils.solidityKeccak256( + ['bytes4', 'address', 'string', 'uint256'], + [funcId2, account, 'hello.eth', inceptionDate], + ), + ), + ) + + const calls = [ + L2ReverseRegistrar.interface.encodeFunctionData( + 'setTextForAddrWithSignature', + [account, 'url', 'http://ens.domains', inceptionDate, signature1], + ), + ] + + await L2ReverseRegistrar.multicall(calls) + + assert.equal( + await L2ReverseRegistrar.text(node, 'url'), + 'http://ens.domains', + ) + + assert.equal(await L2ReverseRegistrar.name(node), 'hello.eth') + }) + }) }) From eea2bd95f9e93671194008796fb371e24bdcf0ba Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Mon, 6 Nov 2023 12:49:22 +0800 Subject: [PATCH 18/37] Add L2 reverse registrar deploy script --- .../00_deploy_reverse_registrar.ts} | 0 .../01_deploy_l2_reverse_registrar.ts | 25 +++++++++++++++++++ 2 files changed, 25 insertions(+) rename deploy/{registry/01_deploy_reverse_registrar.ts => reverseregistrar/00_deploy_reverse_registrar.ts} (100%) create mode 100644 deploy/reverseregistrar/01_deploy_l2_reverse_registrar.ts diff --git a/deploy/registry/01_deploy_reverse_registrar.ts b/deploy/reverseregistrar/00_deploy_reverse_registrar.ts similarity index 100% rename from deploy/registry/01_deploy_reverse_registrar.ts rename to deploy/reverseregistrar/00_deploy_reverse_registrar.ts diff --git a/deploy/reverseregistrar/01_deploy_l2_reverse_registrar.ts b/deploy/reverseregistrar/01_deploy_l2_reverse_registrar.ts new file mode 100644 index 00000000..e4d2870f --- /dev/null +++ b/deploy/reverseregistrar/01_deploy_l2_reverse_registrar.ts @@ -0,0 +1,25 @@ +import { namehash } from 'ethers/lib/utils' +import { ethers } from 'hardhat' +import { DeployFunction } from 'hardhat-deploy/types' +import { HardhatRuntimeEnvironment } from 'hardhat/types' + +// Replace with coinid of L2 +const NAMESPACE = 'optimism' + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const { getNamedAccounts, deployments, network } = hre + const { deploy } = deployments + const { deployer } = await getNamedAccounts() + + await deploy('L2ReverseRegistrar', { + from: deployer, + args: [namehash(`${NAMESPACE}.reverse`)], + log: true, + }) +} + +func.id = 'l2-reverse-registrar' +func.tags = ['L2ReverseRegistrar'] +func.dependencies = [] + +export default func From 569f07abd9c6cdd6d149fe86f98c0aca0e6fedd1 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Mon, 6 Nov 2023 14:38:54 +0800 Subject: [PATCH 19/37] Add tests for multicall --- .../TestL2ReverseRegistrar.js | 50 +++++++++++++++---- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index 85f9516c..2aecdcf2 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -287,10 +287,33 @@ describe('L2ReverseRegistrar', function () { }) }) - describe('setTextForAddrWithSignatureAndOwnable', function () { - it('allows an account to sign a message to allow a relayer to claim the address of a contract that is owned by another contract that the account is a signer of', async () => { - const node = await L2ReverseRegistrar.node(MockOwnable.address) - assert.equal(await L2ReverseRegistrar.text(node, 'url'), '') + describe('Multicallable', function () { + it('setText() + setName()', async () => { + const node = await L2ReverseRegistrar.node(account) + + const calls = [ + L2ReverseRegistrar.interface.encodeFunctionData('setText', [ + 'url', + 'http://multicall.xyz', + ]), + L2ReverseRegistrar.interface.encodeFunctionData('setName', [ + 'hello.eth', + ]), + ] + + await L2ReverseRegistrar.multicall(calls) + + assert.equal( + await L2ReverseRegistrar.text(node, 'url'), + 'http://multicall.xyz', + ) + + assert.equal(await L2ReverseRegistrar.name(node), 'hello.eth') + }) + + it('setTextForAddrWithSignature()', async () => { + const node = await L2ReverseRegistrar.node(account) + assert.equal(await L2ReverseRegistrar.text(node, 'randomKey'), '') const funcId1 = ethers.utils .id(setTextForAddrWithSignatureFuncSig) .substring(0, 10) @@ -300,20 +323,23 @@ describe('L2ReverseRegistrar', function () { .substring(0, 10) const block = await ethers.provider.getBlock('latest') - const inceptionDate = block.timestamp + const signatureExpiry = block.timestamp + 3600 + + console.log('signatureExpiry', signatureExpiry) const signature1 = await signers[0].signMessage( ethers.utils.arrayify( ethers.utils.solidityKeccak256( ['bytes4', 'address', 'string', 'string', 'uint256'], - [funcId1, account, 'url', 'http://ens.domains', inceptionDate], + [funcId1, account, 'url', 'http://ens.domains', signatureExpiry], ), ), ) + const signature2 = await signers[0].signMessage( ethers.utils.arrayify( ethers.utils.solidityKeccak256( ['bytes4', 'address', 'string', 'uint256'], - [funcId2, account, 'hello.eth', inceptionDate], + [funcId2, account, 'hello.eth', signatureExpiry], ), ), ) @@ -321,11 +347,17 @@ describe('L2ReverseRegistrar', function () { const calls = [ L2ReverseRegistrar.interface.encodeFunctionData( 'setTextForAddrWithSignature', - [account, 'url', 'http://ens.domains', inceptionDate, signature1], + [account, 'url', 'http://ens.domains', signatureExpiry, signature1], + ), + L2ReverseRegistrar.interface.encodeFunctionData( + 'setNameForAddrWithSignature', + [account, 'hello.eth', signatureExpiry, signature2], ), ] - await L2ReverseRegistrar.multicall(calls) + const tx = await L2ReverseRegistrar.multicall(calls) + + console.log(tx.blockNumber) assert.equal( await L2ReverseRegistrar.text(node, 'url'), From d25e521e3ebb82e691366f41b026ec0cf016ddd6 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Mon, 6 Nov 2023 15:05:03 +0800 Subject: [PATCH 20/37] Fix inceptionDate comparisons --- .../reverseRegistrar/L2ReverseRegistrar.sol | 15 +++++---- .../TestL2ReverseRegistrar.js | 33 ++++++++++--------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 70f5cd2b..915ab7b6 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -74,8 +74,8 @@ contract L2ReverseRegistrar is if ( !SignatureChecker.isValidSignatureNow(addr, message, signature) || - inceptionDate <= lastUpdated[node] || - inceptionDate < block.timestamp + inceptionDate < lastUpdated[node] || // must be newer than current record + inceptionDate >= block.timestamp // must be in the past ) { revert InvalidSignature(); } @@ -124,8 +124,8 @@ contract L2ReverseRegistrar is message, signature ) && - inceptionDate > lastUpdated[node] && - inceptionDate >= block.timestamp + inceptionDate >= lastUpdated[node] && + inceptionDate < block.timestamp ) { _setName(node, name); return node; @@ -192,13 +192,14 @@ contract L2ReverseRegistrar is if ( !SignatureChecker.isValidSignatureNow(addr, message, signature) || - inceptionDate <= lastUpdated[node] || - inceptionDate < block.timestamp + inceptionDate < lastUpdated[node] || + inceptionDate > block.timestamp ) { revert InvalidSignature(); } _setText(node, key, value); + _setLastUpdated(node, inceptionDate); return node; } @@ -245,7 +246,7 @@ contract L2ReverseRegistrar is signature ) && inceptionDate > lastUpdated[node] && - inceptionDate >= block.timestamp + inceptionDate < block.timestamp ) { _setText(node, key, value); return node; diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index 2aecdcf2..22919811 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -81,7 +81,7 @@ describe('L2ReverseRegistrar', function () { .substring(0, 10) const block = await ethers.provider.getBlock('latest') - const signatureExpiry = block.timestamp + 3600 + const signatureExpiry = block.timestamp const signature = await signers[0].signMessage( ethers.utils.arrayify( ethers.utils.solidityKeccak256( @@ -138,7 +138,7 @@ describe('L2ReverseRegistrar', function () { .substring(0, 10) const block = await ethers.provider.getBlock('latest') - const signatureExpiry = block.timestamp + 3600 + const signatureExpiry = block.timestamp const signature = await signers[0].signMessage( ethers.utils.arrayify( ethers.utils.solidityKeccak256( @@ -190,12 +190,12 @@ describe('L2ReverseRegistrar', function () { .substring(0, 10) const block = await ethers.provider.getBlock('latest') - const signatureExpiry = block.timestamp + 3600 + const inceptionDate = block.timestamp const signature = await signers[0].signMessage( ethers.utils.arrayify( ethers.utils.solidityKeccak256( ['bytes4', 'address', 'string', 'string', 'uint256'], - [funcId, account, 'url', 'http://ens.domains', signatureExpiry], + [funcId, account, 'url', 'http://ens.domains', inceptionDate], ), ), ) @@ -204,7 +204,7 @@ describe('L2ReverseRegistrar', function () { account, 'url', 'http://ens.domains', - signatureExpiry, + inceptionDate, signature, ) @@ -221,12 +221,12 @@ describe('L2ReverseRegistrar', function () { .substring(0, 10) const block = await ethers.provider.getBlock('latest') - const signatureExpiry = block.timestamp + 3600 + const inceptionDate = block.timestamp const signature = await signers[0].signMessage( ethers.utils.arrayify( ethers.utils.solidityKeccak256( ['bytes4', 'address', 'string', 'string', 'uint256'], - [funcId, account, 'url', 'http://ens.domains', signatureExpiry], + [funcId, account, 'url', 'http://ens.domains', inceptionDate], ), ), ) @@ -236,7 +236,7 @@ describe('L2ReverseRegistrar', function () { account, 'url', 'http://some.other.url.com', - signatureExpiry, + inceptionDate, signature, ), ).to.be.revertedWith(`InvalidSignature()`) @@ -252,7 +252,7 @@ describe('L2ReverseRegistrar', function () { .substring(0, 10) const block = await ethers.provider.getBlock('latest') - const signatureExpiry = block.timestamp + 3600 + const signatureExpiry = block.timestamp const signature = await signers[0].signMessage( ethers.utils.arrayify( ethers.utils.solidityKeccak256( @@ -323,14 +323,14 @@ describe('L2ReverseRegistrar', function () { .substring(0, 10) const block = await ethers.provider.getBlock('latest') - const signatureExpiry = block.timestamp + 3600 + const inceptionDate = block.timestamp - console.log('signatureExpiry', signatureExpiry) + console.log('inceptionDate', inceptionDate) const signature1 = await signers[0].signMessage( ethers.utils.arrayify( ethers.utils.solidityKeccak256( ['bytes4', 'address', 'string', 'string', 'uint256'], - [funcId1, account, 'url', 'http://ens.domains', signatureExpiry], + [funcId1, account, 'url', 'http://ens.domains', inceptionDate], ), ), ) @@ -339,7 +339,7 @@ describe('L2ReverseRegistrar', function () { ethers.utils.arrayify( ethers.utils.solidityKeccak256( ['bytes4', 'address', 'string', 'uint256'], - [funcId2, account, 'hello.eth', signatureExpiry], + [funcId2, account, 'hello.eth', inceptionDate], ), ), ) @@ -347,11 +347,11 @@ describe('L2ReverseRegistrar', function () { const calls = [ L2ReverseRegistrar.interface.encodeFunctionData( 'setTextForAddrWithSignature', - [account, 'url', 'http://ens.domains', signatureExpiry, signature1], + [account, 'url', 'http://ens.domains', inceptionDate, signature1], ), L2ReverseRegistrar.interface.encodeFunctionData( 'setNameForAddrWithSignature', - [account, 'hello.eth', signatureExpiry, signature2], + [account, 'hello.eth', inceptionDate, signature2], ), ] @@ -359,6 +359,9 @@ describe('L2ReverseRegistrar', function () { console.log(tx.blockNumber) + const block2 = await ethers.provider.getBlock(tx.blockNumber) + console.log(block2.timestamp) + assert.equal( await L2ReverseRegistrar.text(node, 'url'), 'http://ens.domains', From 5ed5fa94cc73c92bfed4389d19c451c8697d0a73 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Wed, 8 Nov 2023 14:16:44 +0800 Subject: [PATCH 21/37] Refactor setName/Text --- .../reverseRegistrar/L2ReverseRegistrar.sol | 27 +++++++++++-------- .../TestL2ReverseRegistrar.js | 7 +---- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 915ab7b6..88d07d6e 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -80,8 +80,7 @@ contract L2ReverseRegistrar is revert InvalidSignature(); } - _setName(node, name); - _setLastUpdated(node, inceptionDate); + _setName(node, name, inceptionDate); return node; } @@ -127,7 +126,7 @@ contract L2ReverseRegistrar is inceptionDate >= lastUpdated[node] && inceptionDate < block.timestamp ) { - _setName(node, name); + _setName(node, name, inceptionDate); return node; } @@ -147,6 +146,7 @@ contract L2ReverseRegistrar is /** * @dev Sets the `name()` record for the reverse ENS record associated with * the addr provided account. + * Can be used if the addr is a contract that is owned by a SCW. * @param name The name to set for this address. * @return The ENS node hash of the reverse record. */ @@ -156,7 +156,7 @@ contract L2ReverseRegistrar is string memory name ) public authorised(addr) returns (bytes32) { bytes32 node = _getNamehash(addr); - _setName(node, name); + _setName(node, name, block.timestamp); return node; } @@ -198,8 +198,7 @@ contract L2ReverseRegistrar is revert InvalidSignature(); } - _setText(node, key, value); - _setLastUpdated(node, inceptionDate); + _setText(node, key, value, inceptionDate); return node; } @@ -248,7 +247,7 @@ contract L2ReverseRegistrar is inceptionDate > lastUpdated[node] && inceptionDate < block.timestamp ) { - _setText(node, key, value); + _setText(node, key, value, inceptionDate); return node; } @@ -283,17 +282,18 @@ contract L2ReverseRegistrar is string calldata value ) public override authorised(addr) returns (bytes32) { bytes32 node = _getNamehash(addr); - _setText(node, key, value); - _setLastUpdated(node, block.timestamp); + _setText(node, key, value, block.timestamp); return node; } function _setText( bytes32 node, string calldata key, - string calldata value + string calldata value, + uint256 inceptionDate ) internal { versionable_texts[recordVersions[node]][node][key] = value; + _setLastUpdated(node, inceptionDate); emit TextChanged(node, key, key, value); } @@ -316,8 +316,13 @@ contract L2ReverseRegistrar is * @param node The node to update. * @param newName name record */ - function _setName(bytes32 node, string memory newName) internal virtual { + function _setName( + bytes32 node, + string memory newName, + uint256 inceptionDate + ) internal virtual { versionable_names[recordVersions[node]][node] = newName; + _setLastUpdated(node, inceptionDate); emit NameChanged(node, newName); } diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index 22919811..7cb3c1cb 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -355,12 +355,7 @@ describe('L2ReverseRegistrar', function () { ), ] - const tx = await L2ReverseRegistrar.multicall(calls) - - console.log(tx.blockNumber) - - const block2 = await ethers.provider.getBlock(tx.blockNumber) - console.log(block2.timestamp) + await L2ReverseRegistrar.multicall(calls) assert.equal( await L2ReverseRegistrar.text(node, 'url'), From 369cc4a958e5e2295f6c63fc505031d56026bfb8 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Wed, 6 Dec 2023 16:31:55 +0800 Subject: [PATCH 22/37] Remove L2 ReverseClaimer.sol --- .../reverseRegistrar/L2ReverseClaimer.sol | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 contracts/reverseRegistrar/L2ReverseClaimer.sol diff --git a/contracts/reverseRegistrar/L2ReverseClaimer.sol b/contracts/reverseRegistrar/L2ReverseClaimer.sol deleted file mode 100644 index 58cb0d07..00000000 --- a/contracts/reverseRegistrar/L2ReverseClaimer.sol +++ /dev/null @@ -1,21 +0,0 @@ -//SPDX-License-Identifier: MIT -pragma solidity >=0.8.17 <0.9.0; - -import {ENS} from "../registry/ENS.sol"; -import {IL2ReverseRegistrar} from "../reverseRegistrar/IL2ReverseRegistrar.sol"; - -contract L2ReverseClaimer { - constructor( - address l2ReverseRegistrarAddr, - ENS reverseRegistrar, - address claimant - ) { - IL2ReverseRegistrar reverseRegistrar = IL2ReverseRegistrar( - l2ReverseRegistrarAddr - ); - //reverseRegistrar.setName(claimant); - } -} - -// TODO: do we need a way of claiming a reverse node -// so that contracts can delegate ownership to an EoA/Smartcontract? From 6ed6d5f5832c2816dd46bc3c1cc660108b61e8f6 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Wed, 6 Dec 2023 16:39:07 +0800 Subject: [PATCH 23/37] Remove profiles in readme --- contracts/reverseRegistrar/README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contracts/reverseRegistrar/README.md b/contracts/reverseRegistrar/README.md index fdfff8de..0d49824d 100644 --- a/contracts/reverseRegistrar/README.md +++ b/contracts/reverseRegistrar/README.md @@ -3,7 +3,3 @@ ## Summary The L2 Reverse registrar is a combination of a resolver and a reverse registrar that allows the name to be set for a particular reverse node. - -## Resolver Profiles - -Profiles are separate from resolvers as the authorised modifier needs the address, rather than the node From 4136a75a0639b0384d1e69827fd44700523f4939 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Wed, 6 Dec 2023 17:52:56 +0800 Subject: [PATCH 24/37] Remove L2 ReverseResolverBase contract and merge into registrar --- .../reverseRegistrar/IL2ReverseRegistrar.sol | 2 + .../reverseRegistrar/L2ReverseRegistrar.sol | 206 +++++++++++++----- .../L2ReverseResolverBase.sol | 73 ------- .../TestL2ReverseRegistrar.js | 15 ++ 4 files changed, 169 insertions(+), 127 deletions(-) delete mode 100644 contracts/reverseRegistrar/L2ReverseResolverBase.sol diff --git a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol index 84fb0afc..f5f9b253 100644 --- a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol @@ -51,5 +51,7 @@ interface IL2ReverseRegistrar { bytes memory signature ) external returns (bytes32); + function clearRecords(address addr) external; + function node(address addr) external view returns (bytes32); } diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 88d07d6e..58cfd5f0 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -8,7 +8,6 @@ import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "../resolvers/profiles/ITextResolver.sol"; import "../resolvers/profiles/INameResolver.sol"; import "../root/Controllable.sol"; -import "./L2ReverseResolverBase.sol"; import "../resolvers/Multicallable.sol"; error InvalidSignature(); @@ -18,33 +17,68 @@ contract L2ReverseRegistrar is Ownable, ITextResolver, INameResolver, - IL2ReverseRegistrar, - L2ReverseResolverBase + IL2ReverseRegistrar { using ECDSA for bytes32; mapping(bytes32 => uint256) public lastUpdated; mapping(uint64 => mapping(bytes32 => mapping(string => string))) versionable_texts; mapping(uint64 => mapping(bytes32 => string)) versionable_names; + mapping(bytes32 => uint64) internal recordVersions; + event VersionChanged(bytes32 indexed node, uint64 newVersion); + bytes32 public immutable L2_REVERSE_NODE; + + bytes32 constant lookup = + 0x3031323334353637383961626364656600000000000000000000000000000000; event ReverseClaimed(address indexed addr, bytes32 indexed node); /** * @dev Constructor */ - constructor(bytes32 L2ReverseNode) L2ReverseResolverBase(L2ReverseNode) {} + constructor(bytes32 L2ReverseNode) { + L2_REVERSE_NODE = L2ReverseNode; + } - modifier authorised(address addr) override(L2ReverseResolverBase) { + modifier authorised(address addr) { isAuthorised(addr); _; } - function isAuthorised(address addr) internal view override returns (bool) { + modifier authorisedSignature( + bytes32 hash, + address addr, + uint256 inceptionDate, + bytes memory signature + ) { + isAuthorisedWithSignature(hash, addr, inceptionDate, signature); + _; + } + + function isAuthorised(address addr) internal view returns (bool) { require( addr == msg.sender || ownsContract(addr, msg.sender), "ReverseRegistrar: Caller is not a controller or authorised by address or the address itself" ); } + function isAuthorisedWithSignature( + bytes32 hash, + address addr, + uint256 inceptionDate, + bytes memory signature + ) internal view returns (bool) { + bytes32 message = hash.toEthSignedMessageHash(); + bytes32 node = _getNamehash(addr); + + if ( + !SignatureChecker.isValidSignatureNow(addr, message, signature) || + inceptionDate < lastUpdated[node] || // must be newer than current record + inceptionDate >= block.timestamp // must be in the past + ) { + revert InvalidSignature(); + } + } + /** * @dev Sets the name for an addr using a signature that can be verified with ERC1271. * @param addr The reverse record to set @@ -58,28 +92,26 @@ contract L2ReverseRegistrar is string memory name, uint256 inceptionDate, bytes memory signature - ) public override returns (bytes32) { + ) + public + override + authorisedSignature( + keccak256( + abi.encodePacked( + IL2ReverseRegistrar.setNameForAddrWithSignature.selector, + addr, + name, + inceptionDate + ) + ), + addr, + inceptionDate, + signature + ) + returns (bytes32) + { bytes32 node = _getNamehash(addr); - bytes32 hash = keccak256( - abi.encodePacked( - IL2ReverseRegistrar.setNameForAddrWithSignature.selector, - addr, - name, - inceptionDate - ) - ); - - bytes32 message = hash.toEthSignedMessageHash(); - - if ( - !SignatureChecker.isValidSignatureNow(addr, message, signature) || - inceptionDate < lastUpdated[node] || // must be newer than current record - inceptionDate >= block.timestamp // must be in the past - ) { - revert InvalidSignature(); - } - _setName(node, name, inceptionDate); return node; } @@ -175,29 +207,26 @@ contract L2ReverseRegistrar is string calldata value, uint256 inceptionDate, bytes memory signature - ) public override returns (bytes32) { + ) + public + override + authorisedSignature( + keccak256( + abi.encodePacked( + IL2ReverseRegistrar.setTextForAddrWithSignature.selector, + addr, + key, + value, + inceptionDate + ) + ), + addr, + inceptionDate, + signature + ) + returns (bytes32) + { bytes32 node = _getNamehash(addr); - - bytes32 hash = keccak256( - abi.encodePacked( - IL2ReverseRegistrar.setTextForAddrWithSignature.selector, - addr, - key, - value, - inceptionDate - ) - ); - - bytes32 message = hash.toEthSignedMessageHash(); - - if ( - !SignatureChecker.isValidSignatureNow(addr, message, signature) || - inceptionDate < lastUpdated[node] || - inceptionDate > block.timestamp - ) { - revert InvalidSignature(); - } - _setText(node, key, value, inceptionDate); return node; } @@ -338,6 +367,54 @@ contract L2ReverseRegistrar is return versionable_names[recordVersions[node]][node]; } + /** + * Increments the record version associated with an ENS node. + * May only be called by the owner of that node in the ENS registry. + * @param addr The node to update. + */ + function clearRecords(address addr) public virtual authorised(addr) { + bytes32 labelHash = sha3HexAddress(addr); + bytes32 reverseNode = keccak256( + abi.encodePacked(L2_REVERSE_NODE, labelHash) + ); + recordVersions[reverseNode]++; + emit VersionChanged(reverseNode, recordVersions[reverseNode]); + } + + /** + * Increments the record version associated with an ENS node. + * May only be called by the owner of that node in the ENS registry. + * @param addr The node to update. + * @param signature A signature proving ownership of the node. + */ + function clearRecordsWithSignature( + address addr, + uint256 inceptionDate, + bytes memory signature + ) + public + virtual + authorisedSignature( + keccak256( + abi.encodePacked( + IL2ReverseRegistrar.clearRecords.selector, + addr, + inceptionDate + ) + ), + addr, + inceptionDate, + signature + ) + { + bytes32 labelHash = sha3HexAddress(addr); + bytes32 reverseNode = keccak256( + abi.encodePacked(L2_REVERSE_NODE, labelHash) + ); + recordVersions[reverseNode]++; + emit VersionChanged(reverseNode, recordVersions[reverseNode]); + } + /** * @dev Returns the node hash for a given account's reverse records. * @param addr The address to hash @@ -370,16 +447,37 @@ contract L2ReverseRegistrar is function supportsInterface( bytes4 interfaceID - ) - public - view - override(L2ReverseResolverBase, Multicallable) - returns (bool) - { + ) public view override(Multicallable) returns (bool) { return interfaceID == type(IL2ReverseRegistrar).interfaceId || interfaceID == type(ITextResolver).interfaceId || interfaceID == type(INameResolver).interfaceId || super.supportsInterface(interfaceID); } + + /** + * @dev An optimised function to compute the sha3 of the lower-case + * hexadecimal representation of an Ethereum address. + * @param addr The address to hash + * @return ret The SHA3 hash of the lower-case hexadecimal encoding of the + * input address. + */ + function sha3HexAddress(address addr) internal pure returns (bytes32 ret) { + assembly { + for { + let i := 40 + } gt(i, 0) { + + } { + i := sub(i, 1) + mstore8(i, byte(and(addr, 0xf), lookup)) + addr := div(addr, 0x10) + i := sub(i, 1) + mstore8(i, byte(and(addr, 0xf), lookup)) + addr := div(addr, 0x10) + } + + ret := keccak256(0, 40) + } + } } diff --git a/contracts/reverseRegistrar/L2ReverseResolverBase.sol b/contracts/reverseRegistrar/L2ReverseResolverBase.sol deleted file mode 100644 index cd3c5f50..00000000 --- a/contracts/reverseRegistrar/L2ReverseResolverBase.sol +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity >=0.8.4; - -import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; -import "../resolvers/profiles/IVersionableResolver.sol"; - -abstract contract L2ReverseResolverBase is ERC165 { - mapping(bytes32 => uint64) internal recordVersions; - event VersionChanged(bytes32 indexed node, uint64 newVersion); - bytes32 public immutable L2_REVERSE_NODE; - - bytes32 constant lookup = - 0x3031323334353637383961626364656600000000000000000000000000000000; - - function isAuthorised(address addr) internal view virtual returns (bool); - - constructor(bytes32 l2ReverseNode) { - L2_REVERSE_NODE = l2ReverseNode; - } - - modifier authorised(address addr) virtual { - require(isAuthorised(addr)); - _; - } - - /** - * Increments the record version associated with an ENS node. - * May only be called by the owner of that node in the ENS registry. - * @param addr The node to update. - */ - function clearRecords(address addr) public virtual authorised(addr) { - bytes32 labelHash = sha3HexAddress(addr); - bytes32 reverseNode = keccak256( - abi.encodePacked(L2_REVERSE_NODE, labelHash) - ); - recordVersions[reverseNode]++; - emit VersionChanged(reverseNode, recordVersions[reverseNode]); - } - - function supportsInterface( - bytes4 interfaceID - ) public view virtual override returns (bool) { - return - interfaceID == type(IVersionableResolver).interfaceId || - super.supportsInterface(interfaceID); - } - - /** - * @dev An optimised function to compute the sha3 of the lower-case - * hexadecimal representation of an Ethereum address. - * @param addr The address to hash - * @return ret The SHA3 hash of the lower-case hexadecimal encoding of the - * input address. - */ - function sha3HexAddress(address addr) internal pure returns (bytes32 ret) { - assembly { - for { - let i := 40 - } gt(i, 0) { - - } { - i := sub(i, 1) - mstore8(i, byte(and(addr, 0xf), lookup)) - addr := div(addr, 0x10) - i := sub(i, 1) - mstore8(i, byte(and(addr, 0xf), lookup)) - addr := div(addr, 0x10) - } - - ret := keccak256(0, 40) - } - } -} diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index 7cb3c1cb..d0b5fa9c 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -365,4 +365,19 @@ describe('L2ReverseRegistrar', function () { assert.equal(await L2ReverseRegistrar.name(node), 'hello.eth') }) }) + describe('Clear records', function () { + it('clearRecords() clears records', async () => { + const node = await L2ReverseRegistrar.node(account) + await L2ReverseRegistrar.setText('url', 'http://ens.domains') + await L2ReverseRegistrar.setName('hello.eth') + assert.equal( + await L2ReverseRegistrar.text(node, 'url'), + 'http://ens.domains', + ) + assert.equal(await L2ReverseRegistrar.name(node), 'hello.eth') + // await L2ReverseRegistrar.clearRecords() + // assert.equal(await L2ReverseRegistrar.text(node, 'url'), '') + // assert.equal(await L2ReverseRegistrar.name(node), '') + }) + }) }) From 26fc75a04ed26c0bc4c7ec6ed307cbc877ec1bb9 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Wed, 6 Dec 2023 17:54:13 +0800 Subject: [PATCH 25/37] Move supportsInterface below --- .../reverseRegistrar/L2ReverseRegistrar.sol | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 58cfd5f0..2160a34c 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -445,16 +445,6 @@ contract L2ReverseRegistrar is lastUpdated[node] = inceptionDate; } - function supportsInterface( - bytes4 interfaceID - ) public view override(Multicallable) returns (bool) { - return - interfaceID == type(IL2ReverseRegistrar).interfaceId || - interfaceID == type(ITextResolver).interfaceId || - interfaceID == type(INameResolver).interfaceId || - super.supportsInterface(interfaceID); - } - /** * @dev An optimised function to compute the sha3 of the lower-case * hexadecimal representation of an Ethereum address. @@ -480,4 +470,14 @@ contract L2ReverseRegistrar is ret := keccak256(0, 40) } } + + function supportsInterface( + bytes4 interfaceID + ) public view override(Multicallable) returns (bool) { + return + interfaceID == type(IL2ReverseRegistrar).interfaceId || + interfaceID == type(ITextResolver).interfaceId || + interfaceID == type(INameResolver).interfaceId || + super.supportsInterface(interfaceID); + } } From 4ab3a85cddbf202939977b9271aa8eec65b0cf97 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Thu, 7 Dec 2023 13:42:14 +0800 Subject: [PATCH 26/37] Test for clearRecordsWithSignature --- .../reverseRegistrar/IL2ReverseRegistrar.sol | 6 +++ .../reverseRegistrar/L2ReverseRegistrar.sol | 2 +- .../TestL2ReverseRegistrar.js | 49 +++++++++++++++++-- 3 files changed, 53 insertions(+), 4 deletions(-) diff --git a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol index f5f9b253..019b6223 100644 --- a/contracts/reverseRegistrar/IL2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/IL2ReverseRegistrar.sol @@ -53,5 +53,11 @@ interface IL2ReverseRegistrar { function clearRecords(address addr) external; + function clearRecordsWithSignature( + address addr, + uint256 inceptionDate, + bytes memory signature + ) external; + function node(address addr) external view returns (bytes32); } diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 2160a34c..26ad4cd8 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -397,7 +397,7 @@ contract L2ReverseRegistrar is authorisedSignature( keccak256( abi.encodePacked( - IL2ReverseRegistrar.clearRecords.selector, + IL2ReverseRegistrar.clearRecordsWithSignature.selector, addr, inceptionDate ) diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index d0b5fa9c..8bf0340d 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -375,9 +375,52 @@ describe('L2ReverseRegistrar', function () { 'http://ens.domains', ) assert.equal(await L2ReverseRegistrar.name(node), 'hello.eth') - // await L2ReverseRegistrar.clearRecords() - // assert.equal(await L2ReverseRegistrar.text(node, 'url'), '') - // assert.equal(await L2ReverseRegistrar.name(node), '') + await L2ReverseRegistrar.clearRecords(account) + assert.equal(await L2ReverseRegistrar.text(node, 'url'), '') + assert.equal(await L2ReverseRegistrar.name(node), '') + }) + + it('clearRecordsWithSignature() clears records', async () => { + const node = await L2ReverseRegistrar.node(account) + await L2ReverseRegistrar.setText('url', 'http://ens.domains') + await L2ReverseRegistrar.setName('hello.eth') + assert.equal( + await L2ReverseRegistrar.text(node, 'url'), + 'http://ens.domains', + ) + assert.equal(await L2ReverseRegistrar.name(node), 'hello.eth') + + const funcId = ethers.utils + .id('clearRecordsWithSignature(address,uint256,bytes)') + .substring(0, 10) + + const block = await ethers.provider.getBlock('latest') + const signatureExpiry = block.timestamp + const signature = await signers[0].signMessage( + ethers.utils.arrayify( + ethers.utils.solidityKeccak256( + ['bytes4', 'address', 'uint256'], + [funcId, account, signatureExpiry], + ), + ), + ) + + keccak256( + abi.encodePacked( + IL2ReverseRegistrar.setNameForAddrWithSignature.selector, + addr, + name, + inceptionDate, + ), + ), + await L2ReverseRegistrarWithAccount2['clearRecordsWithSignature']( + account, + signatureExpiry, + signature, + ) + + assert.equal(await L2ReverseRegistrar.text(node, 'url'), '') + assert.equal(await L2ReverseRegistrar.name(node), '') }) }) }) From 3c93765d4aae74df6ce67c2ffdb753a397937f35 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Thu, 7 Dec 2023 13:53:13 +0800 Subject: [PATCH 27/37] Add tests for inceptionDate --- .../TestL2ReverseRegistrar.js | 146 ++++++++++++++++-- 1 file changed, 134 insertions(+), 12 deletions(-) diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index 8bf0340d..57dd5792 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -127,6 +127,52 @@ describe('L2ReverseRegistrar', function () { ), ).to.be.revertedWith(`InvalidSignature()`) }) + + it('reverts if inception date is too low', async () => { + const funcId = ethers.utils + .id(setNameForAddrWithSignatureFuncSig) + .substring(0, 10) + + const block = await ethers.provider.getBlock('latest') + const inceptionDate = block.timestamp + const signature = await signers[0].signMessage( + ethers.utils.arrayify( + ethers.utils.solidityKeccak256( + ['bytes4', 'address', 'string', 'uint256'], + [funcId, account, 'hello.eth', inceptionDate], + ), + ), + ) + + await L2ReverseRegistrarWithAccount2['setNameForAddrWithSignature']( + account, + 'hello.eth', + inceptionDate, + signature, + ) + + const node = await L2ReverseRegistrar.node(account) + assert.equal(await L2ReverseRegistrar.name(node), 'hello.eth') + + const inceptionDate2 = 0 + const signature2 = await signers[0].signMessage( + ethers.utils.arrayify( + ethers.utils.solidityKeccak256( + ['bytes4', 'address', 'string', 'uint256'], + [funcId, account, 'hello.eth', inceptionDate2], + ), + ), + ) + + await expect( + L2ReverseRegistrarWithAccount2['setNameForAddrWithSignature']( + account, + 'hello.eth', + inceptionDate2, + signature2, + ), + ).to.be.revertedWith(`InvalidSignature()`) + }) }) describe('setNameForAddrWithSignatureAndOwnable', () => { @@ -241,6 +287,57 @@ describe('L2ReverseRegistrar', function () { ), ).to.be.revertedWith(`InvalidSignature()`) }) + + it('reverts if inceptionDate is too low', async () => { + const funcId = ethers.utils + .id(setTextForAddrWithSignatureFuncSig) + .substring(0, 10) + + const block = await ethers.provider.getBlock('latest') + const inceptionDate = block.timestamp + const signature = await signers[0].signMessage( + ethers.utils.arrayify( + ethers.utils.solidityKeccak256( + ['bytes4', 'address', 'string', 'string', 'uint256'], + [funcId, account, 'url', 'http://ens.domains', inceptionDate], + ), + ), + ) + + await L2ReverseRegistrarWithAccount2['setTextForAddrWithSignature']( + account, + 'url', + 'http://ens.domains', + inceptionDate, + signature, + ) + + const node = await L2ReverseRegistrar.node(account) + assert.equal( + await L2ReverseRegistrar.text(node, 'url'), + 'http://ens.domains', + ) + + const inceptionDate2 = 0 + const signature2 = await signers[0].signMessage( + ethers.utils.arrayify( + ethers.utils.solidityKeccak256( + ['bytes4', 'address', 'string', 'string', 'uint256'], + [funcId, account, 'url', 'http://ens.domains', inceptionDate], + ), + ), + ) + + await expect( + L2ReverseRegistrarWithAccount2['setTextForAddrWithSignature']( + account, + 'url', + 'http://ens.domains', + inceptionDate2, + signature2, + ), + ).to.be.revertedWith(`InvalidSignature()`) + }) }) describe('setTextForAddrWithSignatureAndOwnable', function () { @@ -405,22 +502,47 @@ describe('L2ReverseRegistrar', function () { ), ) - keccak256( - abi.encodePacked( - IL2ReverseRegistrar.setNameForAddrWithSignature.selector, - addr, - name, - inceptionDate, + await L2ReverseRegistrarWithAccount2['clearRecordsWithSignature']( + account, + signatureExpiry, + signature, + ) + + assert.equal(await L2ReverseRegistrar.text(node, 'url'), '') + assert.equal(await L2ReverseRegistrar.name(node), '') + }) + + it('clearRecordsWithSignature() reverts when signature expiry is too low', async () => { + const node = await L2ReverseRegistrar.node(account) + await L2ReverseRegistrar.setText('url', 'http://ens.domains') + await L2ReverseRegistrar.setName('hello.eth') + assert.equal( + await L2ReverseRegistrar.text(node, 'url'), + 'http://ens.domains', + ) + assert.equal(await L2ReverseRegistrar.name(node), 'hello.eth') + + const funcId = ethers.utils + .id('clearRecordsWithSignature(address,uint256,bytes)') + .substring(0, 10) + + const signatureExpiry = 0 + const signature = await signers[0].signMessage( + ethers.utils.arrayify( + ethers.utils.solidityKeccak256( + ['bytes4', 'address', 'uint256'], + [funcId, account, signatureExpiry], + ), ), - ), - await L2ReverseRegistrarWithAccount2['clearRecordsWithSignature']( + ) + + await expect( + L2ReverseRegistrarWithAccount2['clearRecordsWithSignature']( account, signatureExpiry, signature, - ) - - assert.equal(await L2ReverseRegistrar.text(node, 'url'), '') - assert.equal(await L2ReverseRegistrar.name(node), '') + ), + ).to.be.revertedWith(`InvalidSignature()`) }) }) }) From 9bfdad057adafdba75d364c2cea06afbdf5d667b Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Thu, 7 Dec 2023 17:54:20 +0800 Subject: [PATCH 28/37] Fix formatting --- test/reverseRegistrar/TestL2ReverseRegistrar.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index 57dd5792..4ff96305 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -1,7 +1,6 @@ const { expect } = require('chai') const { ethers } = require('hardhat') const { namehash } = require('../test-utils/ens') -const { EMPTY_ADDRESS } = require('../test-utils/constants') describe('L2ReverseRegistrar', function () { let L2ReverseRegistrar @@ -288,7 +287,7 @@ describe('L2ReverseRegistrar', function () { ).to.be.revertedWith(`InvalidSignature()`) }) - it('reverts if inceptionDate is too low', async () => { + it('reverts if inception date is too low', async () => { const funcId = ethers.utils .id(setTextForAddrWithSignatureFuncSig) .substring(0, 10) From 6a86103d2ccf52a5ba60314da4e24f36beb5f9fc Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Wed, 13 Dec 2023 11:37:30 +0800 Subject: [PATCH 29/37] Keep events together --- contracts/reverseRegistrar/L2ReverseRegistrar.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 26ad4cd8..b1f8d67f 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -25,13 +25,13 @@ contract L2ReverseRegistrar is mapping(uint64 => mapping(bytes32 => string)) versionable_names; mapping(bytes32 => uint64) internal recordVersions; event VersionChanged(bytes32 indexed node, uint64 newVersion); + event ReverseClaimed(address indexed addr, bytes32 indexed node); + bytes32 public immutable L2_REVERSE_NODE; bytes32 constant lookup = 0x3031323334353637383961626364656600000000000000000000000000000000; - event ReverseClaimed(address indexed addr, bytes32 indexed node); - /** * @dev Constructor */ From dfb000d207897df6d5421a4ee2411047d76d0943 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Wed, 13 Dec 2023 11:38:35 +0800 Subject: [PATCH 30/37] lower case variable --- contracts/reverseRegistrar/L2ReverseRegistrar.sol | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index b1f8d67f..6caf5ad0 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -27,7 +27,7 @@ contract L2ReverseRegistrar is event VersionChanged(bytes32 indexed node, uint64 newVersion); event ReverseClaimed(address indexed addr, bytes32 indexed node); - bytes32 public immutable L2_REVERSE_NODE; + bytes32 public immutable L2ReverseNode; bytes32 constant lookup = 0x3031323334353637383961626364656600000000000000000000000000000000; @@ -35,8 +35,8 @@ contract L2ReverseRegistrar is /** * @dev Constructor */ - constructor(bytes32 L2ReverseNode) { - L2_REVERSE_NODE = L2ReverseNode; + constructor(bytes32 _L2ReverseNode) { + L2ReverseNode = _L2ReverseNode; } modifier authorised(address addr) { @@ -375,7 +375,7 @@ contract L2ReverseRegistrar is function clearRecords(address addr) public virtual authorised(addr) { bytes32 labelHash = sha3HexAddress(addr); bytes32 reverseNode = keccak256( - abi.encodePacked(L2_REVERSE_NODE, labelHash) + abi.encodePacked(L2ReverseNode, labelHash) ); recordVersions[reverseNode]++; emit VersionChanged(reverseNode, recordVersions[reverseNode]); @@ -409,7 +409,7 @@ contract L2ReverseRegistrar is { bytes32 labelHash = sha3HexAddress(addr); bytes32 reverseNode = keccak256( - abi.encodePacked(L2_REVERSE_NODE, labelHash) + abi.encodePacked(L2ReverseNode, labelHash) ); recordVersions[reverseNode]++; emit VersionChanged(reverseNode, recordVersions[reverseNode]); @@ -421,8 +421,7 @@ contract L2ReverseRegistrar is * @return The ENS node hash. */ function node(address addr) public view override returns (bytes32) { - return - keccak256(abi.encodePacked(L2_REVERSE_NODE, sha3HexAddress(addr))); + return keccak256(abi.encodePacked(L2ReverseNode, sha3HexAddress(addr))); } function ownsContract( @@ -438,7 +437,7 @@ contract L2ReverseRegistrar is function _getNamehash(address addr) internal view returns (bytes32) { bytes32 labelHash = sha3HexAddress(addr); - return keccak256(abi.encodePacked(L2_REVERSE_NODE, labelHash)); + return keccak256(abi.encodePacked(L2ReverseNode, labelHash)); } function _setLastUpdated(bytes32 node, uint256 inceptionDate) internal { From dc6e94da9cf0ed4545ed4bffa186805c3413d065 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Mon, 18 Dec 2023 17:24:53 +0800 Subject: [PATCH 31/37] SignatureOutOfDate() + comment for lookup --- contracts/reverseRegistrar/L2ReverseRegistrar.sol | 10 ++++++++-- test/reverseRegistrar/TestL2ReverseRegistrar.js | 4 ++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 6caf5ad0..77ac331f 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -11,6 +11,7 @@ import "../root/Controllable.sol"; import "../resolvers/Multicallable.sol"; error InvalidSignature(); +error SignatureOutOfDate(); contract L2ReverseRegistrar is Multicallable, @@ -29,6 +30,8 @@ contract L2ReverseRegistrar is bytes32 public immutable L2ReverseNode; + // This is the hex encoding of the string 'abcdefghijklmnopqrstuvwxyz' + // It is used as a constant to lookup the characters of the hex address bytes32 constant lookup = 0x3031323334353637383961626364656600000000000000000000000000000000; @@ -70,12 +73,15 @@ contract L2ReverseRegistrar is bytes32 message = hash.toEthSignedMessageHash(); bytes32 node = _getNamehash(addr); + if (!SignatureChecker.isValidSignatureNow(addr, message, signature)) { + revert InvalidSignature(); + } + if ( - !SignatureChecker.isValidSignatureNow(addr, message, signature) || inceptionDate < lastUpdated[node] || // must be newer than current record inceptionDate >= block.timestamp // must be in the past ) { - revert InvalidSignature(); + revert SignatureOutOfDate(); } } diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index 4ff96305..d31c1346 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -170,7 +170,7 @@ describe('L2ReverseRegistrar', function () { inceptionDate2, signature2, ), - ).to.be.revertedWith(`InvalidSignature()`) + ).to.be.revertedWith(`SignatureOutOfDate()`) }) }) @@ -541,7 +541,7 @@ describe('L2ReverseRegistrar', function () { signatureExpiry, signature, ), - ).to.be.revertedWith(`InvalidSignature()`) + ).to.be.revertedWith(`SignatureOutOfDate()`) }) }) }) From 9eb42f10df5b20389f2d1327c684705c2033ecd7 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Tue, 19 Dec 2023 13:32:22 +0800 Subject: [PATCH 32/37] Add custom error to isAuthorised() --- contracts/reverseRegistrar/L2ReverseRegistrar.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 77ac331f..2a4e4643 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -12,6 +12,7 @@ import "../resolvers/Multicallable.sol"; error InvalidSignature(); error SignatureOutOfDate(); +error Unauthorised(); contract L2ReverseRegistrar is Multicallable, @@ -58,10 +59,9 @@ contract L2ReverseRegistrar is } function isAuthorised(address addr) internal view returns (bool) { - require( - addr == msg.sender || ownsContract(addr, msg.sender), - "ReverseRegistrar: Caller is not a controller or authorised by address or the address itself" - ); + if (addr != msg.sender && !ownsContract(addr, msg.sender)) { + revert Unauthorised(); + } } function isAuthorisedWithSignature( From cbba61f80b4287008c9200aa358f3b47aa5fa6d4 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Tue, 19 Dec 2023 14:00:30 +0800 Subject: [PATCH 33/37] Move authorisation of ownable funcs to modifier --- .../reverseRegistrar/L2ReverseRegistrar.sol | 158 +++++++++++------- 1 file changed, 95 insertions(+), 63 deletions(-) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 2a4e4643..24c6d93d 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -13,6 +13,7 @@ import "../resolvers/Multicallable.sol"; error InvalidSignature(); error SignatureOutOfDate(); error Unauthorised(); +error NotOwnerOfContract(); contract L2ReverseRegistrar is Multicallable, @@ -58,6 +59,23 @@ contract L2ReverseRegistrar is _; } + modifier ownerAndAuthorisedWithSignature( + bytes32 hash, + address addr, + address owner, + uint256 inceptionDate, + bytes memory signature + ) { + isOwnerAndAuthorisedWithSignature( + hash, + addr, + owner, + inceptionDate, + signature + ); + _; + } + function isAuthorised(address addr) internal view returns (bool) { if (addr != msg.sender && !ownsContract(addr, msg.sender)) { revert Unauthorised(); @@ -85,6 +103,38 @@ contract L2ReverseRegistrar is } } + function isOwnerAndAuthorisedWithSignature( + bytes32 hash, + address addr, + address owner, + uint256 inceptionDate, + bytes memory signature + ) internal view returns (bool) { + bytes32 message = hash.toEthSignedMessageHash(); + bytes32 node = _getNamehash(addr); + + if (!ownsContract(addr, owner)) { + revert NotOwnerOfContract(); + } + + if ( + !SignatureChecker.isValidERC1271SignatureNow( + owner, + message, + signature + ) + ) { + revert InvalidSignature(); + } + + if ( + inceptionDate < lastUpdated[node] || // must be newer than current record + inceptionDate >= block.timestamp // must be in the past + ) { + revert SignatureOutOfDate(); + } + } + /** * @dev Sets the name for an addr using a signature that can be verified with ERC1271. * @param addr The reverse record to set @@ -137,38 +187,29 @@ contract L2ReverseRegistrar is string memory name, uint256 inceptionDate, bytes memory signature - ) public returns (bytes32) { + ) + public + ownerAndAuthorisedWithSignature( + keccak256( + abi.encodePacked( + IL2ReverseRegistrar + .setNameForAddrWithSignatureAndOwnable + .selector, + contractAddr, + owner, + name, + inceptionDate + ) + ), + contractAddr, + owner, + inceptionDate, + signature + ) + returns (bytes32) + { bytes32 node = _getNamehash(contractAddr); - - bytes32 hash = keccak256( - abi.encodePacked( - IL2ReverseRegistrar - .setNameForAddrWithSignatureAndOwnable - .selector, - contractAddr, - owner, - name, - inceptionDate - ) - ); - - bytes32 message = hash.toEthSignedMessageHash(); - - if ( - ownsContract(contractAddr, owner) && - SignatureChecker.isValidERC1271SignatureNow( - owner, - message, - signature - ) && - inceptionDate >= lastUpdated[node] && - inceptionDate < block.timestamp - ) { - _setName(node, name, inceptionDate); - return node; - } - - revert InvalidSignature(); + _setName(node, name, inceptionDate); } /** @@ -254,39 +295,30 @@ contract L2ReverseRegistrar is string calldata value, uint256 inceptionDate, bytes memory signature - ) public returns (bytes32) { + ) + public + ownerAndAuthorisedWithSignature( + keccak256( + abi.encodePacked( + IL2ReverseRegistrar + .setTextForAddrWithSignatureAndOwnable + .selector, + contractAddr, + owner, + key, + value, + inceptionDate + ) + ), + contractAddr, + owner, + inceptionDate, + signature + ) + returns (bytes32) + { bytes32 node = _getNamehash(contractAddr); - - bytes32 hash = keccak256( - abi.encodePacked( - IL2ReverseRegistrar - .setTextForAddrWithSignatureAndOwnable - .selector, - contractAddr, - owner, - key, - value, - inceptionDate - ) - ); - - bytes32 message = hash.toEthSignedMessageHash(); - - if ( - ownsContract(contractAddr, owner) && - SignatureChecker.isValidERC1271SignatureNow( - owner, - message, - signature - ) && - inceptionDate > lastUpdated[node] && - inceptionDate < block.timestamp - ) { - _setText(node, key, value, inceptionDate); - return node; - } - - revert InvalidSignature(); + _setText(node, key, value, inceptionDate); } /** From 3b9e8d79bda0d5ec173846cd167dcd903264e4cb Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Wed, 20 Dec 2023 10:58:23 +0800 Subject: [PATCH 34/37] WIP add cointype --- contracts/reverseRegistrar/L2ReverseRegistrar.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index 24c6d93d..a678c03d 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -31,6 +31,7 @@ contract L2ReverseRegistrar is event ReverseClaimed(address indexed addr, bytes32 indexed node); bytes32 public immutable L2ReverseNode; + uint256 public immutable coinType; // This is the hex encoding of the string 'abcdefghijklmnopqrstuvwxyz' // It is used as a constant to lookup the characters of the hex address @@ -40,8 +41,9 @@ contract L2ReverseRegistrar is /** * @dev Constructor */ - constructor(bytes32 _L2ReverseNode) { + constructor(bytes32 _L2ReverseNode, uint256 _coinType) { L2ReverseNode = _L2ReverseNode; + coinType = _coinType; } modifier authorised(address addr) { From 27ad3143020f5875fb4c2b27b342420c4eb83a9d Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Wed, 20 Dec 2023 18:43:53 +0800 Subject: [PATCH 35/37] Move to a double hash signature --- .../reverseRegistrar/L2ReverseRegistrar.sol | 42 ++-- contracts/reverseRegistrar/README.md | 16 ++ .../TestL2ReverseRegistrar.js | 180 ++++++++++++------ 3 files changed, 154 insertions(+), 84 deletions(-) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index a678c03d..b3ef6c4f 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -15,6 +15,11 @@ error SignatureOutOfDate(); error Unauthorised(); error NotOwnerOfContract(); +// Note on inception date +// The inception date is in milliseconds, and so will be divided by 1000 +// when comparing to block.timestamp. This means that the date will be +// rounded down to the nearest second. + contract L2ReverseRegistrar is Multicallable, Ownable, @@ -90,7 +95,8 @@ contract L2ReverseRegistrar is uint256 inceptionDate, bytes memory signature ) internal view returns (bool) { - bytes32 message = hash.toEthSignedMessageHash(); + bytes32 message = keccak256(abi.encodePacked(hash, addr, inceptionDate)) + .toEthSignedMessageHash(); bytes32 node = _getNamehash(addr); if (!SignatureChecker.isValidSignatureNow(addr, message, signature)) { @@ -98,8 +104,8 @@ contract L2ReverseRegistrar is } if ( - inceptionDate < lastUpdated[node] || // must be newer than current record - inceptionDate >= block.timestamp // must be in the past + inceptionDate <= lastUpdated[node] || // must be newer than current record + inceptionDate / 1000 >= block.timestamp // must be in the past ) { revert SignatureOutOfDate(); } @@ -112,7 +118,9 @@ contract L2ReverseRegistrar is uint256 inceptionDate, bytes memory signature ) internal view returns (bool) { - bytes32 message = hash.toEthSignedMessageHash(); + bytes32 message = keccak256( + abi.encodePacked(hash, addr, owner, inceptionDate) + ).toEthSignedMessageHash(); bytes32 node = _getNamehash(addr); if (!ownsContract(addr, owner)) { @@ -130,8 +138,8 @@ contract L2ReverseRegistrar is } if ( - inceptionDate < lastUpdated[node] || // must be newer than current record - inceptionDate >= block.timestamp // must be in the past + inceptionDate <= lastUpdated[node] || // must be newer than current record + inceptionDate / 1000 >= block.timestamp // must be in the past ) { revert SignatureOutOfDate(); } @@ -157,9 +165,7 @@ contract L2ReverseRegistrar is keccak256( abi.encodePacked( IL2ReverseRegistrar.setNameForAddrWithSignature.selector, - addr, - name, - inceptionDate + name ) ), addr, @@ -197,10 +203,7 @@ contract L2ReverseRegistrar is IL2ReverseRegistrar .setNameForAddrWithSignatureAndOwnable .selector, - contractAddr, - owner, - name, - inceptionDate + name ) ), contractAddr, @@ -263,10 +266,8 @@ contract L2ReverseRegistrar is keccak256( abi.encodePacked( IL2ReverseRegistrar.setTextForAddrWithSignature.selector, - addr, key, - value, - inceptionDate + value ) ), addr, @@ -305,11 +306,8 @@ contract L2ReverseRegistrar is IL2ReverseRegistrar .setTextForAddrWithSignatureAndOwnable .selector, - contractAddr, - owner, key, - value, - inceptionDate + value ) ), contractAddr, @@ -437,9 +435,7 @@ contract L2ReverseRegistrar is authorisedSignature( keccak256( abi.encodePacked( - IL2ReverseRegistrar.clearRecordsWithSignature.selector, - addr, - inceptionDate + IL2ReverseRegistrar.clearRecordsWithSignature.selector ) ), addr, diff --git a/contracts/reverseRegistrar/README.md b/contracts/reverseRegistrar/README.md index 0d49824d..169c7623 100644 --- a/contracts/reverseRegistrar/README.md +++ b/contracts/reverseRegistrar/README.md @@ -3,3 +3,19 @@ ## Summary The L2 Reverse registrar is a combination of a resolver and a reverse registrar that allows the name to be set for a particular reverse node. + +## Inception Date + +Inception date is set in milliseconds, so needs to be divided by 1000 to be compared to block.timestamp which is in seconds + +## Setting records + +You can set records using one of the follow functions: + +`setName()`/`setText()` - uses the msg.sender's address and allows you to set a record for that address only + +`setNameForAddr()`/`setTextForAddr()` - uses the address parameter instead of `msg.sender` and checks if the `msg.sender` is authorised by checking if the contract's owner (via the Ownable pattern) is the msg.sender + +`setNameForAddrWithSignature()`/`setTextForAddrWithSignature()` - uses the address parameter instead of `msg.sender` and allows authorisation via a signature + +`setNameForAddrWithSignatureAndOwnable()`/`setTextForAddrWithSignatureOwnable()` - uses the address parameter instead of `msg.sender`. The sender is authorised by checking if the contract's owner (via the Ownable pattern) is the msg.sender, which then checks that the signer has authorised the record on behalf of msg.sender using `ERC1271` diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index d31c1346..7abee952 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -2,6 +2,8 @@ const { expect } = require('chai') const { ethers } = require('hardhat') const { namehash } = require('../test-utils/ens') +const keccak256 = ethers.utils.solidityKeccak256 + describe('L2ReverseRegistrar', function () { let L2ReverseRegistrar let L2ReverseRegistrarWithAccount2 @@ -29,6 +31,7 @@ describe('L2ReverseRegistrar', function () { ) L2ReverseRegistrar = await L2ReverseRegistrarFactory.deploy( namehash('optimism.reverse'), + 123, ) const MockSmartContractWalletFactory = await ethers.getContractFactory( @@ -80,12 +83,16 @@ describe('L2ReverseRegistrar', function () { .substring(0, 10) const block = await ethers.provider.getBlock('latest') - const signatureExpiry = block.timestamp + const inceptionDate = block.timestamp const signature = await signers[0].signMessage( ethers.utils.arrayify( - ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'string', 'uint256'], - [funcId, account, 'hello.eth', signatureExpiry], + keccak256( + ['bytes32', 'address', 'uint256'], + [ + keccak256(['bytes4', 'string'], [funcId, 'hello.eth']), + account, + inceptionDate, + ], ), ), ) @@ -93,7 +100,7 @@ describe('L2ReverseRegistrar', function () { await L2ReverseRegistrarWithAccount2['setNameForAddrWithSignature']( account, 'hello.eth', - signatureExpiry, + inceptionDate, signature, ) @@ -107,12 +114,16 @@ describe('L2ReverseRegistrar', function () { .substring(0, 10) const block = await ethers.provider.getBlock('latest') - const signatureExpiry = block.timestamp + 3600 + const inceptionDate = block.timestamp + 3600 const signature = await signers[0].signMessage( ethers.utils.arrayify( - ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'string', 'uint256'], - [funcId, account, 'hello.eth', signatureExpiry], + keccak256( + ['bytes32', 'address', 'uint256'], + [ + keccak256(['bytes4', 'string'], [funcId, 'hello.eth']), + account, + inceptionDate, + ], ), ), ) @@ -121,7 +132,7 @@ describe('L2ReverseRegistrar', function () { L2ReverseRegistrarWithAccount2[setNameForAddrWithSignatureFuncSig]( account, 'notthesamename.eth', - signatureExpiry, + inceptionDate, signature, ), ).to.be.revertedWith(`InvalidSignature()`) @@ -136,9 +147,13 @@ describe('L2ReverseRegistrar', function () { const inceptionDate = block.timestamp const signature = await signers[0].signMessage( ethers.utils.arrayify( - ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'string', 'uint256'], - [funcId, account, 'hello.eth', inceptionDate], + keccak256( + ['bytes32', 'address', 'uint256'], + [ + keccak256(['bytes4', 'string'], [funcId, 'hello.eth']), + account, + inceptionDate, + ], ), ), ) @@ -156,9 +171,13 @@ describe('L2ReverseRegistrar', function () { const inceptionDate2 = 0 const signature2 = await signers[0].signMessage( ethers.utils.arrayify( - ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'string', 'uint256'], - [funcId, account, 'hello.eth', inceptionDate2], + keccak256( + ['bytes32', 'address', 'uint256'], + [ + keccak256(['bytes4', 'string'], [funcId, 'hello.eth']), + account, + inceptionDate2, + ], ), ), ) @@ -183,17 +202,16 @@ describe('L2ReverseRegistrar', function () { .substring(0, 10) const block = await ethers.provider.getBlock('latest') - const signatureExpiry = block.timestamp + const inceptionDate = block.timestamp const signature = await signers[0].signMessage( ethers.utils.arrayify( - ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'address', 'string', 'uint256'], + keccak256( + ['bytes32', 'address', 'address', 'uint256'], [ - funcId, + keccak256(['bytes4', 'string'], [funcId, 'ownable.eth']), MockOwnable.address, MockSmartContractWallet.address, - 'ownable.eth', - signatureExpiry, + inceptionDate, ], ), ), @@ -205,7 +223,7 @@ describe('L2ReverseRegistrar', function () { MockOwnable.address, MockSmartContractWallet.address, 'ownable.eth', - signatureExpiry, + inceptionDate, signature, ) @@ -238,9 +256,16 @@ describe('L2ReverseRegistrar', function () { const inceptionDate = block.timestamp const signature = await signers[0].signMessage( ethers.utils.arrayify( - ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'string', 'string', 'uint256'], - [funcId, account, 'url', 'http://ens.domains', inceptionDate], + keccak256( + ['bytes32', 'address', 'uint256'], + [ + keccak256( + ['bytes4', 'string', 'string'], + [funcId, 'url', 'http://ens.domains'], + ), + account, + inceptionDate, + ], ), ), ) @@ -269,9 +294,16 @@ describe('L2ReverseRegistrar', function () { const inceptionDate = block.timestamp const signature = await signers[0].signMessage( ethers.utils.arrayify( - ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'string', 'string', 'uint256'], - [funcId, account, 'url', 'http://ens.domains', inceptionDate], + keccak256( + ['bytes32', 'address', 'uint256'], + [ + keccak256( + ['bytes4', 'string', 'string'], + [funcId, 'url', 'http://ens.domains'], + ), + account, + inceptionDate, + ], ), ), ) @@ -296,9 +328,16 @@ describe('L2ReverseRegistrar', function () { const inceptionDate = block.timestamp const signature = await signers[0].signMessage( ethers.utils.arrayify( - ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'string', 'string', 'uint256'], - [funcId, account, 'url', 'http://ens.domains', inceptionDate], + keccak256( + ['bytes32', 'address', 'uint256'], + [ + keccak256( + ['bytes4', 'string', 'string'], + [funcId, 'url', 'http://ens.domains'], + ), + account, + inceptionDate, + ], ), ), ) @@ -320,9 +359,16 @@ describe('L2ReverseRegistrar', function () { const inceptionDate2 = 0 const signature2 = await signers[0].signMessage( ethers.utils.arrayify( - ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'string', 'string', 'uint256'], - [funcId, account, 'url', 'http://ens.domains', inceptionDate], + keccak256( + ['bytes32', 'address', 'uint256'], + [ + keccak256( + ['bytes4', 'string', 'string'], + [funcId, 'url', 'http://ens.domains'], + ), + account, + inceptionDate2, + ], ), ), ) @@ -335,7 +381,7 @@ describe('L2ReverseRegistrar', function () { inceptionDate2, signature2, ), - ).to.be.revertedWith(`InvalidSignature()`) + ).to.be.revertedWith(`SignatureOutOfDate()`) }) }) @@ -348,18 +394,19 @@ describe('L2ReverseRegistrar', function () { .substring(0, 10) const block = await ethers.provider.getBlock('latest') - const signatureExpiry = block.timestamp + const inceptionDate = block.timestamp const signature = await signers[0].signMessage( ethers.utils.arrayify( - ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'address', 'string', 'string', 'uint256'], + keccak256( + ['bytes32', 'address', 'address', 'uint256'], [ - funcId, + keccak256( + ['bytes4', 'string', 'string'], + [funcId, 'url', 'http://ens.domains'], + ), MockOwnable.address, MockSmartContractWallet.address, - 'url', - 'http://ens.domains', - signatureExpiry, + inceptionDate, ], ), ), @@ -372,7 +419,7 @@ describe('L2ReverseRegistrar', function () { MockSmartContractWallet.address, 'url', 'http://ens.domains', - signatureExpiry, + inceptionDate, signature, ) @@ -424,18 +471,29 @@ describe('L2ReverseRegistrar', function () { console.log('inceptionDate', inceptionDate) const signature1 = await signers[0].signMessage( ethers.utils.arrayify( - ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'string', 'string', 'uint256'], - [funcId1, account, 'url', 'http://ens.domains', inceptionDate], + keccak256( + ['bytes32', 'address', 'uint256'], + [ + keccak256( + ['bytes4', 'string', 'string'], + [funcId1, 'url', 'http://ens.domains'], + ), + account, + inceptionDate, + ], ), ), ) const signature2 = await signers[0].signMessage( ethers.utils.arrayify( - ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'string', 'uint256'], - [funcId2, account, 'hello.eth', inceptionDate], + keccak256( + ['bytes32', 'address', 'uint256'], + [ + keccak256(['bytes4', 'string'], [funcId2, 'hello.eth']), + account, + inceptionDate + 1, + ], ), ), ) @@ -447,7 +505,7 @@ describe('L2ReverseRegistrar', function () { ), L2ReverseRegistrar.interface.encodeFunctionData( 'setNameForAddrWithSignature', - [account, 'hello.eth', inceptionDate, signature2], + [account, 'hello.eth', inceptionDate + 1, signature2], ), ] @@ -491,19 +549,19 @@ describe('L2ReverseRegistrar', function () { .substring(0, 10) const block = await ethers.provider.getBlock('latest') - const signatureExpiry = block.timestamp + const inceptionDate = block.timestamp * 1000 const signature = await signers[0].signMessage( ethers.utils.arrayify( - ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'uint256'], - [funcId, account, signatureExpiry], + keccak256( + ['bytes32', 'address', 'uint256'], + [keccak256(['bytes4'], [funcId]), account, inceptionDate], ), ), ) await L2ReverseRegistrarWithAccount2['clearRecordsWithSignature']( account, - signatureExpiry, + inceptionDate, signature, ) @@ -525,12 +583,12 @@ describe('L2ReverseRegistrar', function () { .id('clearRecordsWithSignature(address,uint256,bytes)') .substring(0, 10) - const signatureExpiry = 0 + const inceptionDate = 0 const signature = await signers[0].signMessage( ethers.utils.arrayify( - ethers.utils.solidityKeccak256( - ['bytes4', 'address', 'uint256'], - [funcId, account, signatureExpiry], + keccak256( + ['bytes32', 'address', 'uint256'], + [keccak256(['bytes4'], [funcId]), account, inceptionDate], ), ), ) @@ -538,7 +596,7 @@ describe('L2ReverseRegistrar', function () { await expect( L2ReverseRegistrarWithAccount2['clearRecordsWithSignature']( account, - signatureExpiry, + inceptionDate, signature, ), ).to.be.revertedWith(`SignatureOutOfDate()`) From d2315a929562f56e598075c7be7a196a17aedb73 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Wed, 20 Dec 2023 19:23:51 +0800 Subject: [PATCH 36/37] Add coinType to prevent crosschain replay attacks --- .../reverseRegistrar/L2ReverseRegistrar.sol | 9 ++-- .../TestL2ReverseRegistrar.js | 42 ++++++++++++------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/contracts/reverseRegistrar/L2ReverseRegistrar.sol b/contracts/reverseRegistrar/L2ReverseRegistrar.sol index b3ef6c4f..35b3d1f2 100644 --- a/contracts/reverseRegistrar/L2ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/L2ReverseRegistrar.sol @@ -15,7 +15,7 @@ error SignatureOutOfDate(); error Unauthorised(); error NotOwnerOfContract(); -// Note on inception date +// @note Inception date // The inception date is in milliseconds, and so will be divided by 1000 // when comparing to block.timestamp. This means that the date will be // rounded down to the nearest second. @@ -95,8 +95,9 @@ contract L2ReverseRegistrar is uint256 inceptionDate, bytes memory signature ) internal view returns (bool) { - bytes32 message = keccak256(abi.encodePacked(hash, addr, inceptionDate)) - .toEthSignedMessageHash(); + bytes32 message = keccak256( + abi.encodePacked(hash, addr, inceptionDate, coinType) + ).toEthSignedMessageHash(); bytes32 node = _getNamehash(addr); if (!SignatureChecker.isValidSignatureNow(addr, message, signature)) { @@ -119,7 +120,7 @@ contract L2ReverseRegistrar is bytes memory signature ) internal view returns (bool) { bytes32 message = keccak256( - abi.encodePacked(hash, addr, owner, inceptionDate) + abi.encodePacked(hash, addr, owner, inceptionDate, coinType) ).toEthSignedMessageHash(); bytes32 node = _getNamehash(addr); diff --git a/test/reverseRegistrar/TestL2ReverseRegistrar.js b/test/reverseRegistrar/TestL2ReverseRegistrar.js index 7abee952..b207471e 100644 --- a/test/reverseRegistrar/TestL2ReverseRegistrar.js +++ b/test/reverseRegistrar/TestL2ReverseRegistrar.js @@ -20,6 +20,7 @@ describe('L2ReverseRegistrar', function () { 'setTextForAddrWithSignature(address,string,string,uint256,bytes)' let setTextForAddrWithSignatureAndOwnableFuncSig = 'setTextForAddrWithSignatureAndOwnable(address,address,string,string,uint256,bytes)' + let coinType = 123 before(async function () { signers = await ethers.getSigners() @@ -31,7 +32,7 @@ describe('L2ReverseRegistrar', function () { ) L2ReverseRegistrar = await L2ReverseRegistrarFactory.deploy( namehash('optimism.reverse'), - 123, + coinType, ) const MockSmartContractWalletFactory = await ethers.getContractFactory( @@ -87,11 +88,12 @@ describe('L2ReverseRegistrar', function () { const signature = await signers[0].signMessage( ethers.utils.arrayify( keccak256( - ['bytes32', 'address', 'uint256'], + ['bytes32', 'address', 'uint256', 'uint256'], [ keccak256(['bytes4', 'string'], [funcId, 'hello.eth']), account, inceptionDate, + coinType, ], ), ), @@ -148,11 +150,12 @@ describe('L2ReverseRegistrar', function () { const signature = await signers[0].signMessage( ethers.utils.arrayify( keccak256( - ['bytes32', 'address', 'uint256'], + ['bytes32', 'address', 'uint256', 'uint256'], [ keccak256(['bytes4', 'string'], [funcId, 'hello.eth']), account, inceptionDate, + coinType, ], ), ), @@ -172,11 +175,12 @@ describe('L2ReverseRegistrar', function () { const signature2 = await signers[0].signMessage( ethers.utils.arrayify( keccak256( - ['bytes32', 'address', 'uint256'], + ['bytes32', 'address', 'uint256', 'uint256'], [ keccak256(['bytes4', 'string'], [funcId, 'hello.eth']), account, inceptionDate2, + coinType, ], ), ), @@ -206,12 +210,13 @@ describe('L2ReverseRegistrar', function () { const signature = await signers[0].signMessage( ethers.utils.arrayify( keccak256( - ['bytes32', 'address', 'address', 'uint256'], + ['bytes32', 'address', 'address', 'uint256', 'uint256'], [ keccak256(['bytes4', 'string'], [funcId, 'ownable.eth']), MockOwnable.address, MockSmartContractWallet.address, inceptionDate, + coinType, ], ), ), @@ -257,7 +262,7 @@ describe('L2ReverseRegistrar', function () { const signature = await signers[0].signMessage( ethers.utils.arrayify( keccak256( - ['bytes32', 'address', 'uint256'], + ['bytes32', 'address', 'uint256', 'uint256'], [ keccak256( ['bytes4', 'string', 'string'], @@ -265,6 +270,7 @@ describe('L2ReverseRegistrar', function () { ), account, inceptionDate, + coinType, ], ), ), @@ -329,7 +335,7 @@ describe('L2ReverseRegistrar', function () { const signature = await signers[0].signMessage( ethers.utils.arrayify( keccak256( - ['bytes32', 'address', 'uint256'], + ['bytes32', 'address', 'uint256', 'uint256'], [ keccak256( ['bytes4', 'string', 'string'], @@ -337,6 +343,7 @@ describe('L2ReverseRegistrar', function () { ), account, inceptionDate, + coinType, ], ), ), @@ -360,7 +367,7 @@ describe('L2ReverseRegistrar', function () { const signature2 = await signers[0].signMessage( ethers.utils.arrayify( keccak256( - ['bytes32', 'address', 'uint256'], + ['bytes32', 'address', 'uint256', 'uint256'], [ keccak256( ['bytes4', 'string', 'string'], @@ -368,6 +375,7 @@ describe('L2ReverseRegistrar', function () { ), account, inceptionDate2, + coinType, ], ), ), @@ -398,7 +406,7 @@ describe('L2ReverseRegistrar', function () { const signature = await signers[0].signMessage( ethers.utils.arrayify( keccak256( - ['bytes32', 'address', 'address', 'uint256'], + ['bytes32', 'address', 'address', 'uint256', 'uint256'], [ keccak256( ['bytes4', 'string', 'string'], @@ -407,6 +415,7 @@ describe('L2ReverseRegistrar', function () { MockOwnable.address, MockSmartContractWallet.address, inceptionDate, + coinType, ], ), ), @@ -468,11 +477,10 @@ describe('L2ReverseRegistrar', function () { const block = await ethers.provider.getBlock('latest') const inceptionDate = block.timestamp - console.log('inceptionDate', inceptionDate) const signature1 = await signers[0].signMessage( ethers.utils.arrayify( keccak256( - ['bytes32', 'address', 'uint256'], + ['bytes32', 'address', 'uint256', 'uint256'], [ keccak256( ['bytes4', 'string', 'string'], @@ -480,6 +488,7 @@ describe('L2ReverseRegistrar', function () { ), account, inceptionDate, + coinType, ], ), ), @@ -488,11 +497,12 @@ describe('L2ReverseRegistrar', function () { const signature2 = await signers[0].signMessage( ethers.utils.arrayify( keccak256( - ['bytes32', 'address', 'uint256'], + ['bytes32', 'address', 'uint256', 'uint256'], [ keccak256(['bytes4', 'string'], [funcId2, 'hello.eth']), account, inceptionDate + 1, + coinType, ], ), ), @@ -553,8 +563,8 @@ describe('L2ReverseRegistrar', function () { const signature = await signers[0].signMessage( ethers.utils.arrayify( keccak256( - ['bytes32', 'address', 'uint256'], - [keccak256(['bytes4'], [funcId]), account, inceptionDate], + ['bytes32', 'address', 'uint256', 'uint256'], + [keccak256(['bytes4'], [funcId]), account, inceptionDate, coinType], ), ), ) @@ -587,8 +597,8 @@ describe('L2ReverseRegistrar', function () { const signature = await signers[0].signMessage( ethers.utils.arrayify( keccak256( - ['bytes32', 'address', 'uint256'], - [keccak256(['bytes4'], [funcId]), account, inceptionDate], + ['bytes32', 'address', 'uint256', 'uint256'], + [keccak256(['bytes4'], [funcId]), account, inceptionDate, coinType], ), ), ) From a97255a8acd7ce474991723ad97d9912dafe9071 Mon Sep 17 00:00:00 2001 From: Jeff Lau Date: Thu, 21 Dec 2023 13:43:54 +0800 Subject: [PATCH 37/37] Add namespace to deploy script --- contracts/reverseRegistrar/README.md | 4 ++++ deploy/reverseregistrar/01_deploy_l2_reverse_registrar.ts | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/contracts/reverseRegistrar/README.md b/contracts/reverseRegistrar/README.md index 169c7623..050a50ee 100644 --- a/contracts/reverseRegistrar/README.md +++ b/contracts/reverseRegistrar/README.md @@ -19,3 +19,7 @@ You can set records using one of the follow functions: `setNameForAddrWithSignature()`/`setTextForAddrWithSignature()` - uses the address parameter instead of `msg.sender` and allows authorisation via a signature `setNameForAddrWithSignatureAndOwnable()`/`setTextForAddrWithSignatureOwnable()` - uses the address parameter instead of `msg.sender`. The sender is authorised by checking if the contract's owner (via the Ownable pattern) is the msg.sender, which then checks that the signer has authorised the record on behalf of msg.sender using `ERC1271` + +## Signatures for setting records + +Signatures use a double keccak256 structure that first hashes the function id with the variables that you are passing to that function. It then hashes the result of that with the rest of the parameters. diff --git a/deploy/reverseregistrar/01_deploy_l2_reverse_registrar.ts b/deploy/reverseregistrar/01_deploy_l2_reverse_registrar.ts index e4d2870f..83f4793e 100644 --- a/deploy/reverseregistrar/01_deploy_l2_reverse_registrar.ts +++ b/deploy/reverseregistrar/01_deploy_l2_reverse_registrar.ts @@ -4,7 +4,7 @@ import { DeployFunction } from 'hardhat-deploy/types' import { HardhatRuntimeEnvironment } from 'hardhat/types' // Replace with coinid of L2 -const NAMESPACE = 'optimism' +const COINTYPE = 123 const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const { getNamedAccounts, deployments, network } = hre @@ -13,7 +13,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { await deploy('L2ReverseRegistrar', { from: deployer, - args: [namehash(`${NAMESPACE}.reverse`)], + args: [namehash(`${COINTYPE}.reverse`), COINTYPE], log: true, }) }