From 8a88e4eaa63f6b76d620eb683438e8e1e5567082 Mon Sep 17 00:00:00 2001 From: sshmatrix <19473027+sshmatrix@users.noreply.github.com> Date: Wed, 19 Jul 2023 13:57:19 +0530 Subject: [PATCH] Re-instate Ownerhash[] & signedRedirect() (#29) * README update * Purge masterhash() * README.md update * README.md update: 2 * Reinstate signedRedirect() & ownerhash[] --- README.md | 18 ++-- src/CCIP2ETH.sol | 199 ++++++++++++++++++++++++++++------------- src/GatewayManager.sol | 6 +- src/Interface.sol | 10 ++- test/CCIP2ETH.t.sol | 73 ++++++++++----- 5 files changed, 206 insertions(+), 100 deletions(-) diff --git a/README.md b/README.md index ee8129c..74a7b8d 100644 --- a/README.md +++ b/README.md @@ -64,17 +64,19 @@ To ensure secure record resolution, records must be signed by either the owner o | Text Records | `text(bytes32 node, string memory key)` | `text/.json` | | Ethereum Address | `addr(bytes32 node)` | `address/60.json` | | Contenthash* | `contenthash(bytes32 node)` | `contenthash.json` | -| Multichain Address | `addr(bytes32 node, uint coinType)`| `address/.json` | +| Multichain Address‡ | `addr(bytes32 node, uint coinType)`| `address/.json` | | Public Key | `pubkey(bytes32 node)`| `pubkey.json` | -| Name** | `name(bytes32 node)`| `name.json` | -| Interface | `interfaceImplementer(bytes32 node, bytes4 _selector)`| `interface/0x.json` | -| ABI | `ABI(bytes32 node, uint256 contentTypes)`| `abi/.json` | -| Zonehash | `zonehash(bytes32 node)`| `dnsrecord/zonehash.json` | -| DNS Record | `dnsRecord(bytes32 node, bytes name, uint16 resource) `| `dnsrecord/.json` | +| Name† | `name(bytes32 node)`| `name.json` | +| Interface‡ | `interfaceImplementer(bytes32 node, bytes4 _selector)`| `interface/0x.json` | +| ABI‡ | `ABI(bytes32 node, uint256 contentTypes)`| `abi/.json` | +| Zonehash‡ | `zonehash(bytes32 node)`| `dns/zonehash.json` | +| DNS Record‡ | `dnsRecord(bytes32 node, bytes name, uint16 resource) `| `dns/.json` | -\* This is the user's web-facing contenthash contained inside the recordhash or masterhash +\* This is the user's web-facing contenthash contained inside the recordhash -\*\* Name is not implemented as reverse record; users must use the official ENS on-chain reverse record for that feature. +† Name is not implemented as reverse record; users must use the official ENS on-chain reverse record for that feature + +‡ Available in v1.1 ## CCIP2.ETH Gateways diff --git a/src/CCIP2ETH.sol b/src/CCIP2ETH.sol index b4d54ca..476005b 100644 --- a/src/CCIP2ETH.sol +++ b/src/CCIP2ETH.sol @@ -2,7 +2,6 @@ pragma solidity >0.8.0 <0.9.0; import "./Interface.sol"; -//import "forge-std/Test.sol"; /** * @title Off-Chain ENS Records Manager @@ -11,14 +10,6 @@ import "./Interface.sol"; * Client : https://namesys.eth.limo */ contract CCIP2ETH is iCCIP2ETH { - /// @dev - ONLY TESTNET - /// TODO - Remove before Mainnet deployment - function immolate() external { - address _owner = gateway.owner(); - require(msg.sender == _owner, "NOT_OWNER"); - selfdestruct(payable(_owner)); - } - /// @dev - Revert on fallback fallback() external payable { revert(); @@ -31,16 +22,14 @@ contract CCIP2ETH is iCCIP2ETH { /// Events event ThankYou(address indexed addr, uint256 indexed value); - event UpdateGatewayManager(address indexed oldAddr, address indexed newAddr); + event UpdatedGatewayManager(address indexed oldAddr, address indexed newAddr); event RecordhashChanged(address indexed owner, bytes32 indexed node, bytes contenthash); - event UpdateWrapper(address indexed newAddr, bool indexed status); - event Approved(address owner, bytes32 indexed node, address indexed delegate, bool indexed approved); - event UpdateSupportedInterface(bytes4 indexed sig, bool indexed status); + event UpdatedWrapper(address indexed newAddr, bool indexed status); + event ApprovedSigner(address owner, bytes32 indexed node, address indexed delegate, bool indexed approved); + event UpdatedSupportedInterface(bytes4 indexed sig, bool indexed status); /// Errors error InvalidSignature(string message); - error NotAuthorized(bytes32 node, address addr); - error ContenthashNotSet(bytes32 node); /// @dev - ENS Legacy Registry iENS public immutable ENS = iENS(0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e); @@ -53,6 +42,8 @@ contract CCIP2ETH is iCCIP2ETH { * @notice - Should be in generic ENS contenthash format or base32/base36 string URL format */ mapping(bytes32 => bytes) public recordhash; + /// @dev - Owner-specific contenthash storing records for all names owned by a wallet + mapping(address => bytes) public ownerhash; /// @dev - On-chain singular Manager database /// Note - Manager (= isApprovedSigner) is someone who can manage off-chain records for a domain on behalf of its owner mapping(address => mapping(bytes32 => mapping(address => bool))) public isApprovedSigner; @@ -69,11 +60,11 @@ contract CCIP2ETH is iCCIP2ETH { /// @dev - Sets ENS Mainnet wrapper as Wrapper isWrapper[0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401] = true; - emit UpdateWrapper(0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401, true); + emit UpdatedWrapper(0xD4416b13d2b3a9aBae7AcD5D6C2BbDBE25686401, true); /// @dev - Sets ENS Goerli wrapper as Wrapper; remove before Mainnet deploy [?TODO] //isWrapper[0x114D4603199df73e7D157787f8778E21fCd13066] = true; - //emit UpdateWrapper(0x114D4603199df73e7D157787f8778E21fCd13066, true); + //emit UpdatedWrapper(0x114D4603199df73e7D157787f8778E21fCd13066, true); /// @dev - Set necessary interfaces supportsInterface[iERC165.supportsInterface.selector] = true; @@ -100,45 +91,37 @@ contract CCIP2ETH is iCCIP2ETH { function updateGatewayManager(address _gateway) external { require(msg.sender == gateway.owner(), "ONLY_DEV"); require(msg.sender == iGatewayManager(_gateway).owner(), "BAD_GATEWAY"); - emit UpdateGatewayManager(address(gateway), _gateway); + emit UpdatedGatewayManager(address(gateway), _gateway); gateway = iGatewayManager(_gateway); } /** - * @dev Sets recordhash for a node - * Note - Only ENS owner or manager can call - * @param _node - Namehash of ENS domain - * @param _recordhash - Contenthash to set as recordhash - */ - function setRecordhash(bytes32 _node, bytes calldata _recordhash) external { - address _owner = ENS.owner(_node); - if (isWrapper[_owner]) { - _owner = iToken(_owner).ownerOf(uint256(_node)); - } - require(msg.sender == _owner || isApprovedSigner[_owner][_node][msg.sender], "NOT_AUTHORIZED"); - recordhash[_node] = _recordhash; - emit RecordhashChanged(msg.sender, _node, _recordhash); - } - - /** - * @dev Sets recordhash for a level 1 sub.domain.eth of a node + * @dev Sets recordhash for a node (and subdomains, if provided) * Note - Only ENS owner or manager can call - * @param _subdomain - Level 1 Subdomain label + * @param _subdomains - Array of level subdomain labels; can be empty for domain.eth + * Note - a.b.c.domain.eth = [a, b, c] * @param _node - Namehash of ENS domain * @param _recordhash - Contenthash to set as recordhash */ - function setSubRecordhash(string calldata _subdomain, bytes32 _node, bytes calldata _recordhash) external { - address _owner = ENS.owner(_node); - if (isWrapper[_owner]) { - _owner = iToken(_owner).ownerOf(uint256(_node)); - } - if (msg.sender == _owner || isApprovedSigner[_owner][_node][msg.sender]) { - bytes32 _namehash = keccak256(abi.encodePacked(_node, keccak256(bytes(_subdomain)))); - recordhash[_namehash] = _recordhash; - emit RecordhashChanged(msg.sender, _namehash, _recordhash); + function setRecordhash(string[] calldata _subdomains, bytes32 _node, bytes calldata _recordhash) external { + bytes32 _namehash = _node; + if (_node == bytes32(0)) { + ownerhash[msg.sender] = _recordhash; } else { - revert NotAuthorized(_node, msg.sender); + address _owner = ENS.owner(_node); + if (isWrapper[_owner]) { + _owner = iToken(_owner).ownerOf(uint256(_node)); + } + require(msg.sender == _owner || isApprovedSigner[_owner][_node][msg.sender], "NOT_AUTHORIZED"); + uint256 len = _subdomains.length; + unchecked { + while (len > 0) { + _namehash = keccak256(abi.encodePacked(_namehash, keccak256(bytes(_subdomains[--len])))); + } + } + recordhash[_namehash] = _recordhash; } + emit RecordhashChanged(msg.sender, _namehash, _recordhash); } /** @@ -179,11 +162,14 @@ contract CCIP2ETH is iCCIP2ETH { _recordhash = recordhash[_namehash]; } } + address _owner = ENS.owner(_node); if (_recordhash.length == 0) { - revert("RECORD_NOT_SET"); + if (ownerhash[_owner].length == 0) { + revert("RECORD_NOT_SET"); + } + _recordhash = ownerhash[_owner]; } string memory _recType = gateway.funcToJson(request); // Filename for the requested record - address _owner = ENS.owner(_node); // Update ownership if domain is wrapped if (isWrapper[_owner]) { _owner = iToken(_owner).ownerOf(uint256(_node)); @@ -203,6 +189,45 @@ contract CCIP2ETH is iCCIP2ETH { } } + /** + * @dev Redirects the CCIP-Read request + * @param _encoded - ENS domain to resolve; must be DNS encoded + * @param _requested - Originally requested encoding-specific function to resolve + * @return _selector - Redirected function selector + * @return _namehash - Redirected namehash + * @return _redirectRequest - Redirected request + * @return domain - String-formatted ENS domain + */ + function redirectApplicationService(bytes calldata _encoded, bytes calldata _requested) + external + view + returns (bytes4 _selector, bytes32 _namehash, bytes memory _redirectRequest, string memory domain) + { + uint256 index = 1; + uint256 n = 1; + uint256 len = uint8(bytes1(_encoded[:1])); + bytes[] memory _labels = new bytes[](42); + _labels[0] = _encoded[1:n += len]; + domain = string(_labels[0]); + while (_encoded[n] > 0x0) { + len = uint8(bytes1(_encoded[n:++n])); + _labels[index] = _encoded[n:n += len]; + domain = string.concat(domain, ".", string(_labels[index])); + } + bytes32 _owned; + _namehash = keccak256(abi.encodePacked(bytes32(0), keccak256(_labels[--index]))); + while (index > 0) { + _namehash = keccak256(abi.encodePacked(_namehash, keccak256(_labels[--index]))); + if (ENS.recordExists(_namehash)) { + _owned = _namehash; + } + } + require(_owned != bytes32(0), "NOT_REGISTERED"); + _selector = bytes4(_requested[:4]); + _redirectRequest = abi.encodePacked(_selector, _namehash, _requested.length > 36 ? _requested[36:] : bytes("")); + _namehash = _owned; + } + /** * @dev Checks for manager access to an ENS domain for record management * @param _owner - Owner of ENS domain @@ -221,8 +246,8 @@ contract CCIP2ETH is iCCIP2ETH { ) public view returns (bool) { address _Signer = iCCIP2ETH(this).getSigner( string.concat( - "Requesting Signature To Approve ENS Records Signer\n", - "\nENS Domain: ", + "Requesting Signature To Approve Records Signer\n", + "\nDomain: ", _domain, "\nApproved Signer: eip155:1:", gateway.toChecksumAddress(_approvedSigner), @@ -289,17 +314,69 @@ contract CCIP2ETH is iCCIP2ETH { } if (_type == iCallbackType.signedRecord.selector) { signRequest = string.concat( - "Requesting Signature To Update ENS Record\n", - "\nENS Domain: ", + "Requesting Signature To Update Record\n", + "\nDomain: ", _domain, - "\nRecord Type: ", + "\nType: ", _recType, "\nExtradata: 0x", gateway.bytesToHexString(abi.encodePacked(keccak256(result)), 0), - "\nSigned By: eip155:1:", + "\nSigner: eip155:1:", gateway.toChecksumAddress(_signer) ); require(_signer == iCCIP2ETH(this).getSigner(signRequest, _recordSignature), "BAD_SIGNED_RECORD"); + } else if (_type == iCallbackType.signedRedirect.selector) { + if (result[0] == 0x0) { + signRequest = string.concat( + "Requesting Signature To Redirect ENS Records\n", + "\nENS Domain: ", + _domain, // .domain.eth + "\nExtradata: ", + gateway.bytesToHexString(abi.encodePacked(keccak256(result)), 0), + "\nSigned By: eip155:1:", + gateway.toChecksumAddress(_signer) + ); + require(_signer == iCCIP2ETH(this).getSigner(signRequest, _recordSignature), "BAD_DAPP_SIGNATURE"); + // Signed IPFS redirect + revert OffchainLookup( + address(this), + gateway.randomGateways( + abi.decode(result, (bytes)), // ABI-decode as recordhash to redirect + string.concat("/.well-known/", _path, "/", _recType), + uint256(_checkHash) + ), + abi.encodePacked(uint16(block.timestamp / 60)), + gateway.__fallback.selector, // Fallback; 2nd Callback + abi.encode(_node, block.number - 1, _namehash, _checkHash, _domain, _path, _request) + ); + } + // ENS dApp redirect + // Result should be DNS encoded; result should NOT be ABI-encoded + // Note Last byte is 0x00, meaning end of DNS-encoded stream + require(result[result.length - 1] == 0x0, "BAD_ENS_ENCODED"); + (bytes4 _sig, bytes32 _redirectNamehash, bytes memory _redirectRequest, string memory _redirectDomain) = + CCIP2ETH(this).redirectApplicationService(result, _request); + signRequest = string.concat( + "Requesting Signature To Install dApp Service\n", + "\nDomain: ", + _domain, // e.g. ens.domain.eth + "\ndApp: ", + _redirectDomain, // e.g. app.ens.eth + "\nSigner: eip155:1:", + gateway.toChecksumAddress(_signer) + ); + require(_signer == iCCIP2ETH(this).getSigner(signRequest, _recordSignature), "BAD_DAPP_SIGNATURE"); + address _resolver = ENS.resolver(_redirectNamehash); // Owned node + if (iERC165(_resolver).supportsInterface(iENSIP10.resolve.selector)) { + return iENSIP10(_resolver).resolve(result, _redirectRequest); + } else if (iERC165(_resolver).supportsInterface(_sig)) { + bool ok; + (ok, result) = _resolver.staticcall(_redirectRequest); + require(ok, "BAD_RESOLVER_TYPE"); + require(result.length > 32 || bytes32(result) > bytes32(0), "RECORD_NOT_SET"); + } else { + revert("BAD_RESOLVER_FUNCTION"); + } } else { _namehash; return gateway.__fallback(response, extradata); @@ -354,7 +431,7 @@ contract CCIP2ETH is iCCIP2ETH { */ function approve(bytes32 _node, address _signer, bool _approval) external { isApprovedSigner[msg.sender][_node][_signer] = _approval; - emit Approved(msg.sender, _node, _signer, _approval); + emit ApprovedSigner(msg.sender, _node, _signer, _approval); } /** @@ -369,7 +446,7 @@ contract CCIP2ETH is iCCIP2ETH { require(len == _approval.length, "BAD_LENGTH"); for (uint256 i = 0; i < len; i++) { isApprovedSigner[msg.sender][_node[i]][_signer[i]] = _approval[i]; - emit Approved(msg.sender, _node[i], _signer[i], _approval[i]); + emit ApprovedSigner(msg.sender, _node[i], _signer[i], _approval[i]); } } @@ -394,19 +471,19 @@ contract CCIP2ETH is iCCIP2ETH { function updateSupportedInterface(bytes4 _sig, bool _set) external { require(msg.sender == gateway.owner(), "ONLY_DEV"); supportsInterface[_sig] = _set; - emit UpdateSupportedInterface(_sig, _set); + emit UpdatedSupportedInterface(_sig, _set); } /** - * @dev Add or remove wrapper - * @param _addr - Address of wrapper - * @param _set - State to set for wrapper + * @dev Add or remove ENS wrapper + * @param _addr - Address of ENS wrapper + * @param _set - State to set for new ENS wrapper */ function updateWrapper(address _addr, bool _set) external { require(msg.sender == gateway.owner(), "ONLY_DEV"); require(!_set || _addr.code.length > 0, "ONLY_CONTRACT"); isWrapper[_addr] = _set; - emit UpdateWrapper(_addr, _set); + emit UpdatedWrapper(_addr, _set); } /** diff --git a/src/GatewayManager.sol b/src/GatewayManager.sol index ced33d7..8acf8d9 100644 --- a/src/GatewayManager.sol +++ b/src/GatewayManager.sol @@ -47,7 +47,7 @@ contract GatewayManager is iERC173, iGatewayManager { funcMap[iResolver.pubkey.selector] = "pubkey"; funcMap[iResolver.name.selector] = "name"; funcMap[iResolver.contenthash.selector] = "contenthash"; - funcMap[iResolver.zonehash.selector] = "dns/zone"; + funcMap[iResolver.zonehash.selector] = "dns/zonehash"; /// @dev - Set initial list of secondary gateways Gateways.push("dweb.link"); emit AddGateway("dweb.link"); @@ -315,8 +315,4 @@ contract GatewayManager is iERC173, iGatewayManager { function safeWithdraw(address _token, uint256 _id) external { iToken(_token).safeTransferFrom(THIS, owner, _id); } - - function chunk(bytes calldata _b, uint256 _start, uint256 _end) external pure returns (bytes memory) { - return _b[_start:(_end > _start ? _end : _b.length)]; - } } diff --git a/src/Interface.sol b/src/Interface.sol index 7e06867..4d8d281 100644 --- a/src/Interface.sol +++ b/src/Interface.sol @@ -36,7 +36,7 @@ interface iCCIP2ETH is iENSIP10 { external view returns (address _signer); - function setRecordhash(bytes32 _node, bytes calldata _contenthash) external; + function setRecordhash(string[] memory _subdomains, bytes32 _node, bytes calldata _contenthash) external; function recordhash(bytes32 _node) external view returns (bytes memory _contenthash); } @@ -54,7 +54,6 @@ interface iGatewayManager is iERC173 { external view returns (bytes memory result); - function chunk(bytes calldata _b, uint256 _start, uint256 _end) external pure returns (bytes memory); function addFuncMap(bytes4 _func, string calldata _name) external; function addGateway(string calldata _domain) external; function removeGateway(uint256 _index) external; @@ -98,4 +97,11 @@ interface iCallbackType { bytes memory approvedSignature, // bytes1(..) IF signer is owner or on-chain manager bytes memory result // ABI-encoded result ) external view returns (bytes memory); + + function signedRedirect( + address recordSigner, // Owner OR On-chain Manager OR Off-chain Manager + bytes memory recordSignature, // Signature from signer for redirect value + bytes memory approvedSignature, // bytes1(..) IF signer is owner or on-chain manager + bytes memory redirect // ABI-encoded recordhash OR DNS-encoded domain.eth to redirect + ) external view returns (bytes memory); } diff --git a/test/CCIP2ETH.t.sol b/test/CCIP2ETH.t.sol index 4750f99..b9d5a66 100644 --- a/test/CCIP2ETH.t.sol +++ b/test/CCIP2ETH.t.sol @@ -52,6 +52,7 @@ contract CCIP2ETHTest is Test { /// @dev Test CCIP-Read call for a domain function test2_ResolveLevel2() public { + string[] memory _subdomains = new string[](0); bytes[] memory _name = new bytes[](2); _name[0] = "ccip2"; _name[1] = "eth"; @@ -62,7 +63,7 @@ contract CCIP2ETHTest is Test { vm.prank(_addr); //ENS.setOwner(_namehash, address(this)); //ENS.setResolver(_namehash, address(ccip2eth)); - ccip2eth.setRecordhash(_namehash, _recordhash); + ccip2eth.setRecordhash(_subdomains, _namehash, _recordhash); (string memory _path, string memory _domain) = utils.Format(_encoded); bytes memory _request = abi.encodePacked(iResolver.addr.selector, _namehash); string memory _recType = gateway.funcToJson(_request); @@ -86,6 +87,7 @@ contract CCIP2ETHTest is Test { /// @dev Test subdomain-level CCIP-Read call function test3_ResolveLevel3() public { + string[] memory _subdomains = new string[](0); bytes[] memory _name = new bytes[](3); _name[0] = "blog"; _name[1] = "vitalik"; @@ -96,7 +98,7 @@ contract CCIP2ETHTest is Test { ENS.setOwner(_namehash, address(this)); bytes memory _recordhash = hex"e50101720024080112203c5aba6c9b5055a5fa12281c486188ed8ae2b6ef394b3d981b00d17a4b51735c"; - ccip2eth.setRecordhash(_namehash, _recordhash); + ccip2eth.setRecordhash(_subdomains, _namehash, _recordhash); (string memory _path, string memory _domain) = utils.Format(_encoded); bytes memory _request = abi.encodePacked(iResolver.text.selector, _namehash, abi.encode(string("avatar"))); string memory _recType = gateway.funcToJson(_request); @@ -122,6 +124,7 @@ contract CCIP2ETHTest is Test { /// @dev Test deep CCIP-Read call function test4_ResolveLevel7() public { + string[] memory _subdomains = new string[](0); bytes[] memory _base = new bytes[](2); _base[0] = "vitalik"; _base[1] = "eth"; @@ -131,7 +134,7 @@ contract CCIP2ETHTest is Test { ENS.setOwner(_baseNode, address(this)); // Owner records set at level 2 only bytes memory _recordhash = hex"e50101720024080112203c5aba6c9b5055a5fa12281c486188ed8ae2b6ef394b3d981b00d17a4b51735c"; - ccip2eth.setRecordhash(_baseNode, _recordhash); + ccip2eth.setRecordhash(_subdomains, _baseNode, _recordhash); bytes[] memory _name = new bytes[](7); _name[0] = "never"; @@ -168,6 +171,7 @@ contract CCIP2ETHTest is Test { /// @dev CCIP end-to-end test with on-chain signer function test5_CCIPCallbackApprovedOnChain() public { + string[] memory _subdomains = new string[](0); bytes[] memory _name = new bytes[](2); _name[0] = "domain"; _name[1] = "eth"; @@ -181,7 +185,7 @@ contract CCIP2ETHTest is Test { ccip2eth.approve(_node, _signer, true); bytes memory _recordhash = hex"e50101720024080112203c5aba6c9b5055a5fa12281c486188ed8ae2b6ef394b3d981b00d17a4b51735c"; - ccip2eth.setRecordhash(_node, _recordhash); + ccip2eth.setRecordhash(_subdomains, _node, _recordhash); (string memory _path, string memory _domain) = utils.Format(_encoded); bytes memory _request = abi.encodePacked(iResolver.addr.selector, _node); @@ -207,13 +211,13 @@ contract CCIP2ETHTest is Test { ccip2eth.resolve(_encoded, _request); bytes memory _result = abi.encode(address(this)); string memory signRequest = string.concat( - "Requesting Signature To Update ENS Record\n", - "\nENS Domain: ", + "Requesting Signature To Update Record\n", + "\nDomain: ", _domain, - "\nRecord Type: address/60", + "\nType: address/60", "\nExtradata: 0x", gateway.bytesToHexString(abi.encodePacked(keccak256(_result)), 0), - "\nSigned By: eip155:1:", + "\nSigner: eip155:1:", gateway.toChecksumAddress(address(_signer)) ); bytes32 _digest = keccak256( @@ -232,6 +236,7 @@ contract CCIP2ETHTest is Test { /// @dev CCIP end-to-end test with off-chain signer (with fake parameters) function test6_CCIPCallbackApprovedOffChain() public { + string[] memory _subdomains = new string[](0); bytes[] memory _name = new bytes[](2); _name[0] = "domain"; _name[1] = "eth"; @@ -246,7 +251,7 @@ contract CCIP2ETHTest is Test { bytes memory _recordhash = hex"e50101720024080112203c5aba6c9b5055a5fa12281c486188ed8ae2b6ef394b3d981b00d17a4b51735c"; vm.prank(_owner); - ccip2eth.setRecordhash(_node, _recordhash); + ccip2eth.setRecordhash(_subdomains, _node, _recordhash); (string memory _path, string memory _domain) = utils.Format(_encoded); bytes memory _request = abi.encodePacked(iResolver.addr.selector, _node); @@ -270,13 +275,13 @@ contract CCIP2ETHTest is Test { ccip2eth.resolve(_encoded, _request); bytes memory _result = abi.encode(address(this)); string memory signRequest = string.concat( - "Requesting Signature To Update ENS Record\n", - "\nENS Domain: ", + "Requesting Signature To Update Record\n", + "\nDomain: ", _domain, - "\nRecord Type: address/60", + "\nType: address/60", "\nExtradata: 0x", gateway.bytesToHexString(abi.encodePacked(keccak256(_result)), 0), - "\nSigned By: eip155:1:", + "\nSigner: eip155:1:", gateway.toChecksumAddress(address(_signer)) ); bytes32 _digest = keccak256( @@ -289,8 +294,8 @@ contract CCIP2ETHTest is Test { (uint8 v, bytes32 r, bytes32 s) = vm.sign(SignerKey, _digest); bytes memory _recordSig = abi.encodePacked(r, s, v); signRequest = string.concat( - "Requesting Signature To Approve ENS Records Signer\n", - "\nENS Domain: domain.eth", + "Requesting Signature To Approve Records Signer\n", + "\nDomain: domain.eth", "\nApproved Signer: eip155:1:", gateway.toChecksumAddress(_signer), "\nOwner: eip155:1:", @@ -309,8 +314,8 @@ contract CCIP2ETHTest is Test { } /// @dev CCIP end-to-end with off-chain signer and real parameters - /// Note Recordhash only function test7_CCIPCallbackApprovedOffChain_WithRealParameters() public { + string[] memory _subdomains = new string[](0); bytes[] memory _name = new bytes[](2); _name[0] = "00081"; _name[1] = "eth"; @@ -325,7 +330,7 @@ contract CCIP2ETHTest is Test { bytes memory _recordhash = hex"e501017200240801122008dd085b86d16226791544f4628c4efc0936c69221fef17dfac843d9713233bb"; vm.prank(_owner); - ccip2eth.setRecordhash(_node, _recordhash); // Set recordhash only + ccip2eth.setRecordhash(_subdomains, _node, _recordhash); // Set recordhash (string memory _path, string memory _domain) = utils.Format(_encoded); bytes memory _request = abi.encodePacked(iResolver.addr.selector, _node); @@ -349,13 +354,13 @@ contract CCIP2ETHTest is Test { ccip2eth.resolve(_encoded, _request); bytes memory _result = abi.encode(address(0x1111000000000000000000000000000000000001)); string memory signRequest = string.concat( - "Requesting Signature To Update ENS Record\n", - "\nENS Domain: ", + "Requesting Signature To Update Record\n", + "\nDomain: ", _domain, - "\nRecord Type: address/60", + "\nType: address/60", "\nExtradata: 0x", gateway.bytesToHexString(abi.encodePacked(keccak256(_result)), 0), - "\nSigned By: eip155:1:", + "\nSigner: eip155:1:", gateway.toChecksumAddress(address(_signer)) ); bytes32 _digest = keccak256( @@ -368,8 +373,8 @@ contract CCIP2ETHTest is Test { (uint8 v, bytes32 r, bytes32 s) = vm.sign(SignerKey, _digest); bytes memory _recordSig = abi.encodePacked(r, s, v); signRequest = string.concat( - "Requesting Signature To Approve ENS Records Signer\n", - "\nENS Domain: ", + "Requesting Signature To Approve Records Signer\n", + "\nDomain: ", _domain, "\nApproved Signer: eip155:1:", gateway.toChecksumAddress(_signer), @@ -387,6 +392,26 @@ contract CCIP2ETHTest is Test { abi.encodeWithSelector(iCallbackType.signedRecord.selector, _signer, _recordSig, _approvedSig, _result); assertEq(_result, ccip2eth.__callback(_response, _extraData)); } + + /// @dev Test setting deep recordhash + function test8_setDeepRecordhash() public { + string[] memory _subdomains = new string[](2); + _subdomains[0] = "hello"; + _subdomains[1] = "world"; + bytes[] memory _name = new bytes[](2); + _name[0] = "domain"; + _name[1] = "eth"; + (bytes32 _node, bytes memory _encoded) = utils.Encode(_name); + + uint256 OwnerKey = 0xdddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd; + address _owner = vm.addr(OwnerKey); + vm.prank(ENS.owner(_node)); + ENS.setOwner(_node, _owner); + bytes memory _recordhash = + hex"e501017200240801122008dd085b86d16226791544f4628c4efc0936c69221fef17dfac843d9713233bb"; + vm.prank(_owner); + ccip2eth.setRecordhash(_subdomains, _node, _recordhash); // Set recordhash for 'hello.world.domain.eth' + } } /// @dev Utility functions @@ -419,7 +444,7 @@ contract Utils { } } - function chunk(bytes calldata _b, uint256 _start, uint256 _end) external pure returns (bytes memory) { + function getBytes(bytes calldata _b, uint256 _start, uint256 _end) external pure returns (bytes memory) { return _b[_start:_end == 0 ? _b.length : _end]; } }