From 89ad8ed25da8b3982975a78a69bbf5b67d89fc48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Tue, 20 Feb 2024 11:36:55 +0100 Subject: [PATCH 01/16] concatenate multiple text fields --- .../dnsregistrar/OffchainDNSResolver.sol | 29 ++++- .../mocks/DummyExtendedDNSSECResolver2.sol | 103 ++++++++++++++++++ test/dnsregistrar/TestOffchainDNSResolver.js | 84 +++++++++++++- 3 files changed, 209 insertions(+), 7 deletions(-) create mode 100644 contracts/dnsregistrar/mocks/DummyExtendedDNSSECResolver2.sol diff --git a/contracts/dnsregistrar/OffchainDNSResolver.sol b/contracts/dnsregistrar/OffchainDNSResolver.sol index dd39a6ac..abbb4869 100644 --- a/contracts/dnsregistrar/OffchainDNSResolver.sol +++ b/contracts/dnsregistrar/OffchainDNSResolver.sol @@ -87,7 +87,7 @@ contract OffchainDNSResolver is IExtendedResolver, IERC165 { // Ignore records with wrong name, type, or class bytes memory rrname = RRUtils.readName(iter.data, iter.offset); if ( - !rrname.equals(name) || + !rrname.equals(stripWildcard(name)) || iter.class != CLASS_INET || iter.dnstype != TYPE_TXT ) { @@ -168,6 +168,19 @@ contract OffchainDNSResolver is IExtendedResolver, IERC165 { ); } + function stripWildcard( + bytes memory name + ) public pure returns (bytes memory) { + if (name.length > 4 && name[0] == "*" && name[1] == ".") { + bytes memory strippedName = new bytes(name.length - 2); + for (uint i = 2; i < name.length; i++) { + strippedName[i - 2] = name[i]; + } + return strippedName; + } + return name; + } + function parseRR( bytes memory data, uint256 idx, @@ -199,10 +212,16 @@ contract OffchainDNSResolver is IExtendedResolver, IERC165 { uint256 startIdx, uint256 lastIdx ) internal pure returns (bytes memory) { - // TODO: Concatenate multiple text fields - uint256 fieldLength = data.readUint8(startIdx); - assert(startIdx + fieldLength < lastIdx); - return data.substring(startIdx + 1, fieldLength); + bytes memory result = new bytes(0); + uint256 idx = startIdx; + while (idx < lastIdx) { + uint256 fieldLength = data.readUint8(idx); + assert(idx + fieldLength + 1 <= lastIdx); + bytes memory field = data.substring(idx + 1, fieldLength); + result = abi.encodePacked(result, field); + idx += fieldLength + 1; + } + return result; } function parseAndResolve( diff --git a/contracts/dnsregistrar/mocks/DummyExtendedDNSSECResolver2.sol b/contracts/dnsregistrar/mocks/DummyExtendedDNSSECResolver2.sol new file mode 100644 index 00000000..e6136ee5 --- /dev/null +++ b/contracts/dnsregistrar/mocks/DummyExtendedDNSSECResolver2.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.4; + +import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import "../../resolvers/profiles/IExtendedDNSResolver.sol"; +import "../../resolvers/profiles/IAddressResolver.sol"; +import "../../resolvers/profiles/IAddrResolver.sol"; +import "../../resolvers/profiles/ITextResolver.sol"; +import "../../utils/HexUtils.sol"; + +contract DummyExtendedDNSSECResolver2 is IExtendedDNSResolver, IERC165 { + using HexUtils for *; + + uint256 private constant COIN_TYPE_ETH = 60; + uint256 private constant ADDRESS_LENGTH = 40; + + error NotImplemented(); + error InvalidAddressFormat(); + + function supportsInterface( + bytes4 interfaceId + ) external view virtual override returns (bool) { + return interfaceId == type(IExtendedDNSResolver).interfaceId; + } + + function resolve( + bytes calldata /* name */, + bytes calldata data, + bytes calldata context + ) external pure override returns (bytes memory) { + bytes4 selector = bytes4(data); + if ( + selector == IAddrResolver.addr.selector || + selector == IAddressResolver.addr.selector + ) { + // Parse address from context + bytes memory addrBytes = _parseAddressFromContext(context); + return abi.encode(address(uint160(uint256(bytes32(addrBytes))))); + } else if (selector == ITextResolver.text.selector) { + // Parse text value from context + (, string memory key) = abi.decode(data[4:], (bytes32, string)); + string memory value = _parseTextFromContext(context, key); + return abi.encode(value); + } + revert NotImplemented(); + } + + function _parseAddressFromContext( + bytes memory context + ) internal pure returns (bytes memory) { + // Parse address from concatenated context + for (uint256 i = 0; i < context.length - ADDRESS_LENGTH + 2; i++) { + if (context[i] == "0" && context[i + 1] == "x") { + bytes memory candidate = new bytes(ADDRESS_LENGTH); + for (uint256 j = 0; j < ADDRESS_LENGTH; j++) { + candidate[j] = context[i + j + 2]; + } + + (address candidateAddr, bool valid) = candidate.hexToAddress( + 0, + ADDRESS_LENGTH + ); + if (valid) { + return abi.encode(candidateAddr); + } + } + } + revert InvalidAddressFormat(); + } + + function _parseTextFromContext( + bytes calldata context, + string memory key + ) internal pure returns (string memory) { + // Parse key-value pairs from concatenated context + string memory value = ""; + bool foundKey = false; + for (uint256 i = 0; i < context.length; i++) { + if (foundKey && context[i] == "=") { + i++; + while (i < context.length && context[i] != " ") { + string memory charStr = string( + abi.encodePacked(bytes1(context[i])) + ); + value = string(abi.encodePacked(value, charStr)); + i++; + } + return value; + } + if (!foundKey && bytes(key)[0] == context[i]) { + bool isMatch = true; + for (uint256 j = 1; j < bytes(key).length; j++) { + if (context[i + j] != bytes(key)[j]) { + isMatch = false; + break; + } + } + foundKey = isMatch; + } + } + return ""; + } +} diff --git a/test/dnsregistrar/TestOffchainDNSResolver.js b/test/dnsregistrar/TestOffchainDNSResolver.js index 3c325d4f..4be9a9ca 100644 --- a/test/dnsregistrar/TestOffchainDNSResolver.js +++ b/test/dnsregistrar/TestOffchainDNSResolver.js @@ -11,6 +11,11 @@ const PublicResolver = artifacts.require('./PublicResolver.sol') const DummyExtendedDNSSECResolver = artifacts.require( './DummyExtendedDNSSECResolver.sol', ) + +const DummyExtendedDNSSECResolver2 = artifacts.require( + './DummyExtendedDNSSECResolver2.sol', +) + const DummyLegacyTextResolver = artifacts.require( './DummyLegacyTextResolver.sol', ) @@ -148,8 +153,8 @@ contract('OffchainDNSResolver', function (accounts) { ) const dnsName = utils.hexEncodeName(name) const extraData = ethers.utils.defaultAbiCoder.encode( - ['bytes', 'bytes', 'bytes4'], - [dnsName, callData, '0x00000000'], + ['bytes', 'bytes'], + [dnsName, callData], ) return offchainDNSResolver.resolveCallback(response, extraData) } @@ -456,4 +461,79 @@ contract('OffchainDNSResolver', function (accounts) { doDNSResolveCallback(name, [`ENS1 ${dummyResolver.address}`], callData), ).to.be.revertedWith('InvalidOperation') }) + + it('should correctly concatenate multiple texts in the TXT record and resolve', async function () { + const COIN_TYPE_ETH = 60 + const name = 'test.test' + const testAddress = '0xfefeFEFeFEFEFEFEFeFefefefefeFEfEfefefEfe' + const resolver = await DummyExtendedDNSSECResolver2.new() + const pr = await PublicResolver.at(resolver.address) + const callDataAddr = pr.contract.methods['addr(bytes32,uint256)']( + namehash.hash(name), + COIN_TYPE_ETH, + ).encodeABI() + const resultAddr = await doDNSResolveCallback( + name, + [`ENS1 ${resolver.address} ${testAddress} smth=smth.eth`], + callDataAddr, + ) + expect( + ethers.utils.defaultAbiCoder.decode(['address'], resultAddr)[0], + ).to.equal(testAddress) + + const callDataText = pr.contract.methods['text(bytes32,string)']( + namehash.hash(name), + 'smth', + ).encodeABI() + const resultText = await doDNSResolveCallback( + name, + [`ENS1 ${resolver.address} ${testAddress} smth=smth.eth`], + callDataText, + ) + + expect( + ethers.utils.defaultAbiCoder.decode(['string'], resultText)[0], + ).to.equal('smth.eth') + }) + + it('should correctly do text resolution regardless of order', async function () { + const name = 'test.test' + const testAddress = '0xfefeFEFeFEFEFEFEFeFefefefefeFEfEfefefEfe' + const resolver = await DummyExtendedDNSSECResolver2.new() + const pr = await PublicResolver.at(resolver.address) + + const callDataText = pr.contract.methods['text(bytes32,string)']( + namehash.hash(name), + 'smth', + ).encodeABI() + const resultText = await doDNSResolveCallback( + name, + [`ENS1 ${resolver.address} smth=smth.eth ${testAddress}`], + callDataText, + ) + + expect( + ethers.utils.defaultAbiCoder.decode(['string'], resultText)[0], + ).to.equal('smth.eth') + }) + + it('should correctly do text resolution regardless of key-value pair amount', async function () { + const name = 'test.test' + const resolver = await DummyExtendedDNSSECResolver2.new() + const pr = await PublicResolver.at(resolver.address) + + const callDataText = pr.contract.methods['text(bytes32,string)']( + namehash.hash(name), + 'bla', + ).encodeABI() + const resultText = await doDNSResolveCallback( + name, + [`ENS1 ${resolver.address} smth=smth.eth bla=bla.eth`], + callDataText, + ) + + expect( + ethers.utils.defaultAbiCoder.decode(['string'], resultText)[0], + ).to.equal('bla.eth') + }) }) From 288087982d1af9b4e1a78646a7a5f6901b1c01ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Tue, 20 Feb 2024 12:48:14 +0100 Subject: [PATCH 02/16] update wildcard check without copying name, concatenate text in O(n) --- .../dnsregistrar/OffchainDNSResolver.sol | 36 +++++++++++-------- contracts/dnssec-oracle/BytesUtils.sol | 21 +++++++++++ 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/contracts/dnsregistrar/OffchainDNSResolver.sol b/contracts/dnsregistrar/OffchainDNSResolver.sol index abbb4869..bfd071f4 100644 --- a/contracts/dnsregistrar/OffchainDNSResolver.sol +++ b/contracts/dnsregistrar/OffchainDNSResolver.sol @@ -86,8 +86,13 @@ contract OffchainDNSResolver is IExtendedResolver, IERC165 { ) { // Ignore records with wrong name, type, or class bytes memory rrname = RRUtils.readName(iter.data, iter.offset); + uint256 nameOffset = 0; + if (checkWildcard(name)) { + nameOffset = 2; + } + if ( - !rrname.equals(stripWildcard(name)) || + !name.equals(nameOffset, rrname, 0, name.length - nameOffset) || iter.class != CLASS_INET || iter.dnstype != TYPE_TXT ) { @@ -168,17 +173,10 @@ contract OffchainDNSResolver is IExtendedResolver, IERC165 { ); } - function stripWildcard( + function checkWildcard( bytes memory name - ) public pure returns (bytes memory) { - if (name.length > 4 && name[0] == "*" && name[1] == ".") { - bytes memory strippedName = new bytes(name.length - 2); - for (uint i = 2; i < name.length; i++) { - strippedName[i - 2] = name[i]; - } - return strippedName; - } - return name; + ) public pure returns (bool isWildcard) { + return name.length > 4 && uint8(name[0]) == 1 && name[1] == "*"; } function parseRR( @@ -212,13 +210,21 @@ contract OffchainDNSResolver is IExtendedResolver, IERC165 { uint256 startIdx, uint256 lastIdx ) internal pure returns (bytes memory) { - bytes memory result = new bytes(0); + uint256 totalLength = 0; uint256 idx = startIdx; while (idx < lastIdx) { uint256 fieldLength = data.readUint8(idx); - assert(idx + fieldLength + 1 <= lastIdx); - bytes memory field = data.substring(idx + 1, fieldLength); - result = abi.encodePacked(result, field); + totalLength += fieldLength; + idx += fieldLength + 1; + } + + bytes memory result = new bytes(totalLength); + idx = startIdx; + uint256 resultIdx = 0; + while (idx < lastIdx) { + uint256 fieldLength = data.readUint8(idx); + result.strcpy(resultIdx, data, idx + 1, fieldLength); + resultIdx += fieldLength; idx += fieldLength + 1; } return result; diff --git a/contracts/dnssec-oracle/BytesUtils.sol b/contracts/dnssec-oracle/BytesUtils.sol index 96344ce5..47eed5b4 100644 --- a/contracts/dnssec-oracle/BytesUtils.sol +++ b/contracts/dnssec-oracle/BytesUtils.sol @@ -291,6 +291,27 @@ library BytesUtils { } } + function strcpy( + bytes memory self, + uint256 selfOffset, + bytes memory src, + uint256 srcOffset, + uint256 length + ) internal pure { + require(selfOffset + length <= self.length); + require(srcOffset + length <= src.length); + + uint256 selfPtr; + uint256 srcPtr; + + assembly { + selfPtr := add(add(self, 32), selfOffset) + srcPtr := add(add(src, 32), srcOffset) + } + + memcpy(selfPtr, srcPtr, length); + } + /* * @dev Copies a substring into a new byte string. * @param self The byte string to copy from. From 2be5088795aa505bf6a9d7a7b2a2407cfe16eec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Tue, 26 Mar 2024 22:48:56 +0100 Subject: [PATCH 03/16] sync testing with feature/better-offchain-dns --- .../mocks/DummyExtendedDNSSECResolver2.sol | 228 +++++++++++++----- contracts/utils/HexUtils.sol | 24 +- contracts/utils/TestHexUtils.sol | 8 + test/dnsregistrar/TestOffchainDNSResolver.js | 23 +- 4 files changed, 213 insertions(+), 70 deletions(-) diff --git a/contracts/dnsregistrar/mocks/DummyExtendedDNSSECResolver2.sol b/contracts/dnsregistrar/mocks/DummyExtendedDNSSECResolver2.sol index e6136ee5..0f0e348c 100644 --- a/contracts/dnsregistrar/mocks/DummyExtendedDNSSECResolver2.sol +++ b/contracts/dnsregistrar/mocks/DummyExtendedDNSSECResolver2.sol @@ -2,20 +2,23 @@ pragma solidity ^0.8.4; import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import "@openzeppelin/contracts/utils/Strings.sol"; import "../../resolvers/profiles/IExtendedDNSResolver.sol"; import "../../resolvers/profiles/IAddressResolver.sol"; import "../../resolvers/profiles/IAddrResolver.sol"; import "../../resolvers/profiles/ITextResolver.sol"; import "../../utils/HexUtils.sol"; +import "../../dnssec-oracle/BytesUtils.sol"; contract DummyExtendedDNSSECResolver2 is IExtendedDNSResolver, IERC165 { using HexUtils for *; + using BytesUtils for *; + using Strings for *; uint256 private constant COIN_TYPE_ETH = 60; - uint256 private constant ADDRESS_LENGTH = 40; error NotImplemented(); - error InvalidAddressFormat(); + error InvalidAddressFormat(bytes addr); function supportsInterface( bytes4 interfaceId @@ -29,73 +32,188 @@ contract DummyExtendedDNSSECResolver2 is IExtendedDNSResolver, IERC165 { bytes calldata context ) external pure override returns (bytes memory) { bytes4 selector = bytes4(data); - if ( - selector == IAddrResolver.addr.selector || - selector == IAddressResolver.addr.selector - ) { - // Parse address from context - bytes memory addrBytes = _parseAddressFromContext(context); - return abi.encode(address(uint160(uint256(bytes32(addrBytes))))); + if (selector == IAddrResolver.addr.selector) { + return _resolveAddr(context); + } else if (selector == IAddressResolver.addr.selector) { + return _resolveAddress(data, context); } else if (selector == ITextResolver.text.selector) { - // Parse text value from context - (, string memory key) = abi.decode(data[4:], (bytes32, string)); - string memory value = _parseTextFromContext(context, key); - return abi.encode(value); + return _resolveText(data, context); } revert NotImplemented(); } - function _parseAddressFromContext( - bytes memory context + function _resolveAddress( + bytes calldata data, + bytes calldata context ) internal pure returns (bytes memory) { - // Parse address from concatenated context - for (uint256 i = 0; i < context.length - ADDRESS_LENGTH + 2; i++) { - if (context[i] == "0" && context[i + 1] == "x") { - bytes memory candidate = new bytes(ADDRESS_LENGTH); - for (uint256 j = 0; j < ADDRESS_LENGTH; j++) { - candidate[j] = context[i + j + 2]; - } + (, uint256 coinType) = abi.decode(data[4:], (bytes32, uint256)); + bytes memory value; + // Per https://docs.ens.domains/ensip/11#specification + if (coinType & 0x80000000 != 0) { + value = _findValue( + context, + bytes.concat( + "a[e", + bytes((coinType & 0x7fffffff).toString()), + "]=" + ) + ); + } else { + value = _findValue( + context, + bytes.concat("a[", bytes(coinType.toString()), "]=") + ); + } + if (value.length == 0) { + return value; + } + (bytes memory record, bool valid) = value.hexToBytes(2, value.length); + if (!valid) revert InvalidAddressFormat(value); + return record; + } - (address candidateAddr, bool valid) = candidate.hexToAddress( - 0, - ADDRESS_LENGTH - ); - if (valid) { - return abi.encode(candidateAddr); - } - } + function _resolveAddr( + bytes calldata context + ) internal pure returns (bytes memory) { + bytes memory value = _findValue(context, "a[60]="); + if (value.length == 0) { + return value; } - revert InvalidAddressFormat(); + (bytes memory record, bool valid) = value.hexToBytes(2, value.length); + if (!valid) revert InvalidAddressFormat(value); + return record; + } + + function _resolveText( + bytes calldata data, + bytes calldata context + ) internal pure returns (bytes memory) { + (, string memory key) = abi.decode(data[4:], (bytes32, string)); + bytes memory value = _findValue( + context, + bytes.concat("t[", bytes(key), "]=") + ); + return value; } - function _parseTextFromContext( - bytes calldata context, - string memory key - ) internal pure returns (string memory) { - // Parse key-value pairs from concatenated context - string memory value = ""; - bool foundKey = false; - for (uint256 i = 0; i < context.length; i++) { - if (foundKey && context[i] == "=") { - i++; - while (i < context.length && context[i] != " ") { - string memory charStr = string( - abi.encodePacked(bytes1(context[i])) - ); - value = string(abi.encodePacked(value, charStr)); - i++; + uint256 constant STATE_START = 0; + uint256 constant STATE_IGNORED_KEY = 1; + uint256 constant STATE_IGNORED_KEY_ARG = 2; + uint256 constant STATE_VALUE = 3; + uint256 constant STATE_QUOTED_VALUE = 4; + uint256 constant STATE_UNQUOTED_VALUE = 5; + uint256 constant STATE_IGNORED_VALUE = 6; + uint256 constant STATE_IGNORED_QUOTED_VALUE = 7; + uint256 constant STATE_IGNORED_UNQUOTED_VALUE = 8; + + function _findValue( + bytes memory data, + bytes memory key + ) internal pure returns (bytes memory value) { + uint256 state = STATE_START; + uint256 len = data.length; + for (uint256 i = 0; i < len; ) { + if (state == STATE_START) { + if (data.equals(i, key, 0, key.length)) { + i += key.length; + state = STATE_VALUE; + } else { + state = STATE_IGNORED_KEY; } - return value; - } - if (!foundKey && bytes(key)[0] == context[i]) { - bool isMatch = true; - for (uint256 j = 1; j < bytes(key).length; j++) { - if (context[i + j] != bytes(key)[j]) { - isMatch = false; + } else if (state == STATE_IGNORED_KEY) { + for (; i < len; i++) { + if (data[i] == "=") { + state = STATE_IGNORED_VALUE; + i += 1; + break; + } else if (data[i] == "[") { + state = STATE_IGNORED_KEY_ARG; + i += 1; + break; + } + } + } else if (state == STATE_IGNORED_KEY_ARG) { + for (; i < len; i++) { + if (data[i] == "]") { + state = STATE_IGNORED_VALUE; + i += 1; + if (data[i] == "=") { + i += 1; + } + break; + } + } + } else if (state == STATE_VALUE) { + if (data[i] == "'") { + state = STATE_QUOTED_VALUE; + i += 1; + } else { + state = STATE_UNQUOTED_VALUE; + } + } else if (state == STATE_QUOTED_VALUE) { + uint256 start = i; + uint256 valueLen = 0; + bool escaped = false; + for (; i < len; i++) { + if (escaped) { + data[start + valueLen] = data[i]; + valueLen += 1; + escaped = false; + } else { + if (data[i] == "\\") { + escaped = true; + } else if (data[i] == "'") { + return data.substring(start, valueLen); + } else { + data[start + valueLen] = data[i]; + valueLen += 1; + } + } + } + } else if (state == STATE_UNQUOTED_VALUE) { + uint256 start = i; + for (; i < len; i++) { + if (data[i] == " ") { + return data.substring(start, i - start); + } + } + return data.substring(start, len - start); + } else if (state == STATE_IGNORED_VALUE) { + if (data[i] == "'") { + state = STATE_IGNORED_QUOTED_VALUE; + i += 1; + } else { + state = STATE_IGNORED_UNQUOTED_VALUE; + } + } else if (state == STATE_IGNORED_QUOTED_VALUE) { + bool escaped = false; + for (; i < len; i++) { + if (escaped) { + escaped = false; + } else { + if (data[i] == "\\") { + escaped = true; + } else if (data[i] == "'") { + i += 1; + while (data[i] == " ") { + i += 1; + } + state = STATE_START; + break; + } + } + } + } else { + assert(state == STATE_IGNORED_UNQUOTED_VALUE); + for (; i < len; i++) { + if (data[i] == " ") { + while (data[i] == " ") { + i += 1; + } + state = STATE_START; break; } } - foundKey = isMatch; } } return ""; diff --git a/contracts/utils/HexUtils.sol b/contracts/utils/HexUtils.sol index 7cb3a1e0..35ad52d0 100644 --- a/contracts/utils/HexUtils.sol +++ b/contracts/utils/HexUtils.sol @@ -12,11 +12,29 @@ library HexUtils { bytes memory str, uint256 idx, uint256 lastIdx - ) internal pure returns (bytes32 r, bool valid) { + ) internal pure returns (bytes32, bool) { + require(lastIdx - idx <= 64); + (bytes memory r, bool valid) = hexToBytes(str, idx, lastIdx); + if (!valid) { + return (bytes32(0), false); + } + bytes32 ret; + assembly { + ret := shr(mul(4, sub(64, sub(lastIdx, idx))), mload(add(r, 32))) + } + return (ret, true); + } + + function hexToBytes( + bytes memory str, + uint256 idx, + uint256 lastIdx + ) internal pure returns (bytes memory r, bool valid) { uint256 hexLength = lastIdx - idx; - if ((hexLength != 64 && hexLength != 40) || hexLength % 2 == 1) { + if (hexLength % 2 == 1) { revert("Invalid string length"); } + r = new bytes(hexLength / 2); valid = true; assembly { // check that the index to read to is not past the end of the string @@ -58,7 +76,7 @@ library HexUtils { break } let combined := or(shl(4, byte1), byte2) - r := or(shl(8, r), combined) + mstore8(add(add(r, 32), div(sub(i, idx), 2)), combined) } } } diff --git a/contracts/utils/TestHexUtils.sol b/contracts/utils/TestHexUtils.sol index 6dd9088e..b6624f09 100644 --- a/contracts/utils/TestHexUtils.sol +++ b/contracts/utils/TestHexUtils.sol @@ -6,6 +6,14 @@ import {HexUtils} from "./HexUtils.sol"; contract TestHexUtils { using HexUtils for *; + function hexToBytes( + bytes calldata name, + uint256 idx, + uint256 lastInx + ) public pure returns (bytes memory, bool) { + return name.hexToBytes(idx, lastInx); + } + function hexStringToBytes32( bytes calldata name, uint256 idx, diff --git a/test/dnsregistrar/TestOffchainDNSResolver.js b/test/dnsregistrar/TestOffchainDNSResolver.js index 4be9a9ca..996aa641 100644 --- a/test/dnsregistrar/TestOffchainDNSResolver.js +++ b/test/dnsregistrar/TestOffchainDNSResolver.js @@ -468,18 +468,17 @@ contract('OffchainDNSResolver', function (accounts) { const testAddress = '0xfefeFEFeFEFEFEFEFeFefefefefeFEfEfefefEfe' const resolver = await DummyExtendedDNSSECResolver2.new() const pr = await PublicResolver.at(resolver.address) - const callDataAddr = pr.contract.methods['addr(bytes32,uint256)']( + const callDataAddr = pr.contract.methods['addr(bytes32)']( namehash.hash(name), - COIN_TYPE_ETH, ).encodeABI() const resultAddr = await doDNSResolveCallback( name, - [`ENS1 ${resolver.address} ${testAddress} smth=smth.eth`], + [ + `ENS1 ${resolver.address} a[${COIN_TYPE_ETH}]=${testAddress} t[smth]=smth.eth`, + ], callDataAddr, ) - expect( - ethers.utils.defaultAbiCoder.decode(['address'], resultAddr)[0], - ).to.equal(testAddress) + expect(resultAddr).to.equal(testAddress.toLowerCase()) const callDataText = pr.contract.methods['text(bytes32,string)']( namehash.hash(name), @@ -487,12 +486,12 @@ contract('OffchainDNSResolver', function (accounts) { ).encodeABI() const resultText = await doDNSResolveCallback( name, - [`ENS1 ${resolver.address} ${testAddress} smth=smth.eth`], + [`ENS1 ${resolver.address} a[60]=${testAddress} t[smth]=smth.eth`], callDataText, ) expect( - ethers.utils.defaultAbiCoder.decode(['string'], resultText)[0], + ethers.utils.toUtf8String(ethers.utils.arrayify(resultText)), ).to.equal('smth.eth') }) @@ -508,12 +507,12 @@ contract('OffchainDNSResolver', function (accounts) { ).encodeABI() const resultText = await doDNSResolveCallback( name, - [`ENS1 ${resolver.address} smth=smth.eth ${testAddress}`], + [`ENS1 ${resolver.address} t[smth]=smth.eth ${testAddress}`], callDataText, ) expect( - ethers.utils.defaultAbiCoder.decode(['string'], resultText)[0], + ethers.utils.toUtf8String(ethers.utils.arrayify(resultText)), ).to.equal('smth.eth') }) @@ -528,12 +527,12 @@ contract('OffchainDNSResolver', function (accounts) { ).encodeABI() const resultText = await doDNSResolveCallback( name, - [`ENS1 ${resolver.address} smth=smth.eth bla=bla.eth`], + [`ENS1 ${resolver.address} t[smth]=smth.eth t[bla]=bla.eth`], callDataText, ) expect( - ethers.utils.defaultAbiCoder.decode(['string'], resultText)[0], + ethers.utils.toUtf8String(ethers.utils.arrayify(resultText)), ).to.equal('bla.eth') }) }) From e9894f64b21955f26a249cef3a515f4247c8f819 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Wed, 27 Mar 2024 18:40:25 +0200 Subject: [PATCH 04/16] update testhexutils --- test/utils/TestHexUtils.js | 57 ++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/test/utils/TestHexUtils.js b/test/utils/TestHexUtils.js index 5d39ee8e..f45d31bf 100644 --- a/test/utils/TestHexUtils.js +++ b/test/utils/TestHexUtils.js @@ -16,6 +16,46 @@ describe('HexUtils', () => { HexUtils = await HexUtilsFactory.deploy() }) + describe('hexToBytes()', () => { + it('Converts a hex string to bytes', async () => { + let [bytes32, valid] = await HexUtils.hexToBytes( + toUtf8Bytes( + '5cee339e13375638553bdf5a6e36ba80fb9f6a4f0783680884d92b558aa471da', + ), + 0, + 64, + ) + expect(valid).to.equal(true) + expect(bytes32).to.equal( + '0x5cee339e13375638553bdf5a6e36ba80fb9f6a4f0783680884d92b558aa471da', + ) + }) + + it('Handles short strings', async () => { + let [bytes32, valid] = await HexUtils.hexToBytes( + toUtf8Bytes('5cee'), + 0, + 4, + ) + expect(valid).to.equal(true) + expect(bytes32).to.equal('0x5cee') + }) + + it('Handles long strings', async () => { + let [bytes32, valid] = await HexUtils.hexToBytes( + toUtf8Bytes( + '5cee339e13375638553bdf5a6e36ba80fb9f6a4f0783680884d92b558aa471da010203', + ), + 0, + 70, + ) + expect(valid).to.equal(true) + expect(bytes32).to.equal( + '0x5cee339e13375638553bdf5a6e36ba80fb9f6a4f0783680884d92b558aa471da010203', + ) + }) + }) + describe('hexStringToBytes32()', () => { it('Converts a hex string to bytes32', async () => { let [bytes32, valid] = await HexUtils.hexStringToBytes32( @@ -25,10 +65,10 @@ describe('HexUtils', () => { 0, 64, ) + expect(valid).to.equal(true) expect(bytes32).to.equal( '0x5cee339e13375638553bdf5a6e36ba80fb9f6a4f0783680884d92b558aa471da', ) - expect(valid).to.equal(true) }) it('Uses the correct index to read from', async () => { let [bytes32, valid] = await HexUtils.hexStringToBytes32( @@ -38,10 +78,10 @@ describe('HexUtils', () => { 7, 71, ) + expect(valid).to.equal(true) expect(bytes32).to.equal( '0x5cee339e13375638553bdf5a6e36ba80fb9f6a4f0783680884d92b558aa471da', ) - expect(valid).to.equal(true) }) it('Correctly parses all hex characters', async () => { let [bytes32, valid] = await HexUtils.hexStringToBytes32( @@ -49,10 +89,10 @@ describe('HexUtils', () => { 0, 40, ) + expect(valid).to.equal(true) expect(bytes32).to.equal( '0x0000000000000000000000000123456789abcdefabcdef0123456789abcdefab', ) - expect(valid).to.equal(true) }) it('Returns invalid when the string contains non-hex characters', async () => { const [bytes32, valid] = await HexUtils.hexStringToBytes32( @@ -62,8 +102,8 @@ describe('HexUtils', () => { 0, 64, ) - expect(bytes32).to.equal(NULL_HASH) expect(valid).to.equal(false) + expect(bytes32).to.equal(NULL_HASH) }) it('Reverts when the string is too short', async () => { await expect( @@ -87,8 +127,8 @@ describe('HexUtils', () => { 0, 40, ) - expect(address).to.equal('0x5ceE339e13375638553bdF5a6e36BA80fB9f6a4F') expect(valid).to.equal(true) + expect(address).to.equal('0x5ceE339e13375638553bdF5a6e36BA80fB9f6a4F') }) it('Does not allow sizes smaller than 40 characters', async () => { let [address, valid] = await HexUtils.hexToAddress( @@ -98,8 +138,8 @@ describe('HexUtils', () => { 0, 39, ) - expect(address).to.equal('0x0000000000000000000000000000000000000000') expect(valid).to.equal(false) + expect(address).to.equal('0x0000000000000000000000000000000000000000') }) }) @@ -117,11 +157,6 @@ describe('HexUtils', () => { ).to.be.reverted }) - it('not enough length', async () => { - await expect(HexUtils.hexStringToBytes32(toUtf8Bytes(hex32Bytes), 0, 2)) - .to.be.reverted - }) - it('exceed length', async () => { await expect( HexUtils.hexStringToBytes32( From 0758752639e504a2f5c104da25ed5882194735b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Thu, 22 Aug 2024 13:42:48 +0200 Subject: [PATCH 05/16] remove DummyDNSResolver2 --- .../mocks/DummyExtendedDNSSECResolver2.sol | 221 ------------------ test/dnsregistrar/TestOffchainDNSResolver.ts | 15 +- 2 files changed, 3 insertions(+), 233 deletions(-) delete mode 100644 contracts/dnsregistrar/mocks/DummyExtendedDNSSECResolver2.sol diff --git a/contracts/dnsregistrar/mocks/DummyExtendedDNSSECResolver2.sol b/contracts/dnsregistrar/mocks/DummyExtendedDNSSECResolver2.sol deleted file mode 100644 index fd2694a8..00000000 --- a/contracts/dnsregistrar/mocks/DummyExtendedDNSSECResolver2.sol +++ /dev/null @@ -1,221 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; - -import "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -import "@openzeppelin/contracts/utils/Strings.sol"; -import "../../resolvers/profiles/IExtendedDNSResolver.sol"; -import "../../resolvers/profiles/IAddressResolver.sol"; -import "../../resolvers/profiles/IAddrResolver.sol"; -import "../../resolvers/profiles/ITextResolver.sol"; -import "../../utils/HexUtils.sol"; -import "../../utils/BytesUtils.sol"; - -contract DummyExtendedDNSSECResolver2 is IExtendedDNSResolver, IERC165 { - using HexUtils for *; - using BytesUtils for *; - using Strings for *; - - uint256 private constant COIN_TYPE_ETH = 60; - - error NotImplemented(); - error InvalidAddressFormat(bytes addr); - - function supportsInterface( - bytes4 interfaceId - ) external view virtual override returns (bool) { - return interfaceId == type(IExtendedDNSResolver).interfaceId; - } - - function resolve( - bytes calldata /* name */, - bytes calldata data, - bytes calldata context - ) external pure override returns (bytes memory) { - bytes4 selector = bytes4(data); - if (selector == IAddrResolver.addr.selector) { - return _resolveAddr(context); - } else if (selector == IAddressResolver.addr.selector) { - return _resolveAddress(data, context); - } else if (selector == ITextResolver.text.selector) { - return _resolveText(data, context); - } - revert NotImplemented(); - } - - function _resolveAddress( - bytes calldata data, - bytes calldata context - ) internal pure returns (bytes memory) { - (, uint256 coinType) = abi.decode(data[4:], (bytes32, uint256)); - bytes memory value; - // Per https://docs.ens.domains/ensip/11#specification - if (coinType & 0x80000000 != 0) { - value = _findValue( - context, - bytes.concat( - "a[e", - bytes((coinType & 0x7fffffff).toString()), - "]=" - ) - ); - } else { - value = _findValue( - context, - bytes.concat("a[", bytes(coinType.toString()), "]=") - ); - } - if (value.length == 0) { - return value; - } - (bytes memory record, bool valid) = value.hexToBytes(2, value.length); - if (!valid) revert InvalidAddressFormat(value); - return record; - } - - function _resolveAddr( - bytes calldata context - ) internal pure returns (bytes memory) { - bytes memory value = _findValue(context, "a[60]="); - if (value.length == 0) { - return value; - } - (bytes memory record, bool valid) = value.hexToBytes(2, value.length); - if (!valid) revert InvalidAddressFormat(value); - return record; - } - - function _resolveText( - bytes calldata data, - bytes calldata context - ) internal pure returns (bytes memory) { - (, string memory key) = abi.decode(data[4:], (bytes32, string)); - bytes memory value = _findValue( - context, - bytes.concat("t[", bytes(key), "]=") - ); - return value; - } - - uint256 constant STATE_START = 0; - uint256 constant STATE_IGNORED_KEY = 1; - uint256 constant STATE_IGNORED_KEY_ARG = 2; - uint256 constant STATE_VALUE = 3; - uint256 constant STATE_QUOTED_VALUE = 4; - uint256 constant STATE_UNQUOTED_VALUE = 5; - uint256 constant STATE_IGNORED_VALUE = 6; - uint256 constant STATE_IGNORED_QUOTED_VALUE = 7; - uint256 constant STATE_IGNORED_UNQUOTED_VALUE = 8; - - function _findValue( - bytes memory data, - bytes memory key - ) internal pure returns (bytes memory value) { - uint256 state = STATE_START; - uint256 len = data.length; - for (uint256 i = 0; i < len; ) { - if (state == STATE_START) { - if (data.equals(i, key, 0, key.length)) { - i += key.length; - state = STATE_VALUE; - } else { - state = STATE_IGNORED_KEY; - } - } else if (state == STATE_IGNORED_KEY) { - for (; i < len; i++) { - if (data[i] == "=") { - state = STATE_IGNORED_VALUE; - i += 1; - break; - } else if (data[i] == "[") { - state = STATE_IGNORED_KEY_ARG; - i += 1; - break; - } - } - } else if (state == STATE_IGNORED_KEY_ARG) { - for (; i < len; i++) { - if (data[i] == "]") { - state = STATE_IGNORED_VALUE; - i += 1; - if (data[i] == "=") { - i += 1; - } - break; - } - } - } else if (state == STATE_VALUE) { - if (data[i] == "'") { - state = STATE_QUOTED_VALUE; - i += 1; - } else { - state = STATE_UNQUOTED_VALUE; - } - } else if (state == STATE_QUOTED_VALUE) { - uint256 start = i; - uint256 valueLen = 0; - bool escaped = false; - for (; i < len; i++) { - if (escaped) { - data[start + valueLen] = data[i]; - valueLen += 1; - escaped = false; - } else { - if (data[i] == "\\") { - escaped = true; - } else if (data[i] == "'") { - return data.substring(start, valueLen); - } else { - data[start + valueLen] = data[i]; - valueLen += 1; - } - } - } - } else if (state == STATE_UNQUOTED_VALUE) { - uint256 start = i; - for (; i < len; i++) { - if (data[i] == " ") { - return data.substring(start, i - start); - } - } - return data.substring(start, len - start); - } else if (state == STATE_IGNORED_VALUE) { - if (data[i] == "'") { - state = STATE_IGNORED_QUOTED_VALUE; - i += 1; - } else { - state = STATE_IGNORED_UNQUOTED_VALUE; - } - } else if (state == STATE_IGNORED_QUOTED_VALUE) { - bool escaped = false; - for (; i < len; i++) { - if (escaped) { - escaped = false; - } else { - if (data[i] == "\\") { - escaped = true; - } else if (data[i] == "'") { - i += 1; - while (data[i] == " ") { - i += 1; - } - state = STATE_START; - break; - } - } - } - } else { - assert(state == STATE_IGNORED_UNQUOTED_VALUE); - for (; i < len; i++) { - if (data[i] == " ") { - while (data[i] == " ") { - i += 1; - } - state = STATE_START; - break; - } - } - } - } - return ""; - } -} diff --git a/test/dnsregistrar/TestOffchainDNSResolver.ts b/test/dnsregistrar/TestOffchainDNSResolver.ts index 2c1ac824..b766d32c 100644 --- a/test/dnsregistrar/TestOffchainDNSResolver.ts +++ b/test/dnsregistrar/TestOffchainDNSResolver.ts @@ -598,10 +598,7 @@ describe('OffchainDNSResolver', () => { [], ) - const resolver2 = await hre.viem.deployContract( - 'DummyExtendedDNSSECResolver2', - [], - ) + const resolver2 = await hre.viem.deployContract('ExtendedDNSResolver', []) const name = 'test.test' const COIN_TYPE_ETH = 60 @@ -687,10 +684,7 @@ describe('OffchainDNSResolver', () => { fixture, ) - const resolver = await hre.viem.deployContract( - 'DummyExtendedDNSSECResolver2', - [], - ) + const resolver = await hre.viem.deployContract('ExtendedDNSResolver', []) const callDataText = encodeFunctionData({ abi: publicResolverAbi, @@ -714,10 +708,7 @@ describe('OffchainDNSResolver', () => { fixture, ) - const resolver = await hre.viem.deployContract( - 'DummyExtendedDNSSECResolver2', - [], - ) + const resolver = await hre.viem.deployContract('ExtendedDNSResolver', []) const callDataText = encodeFunctionData({ abi: publicResolverAbi, From c3a08363f299b845d7e632b61198673104f7f6c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Thu, 22 Aug 2024 13:55:51 +0200 Subject: [PATCH 06/16] update extendeddnsresolver deployments file --- deployments/mainnet/ExtendedDNSResolver.json | 37 ++++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/deployments/mainnet/ExtendedDNSResolver.json b/deployments/mainnet/ExtendedDNSResolver.json index c6872480..9f284b54 100644 --- a/deployments/mainnet/ExtendedDNSResolver.json +++ b/deployments/mainnet/ExtendedDNSResolver.json @@ -1,8 +1,14 @@ { - "address": "0x238A8F792dFA6033814B18618aD4100654aeef01", + "address": "0x08769D484a7Cd9c4A98E928D9E270221F3E8578c", "abi": [ { - "inputs": [], + "inputs": [ + { + "internalType": "bytes", + "name": "addr", + "type": "bytes" + } + ], "name": "InvalidAddressFormat", "type": "error" }, @@ -60,29 +66,30 @@ "type": "function" } ], - "transactionHash": "0x49894a0ebf1294f19adad02ff24b181f50073c00cc19dac085c43b9de1363650", + "transactionHash": "0x073a2781e7de2952710c167993a7900eb4c2c3838aeea1640666744aa7452dc8", "receipt": { "to": null, "from": "0x0904Dac3347eA47d208F3Fd67402D039a3b99859", - "contractAddress": "0x238A8F792dFA6033814B18618aD4100654aeef01", - "transactionIndex": 23, - "gasUsed": "422726", + "contractAddress": "0x08769D484a7Cd9c4A98E928D9E270221F3E8578c", + "transactionIndex": 98, + "gasUsed": "1116071", "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "blockHash": "0x478d0a21285cee90daa3c2144252894733be7877f9f94ef110aa576cd1c9b715", - "transactionHash": "0x49894a0ebf1294f19adad02ff24b181f50073c00cc19dac085c43b9de1363650", + "blockHash": "0xa2aa6d335649626902472977466df67c0a08f739bc0fe0bef2ff7d5d900c4bfc", + "transactionHash": "0x073a2781e7de2952710c167993a7900eb4c2c3838aeea1640666744aa7452dc8", "logs": [], - "blockNumber": 19020789, - "cumulativeGasUsed": "3404020", + "blockNumber": 20426738, + "cumulativeGasUsed": "11217248", "status": 1, "byzantium": true }, "args": [], - "numDeployments": 1, - "solcInputHash": "dd9e022689821cffaeb04b9ddbda87ae", - "metadata": "{\"compiler\":{\"version\":\"0.8.17+commit.8df45f5f\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"name\":\"InvalidAddressFormat\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotImplemented\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"context\",\"type\":\"bytes\"}],\"name\":\"resolve\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"supportsInterface(bytes4)\":{\"details\":\"Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] to learn more about how these ids are created. This function call must use less than 30 000 gas.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/resolvers/profiles/ExtendedDNSResolver.sol\":\"ExtendedDNSResolver\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":1200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1\",\"license\":\"MIT\"},\"contracts/resolvers/profiles/ExtendedDNSResolver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/utils/introspection/IERC165.sol\\\";\\nimport \\\"../../resolvers/profiles/IExtendedDNSResolver.sol\\\";\\nimport \\\"../../resolvers/profiles/IAddressResolver.sol\\\";\\nimport \\\"../../resolvers/profiles/IAddrResolver.sol\\\";\\nimport \\\"../../utils/HexUtils.sol\\\";\\n\\ncontract ExtendedDNSResolver is IExtendedDNSResolver, IERC165 {\\n using HexUtils for *;\\n\\n uint256 private constant COIN_TYPE_ETH = 60;\\n\\n error NotImplemented();\\n error InvalidAddressFormat();\\n\\n function supportsInterface(\\n bytes4 interfaceId\\n ) external view virtual override returns (bool) {\\n return interfaceId == type(IExtendedDNSResolver).interfaceId;\\n }\\n\\n function resolve(\\n bytes calldata /* name */,\\n bytes calldata data,\\n bytes calldata context\\n ) external pure override returns (bytes memory) {\\n bytes4 selector = bytes4(data);\\n if (\\n selector == IAddrResolver.addr.selector ||\\n selector == IAddressResolver.addr.selector\\n ) {\\n if (selector == IAddressResolver.addr.selector) {\\n (, uint256 coinType) = abi.decode(data[4:], (bytes32, uint256));\\n if (coinType != COIN_TYPE_ETH) return abi.encode(\\\"\\\");\\n }\\n (address record, bool valid) = context.hexToAddress(\\n 2,\\n context.length\\n );\\n if (!valid) revert InvalidAddressFormat();\\n return abi.encode(record);\\n }\\n revert NotImplemented();\\n }\\n}\\n\",\"keccak256\":\"0xe49059d038b1e57513359d5fc05f44e7697bd0d1ccb5a979173e2cac429756ed\",\"license\":\"MIT\"},\"contracts/resolvers/profiles/IAddrResolver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity >=0.8.4;\\n\\n/**\\n * Interface for the legacy (ETH-only) addr function.\\n */\\ninterface IAddrResolver {\\n event AddrChanged(bytes32 indexed node, address a);\\n\\n /**\\n * Returns the address associated with an ENS node.\\n * @param node The ENS node to query.\\n * @return The associated address.\\n */\\n function addr(bytes32 node) external view returns (address payable);\\n}\\n\",\"keccak256\":\"0x2ad7f2fc60ebe0f93745fe70247f6a854f66af732483fda2a3c5e055614445e8\",\"license\":\"MIT\"},\"contracts/resolvers/profiles/IAddressResolver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity >=0.8.4;\\n\\n/**\\n * Interface for the new (multicoin) addr function.\\n */\\ninterface IAddressResolver {\\n event AddressChanged(\\n bytes32 indexed node,\\n uint256 coinType,\\n bytes newAddress\\n );\\n\\n function addr(\\n bytes32 node,\\n uint256 coinType\\n ) external view returns (bytes memory);\\n}\\n\",\"keccak256\":\"0x411447c1e90c51e09702815a85ec725ffbbe37cf96e8cc4d2a8bd4ad8a59d73e\",\"license\":\"MIT\"},\"contracts/resolvers/profiles/IExtendedDNSResolver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.4;\\n\\ninterface IExtendedDNSResolver {\\n function resolve(\\n bytes memory name,\\n bytes memory data,\\n bytes memory context\\n ) external view returns (bytes memory);\\n}\\n\",\"keccak256\":\"0x541f8799c34ff9e7035d09f06ae0f0f8a16b6065e9b60a15670b957321630f72\",\"license\":\"MIT\"},\"contracts/utils/HexUtils.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.4;\\n\\nlibrary HexUtils {\\n /**\\n * @dev Attempts to parse bytes32 from a hex string\\n * @param str The string to parse\\n * @param idx The offset to start parsing at\\n * @param lastIdx The (exclusive) last index in `str` to consider. Use `str.length` to scan the whole string.\\n */\\n function hexStringToBytes32(\\n bytes memory str,\\n uint256 idx,\\n uint256 lastIdx\\n ) internal pure returns (bytes32 r, bool valid) {\\n uint256 hexLength = lastIdx - idx;\\n if ((hexLength != 64 && hexLength != 40) || hexLength % 2 == 1) {\\n revert(\\\"Invalid string length\\\");\\n }\\n valid = true;\\n assembly {\\n // check that the index to read to is not past the end of the string\\n if gt(lastIdx, mload(str)) {\\n revert(0, 0)\\n }\\n\\n function getHex(c) -> ascii {\\n // chars 48-57: 0-9\\n if and(gt(c, 47), lt(c, 58)) {\\n ascii := sub(c, 48)\\n leave\\n }\\n // chars 65-70: A-F\\n if and(gt(c, 64), lt(c, 71)) {\\n ascii := add(sub(c, 65), 10)\\n leave\\n }\\n // chars 97-102: a-f\\n if and(gt(c, 96), lt(c, 103)) {\\n ascii := add(sub(c, 97), 10)\\n leave\\n }\\n // invalid char\\n ascii := 0xff\\n }\\n\\n let ptr := add(str, 32)\\n for {\\n let i := idx\\n } lt(i, lastIdx) {\\n i := add(i, 2)\\n } {\\n let byte1 := getHex(byte(0, mload(add(ptr, i))))\\n let byte2 := getHex(byte(0, mload(add(ptr, add(i, 1)))))\\n // if either byte is invalid, set invalid and break loop\\n if or(eq(byte1, 0xff), eq(byte2, 0xff)) {\\n valid := false\\n break\\n }\\n let combined := or(shl(4, byte1), byte2)\\n r := or(shl(8, r), combined)\\n }\\n }\\n }\\n\\n /**\\n * @dev Attempts to parse an address from a hex string\\n * @param str The string to parse\\n * @param idx The offset to start parsing at\\n * @param lastIdx The (exclusive) last index in `str` to consider. Use `str.length` to scan the whole string.\\n */\\n function hexToAddress(\\n bytes memory str,\\n uint256 idx,\\n uint256 lastIdx\\n ) internal pure returns (address, bool) {\\n if (lastIdx - idx < 40) return (address(0x0), false);\\n (bytes32 r, bool valid) = hexStringToBytes32(str, idx, lastIdx);\\n return (address(uint160(uint256(r))), valid);\\n }\\n}\\n\",\"keccak256\":\"0x4a8a9c72d6f3effb80b310faa6dc273e7adbc3b949df9c7a42e290e5b13519f3\",\"license\":\"MIT\"}},\"version\":1}", - "bytecode": "0x608060405234801561001057600080fd5b506106ba806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806301ffc9a71461003b5780638ef98a7e1461008d575b600080fd5b61007861004936600461045d565b6001600160e01b0319167f8ef98a7e000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b6100a061009b3660046104d7565b6100ad565b6040516100849190610571565b606060006100bb85876105bf565b90506001600160e01b031981167f3b3b57de00000000000000000000000000000000000000000000000000000000148061011e57506001600160e01b031981167ff1cb7e0600000000000000000000000000000000000000000000000000000000145b15610271577f0e3481fa000000000000000000000000000000000000000000000000000000006001600160e01b03198216016101b0576000610163866004818a6105ef565b8101906101709190610619565b915050603c81146101ae5760405160200161019690602080825260009082015260400190565b604051602081830303815290604052925050506102a3565b505b6000806101fc60028787905088888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509294939250506102ad9050565b9150915080610237576040517fc9e47ee500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805173ffffffffffffffffffffffffffffffffffffffff841660208201520160405160208183030381529060405293505050506102a3565b6040517fd623472500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b9695505050505050565b60008060286102bc858561063b565b10156102cd575060009050806102e3565b6000806102db8787876102eb565b909450925050505b935093915050565b600080806102f9858561063b565b90508060401415801561030d575080602814155b80610322575061031e600282610662565b6001145b1561038d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f496e76616c696420737472696e67206c656e6774680000000000000000000000604482015260640160405180910390fd5b60019150855184111561039f57600080fd5b6103f0565b6000603a8210602f831116156103bc5750602f190190565b604782106040831116156103d257506036190190565b606782106060831116156103e857506056190190565b5060ff919050565b60208601855b858110156104525761040d8183015160001a6103a4565b61041f6001830184015160001a6103a4565b60ff811460ff8314171561043857600095505050610452565b60049190911b1760089590951b94909417936002016103f6565b505050935093915050565b60006020828403121561046f57600080fd5b81356001600160e01b03198116811461048757600080fd5b9392505050565b60008083601f8401126104a057600080fd5b50813567ffffffffffffffff8111156104b857600080fd5b6020830191508360208285010111156104d057600080fd5b9250929050565b600080600080600080606087890312156104f057600080fd5b863567ffffffffffffffff8082111561050857600080fd5b6105148a838b0161048e565b9098509650602089013591508082111561052d57600080fd5b6105398a838b0161048e565b9096509450604089013591508082111561055257600080fd5b5061055f89828a0161048e565b979a9699509497509295939492505050565b600060208083528351808285015260005b8181101561059e57858101830151858201604001528201610582565b506000604082860101526040601f19601f8301168501019250505092915050565b6001600160e01b031981358181169160048510156105e75780818660040360031b1b83161692505b505092915050565b600080858511156105ff57600080fd5b8386111561060c57600080fd5b5050820193919092039150565b6000806040838503121561062c57600080fd5b50508035926020909101359150565b8181038181111561065c57634e487b7160e01b600052601160045260246000fd5b92915050565b60008261067f57634e487b7160e01b600052601260045260246000fd5b50069056fea264697066735822122048092a531a20e262efce54779239b612bcaff145bb48f1dae6c4641143e50c4164736f6c63430008110033", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806301ffc9a71461003b5780638ef98a7e1461008d575b600080fd5b61007861004936600461045d565b6001600160e01b0319167f8ef98a7e000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b6100a061009b3660046104d7565b6100ad565b6040516100849190610571565b606060006100bb85876105bf565b90506001600160e01b031981167f3b3b57de00000000000000000000000000000000000000000000000000000000148061011e57506001600160e01b031981167ff1cb7e0600000000000000000000000000000000000000000000000000000000145b15610271577f0e3481fa000000000000000000000000000000000000000000000000000000006001600160e01b03198216016101b0576000610163866004818a6105ef565b8101906101709190610619565b915050603c81146101ae5760405160200161019690602080825260009082015260400190565b604051602081830303815290604052925050506102a3565b505b6000806101fc60028787905088888080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509294939250506102ad9050565b9150915080610237576040517fc9e47ee500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040805173ffffffffffffffffffffffffffffffffffffffff841660208201520160405160208183030381529060405293505050506102a3565b6040517fd623472500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b9695505050505050565b60008060286102bc858561063b565b10156102cd575060009050806102e3565b6000806102db8787876102eb565b909450925050505b935093915050565b600080806102f9858561063b565b90508060401415801561030d575080602814155b80610322575061031e600282610662565b6001145b1561038d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f496e76616c696420737472696e67206c656e6774680000000000000000000000604482015260640160405180910390fd5b60019150855184111561039f57600080fd5b6103f0565b6000603a8210602f831116156103bc5750602f190190565b604782106040831116156103d257506036190190565b606782106060831116156103e857506056190190565b5060ff919050565b60208601855b858110156104525761040d8183015160001a6103a4565b61041f6001830184015160001a6103a4565b60ff811460ff8314171561043857600095505050610452565b60049190911b1760089590951b94909417936002016103f6565b505050935093915050565b60006020828403121561046f57600080fd5b81356001600160e01b03198116811461048757600080fd5b9392505050565b60008083601f8401126104a057600080fd5b50813567ffffffffffffffff8111156104b857600080fd5b6020830191508360208285010111156104d057600080fd5b9250929050565b600080600080600080606087890312156104f057600080fd5b863567ffffffffffffffff8082111561050857600080fd5b6105148a838b0161048e565b9098509650602089013591508082111561052d57600080fd5b6105398a838b0161048e565b9096509450604089013591508082111561055257600080fd5b5061055f89828a0161048e565b979a9699509497509295939492505050565b600060208083528351808285015260005b8181101561059e57858101830151858201604001528201610582565b506000604082860101526040601f19601f8301168501019250505092915050565b6001600160e01b031981358181169160048510156105e75780818660040360031b1b83161692505b505092915050565b600080858511156105ff57600080fd5b8386111561060c57600080fd5b5050820193919092039150565b6000806040838503121561062c57600080fd5b50508035926020909101359150565b8181038181111561065c57634e487b7160e01b600052601160045260246000fd5b92915050565b60008261067f57634e487b7160e01b600052601260045260246000fd5b50069056fea264697066735822122048092a531a20e262efce54779239b612bcaff145bb48f1dae6c4641143e50c4164736f6c63430008110033", + "numDeployments": 2, + "solcInputHash": "96d118177ae253bdd3815d2757c11cd9", + "metadata": "{\"compiler\":{\"version\":\"0.8.17+commit.8df45f5f\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"addr\",\"type\":\"bytes\"}],\"name\":\"InvalidAddressFormat\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotImplemented\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"context\",\"type\":\"bytes\"}],\"name\":\"resolve\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes4\",\"name\":\"interfaceId\",\"type\":\"bytes4\"}],\"name\":\"supportsInterface\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Resolves names on ENS by interpreting record data stored in a DNS TXT record. This resolver implements the IExtendedDNSResolver interface, meaning that when a DNS name specifies it as the resolver via a TXT record, this resolver's resolve() method is invoked, and is passed any additional information from that text record. This resolver implements a simple text parser allowing a variety of records to be specified in text, which will then be used to resolve the name in ENS. To use this, set a TXT record on your DNS name in the following format: ENS1
For example: ENS1 2.dnsname.ens.eth a[60]=0x1234... The record data consists of a series of key=value pairs, separated by spaces. Keys may have an optional argument in square brackets, and values may be either unquoted - in which case they may not contain spaces - or single-quoted. Single quotes in a quoted value may be backslash-escaped. \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510 \\u2502 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u2502 \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2534\\u2500\\u2524\\\" \\\"\\u2502\\u25c4\\u2500\\u2534\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510 \\u2502 \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2502 \\u2502 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u2502 ^\\u2500\\u2534\\u2500\\u25ba\\u2502key\\u251c\\u2500\\u252c\\u2500\\u25ba\\u2502\\\"[\\\"\\u251c\\u2500\\u2500\\u2500\\u25ba\\u2502arg\\u251c\\u2500\\u2500\\u2500\\u25ba\\u2502\\\"]\\\"\\u251c\\u2500\\u252c\\u2500\\u25ba\\u2502\\\"=\\\"\\u251c\\u2500\\u252c\\u2500\\u25ba\\u2502\\\"'\\\"\\u251c\\u2500\\u2500\\u2500\\u25ba\\u2502quoted_value\\u251c\\u2500\\u2500\\u2500\\u25ba\\u2502\\\"'\\\"\\u251c\\u2500\\u253c\\u2500$ \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2502 \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2502 \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2502 \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518 \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2502 \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518 \\u2502 \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510 \\u2502 \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u25ba\\u2502unquoted_value\\u251c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518 \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518 Record types: - a[] - Specifies how an `addr()` request should be resolved for the specified `coinType`. Ethereum has `coinType` 60. The value must be 0x-prefixed hexadecimal, and will be returned unmodified; this means that non-EVM addresses will need to be translated into binary format and then encoded in hex. Examples: - a[60]=0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7 - a[0]=0x00149010587f8364b964fcaa70687216b53bd2cbd798 - a[e] - Specifies how an `addr()` request should be resolved for the specified `chainId`. The value must be 0x-prefixed hexadecimal. When encoding an address for an EVM-based cryptocurrency that uses a chainId instead of a coinType, this syntax *must* be used in place of the coin type - eg, Optimism is `a[e10]`, not `a[2147483658]`. A list of supported cryptocurrencies for both syntaxes can be found here: https://github.com/ensdomains/address-encoder/blob/master/docs/supported-cryptocurrencies.md Example: - a[e10]=0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7 - t[] - Specifies how a `text()` request should be resolved for the specified `key`. Examples: - t[com.twitter]=nicksdjohnson - t[url]='https://ens.domains/' - t[note]='I\\\\'m great'\",\"kind\":\"dev\",\"methods\":{\"supportsInterface(bytes4)\":{\"details\":\"Returns true if this contract implements the interface defined by `interfaceId`. See the corresponding https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section] to learn more about how these ids are created. This function call must use less than 30 000 gas.\"}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"contracts/resolvers/profiles/ExtendedDNSResolver.sol\":\"ExtendedDNSResolver\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":1200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/utils/Strings.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)\\n\\npragma solidity ^0.8.0;\\n\\nimport \\\"./math/Math.sol\\\";\\nimport \\\"./math/SignedMath.sol\\\";\\n\\n/**\\n * @dev String operations.\\n */\\nlibrary Strings {\\n bytes16 private constant _SYMBOLS = \\\"0123456789abcdef\\\";\\n uint8 private constant _ADDRESS_LENGTH = 20;\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` decimal representation.\\n */\\n function toString(uint256 value) internal pure returns (string memory) {\\n unchecked {\\n uint256 length = Math.log10(value) + 1;\\n string memory buffer = new string(length);\\n uint256 ptr;\\n /// @solidity memory-safe-assembly\\n assembly {\\n ptr := add(buffer, add(32, length))\\n }\\n while (true) {\\n ptr--;\\n /// @solidity memory-safe-assembly\\n assembly {\\n mstore8(ptr, byte(mod(value, 10), _SYMBOLS))\\n }\\n value /= 10;\\n if (value == 0) break;\\n }\\n return buffer;\\n }\\n }\\n\\n /**\\n * @dev Converts a `int256` to its ASCII `string` decimal representation.\\n */\\n function toString(int256 value) internal pure returns (string memory) {\\n return string(abi.encodePacked(value < 0 ? \\\"-\\\" : \\\"\\\", toString(SignedMath.abs(value))));\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.\\n */\\n function toHexString(uint256 value) internal pure returns (string memory) {\\n unchecked {\\n return toHexString(value, Math.log256(value) + 1);\\n }\\n }\\n\\n /**\\n * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.\\n */\\n function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {\\n bytes memory buffer = new bytes(2 * length + 2);\\n buffer[0] = \\\"0\\\";\\n buffer[1] = \\\"x\\\";\\n for (uint256 i = 2 * length + 1; i > 1; --i) {\\n buffer[i] = _SYMBOLS[value & 0xf];\\n value >>= 4;\\n }\\n require(value == 0, \\\"Strings: hex length insufficient\\\");\\n return string(buffer);\\n }\\n\\n /**\\n * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.\\n */\\n function toHexString(address addr) internal pure returns (string memory) {\\n return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);\\n }\\n\\n /**\\n * @dev Returns true if the two strings are equal.\\n */\\n function equal(string memory a, string memory b) internal pure returns (bool) {\\n return keccak256(bytes(a)) == keccak256(bytes(b));\\n }\\n}\\n\",\"keccak256\":\"0x3088eb2868e8d13d89d16670b5f8612c4ab9ff8956272837d8e90106c59c14a0\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/introspection/IERC165.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Interface of the ERC165 standard, as defined in the\\n * https://eips.ethereum.org/EIPS/eip-165[EIP].\\n *\\n * Implementers can declare support of contract interfaces, which can then be\\n * queried by others ({ERC165Checker}).\\n *\\n * For an implementation, see {ERC165}.\\n */\\ninterface IERC165 {\\n /**\\n * @dev Returns true if this contract implements the interface defined by\\n * `interfaceId`. See the corresponding\\n * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]\\n * to learn more about how these ids are created.\\n *\\n * This function call must use less than 30 000 gas.\\n */\\n function supportsInterface(bytes4 interfaceId) external view returns (bool);\\n}\\n\",\"keccak256\":\"0x447a5f3ddc18419d41ff92b3773fb86471b1db25773e07f877f548918a185bf1\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/math/Math.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Standard math utilities missing in the Solidity language.\\n */\\nlibrary Math {\\n enum Rounding {\\n Down, // Toward negative infinity\\n Up, // Toward infinity\\n Zero // Toward zero\\n }\\n\\n /**\\n * @dev Returns the largest of two numbers.\\n */\\n function max(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a > b ? a : b;\\n }\\n\\n /**\\n * @dev Returns the smallest of two numbers.\\n */\\n function min(uint256 a, uint256 b) internal pure returns (uint256) {\\n return a < b ? a : b;\\n }\\n\\n /**\\n * @dev Returns the average of two numbers. The result is rounded towards\\n * zero.\\n */\\n function average(uint256 a, uint256 b) internal pure returns (uint256) {\\n // (a + b) / 2 can overflow.\\n return (a & b) + (a ^ b) / 2;\\n }\\n\\n /**\\n * @dev Returns the ceiling of the division of two numbers.\\n *\\n * This differs from standard division with `/` in that it rounds up instead\\n * of rounding down.\\n */\\n function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {\\n // (a + b - 1) / b can overflow on addition, so we distribute.\\n return a == 0 ? 0 : (a - 1) / b + 1;\\n }\\n\\n /**\\n * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0\\n * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)\\n * with further edits by Uniswap Labs also under MIT license.\\n */\\n function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {\\n unchecked {\\n // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use\\n // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256\\n // variables such that product = prod1 * 2^256 + prod0.\\n uint256 prod0; // Least significant 256 bits of the product\\n uint256 prod1; // Most significant 256 bits of the product\\n assembly {\\n let mm := mulmod(x, y, not(0))\\n prod0 := mul(x, y)\\n prod1 := sub(sub(mm, prod0), lt(mm, prod0))\\n }\\n\\n // Handle non-overflow cases, 256 by 256 division.\\n if (prod1 == 0) {\\n // Solidity will revert if denominator == 0, unlike the div opcode on its own.\\n // The surrounding unchecked block does not change this fact.\\n // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.\\n return prod0 / denominator;\\n }\\n\\n // Make sure the result is less than 2^256. Also prevents denominator == 0.\\n require(denominator > prod1, \\\"Math: mulDiv overflow\\\");\\n\\n ///////////////////////////////////////////////\\n // 512 by 256 division.\\n ///////////////////////////////////////////////\\n\\n // Make division exact by subtracting the remainder from [prod1 prod0].\\n uint256 remainder;\\n assembly {\\n // Compute remainder using mulmod.\\n remainder := mulmod(x, y, denominator)\\n\\n // Subtract 256 bit number from 512 bit number.\\n prod1 := sub(prod1, gt(remainder, prod0))\\n prod0 := sub(prod0, remainder)\\n }\\n\\n // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.\\n // See https://cs.stackexchange.com/q/138556/92363.\\n\\n // Does not overflow because the denominator cannot be zero at this stage in the function.\\n uint256 twos = denominator & (~denominator + 1);\\n assembly {\\n // Divide denominator by twos.\\n denominator := div(denominator, twos)\\n\\n // Divide [prod1 prod0] by twos.\\n prod0 := div(prod0, twos)\\n\\n // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.\\n twos := add(div(sub(0, twos), twos), 1)\\n }\\n\\n // Shift in bits from prod1 into prod0.\\n prod0 |= prod1 * twos;\\n\\n // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such\\n // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for\\n // four bits. That is, denominator * inv = 1 mod 2^4.\\n uint256 inverse = (3 * denominator) ^ 2;\\n\\n // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works\\n // in modular arithmetic, doubling the correct bits in each step.\\n inverse *= 2 - denominator * inverse; // inverse mod 2^8\\n inverse *= 2 - denominator * inverse; // inverse mod 2^16\\n inverse *= 2 - denominator * inverse; // inverse mod 2^32\\n inverse *= 2 - denominator * inverse; // inverse mod 2^64\\n inverse *= 2 - denominator * inverse; // inverse mod 2^128\\n inverse *= 2 - denominator * inverse; // inverse mod 2^256\\n\\n // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.\\n // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is\\n // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1\\n // is no longer required.\\n result = prod0 * inverse;\\n return result;\\n }\\n }\\n\\n /**\\n * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.\\n */\\n function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {\\n uint256 result = mulDiv(x, y, denominator);\\n if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {\\n result += 1;\\n }\\n return result;\\n }\\n\\n /**\\n * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.\\n *\\n * Inspired by Henry S. Warren, Jr.'s \\\"Hacker's Delight\\\" (Chapter 11).\\n */\\n function sqrt(uint256 a) internal pure returns (uint256) {\\n if (a == 0) {\\n return 0;\\n }\\n\\n // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.\\n //\\n // We know that the \\\"msb\\\" (most significant bit) of our target number `a` is a power of 2 such that we have\\n // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.\\n //\\n // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`\\n // \\u2192 `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`\\n // \\u2192 `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`\\n //\\n // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.\\n uint256 result = 1 << (log2(a) >> 1);\\n\\n // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,\\n // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at\\n // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision\\n // into the expected uint128 result.\\n unchecked {\\n result = (result + a / result) >> 1;\\n result = (result + a / result) >> 1;\\n result = (result + a / result) >> 1;\\n result = (result + a / result) >> 1;\\n result = (result + a / result) >> 1;\\n result = (result + a / result) >> 1;\\n result = (result + a / result) >> 1;\\n return min(result, a / result);\\n }\\n }\\n\\n /**\\n * @notice Calculates sqrt(a), following the selected rounding direction.\\n */\\n function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {\\n unchecked {\\n uint256 result = sqrt(a);\\n return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);\\n }\\n }\\n\\n /**\\n * @dev Return the log in base 2, rounded down, of a positive value.\\n * Returns 0 if given 0.\\n */\\n function log2(uint256 value) internal pure returns (uint256) {\\n uint256 result = 0;\\n unchecked {\\n if (value >> 128 > 0) {\\n value >>= 128;\\n result += 128;\\n }\\n if (value >> 64 > 0) {\\n value >>= 64;\\n result += 64;\\n }\\n if (value >> 32 > 0) {\\n value >>= 32;\\n result += 32;\\n }\\n if (value >> 16 > 0) {\\n value >>= 16;\\n result += 16;\\n }\\n if (value >> 8 > 0) {\\n value >>= 8;\\n result += 8;\\n }\\n if (value >> 4 > 0) {\\n value >>= 4;\\n result += 4;\\n }\\n if (value >> 2 > 0) {\\n value >>= 2;\\n result += 2;\\n }\\n if (value >> 1 > 0) {\\n result += 1;\\n }\\n }\\n return result;\\n }\\n\\n /**\\n * @dev Return the log in base 2, following the selected rounding direction, of a positive value.\\n * Returns 0 if given 0.\\n */\\n function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {\\n unchecked {\\n uint256 result = log2(value);\\n return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);\\n }\\n }\\n\\n /**\\n * @dev Return the log in base 10, rounded down, of a positive value.\\n * Returns 0 if given 0.\\n */\\n function log10(uint256 value) internal pure returns (uint256) {\\n uint256 result = 0;\\n unchecked {\\n if (value >= 10 ** 64) {\\n value /= 10 ** 64;\\n result += 64;\\n }\\n if (value >= 10 ** 32) {\\n value /= 10 ** 32;\\n result += 32;\\n }\\n if (value >= 10 ** 16) {\\n value /= 10 ** 16;\\n result += 16;\\n }\\n if (value >= 10 ** 8) {\\n value /= 10 ** 8;\\n result += 8;\\n }\\n if (value >= 10 ** 4) {\\n value /= 10 ** 4;\\n result += 4;\\n }\\n if (value >= 10 ** 2) {\\n value /= 10 ** 2;\\n result += 2;\\n }\\n if (value >= 10 ** 1) {\\n result += 1;\\n }\\n }\\n return result;\\n }\\n\\n /**\\n * @dev Return the log in base 10, following the selected rounding direction, of a positive value.\\n * Returns 0 if given 0.\\n */\\n function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {\\n unchecked {\\n uint256 result = log10(value);\\n return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);\\n }\\n }\\n\\n /**\\n * @dev Return the log in base 256, rounded down, of a positive value.\\n * Returns 0 if given 0.\\n *\\n * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.\\n */\\n function log256(uint256 value) internal pure returns (uint256) {\\n uint256 result = 0;\\n unchecked {\\n if (value >> 128 > 0) {\\n value >>= 128;\\n result += 16;\\n }\\n if (value >> 64 > 0) {\\n value >>= 64;\\n result += 8;\\n }\\n if (value >> 32 > 0) {\\n value >>= 32;\\n result += 4;\\n }\\n if (value >> 16 > 0) {\\n value >>= 16;\\n result += 2;\\n }\\n if (value >> 8 > 0) {\\n result += 1;\\n }\\n }\\n return result;\\n }\\n\\n /**\\n * @dev Return the log in base 256, following the selected rounding direction, of a positive value.\\n * Returns 0 if given 0.\\n */\\n function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {\\n unchecked {\\n uint256 result = log256(value);\\n return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);\\n }\\n }\\n}\\n\",\"keccak256\":\"0xe4455ac1eb7fc497bb7402579e7b4d64d928b846fce7d2b6fde06d366f21c2b3\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/math/SignedMath.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)\\n\\npragma solidity ^0.8.0;\\n\\n/**\\n * @dev Standard signed math utilities missing in the Solidity language.\\n */\\nlibrary SignedMath {\\n /**\\n * @dev Returns the largest of two signed numbers.\\n */\\n function max(int256 a, int256 b) internal pure returns (int256) {\\n return a > b ? a : b;\\n }\\n\\n /**\\n * @dev Returns the smallest of two signed numbers.\\n */\\n function min(int256 a, int256 b) internal pure returns (int256) {\\n return a < b ? a : b;\\n }\\n\\n /**\\n * @dev Returns the average of two signed numbers without overflow.\\n * The result is rounded towards zero.\\n */\\n function average(int256 a, int256 b) internal pure returns (int256) {\\n // Formula from the book \\\"Hacker's Delight\\\"\\n int256 x = (a & b) + ((a ^ b) >> 1);\\n return x + (int256(uint256(x) >> 255) & (a ^ b));\\n }\\n\\n /**\\n * @dev Returns the absolute unsigned value of a signed value.\\n */\\n function abs(int256 n) internal pure returns (uint256) {\\n unchecked {\\n // must be unchecked in order to support `n = type(int256).min`\\n return uint256(n >= 0 ? n : -n);\\n }\\n }\\n}\\n\",\"keccak256\":\"0xf92515413956f529d95977adc9b0567d583c6203fc31ab1c23824c35187e3ddc\",\"license\":\"MIT\"},\"contracts/resolvers/profiles/ExtendedDNSResolver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.4;\\n\\nimport \\\"@openzeppelin/contracts/utils/introspection/IERC165.sol\\\";\\nimport \\\"@openzeppelin/contracts/utils/Strings.sol\\\";\\nimport \\\"../../resolvers/profiles/IExtendedDNSResolver.sol\\\";\\nimport \\\"../../resolvers/profiles/IAddressResolver.sol\\\";\\nimport \\\"../../resolvers/profiles/IAddrResolver.sol\\\";\\nimport \\\"../../resolvers/profiles/ITextResolver.sol\\\";\\nimport \\\"../../utils/HexUtils.sol\\\";\\nimport \\\"../../utils/BytesUtils.sol\\\";\\n\\n/**\\n * @dev Resolves names on ENS by interpreting record data stored in a DNS TXT record.\\n * This resolver implements the IExtendedDNSResolver interface, meaning that when\\n * a DNS name specifies it as the resolver via a TXT record, this resolver's\\n * resolve() method is invoked, and is passed any additional information from that\\n * text record. This resolver implements a simple text parser allowing a variety\\n * of records to be specified in text, which will then be used to resolve the name\\n * in ENS.\\n *\\n * To use this, set a TXT record on your DNS name in the following format:\\n * ENS1
\\n *\\n * For example:\\n * ENS1 2.dnsname.ens.eth a[60]=0x1234...\\n *\\n * The record data consists of a series of key=value pairs, separated by spaces. Keys\\n * may have an optional argument in square brackets, and values may be either unquoted\\n * - in which case they may not contain spaces - or single-quoted. Single quotes in\\n * a quoted value may be backslash-escaped.\\n *\\n *\\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\\n * \\u2502 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u2502\\n * \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2534\\u2500\\u2524\\\" \\\"\\u2502\\u25c4\\u2500\\u2534\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510\\n * \\u2502 \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2502\\n * \\u2502 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510 \\u250c\\u2500\\u2500\\u2500\\u2510 \\u2502\\n * ^\\u2500\\u2534\\u2500\\u25ba\\u2502key\\u251c\\u2500\\u252c\\u2500\\u25ba\\u2502\\\"[\\\"\\u251c\\u2500\\u2500\\u2500\\u25ba\\u2502arg\\u251c\\u2500\\u2500\\u2500\\u25ba\\u2502\\\"]\\\"\\u251c\\u2500\\u252c\\u2500\\u25ba\\u2502\\\"=\\\"\\u251c\\u2500\\u252c\\u2500\\u25ba\\u2502\\\"'\\\"\\u251c\\u2500\\u2500\\u2500\\u25ba\\u2502quoted_value\\u251c\\u2500\\u2500\\u2500\\u25ba\\u2502\\\"'\\\"\\u251c\\u2500\\u253c\\u2500$\\n * \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2502 \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2502 \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2502 \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518 \\u2514\\u2500\\u2500\\u2500\\u2518 \\u2502\\n * \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518 \\u2502 \\u250c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2510 \\u2502\\n * \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u25ba\\u2502unquoted_value\\u251c\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\\n * \\u2514\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2500\\u2518\\n *\\n * Record types:\\n * - a[] - Specifies how an `addr()` request should be resolved for the specified\\n * `coinType`. Ethereum has `coinType` 60. The value must be 0x-prefixed hexadecimal, and will\\n * be returned unmodified; this means that non-EVM addresses will need to be translated\\n * into binary format and then encoded in hex.\\n * Examples:\\n * - a[60]=0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7\\n * - a[0]=0x00149010587f8364b964fcaa70687216b53bd2cbd798\\n * - a[e] - Specifies how an `addr()` request should be resolved for the specified\\n * `chainId`. The value must be 0x-prefixed hexadecimal. When encoding an address for an\\n * EVM-based cryptocurrency that uses a chainId instead of a coinType, this syntax *must*\\n * be used in place of the coin type - eg, Optimism is `a[e10]`, not `a[2147483658]`.\\n * A list of supported cryptocurrencies for both syntaxes can be found here:\\n * https://github.com/ensdomains/address-encoder/blob/master/docs/supported-cryptocurrencies.md\\n * Example:\\n * - a[e10]=0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7\\n * - t[] - Specifies how a `text()` request should be resolved for the specified `key`.\\n * Examples:\\n * - t[com.twitter]=nicksdjohnson\\n * - t[url]='https://ens.domains/'\\n * - t[note]='I\\\\'m great'\\n */\\ncontract ExtendedDNSResolver is IExtendedDNSResolver, IERC165 {\\n using HexUtils for *;\\n using BytesUtils for *;\\n using Strings for *;\\n\\n uint256 private constant COIN_TYPE_ETH = 60;\\n\\n error NotImplemented();\\n error InvalidAddressFormat(bytes addr);\\n\\n function supportsInterface(\\n bytes4 interfaceId\\n ) external view virtual override returns (bool) {\\n return interfaceId == type(IExtendedDNSResolver).interfaceId;\\n }\\n\\n function resolve(\\n bytes calldata /* name */,\\n bytes calldata data,\\n bytes calldata context\\n ) external pure override returns (bytes memory) {\\n bytes4 selector = bytes4(data);\\n if (selector == IAddrResolver.addr.selector) {\\n return _resolveAddr(context);\\n } else if (selector == IAddressResolver.addr.selector) {\\n return _resolveAddress(data, context);\\n } else if (selector == ITextResolver.text.selector) {\\n return _resolveText(data, context);\\n }\\n revert NotImplemented();\\n }\\n\\n function _resolveAddress(\\n bytes calldata data,\\n bytes calldata context\\n ) internal pure returns (bytes memory) {\\n (, uint256 coinType) = abi.decode(data[4:], (bytes32, uint256));\\n bytes memory value;\\n // Per https://docs.ens.domains/ensip/11#specification\\n if (coinType & 0x80000000 != 0) {\\n value = _findValue(\\n context,\\n bytes.concat(\\n \\\"a[e\\\",\\n bytes((coinType & 0x7fffffff).toString()),\\n \\\"]=\\\"\\n )\\n );\\n } else {\\n value = _findValue(\\n context,\\n bytes.concat(\\\"a[\\\", bytes(coinType.toString()), \\\"]=\\\")\\n );\\n }\\n if (value.length == 0) {\\n return value;\\n }\\n (bytes memory record, bool valid) = value.hexToBytes(2, value.length);\\n if (!valid) revert InvalidAddressFormat(value);\\n return record;\\n }\\n\\n function _resolveAddr(\\n bytes calldata context\\n ) internal pure returns (bytes memory) {\\n bytes memory value = _findValue(context, \\\"a[60]=\\\");\\n if (value.length == 0) {\\n return value;\\n }\\n (bytes memory record, bool valid) = value.hexToBytes(2, value.length);\\n if (!valid) revert InvalidAddressFormat(value);\\n return record;\\n }\\n\\n function _resolveText(\\n bytes calldata data,\\n bytes calldata context\\n ) internal pure returns (bytes memory) {\\n (, string memory key) = abi.decode(data[4:], (bytes32, string));\\n bytes memory value = _findValue(\\n context,\\n bytes.concat(\\\"t[\\\", bytes(key), \\\"]=\\\")\\n );\\n return value;\\n }\\n\\n uint256 constant STATE_START = 0;\\n uint256 constant STATE_IGNORED_KEY = 1;\\n uint256 constant STATE_IGNORED_KEY_ARG = 2;\\n uint256 constant STATE_VALUE = 3;\\n uint256 constant STATE_QUOTED_VALUE = 4;\\n uint256 constant STATE_UNQUOTED_VALUE = 5;\\n uint256 constant STATE_IGNORED_VALUE = 6;\\n uint256 constant STATE_IGNORED_QUOTED_VALUE = 7;\\n uint256 constant STATE_IGNORED_UNQUOTED_VALUE = 8;\\n\\n /**\\n * @dev Implements a DFA to parse the text record, looking for an entry\\n * matching `key`.\\n * @param data The text record to parse.\\n * @param key The exact key to search for.\\n * @return value The value if found, or an empty string if `key` does not exist.\\n */\\n function _findValue(\\n bytes memory data,\\n bytes memory key\\n ) internal pure returns (bytes memory value) {\\n // Here we use a simple state machine to parse the text record. We\\n // process characters one at a time; each character can trigger a\\n // transition to a new state, or terminate the DFA and return a value.\\n // For states that expect to process a number of tokens, we use\\n // inner loops for efficiency reasons, to avoid the need to go\\n // through the outer loop and switch statement for every character.\\n uint256 state = STATE_START;\\n uint256 len = data.length;\\n for (uint256 i = 0; i < len; ) {\\n if (state == STATE_START) {\\n // Look for a matching key.\\n if (data.equals(i, key, 0, key.length)) {\\n i += key.length;\\n state = STATE_VALUE;\\n } else {\\n state = STATE_IGNORED_KEY;\\n }\\n } else if (state == STATE_IGNORED_KEY) {\\n for (; i < len; i++) {\\n if (data[i] == \\\"=\\\") {\\n state = STATE_IGNORED_VALUE;\\n i += 1;\\n break;\\n } else if (data[i] == \\\"[\\\") {\\n state = STATE_IGNORED_KEY_ARG;\\n i += 1;\\n break;\\n }\\n }\\n } else if (state == STATE_IGNORED_KEY_ARG) {\\n for (; i < len; i++) {\\n if (data[i] == \\\"]\\\") {\\n state = STATE_IGNORED_VALUE;\\n i += 1;\\n if (data[i] == \\\"=\\\") {\\n i += 1;\\n }\\n break;\\n }\\n }\\n } else if (state == STATE_VALUE) {\\n if (data[i] == \\\"'\\\") {\\n state = STATE_QUOTED_VALUE;\\n i += 1;\\n } else {\\n state = STATE_UNQUOTED_VALUE;\\n }\\n } else if (state == STATE_QUOTED_VALUE) {\\n uint256 start = i;\\n uint256 valueLen = 0;\\n bool escaped = false;\\n for (; i < len; i++) {\\n if (escaped) {\\n data[start + valueLen] = data[i];\\n valueLen += 1;\\n escaped = false;\\n } else {\\n if (data[i] == \\\"\\\\\\\\\\\") {\\n escaped = true;\\n } else if (data[i] == \\\"'\\\") {\\n return data.substring(start, valueLen);\\n } else {\\n data[start + valueLen] = data[i];\\n valueLen += 1;\\n }\\n }\\n }\\n } else if (state == STATE_UNQUOTED_VALUE) {\\n uint256 start = i;\\n for (; i < len; i++) {\\n if (data[i] == \\\" \\\") {\\n return data.substring(start, i - start);\\n }\\n }\\n return data.substring(start, len - start);\\n } else if (state == STATE_IGNORED_VALUE) {\\n if (data[i] == \\\"'\\\") {\\n state = STATE_IGNORED_QUOTED_VALUE;\\n i += 1;\\n } else {\\n state = STATE_IGNORED_UNQUOTED_VALUE;\\n }\\n } else if (state == STATE_IGNORED_QUOTED_VALUE) {\\n bool escaped = false;\\n for (; i < len; i++) {\\n if (escaped) {\\n escaped = false;\\n } else {\\n if (data[i] == \\\"\\\\\\\\\\\") {\\n escaped = true;\\n } else if (data[i] == \\\"'\\\") {\\n i += 1;\\n while (data[i] == \\\" \\\") {\\n i += 1;\\n }\\n state = STATE_START;\\n break;\\n }\\n }\\n }\\n } else {\\n assert(state == STATE_IGNORED_UNQUOTED_VALUE);\\n for (; i < len; i++) {\\n if (data[i] == \\\" \\\") {\\n while (data[i] == \\\" \\\") {\\n i += 1;\\n }\\n state = STATE_START;\\n break;\\n }\\n }\\n }\\n }\\n return \\\"\\\";\\n }\\n}\\n\",\"keccak256\":\"0xb8954b152d8fb29cd7239e33d0c17c3dfb52ff7cd6d4c060bb72e62b0ae8e197\",\"license\":\"MIT\"},\"contracts/resolvers/profiles/IAddrResolver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity >=0.8.4;\\n\\n/**\\n * Interface for the legacy (ETH-only) addr function.\\n */\\ninterface IAddrResolver {\\n event AddrChanged(bytes32 indexed node, address a);\\n\\n /**\\n * Returns the address associated with an ENS node.\\n * @param node The ENS node to query.\\n * @return The associated address.\\n */\\n function addr(bytes32 node) external view returns (address payable);\\n}\\n\",\"keccak256\":\"0x2ad7f2fc60ebe0f93745fe70247f6a854f66af732483fda2a3c5e055614445e8\",\"license\":\"MIT\"},\"contracts/resolvers/profiles/IAddressResolver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity >=0.8.4;\\n\\n/**\\n * Interface for the new (multicoin) addr function.\\n */\\ninterface IAddressResolver {\\n event AddressChanged(\\n bytes32 indexed node,\\n uint256 coinType,\\n bytes newAddress\\n );\\n\\n function addr(\\n bytes32 node,\\n uint256 coinType\\n ) external view returns (bytes memory);\\n}\\n\",\"keccak256\":\"0x411447c1e90c51e09702815a85ec725ffbbe37cf96e8cc4d2a8bd4ad8a59d73e\",\"license\":\"MIT\"},\"contracts/resolvers/profiles/IExtendedDNSResolver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.4;\\n\\ninterface IExtendedDNSResolver {\\n function resolve(\\n bytes memory name,\\n bytes memory data,\\n bytes memory context\\n ) external view returns (bytes memory);\\n}\\n\",\"keccak256\":\"0x541f8799c34ff9e7035d09f06ae0f0f8a16b6065e9b60a15670b957321630f72\",\"license\":\"MIT\"},\"contracts/resolvers/profiles/ITextResolver.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity >=0.8.4;\\n\\ninterface ITextResolver {\\n event TextChanged(\\n bytes32 indexed node,\\n string indexed indexedKey,\\n string key,\\n string value\\n );\\n\\n /**\\n * Returns the text data associated with an ENS node and key.\\n * @param node The ENS node to query.\\n * @param key The text data key to query.\\n * @return The associated text data.\\n */\\n function text(\\n bytes32 node,\\n string calldata key\\n ) external view returns (string memory);\\n}\\n\",\"keccak256\":\"0x7c5debb3c42cd9f5de2274ea7aa053f238608314b62db441c40e31cea2543fd5\",\"license\":\"MIT\"},\"contracts/utils/BytesUtils.sol\":{\"content\":\"//SPDX-License-Identifier: MIT\\npragma solidity ^0.8.4;\\n\\nlibrary BytesUtils {\\n error OffsetOutOfBoundsError(uint256 offset, uint256 length);\\n\\n /*\\n * @dev Returns the keccak-256 hash of a byte range.\\n * @param self The byte string to hash.\\n * @param offset The position to start hashing at.\\n * @param len The number of bytes to hash.\\n * @return The hash of the byte range.\\n */\\n function keccak(\\n bytes memory self,\\n uint256 offset,\\n uint256 len\\n ) internal pure returns (bytes32 ret) {\\n require(offset + len <= self.length);\\n assembly {\\n ret := keccak256(add(add(self, 32), offset), len)\\n }\\n }\\n\\n /**\\n * @dev Returns the ENS namehash of a DNS-encoded name.\\n * @param self The DNS-encoded name to hash.\\n * @param offset The offset at which to start hashing.\\n * @return The namehash of the name.\\n */\\n function namehash(\\n bytes memory self,\\n uint256 offset\\n ) internal pure returns (bytes32) {\\n (bytes32 labelhash, uint256 newOffset) = readLabel(self, offset);\\n if (labelhash == bytes32(0)) {\\n require(offset == self.length - 1, \\\"namehash: Junk at end of name\\\");\\n return bytes32(0);\\n }\\n return\\n keccak256(abi.encodePacked(namehash(self, newOffset), labelhash));\\n }\\n\\n /**\\n * @dev Returns the keccak-256 hash of a DNS-encoded label, and the offset to the start of the next label.\\n * @param self The byte string to read a label from.\\n * @param idx The index to read a label at.\\n * @return labelhash The hash of the label at the specified index, or 0 if it is the last label.\\n * @return newIdx The index of the start of the next label.\\n */\\n function readLabel(\\n bytes memory self,\\n uint256 idx\\n ) internal pure returns (bytes32 labelhash, uint256 newIdx) {\\n require(idx < self.length, \\\"readLabel: Index out of bounds\\\");\\n uint256 len = uint256(uint8(self[idx]));\\n if (len > 0) {\\n labelhash = keccak(self, idx + 1, len);\\n } else {\\n labelhash = bytes32(0);\\n }\\n newIdx = idx + len + 1;\\n }\\n\\n /*\\n * @dev Returns a positive number if `other` comes lexicographically after\\n * `self`, a negative number if it comes before, or zero if the\\n * contents of the two bytes are equal.\\n * @param self The first bytes to compare.\\n * @param other The second bytes to compare.\\n * @return The result of the comparison.\\n */\\n function compare(\\n bytes memory self,\\n bytes memory other\\n ) internal pure returns (int256) {\\n return compare(self, 0, self.length, other, 0, other.length);\\n }\\n\\n /*\\n * @dev Returns a positive number if `other` comes lexicographically after\\n * `self`, a negative number if it comes before, or zero if the\\n * contents of the two bytes are equal. Comparison is done per-rune,\\n * on unicode codepoints.\\n * @param self The first bytes to compare.\\n * @param offset The offset of self.\\n * @param len The length of self.\\n * @param other The second bytes to compare.\\n * @param otheroffset The offset of the other string.\\n * @param otherlen The length of the other string.\\n * @return The result of the comparison.\\n */\\n function compare(\\n bytes memory self,\\n uint256 offset,\\n uint256 len,\\n bytes memory other,\\n uint256 otheroffset,\\n uint256 otherlen\\n ) internal pure returns (int256) {\\n if (offset + len > self.length) {\\n revert OffsetOutOfBoundsError(offset + len, self.length);\\n }\\n if (otheroffset + otherlen > other.length) {\\n revert OffsetOutOfBoundsError(otheroffset + otherlen, other.length);\\n }\\n\\n uint256 shortest = len;\\n if (otherlen < len) shortest = otherlen;\\n\\n uint256 selfptr;\\n uint256 otherptr;\\n\\n assembly {\\n selfptr := add(self, add(offset, 32))\\n otherptr := add(other, add(otheroffset, 32))\\n }\\n for (uint256 idx = 0; idx < shortest; idx += 32) {\\n uint256 a;\\n uint256 b;\\n assembly {\\n a := mload(selfptr)\\n b := mload(otherptr)\\n }\\n if (a != b) {\\n // Mask out irrelevant bytes and check again\\n uint256 mask;\\n if (shortest - idx >= 32) {\\n mask = type(uint256).max;\\n } else {\\n mask = ~(2 ** (8 * (idx + 32 - shortest)) - 1);\\n }\\n int256 diff = int256(a & mask) - int256(b & mask);\\n if (diff != 0) return diff;\\n }\\n selfptr += 32;\\n otherptr += 32;\\n }\\n\\n return int256(len) - int256(otherlen);\\n }\\n\\n /*\\n * @dev Returns true if the two byte ranges are equal.\\n * @param self The first byte range to compare.\\n * @param offset The offset into the first byte range.\\n * @param other The second byte range to compare.\\n * @param otherOffset The offset into the second byte range.\\n * @param len The number of bytes to compare\\n * @return True if the byte ranges are equal, false otherwise.\\n */\\n function equals(\\n bytes memory self,\\n uint256 offset,\\n bytes memory other,\\n uint256 otherOffset,\\n uint256 len\\n ) internal pure returns (bool) {\\n return keccak(self, offset, len) == keccak(other, otherOffset, len);\\n }\\n\\n /*\\n * @dev Returns true if the two byte ranges are equal with offsets.\\n * @param self The first byte range to compare.\\n * @param offset The offset into the first byte range.\\n * @param other The second byte range to compare.\\n * @param otherOffset The offset into the second byte range.\\n * @return True if the byte ranges are equal, false otherwise.\\n */\\n function equals(\\n bytes memory self,\\n uint256 offset,\\n bytes memory other,\\n uint256 otherOffset\\n ) internal pure returns (bool) {\\n return\\n keccak(self, offset, self.length - offset) ==\\n keccak(other, otherOffset, other.length - otherOffset);\\n }\\n\\n /*\\n * @dev Compares a range of 'self' to all of 'other' and returns True iff\\n * they are equal.\\n * @param self The first byte range to compare.\\n * @param offset The offset into the first byte range.\\n * @param other The second byte range to compare.\\n * @return True if the byte ranges are equal, false otherwise.\\n */\\n function equals(\\n bytes memory self,\\n uint256 offset,\\n bytes memory other\\n ) internal pure returns (bool) {\\n return\\n self.length == offset + other.length &&\\n equals(self, offset, other, 0, other.length);\\n }\\n\\n /*\\n * @dev Returns true if the two byte ranges are equal.\\n * @param self The first byte range to compare.\\n * @param other The second byte range to compare.\\n * @return True if the byte ranges are equal, false otherwise.\\n */\\n function equals(\\n bytes memory self,\\n bytes memory other\\n ) internal pure returns (bool) {\\n return\\n self.length == other.length &&\\n equals(self, 0, other, 0, self.length);\\n }\\n\\n /*\\n * @dev Returns the 8-bit number at the specified index of self.\\n * @param self The byte string.\\n * @param idx The index into the bytes\\n * @return The specified 8 bits of the string, interpreted as an integer.\\n */\\n function readUint8(\\n bytes memory self,\\n uint256 idx\\n ) internal pure returns (uint8 ret) {\\n return uint8(self[idx]);\\n }\\n\\n /*\\n * @dev Returns the 16-bit number at the specified index of self.\\n * @param self The byte string.\\n * @param idx The index into the bytes\\n * @return The specified 16 bits of the string, interpreted as an integer.\\n */\\n function readUint16(\\n bytes memory self,\\n uint256 idx\\n ) internal pure returns (uint16 ret) {\\n require(idx + 2 <= self.length);\\n assembly {\\n ret := and(mload(add(add(self, 2), idx)), 0xFFFF)\\n }\\n }\\n\\n /*\\n * @dev Returns the 32-bit number at the specified index of self.\\n * @param self The byte string.\\n * @param idx The index into the bytes\\n * @return The specified 32 bits of the string, interpreted as an integer.\\n */\\n function readUint32(\\n bytes memory self,\\n uint256 idx\\n ) internal pure returns (uint32 ret) {\\n require(idx + 4 <= self.length);\\n assembly {\\n ret := and(mload(add(add(self, 4), idx)), 0xFFFFFFFF)\\n }\\n }\\n\\n /*\\n * @dev Returns the 32 byte value at the specified index of self.\\n * @param self The byte string.\\n * @param idx The index into the bytes\\n * @return The specified 32 bytes of the string.\\n */\\n function readBytes32(\\n bytes memory self,\\n uint256 idx\\n ) internal pure returns (bytes32 ret) {\\n require(idx + 32 <= self.length);\\n assembly {\\n ret := mload(add(add(self, 32), idx))\\n }\\n }\\n\\n /*\\n * @dev Returns the 32 byte value at the specified index of self.\\n * @param self The byte string.\\n * @param idx The index into the bytes\\n * @return The specified 32 bytes of the string.\\n */\\n function readBytes20(\\n bytes memory self,\\n uint256 idx\\n ) internal pure returns (bytes20 ret) {\\n require(idx + 20 <= self.length);\\n assembly {\\n ret := and(\\n mload(add(add(self, 32), idx)),\\n 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000\\n )\\n }\\n }\\n\\n /*\\n * @dev Returns the n byte value at the specified index of self.\\n * @param self The byte string.\\n * @param idx The index into the bytes.\\n * @param len The number of bytes.\\n * @return The specified 32 bytes of the string.\\n */\\n function readBytesN(\\n bytes memory self,\\n uint256 idx,\\n uint256 len\\n ) internal pure returns (bytes32 ret) {\\n require(len <= 32);\\n require(idx + len <= self.length);\\n assembly {\\n let mask := not(sub(exp(256, sub(32, len)), 1))\\n ret := and(mload(add(add(self, 32), idx)), mask)\\n }\\n }\\n\\n function memcpy(uint256 dest, uint256 src, uint256 len) private pure {\\n // Copy word-length chunks while possible\\n for (; len >= 32; len -= 32) {\\n assembly {\\n mstore(dest, mload(src))\\n }\\n dest += 32;\\n src += 32;\\n }\\n\\n // Copy remaining bytes\\n unchecked {\\n uint256 mask = (256 ** (32 - len)) - 1;\\n assembly {\\n let srcpart := and(mload(src), not(mask))\\n let destpart := and(mload(dest), mask)\\n mstore(dest, or(destpart, srcpart))\\n }\\n }\\n }\\n\\n /*\\n * @dev Copies a substring into a new byte string.\\n * @param self The byte string to copy from.\\n * @param offset The offset to start copying at.\\n * @param len The number of bytes to copy.\\n */\\n function substring(\\n bytes memory self,\\n uint256 offset,\\n uint256 len\\n ) internal pure returns (bytes memory) {\\n require(offset + len <= self.length);\\n\\n bytes memory ret = new bytes(len);\\n uint256 dest;\\n uint256 src;\\n\\n assembly {\\n dest := add(ret, 32)\\n src := add(add(self, 32), offset)\\n }\\n memcpy(dest, src, len);\\n\\n return ret;\\n }\\n\\n // Maps characters from 0x30 to 0x7A to their base32 values.\\n // 0xFF represents invalid characters in that range.\\n bytes constant base32HexTable =\\n hex\\\"00010203040506070809FFFFFFFFFFFFFF0A0B0C0D0E0F101112131415161718191A1B1C1D1E1FFFFFFFFFFFFFFFFFFFFF0A0B0C0D0E0F101112131415161718191A1B1C1D1E1F\\\";\\n\\n /**\\n * @dev Decodes unpadded base32 data of up to one word in length.\\n * @param self The data to decode.\\n * @param off Offset into the string to start at.\\n * @param len Number of characters to decode.\\n * @return The decoded data, left aligned.\\n */\\n function base32HexDecodeWord(\\n bytes memory self,\\n uint256 off,\\n uint256 len\\n ) internal pure returns (bytes32) {\\n require(len <= 52);\\n\\n uint256 ret = 0;\\n uint8 decoded;\\n for (uint256 i = 0; i < len; i++) {\\n bytes1 char = self[off + i];\\n require(char >= 0x30 && char <= 0x7A);\\n decoded = uint8(base32HexTable[uint256(uint8(char)) - 0x30]);\\n require(decoded <= 0x20);\\n if (i == len - 1) {\\n break;\\n }\\n ret = (ret << 5) | decoded;\\n }\\n\\n uint256 bitlen = len * 5;\\n if (len % 8 == 0) {\\n // Multiple of 8 characters, no padding\\n ret = (ret << 5) | decoded;\\n } else if (len % 8 == 2) {\\n // Two extra characters - 1 byte\\n ret = (ret << 3) | (decoded >> 2);\\n bitlen -= 2;\\n } else if (len % 8 == 4) {\\n // Four extra characters - 2 bytes\\n ret = (ret << 1) | (decoded >> 4);\\n bitlen -= 4;\\n } else if (len % 8 == 5) {\\n // Five extra characters - 3 bytes\\n ret = (ret << 4) | (decoded >> 1);\\n bitlen -= 1;\\n } else if (len % 8 == 7) {\\n // Seven extra characters - 4 bytes\\n ret = (ret << 2) | (decoded >> 3);\\n bitlen -= 3;\\n } else {\\n revert();\\n }\\n\\n return bytes32(ret << (256 - bitlen));\\n }\\n\\n /**\\n * @dev Finds the first occurrence of the byte `needle` in `self`.\\n * @param self The string to search\\n * @param off The offset to start searching at\\n * @param len The number of bytes to search\\n * @param needle The byte to search for\\n * @return The offset of `needle` in `self`, or 2**256-1 if it was not found.\\n */\\n function find(\\n bytes memory self,\\n uint256 off,\\n uint256 len,\\n bytes1 needle\\n ) internal pure returns (uint256) {\\n for (uint256 idx = off; idx < off + len; idx++) {\\n if (self[idx] == needle) {\\n return idx;\\n }\\n }\\n return type(uint256).max;\\n }\\n}\\n\",\"keccak256\":\"0xc566a3569af880a096a9bfb2fbb77060ef7aecde1a205dc26446a58877412060\",\"license\":\"MIT\"},\"contracts/utils/HexUtils.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.4;\\n\\nlibrary HexUtils {\\n /**\\n * @dev Attempts to parse bytes32 from a hex string\\n * @param str The string to parse\\n * @param idx The offset to start parsing at\\n * @param lastIdx The (exclusive) last index in `str` to consider. Use `str.length` to scan the whole string.\\n */\\n function hexStringToBytes32(\\n bytes memory str,\\n uint256 idx,\\n uint256 lastIdx\\n ) internal pure returns (bytes32, bool) {\\n require(lastIdx - idx <= 64);\\n (bytes memory r, bool valid) = hexToBytes(str, idx, lastIdx);\\n if (!valid) {\\n return (bytes32(0), false);\\n }\\n bytes32 ret;\\n assembly {\\n ret := shr(mul(4, sub(64, sub(lastIdx, idx))), mload(add(r, 32)))\\n }\\n return (ret, true);\\n }\\n\\n function hexToBytes(\\n bytes memory str,\\n uint256 idx,\\n uint256 lastIdx\\n ) internal pure returns (bytes memory r, bool valid) {\\n uint256 hexLength = lastIdx - idx;\\n if (hexLength % 2 == 1) {\\n revert(\\\"Invalid string length\\\");\\n }\\n r = new bytes(hexLength / 2);\\n valid = true;\\n assembly {\\n // check that the index to read to is not past the end of the string\\n if gt(lastIdx, mload(str)) {\\n revert(0, 0)\\n }\\n\\n function getHex(c) -> ascii {\\n // chars 48-57: 0-9\\n if and(gt(c, 47), lt(c, 58)) {\\n ascii := sub(c, 48)\\n leave\\n }\\n // chars 65-70: A-F\\n if and(gt(c, 64), lt(c, 71)) {\\n ascii := add(sub(c, 65), 10)\\n leave\\n }\\n // chars 97-102: a-f\\n if and(gt(c, 96), lt(c, 103)) {\\n ascii := add(sub(c, 97), 10)\\n leave\\n }\\n // invalid char\\n ascii := 0xff\\n }\\n\\n let ptr := add(str, 32)\\n for {\\n let i := idx\\n } lt(i, lastIdx) {\\n i := add(i, 2)\\n } {\\n let byte1 := getHex(byte(0, mload(add(ptr, i))))\\n let byte2 := getHex(byte(0, mload(add(ptr, add(i, 1)))))\\n // if either byte is invalid, set invalid and break loop\\n if or(eq(byte1, 0xff), eq(byte2, 0xff)) {\\n valid := false\\n break\\n }\\n let combined := or(shl(4, byte1), byte2)\\n mstore8(add(add(r, 32), div(sub(i, idx), 2)), combined)\\n }\\n }\\n }\\n\\n /**\\n * @dev Attempts to parse an address from a hex string\\n * @param str The string to parse\\n * @param idx The offset to start parsing at\\n * @param lastIdx The (exclusive) last index in `str` to consider. Use `str.length` to scan the whole string.\\n */\\n function hexToAddress(\\n bytes memory str,\\n uint256 idx,\\n uint256 lastIdx\\n ) internal pure returns (address, bool) {\\n if (lastIdx - idx < 40) return (address(0x0), false);\\n (bytes32 r, bool valid) = hexStringToBytes32(str, idx, lastIdx);\\n return (address(uint160(uint256(r))), valid);\\n }\\n\\n /**\\n * @dev Attempts to convert an address to a hex string\\n * @param addr The _addr to parse\\n */\\n function addressToHex(address addr) internal pure returns (string memory) {\\n bytes memory hexString = new bytes(40);\\n for (uint i = 0; i < 20; i++) {\\n bytes1 byteValue = bytes1(uint8(uint160(addr) >> (8 * (19 - i))));\\n bytes1 highNibble = bytes1(uint8(byteValue) / 16);\\n bytes1 lowNibble = bytes1(\\n uint8(byteValue) - 16 * uint8(highNibble)\\n );\\n hexString[2 * i] = _nibbleToHexChar(highNibble);\\n hexString[2 * i + 1] = _nibbleToHexChar(lowNibble);\\n }\\n return string(hexString);\\n }\\n\\n function _nibbleToHexChar(\\n bytes1 nibble\\n ) internal pure returns (bytes1 hexChar) {\\n if (uint8(nibble) < 10) return bytes1(uint8(nibble) + 0x30);\\n else return bytes1(uint8(nibble) + 0x57);\\n }\\n}\\n\",\"keccak256\":\"0xd6a9ab6d19632f634ee0f29173278fb4ba1d90fbbb470e779d76f278a8a2b90d\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x608060405234801561001057600080fd5b5061134e806100206000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c806301ffc9a71461003b5780638ef98a7e1461008d575b600080fd5b610078610049366004610ec7565b6001600160e01b0319167f8ef98a7e000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b6100a061009b366004610f41565b6100ad565b6040516100849190610fff565b606060006100bb8587611032565b90507fc4c4a822000000000000000000000000000000000000000000000000000000006001600160e01b0319821601610100576100f884846101b6565b9150506101ac565b7f0e3481fa000000000000000000000000000000000000000000000000000000006001600160e01b031982160161013d576100f886868686610292565b7fa62e2bc4000000000000000000000000000000000000000000000000000000006001600160e01b031982160161017a576100f8868686866103ed565b6040517fd623472500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b9695505050505050565b6060600061022e84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505060408051808201909152600681527f615b36305d3d0000000000000000000000000000000000000000000000000000602082015291506104659050565b9050805160000361024057905061028c565b60008061025a6002845185610a7e9092919063ffffffff16565b91509150806102875782604051630f79e00960e21b815260040161027e9190610fff565b60405180910390fd5b509150505b92915050565b606060006102a38560048189611062565b8101906102b0919061108c565b9150506060816380000000166000146103375761033085858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061030c92505050637fffffff8516610c26565b60405160200161031c91906110ae565b604051602081830303815290604052610465565b905061038f565b61038c85858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061037c9250869150610c269050565b60405160200161031c91906110ff565b90505b80516000036103a15791506103e59050565b6000806103bb6002845185610a7e9092919063ffffffff16565b91509150806103df5782604051630f79e00960e21b815260040161027e9190610fff565b50925050505b949350505050565b606060006103fe8560048189611062565b81019061040b9190611166565b915050600061045a85858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505060405161031c9250869150602001611221565b979650505050505050565b8151606090600090815b81811015610a6557826104b557845161049090879083908890600090610cc6565b156104ac5784516104a1908261126f565b90506003925061046f565b6001925061046f565b60018303610574575b8181101561056f578581815181106104d8576104d8611282565b01602001516001600160f81b031916603d60f81b03610507576006925061050060018261126f565b905061046f565b85818151811061051957610519611282565b01602001516001600160f81b0319167f5b000000000000000000000000000000000000000000000000000000000000000361055d576002925061050060018261126f565b8061056781611298565b9150506104be565b61046f565b60028303610625575b8181101561056f5785818151811061059757610597611282565b01602001516001600160f81b0319167f5d000000000000000000000000000000000000000000000000000000000000000361061357600692506105db60018261126f565b90508581815181106105ef576105ef611282565b01602001516001600160f81b031916603d60f81b0361056f5761050060018261126f565b8061061d81611298565b91505061057d565b600383036106705785818151811061063f5761063f611282565b01602001516001600160f81b031916602760f81b03610667576004925061050060018261126f565b6005925061046f565b6004830361081557806000805b8484101561080d57801561070c5788848151811061069d5761069d611282565b01602001516001600160f81b031916896106b7848661126f565b815181106106c7576106c7611282565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535061070160018361126f565b9150600090506107fb565b88848151811061071e5761071e611282565b01602001516001600160f81b031916601760fa1b0361073f575060016107fb565b88848151811061075157610751611282565b01602001516001600160f81b031916602760f81b0361078257610775898484610ce9565b965050505050505061028c565b88848151811061079457610794611282565b01602001516001600160f81b031916896107ae848661126f565b815181106107be576107be611282565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053506107f860018361126f565b91505b8361080581611298565b94505061067d565b50505061046f565b6005830361089357805b828210156108855786828151811061083957610839611282565b01602001516001600160f81b031916600160fd1b03610873576108688161086081856112b1565b899190610ce9565b94505050505061028c565b8161087d81611298565b92505061081f565b6108688161086081866112b1565b600683036108de578581815181106108ad576108ad611282565b01602001516001600160f81b031916602760f81b036108d5576007925061050060018261126f565b6008925061046f565b600783036109c95760005b828210156109c35780156108ff575060006109b1565b86828151811061091157610911611282565b01602001516001600160f81b031916601760fa1b03610932575060016109b1565b86828151811061094457610944611282565b01602001516001600160f81b031916602760f81b036109b15761096860018361126f565b91505b86828151811061097d5761097d611282565b01602001516001600160f81b031916600160fd1b036109a8576109a160018361126f565b915061096b565b600093506109c3565b816109bb81611298565b9250506108e9565b5061046f565b600883146109d9576109d96112c4565b8181101561056f578581815181106109f3576109f3611282565b01602001516001600160f81b031916600160fd1b03610a53575b858181518110610a1f57610a1f611282565b01602001516001600160f81b031916600160fd1b03610a4a57610a4360018261126f565b9050610a0d565b6000925061046f565b80610a5d81611298565b9150506109d9565b5050604080516020810190915260008152949350505050565b6060600080610a8d85856112b1565b9050610a9a6002826112f0565b600103610b03576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f496e76616c696420737472696e67206c656e6774680000000000000000000000604482015260640161027e565b610b0e600282611304565b67ffffffffffffffff811115610b2657610b26611150565b6040519080825280601f01601f191660200182016040528015610b50576020820181803683370190505b509250600191508551841115610b6557600080fd5b610bb6565b6000603a8210602f83111615610b825750602f190190565b60478210604083111615610b9857506036190190565b60678210606083111615610bae57506056190190565b5060ff919050565b60208601855b85811015610c1b57610bd38183015160001a610b6a565b610be56001830184015160001a610b6a565b60ff811460ff83141715610bfe57600095505050610c1b565b60049190911b178060028984030487016020015350600201610bbc565b505050935093915050565b60606000610c3383610d6b565b600101905060008167ffffffffffffffff811115610c5357610c53611150565b6040519080825280601f01601f191660200182016040528015610c7d576020820181803683370190505b5090508181016020015b600019017f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a8504945084610c8757509392505050565b6000610cd3848484610e4d565b610cde878785610e4d565b149695505050505050565b8251606090610cf8838561126f565b1115610d0357600080fd5b60008267ffffffffffffffff811115610d1e57610d1e611150565b6040519080825280601f01601f191660200182016040528015610d48576020820181803683370190505b50905060208082019086860101610d60828287610e71565b509095945050505050565b6000807a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310610db4577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000830492506040015b6d04ee2d6d415b85acef81000000008310610de0576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc100008310610dfe57662386f26fc10000830492506010015b6305f5e1008310610e16576305f5e100830492506008015b6127108310610e2a57612710830492506004015b60648310610e3c576064830492506002015b600a831061028c5760010192915050565b8251600090610e5c838561126f565b1115610e6757600080fd5b5091016020012090565b60208110610ea95781518352610e8860208461126f565b9250610e9560208361126f565b9150610ea26020826112b1565b9050610e71565b905182516020929092036101000a6000190180199091169116179052565b600060208284031215610ed957600080fd5b81356001600160e01b031981168114610ef157600080fd5b9392505050565b60008083601f840112610f0a57600080fd5b50813567ffffffffffffffff811115610f2257600080fd5b602083019150836020828501011115610f3a57600080fd5b9250929050565b60008060008060008060608789031215610f5a57600080fd5b863567ffffffffffffffff80821115610f7257600080fd5b610f7e8a838b01610ef8565b90985096506020890135915080821115610f9757600080fd5b610fa38a838b01610ef8565b90965094506040890135915080821115610fbc57600080fd5b50610fc989828a01610ef8565b979a9699509497509295939492505050565b60005b83811015610ff6578181015183820152602001610fde565b50506000910152565b602081526000825180602084015261101e816040850160208701610fdb565b601f01601f19169190910160400192915050565b6001600160e01b0319813581811691600485101561105a5780818660040360031b1b83161692505b505092915050565b6000808585111561107257600080fd5b8386111561107f57600080fd5b5050820193919092039150565b6000806040838503121561109f57600080fd5b50508035926020909101359150565b7f615b6500000000000000000000000000000000000000000000000000000000008152600082516110e6816003850160208701610fdb565b615d3d60f01b6003939091019283015250600501919050565b7f615b000000000000000000000000000000000000000000000000000000000000815260008251611137816002850160208701610fdb565b615d3d60f01b6002939091019283015250600401919050565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561117957600080fd5b82359150602083013567ffffffffffffffff8082111561119857600080fd5b818501915085601f8301126111ac57600080fd5b8135818111156111be576111be611150565b604051601f8201601f19908116603f011681019083821181831017156111e6576111e6611150565b816040528281528860208487010111156111ff57600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b7f745b000000000000000000000000000000000000000000000000000000000000815260008251611137816002850160208701610fdb565b634e487b7160e01b600052601160045260246000fd5b8082018082111561028c5761028c611259565b634e487b7160e01b600052603260045260246000fd5b6000600182016112aa576112aa611259565b5060010190565b8181038181111561028c5761028c611259565b634e487b7160e01b600052600160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b6000826112ff576112ff6112da565b500690565b600082611313576113136112da565b50049056fea26469706673582212203572cfc5fcb1ebedbb66aca92142c81b01bd1363a5a1c1b46d18a861668d369764736f6c63430008110033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100365760003560e01c806301ffc9a71461003b5780638ef98a7e1461008d575b600080fd5b610078610049366004610ec7565b6001600160e01b0319167f8ef98a7e000000000000000000000000000000000000000000000000000000001490565b60405190151581526020015b60405180910390f35b6100a061009b366004610f41565b6100ad565b6040516100849190610fff565b606060006100bb8587611032565b90507fc4c4a822000000000000000000000000000000000000000000000000000000006001600160e01b0319821601610100576100f884846101b6565b9150506101ac565b7f0e3481fa000000000000000000000000000000000000000000000000000000006001600160e01b031982160161013d576100f886868686610292565b7fa62e2bc4000000000000000000000000000000000000000000000000000000006001600160e01b031982160161017a576100f8868686866103ed565b6040517fd623472500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b9695505050505050565b6060600061022e84848080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505060408051808201909152600681527f615b36305d3d0000000000000000000000000000000000000000000000000000602082015291506104659050565b9050805160000361024057905061028c565b60008061025a6002845185610a7e9092919063ffffffff16565b91509150806102875782604051630f79e00960e21b815260040161027e9190610fff565b60405180910390fd5b509150505b92915050565b606060006102a38560048189611062565b8101906102b0919061108c565b9150506060816380000000166000146103375761033085858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061030c92505050637fffffff8516610c26565b60405160200161031c91906110ae565b604051602081830303815290604052610465565b905061038f565b61038c85858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061037c9250869150610c269050565b60405160200161031c91906110ff565b90505b80516000036103a15791506103e59050565b6000806103bb6002845185610a7e9092919063ffffffff16565b91509150806103df5782604051630f79e00960e21b815260040161027e9190610fff565b50925050505b949350505050565b606060006103fe8560048189611062565b81019061040b9190611166565b915050600061045a85858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152505060405161031c9250869150602001611221565b979650505050505050565b8151606090600090815b81811015610a6557826104b557845161049090879083908890600090610cc6565b156104ac5784516104a1908261126f565b90506003925061046f565b6001925061046f565b60018303610574575b8181101561056f578581815181106104d8576104d8611282565b01602001516001600160f81b031916603d60f81b03610507576006925061050060018261126f565b905061046f565b85818151811061051957610519611282565b01602001516001600160f81b0319167f5b000000000000000000000000000000000000000000000000000000000000000361055d576002925061050060018261126f565b8061056781611298565b9150506104be565b61046f565b60028303610625575b8181101561056f5785818151811061059757610597611282565b01602001516001600160f81b0319167f5d000000000000000000000000000000000000000000000000000000000000000361061357600692506105db60018261126f565b90508581815181106105ef576105ef611282565b01602001516001600160f81b031916603d60f81b0361056f5761050060018261126f565b8061061d81611298565b91505061057d565b600383036106705785818151811061063f5761063f611282565b01602001516001600160f81b031916602760f81b03610667576004925061050060018261126f565b6005925061046f565b6004830361081557806000805b8484101561080d57801561070c5788848151811061069d5761069d611282565b01602001516001600160f81b031916896106b7848661126f565b815181106106c7576106c7611282565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a90535061070160018361126f565b9150600090506107fb565b88848151811061071e5761071e611282565b01602001516001600160f81b031916601760fa1b0361073f575060016107fb565b88848151811061075157610751611282565b01602001516001600160f81b031916602760f81b0361078257610775898484610ce9565b965050505050505061028c565b88848151811061079457610794611282565b01602001516001600160f81b031916896107ae848661126f565b815181106107be576107be611282565b60200101907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916908160001a9053506107f860018361126f565b91505b8361080581611298565b94505061067d565b50505061046f565b6005830361089357805b828210156108855786828151811061083957610839611282565b01602001516001600160f81b031916600160fd1b03610873576108688161086081856112b1565b899190610ce9565b94505050505061028c565b8161087d81611298565b92505061081f565b6108688161086081866112b1565b600683036108de578581815181106108ad576108ad611282565b01602001516001600160f81b031916602760f81b036108d5576007925061050060018261126f565b6008925061046f565b600783036109c95760005b828210156109c35780156108ff575060006109b1565b86828151811061091157610911611282565b01602001516001600160f81b031916601760fa1b03610932575060016109b1565b86828151811061094457610944611282565b01602001516001600160f81b031916602760f81b036109b15761096860018361126f565b91505b86828151811061097d5761097d611282565b01602001516001600160f81b031916600160fd1b036109a8576109a160018361126f565b915061096b565b600093506109c3565b816109bb81611298565b9250506108e9565b5061046f565b600883146109d9576109d96112c4565b8181101561056f578581815181106109f3576109f3611282565b01602001516001600160f81b031916600160fd1b03610a53575b858181518110610a1f57610a1f611282565b01602001516001600160f81b031916600160fd1b03610a4a57610a4360018261126f565b9050610a0d565b6000925061046f565b80610a5d81611298565b9150506109d9565b5050604080516020810190915260008152949350505050565b6060600080610a8d85856112b1565b9050610a9a6002826112f0565b600103610b03576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601560248201527f496e76616c696420737472696e67206c656e6774680000000000000000000000604482015260640161027e565b610b0e600282611304565b67ffffffffffffffff811115610b2657610b26611150565b6040519080825280601f01601f191660200182016040528015610b50576020820181803683370190505b509250600191508551841115610b6557600080fd5b610bb6565b6000603a8210602f83111615610b825750602f190190565b60478210604083111615610b9857506036190190565b60678210606083111615610bae57506056190190565b5060ff919050565b60208601855b85811015610c1b57610bd38183015160001a610b6a565b610be56001830184015160001a610b6a565b60ff811460ff83141715610bfe57600095505050610c1b565b60049190911b178060028984030487016020015350600201610bbc565b505050935093915050565b60606000610c3383610d6b565b600101905060008167ffffffffffffffff811115610c5357610c53611150565b6040519080825280601f01601f191660200182016040528015610c7d576020820181803683370190505b5090508181016020015b600019017f3031323334353637383961626364656600000000000000000000000000000000600a86061a8153600a8504945084610c8757509392505050565b6000610cd3848484610e4d565b610cde878785610e4d565b149695505050505050565b8251606090610cf8838561126f565b1115610d0357600080fd5b60008267ffffffffffffffff811115610d1e57610d1e611150565b6040519080825280601f01601f191660200182016040528015610d48576020820181803683370190505b50905060208082019086860101610d60828287610e71565b509095945050505050565b6000807a184f03e93ff9f4daa797ed6e38ed64bf6a1f0100000000000000008310610db4577a184f03e93ff9f4daa797ed6e38ed64bf6a1f010000000000000000830492506040015b6d04ee2d6d415b85acef81000000008310610de0576d04ee2d6d415b85acef8100000000830492506020015b662386f26fc100008310610dfe57662386f26fc10000830492506010015b6305f5e1008310610e16576305f5e100830492506008015b6127108310610e2a57612710830492506004015b60648310610e3c576064830492506002015b600a831061028c5760010192915050565b8251600090610e5c838561126f565b1115610e6757600080fd5b5091016020012090565b60208110610ea95781518352610e8860208461126f565b9250610e9560208361126f565b9150610ea26020826112b1565b9050610e71565b905182516020929092036101000a6000190180199091169116179052565b600060208284031215610ed957600080fd5b81356001600160e01b031981168114610ef157600080fd5b9392505050565b60008083601f840112610f0a57600080fd5b50813567ffffffffffffffff811115610f2257600080fd5b602083019150836020828501011115610f3a57600080fd5b9250929050565b60008060008060008060608789031215610f5a57600080fd5b863567ffffffffffffffff80821115610f7257600080fd5b610f7e8a838b01610ef8565b90985096506020890135915080821115610f9757600080fd5b610fa38a838b01610ef8565b90965094506040890135915080821115610fbc57600080fd5b50610fc989828a01610ef8565b979a9699509497509295939492505050565b60005b83811015610ff6578181015183820152602001610fde565b50506000910152565b602081526000825180602084015261101e816040850160208701610fdb565b601f01601f19169190910160400192915050565b6001600160e01b0319813581811691600485101561105a5780818660040360031b1b83161692505b505092915050565b6000808585111561107257600080fd5b8386111561107f57600080fd5b5050820193919092039150565b6000806040838503121561109f57600080fd5b50508035926020909101359150565b7f615b6500000000000000000000000000000000000000000000000000000000008152600082516110e6816003850160208701610fdb565b615d3d60f01b6003939091019283015250600501919050565b7f615b000000000000000000000000000000000000000000000000000000000000815260008251611137816002850160208701610fdb565b615d3d60f01b6002939091019283015250600401919050565b634e487b7160e01b600052604160045260246000fd5b6000806040838503121561117957600080fd5b82359150602083013567ffffffffffffffff8082111561119857600080fd5b818501915085601f8301126111ac57600080fd5b8135818111156111be576111be611150565b604051601f8201601f19908116603f011681019083821181831017156111e6576111e6611150565b816040528281528860208487010111156111ff57600080fd5b8260208601602083013760006020848301015280955050505050509250929050565b7f745b000000000000000000000000000000000000000000000000000000000000815260008251611137816002850160208701610fdb565b634e487b7160e01b600052601160045260246000fd5b8082018082111561028c5761028c611259565b634e487b7160e01b600052603260045260246000fd5b6000600182016112aa576112aa611259565b5060010190565b8181038181111561028c5761028c611259565b634e487b7160e01b600052600160045260246000fd5b634e487b7160e01b600052601260045260246000fd5b6000826112ff576112ff6112da565b500690565b600082611313576113136112da565b50049056fea26469706673582212203572cfc5fcb1ebedbb66aca92142c81b01bd1363a5a1c1b46d18a861668d369764736f6c63430008110033", "devdoc": { + "details": "Resolves names on ENS by interpreting record data stored in a DNS TXT record. This resolver implements the IExtendedDNSResolver interface, meaning that when a DNS name specifies it as the resolver via a TXT record, this resolver's resolve() method is invoked, and is passed any additional information from that text record. This resolver implements a simple text parser allowing a variety of records to be specified in text, which will then be used to resolve the name in ENS. To use this, set a TXT record on your DNS name in the following format: ENS1
For example: ENS1 2.dnsname.ens.eth a[60]=0x1234... The record data consists of a series of key=value pairs, separated by spaces. Keys may have an optional argument in square brackets, and values may be either unquoted - in which case they may not contain spaces - or single-quoted. Single quotes in a quoted value may be backslash-escaped. ┌────────┐ │ ┌───┐ │ ┌──────────────────────────────┴─┤\" \"│◄─┴────────────────────────────────────────┐ │ └───┘ │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌────────────┐ ┌───┐ │ ^─┴─►│key├─┬─►│\"[\"├───►│arg├───►│\"]\"├─┬─►│\"=\"├─┬─►│\"'\"├───►│quoted_value├───►│\"'\"├─┼─$ └───┘ │ └───┘ └───┘ └───┘ │ └───┘ │ └───┘ └────────────┘ └───┘ │ └──────────────────────────┘ │ ┌──────────────┐ │ └─────────►│unquoted_value├─────────┘ └──────────────┘ Record types: - a[] - Specifies how an `addr()` request should be resolved for the specified `coinType`. Ethereum has `coinType` 60. The value must be 0x-prefixed hexadecimal, and will be returned unmodified; this means that non-EVM addresses will need to be translated into binary format and then encoded in hex. Examples: - a[60]=0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7 - a[0]=0x00149010587f8364b964fcaa70687216b53bd2cbd798 - a[e] - Specifies how an `addr()` request should be resolved for the specified `chainId`. The value must be 0x-prefixed hexadecimal. When encoding an address for an EVM-based cryptocurrency that uses a chainId instead of a coinType, this syntax *must* be used in place of the coin type - eg, Optimism is `a[e10]`, not `a[2147483658]`. A list of supported cryptocurrencies for both syntaxes can be found here: https://github.com/ensdomains/address-encoder/blob/master/docs/supported-cryptocurrencies.md Example: - a[e10]=0xFe89cc7aBB2C4183683ab71653C4cdc9B02D44b7 - t[] - Specifies how a `text()` request should be resolved for the specified `key`. Examples: - t[com.twitter]=nicksdjohnson - t[url]='https://ens.domains/' - t[note]='I\\'m great'", "kind": "dev", "methods": { "supportsInterface(bytes4)": { From 67e314551a9e8f9429ebb8231c30ecee2f3d9ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Tue, 27 Aug 2024 16:36:31 +0200 Subject: [PATCH 07/16] fix wildcard check by comparing to it's hex representation --- contracts/dnsregistrar/OffchainDNSResolver.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/dnsregistrar/OffchainDNSResolver.sol b/contracts/dnsregistrar/OffchainDNSResolver.sol index d4f777ae..8c21582e 100644 --- a/contracts/dnsregistrar/OffchainDNSResolver.sol +++ b/contracts/dnsregistrar/OffchainDNSResolver.sol @@ -176,7 +176,7 @@ contract OffchainDNSResolver is IExtendedResolver, IERC165 { function checkWildcard( bytes memory name ) public pure returns (bool isWildcard) { - return name.length > 4 && uint8(name[0]) == 1 && name[1] == "*"; + return name.length > 4 && uint8(name[0]) == 1 && name[1] == 0x2A; } function parseRR( From 92d01c613e4c3489fac22770ed963f0903a7f739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Thu, 24 Oct 2024 14:51:35 +0200 Subject: [PATCH 08/16] revert wildcard support --- contracts/dnsregistrar/OffchainDNSResolver.sol | 9 --------- test/dnsregistrar/TestOffchainDNSResolver.ts | 9 +++++---- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/contracts/dnsregistrar/OffchainDNSResolver.sol b/contracts/dnsregistrar/OffchainDNSResolver.sol index 8c21582e..2431fe4c 100644 --- a/contracts/dnsregistrar/OffchainDNSResolver.sol +++ b/contracts/dnsregistrar/OffchainDNSResolver.sol @@ -87,9 +87,6 @@ contract OffchainDNSResolver is IExtendedResolver, IERC165 { // Ignore records with wrong name, type, or class bytes memory rrname = RRUtils.readName(iter.data, iter.offset); uint256 nameOffset = 0; - if (checkWildcard(name)) { - nameOffset = 2; - } if ( !name.equals(nameOffset, rrname, 0, name.length - nameOffset) || @@ -173,12 +170,6 @@ contract OffchainDNSResolver is IExtendedResolver, IERC165 { ); } - function checkWildcard( - bytes memory name - ) public pure returns (bool isWildcard) { - return name.length > 4 && uint8(name[0]) == 1 && name[1] == 0x2A; - } - function parseRR( bytes memory data, uint256 idx, diff --git a/test/dnsregistrar/TestOffchainDNSResolver.ts b/test/dnsregistrar/TestOffchainDNSResolver.ts index 982ffd09..cfe1fc76 100644 --- a/test/dnsregistrar/TestOffchainDNSResolver.ts +++ b/test/dnsregistrar/TestOffchainDNSResolver.ts @@ -14,7 +14,6 @@ import { zeroHash, type Hex, encodeFunctionResult, - stringToHex, } from 'viem' import { expiration, @@ -622,7 +621,9 @@ describe('OffchainDNSResolver', () => { ], calldata: calldataAddr, }), - ).resolves.toEqual(testAddress.toLowerCase() as `0x${string}`) + ).resolves.toEqual( + encodeAbiParameters([{ type: 'address' }], [testAddress as Address]), + ) const callDataText = encodeFunctionData({ abi: publicResolverAbi, @@ -703,7 +704,7 @@ describe('OffchainDNSResolver', () => { texts: [`ENS1 ${resolver.address} t[smth]=smth.eth ${testAddress}`], calldata: callDataText, }), - ).resolves.toEqual(stringToHex('smth.eth')) + ).resolves.toEqual(encodeAbiParameters([{ type: 'string' }], ['smth.eth'])) }) it('should correctly do text resolution regardless of key-value pair amount', async function () { @@ -727,6 +728,6 @@ describe('OffchainDNSResolver', () => { texts: [`ENS1 ${resolver.address} t[smth]=smth.eth t[bla]=bla.eth`], calldata: callDataText, }), - ).resolves.toEqual(stringToHex('bla.eth')) + ).resolves.toEqual(encodeAbiParameters([{ type: 'string' }], ['bla.eth'])) }) }) From c03968bbb06e2ee7c50c7c6a54bda54be8c135f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Thu, 24 Oct 2024 15:06:22 +0200 Subject: [PATCH 09/16] update deprecated experimental loader, bump hardhat --- bun.lockb | Bin 236719 -> 243187 bytes loader.mjs | 4 ++++ package.json | 10 +++++----- scripts/deploy-test.ts | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 loader.mjs diff --git a/bun.lockb b/bun.lockb index e638a6ff19b365137f18bdb55960bdc287f92bbe..cdfe22187446ef1272d45e63dc87e2d88a0287a6 100755 GIT binary patch delta 17243 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 NqC@5I2=zFx{{o0YqrU(E delta 14717 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 diff --git a/loader.mjs b/loader.mjs new file mode 100644 index 00000000..2b08a40f --- /dev/null +++ b/loader.mjs @@ -0,0 +1,4 @@ +import { register } from 'node:module' +import { pathToFileURL } from 'node:url' + +register('ts-node/esm/transpile-only', pathToFileURL('./')) diff --git a/package.json b/package.json index 2040ce41..a6a09cb4 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,10 @@ "description": "ENS contracts", "type": "module", "scripts": { - "compile": "NODE_OPTIONS=\"--experimental-loader ts-node/esm/transpile-only\" hardhat compile", - "test": "NODE_OPTIONS=\"--experimental-loader ts-node/esm/transpile-only\" hardhat test", - "test:parallel": "NODE_OPTIONS=\"--experimental-loader ts-node/esm/transpile-only\" hardhat test ./test/**/Test*.ts --parallel", - "test:local": "hardhat --network localhost test", + "compile": "NODE_OPTIONS=\"--import=./loader.mjs\" hardhat compile", + "test": "NODE_OPTIONS=\"--import=./loader.mjs\" hardhat test", + "test:parallel": "NODE_OPTIONS=\"--import=./loader.mjs\" hardhat test ./test/**/Test*.ts --parallel", + "test:local": "NODE_OPTIONS=\"--import=./loader.mjs\" hardhat --network localhost test", "test:deploy": "bun ./scripts/deploy-test.ts", "lint": "hardhat check", "build": "rm -rf ./build/deploy ./build/hardhat.config.js && hardhat compile && tsc", @@ -37,7 +37,7 @@ "abitype": "^1.0.2", "chai": "^5.1.1", "dotenv": "^16.4.5", - "hardhat": "^2.22.2", + "hardhat": "^2.22.9", "hardhat-abi-exporter": "^2.9.0", "hardhat-contract-sizer": "^2.6.1", "hardhat-deploy": "^0.12.4", diff --git a/scripts/deploy-test.ts b/scripts/deploy-test.ts index 80b92389..c527b044 100644 --- a/scripts/deploy-test.ts +++ b/scripts/deploy-test.ts @@ -21,7 +21,7 @@ execSync('bun run hardhat --network localhost deploy', { stdio: 'inherit', env: { ...process.env, - NODE_OPTIONS: '--experimental-loader ts-node/esm/transpile-only', + NODE_OPTIONS: '--import=./loader.mjs', BATCH_GATEWAY_URLS: '["https://example.com/"]', }, }) From 7ff6a565fbe2915057052725dbac5df1ac53e0b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Thu, 24 Oct 2024 15:09:45 +0200 Subject: [PATCH 10/16] update deprecated buffer use --- deploy/dnssec-oracle/10_deploy_oracle.ts | 6 ++--- test/dnssec-oracle/TestDNSSEC.ts | 30 ++++++++++++------------ test/fixtures/anchors.ts | 6 ++--- test/fixtures/dns.ts | 6 ++--- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/deploy/dnssec-oracle/10_deploy_oracle.ts b/deploy/dnssec-oracle/10_deploy_oracle.ts index 2dd1a835..bdb265aa 100644 --- a/deploy/dnssec-oracle/10_deploy_oracle.ts +++ b/deploy/dnssec-oracle/10_deploy_oracle.ts @@ -12,7 +12,7 @@ const realAnchors = [ keyTag: 19036, algorithm: 8, digestType: 2, - digest: new Buffer( + digest: Buffer.from( '49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5', 'hex', ), @@ -27,7 +27,7 @@ const realAnchors = [ keyTag: 20326, algorithm: 8, digestType: 2, - digest: new Buffer( + digest: Buffer.from( 'E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D', 'hex', ), @@ -44,7 +44,7 @@ const dummyAnchor = { keyTag: 1278, // Empty body, flags == 0x0101, algorithm = 253, body = 0x0000 algorithm: 253, digestType: 253, - digest: new Buffer('', 'hex'), + digest: Buffer.from('', 'hex'), }, } diff --git a/test/dnssec-oracle/TestDNSSEC.ts b/test/dnssec-oracle/TestDNSSEC.ts index 95a7f138..5db12200 100644 --- a/test/dnssec-oracle/TestDNSSEC.ts +++ b/test/dnssec-oracle/TestDNSSEC.ts @@ -258,7 +258,7 @@ describe('DNSSEC', () => { inception, keyTag: 1278, signersName: '.', - signature: new Buffer([]), + signature: Buffer.from([]), }, }, rrs: [ @@ -297,7 +297,7 @@ describe('DNSSEC', () => { inception, keyTag: 1278, signersName: '.', - signature: new Buffer([]), + signature: Buffer.from([]), }, }, rrs: [ @@ -338,7 +338,7 @@ describe('DNSSEC', () => { inception, keyTag: 1278, signersName: '.', - signature: new Buffer([]), + signature: Buffer.from([]), }, }, rrs: [ @@ -379,7 +379,7 @@ describe('DNSSEC', () => { inception, keyTag: 1278, signersName: '.', - signature: new Buffer([]), + signature: Buffer.from([]), }, }, rrs: [ @@ -420,7 +420,7 @@ describe('DNSSEC', () => { inception, keyTag: 1278, signersName: 'com', - signature: new Buffer([]), + signature: Buffer.from([]), }, }, rrs: [ @@ -461,7 +461,7 @@ describe('DNSSEC', () => { inception, keyTag: 1278, signersName: '.', - signature: new Buffer([]), + signature: Buffer.from([]), }, }, rrs: [ @@ -494,7 +494,7 @@ describe('DNSSEC', () => { inception, keyTag: 1278, signersName: 'xample', - signature: new Buffer([]), + signature: Buffer.from([]), }, }, rrs: [ @@ -535,7 +535,7 @@ describe('DNSSEC', () => { inception, keyTag: 1278, signersName: '.', - signature: new Buffer([]), + signature: Buffer.from([]), }, }, rrs: [ @@ -568,7 +568,7 @@ describe('DNSSEC', () => { inception, keyTag: 1275, signersName: 'test', - signature: new Buffer([]), + signature: Buffer.from([]), }, }, rrs: [ @@ -667,7 +667,7 @@ describe('DNSSEC', () => { inception, keyTag: 1278, signersName: '.', - signature: new Buffer([]), + signature: Buffer.from([]), }, }, rrs: [ @@ -680,7 +680,7 @@ describe('DNSSEC', () => { keyTag: 1278, // Empty body, flags == 0x0101, algorithm = 253, body = 0x0000 algorithm: 253, digestType: 253, - digest: new Buffer('', 'hex'), + digest: Buffer.from('', 'hex'), }, }, ], @@ -701,7 +701,7 @@ describe('DNSSEC', () => { inception, keyTag: 1278, signersName: 'foo', - signature: new Buffer([]), + signature: Buffer.from([]), }, }, rrs: [ @@ -746,7 +746,7 @@ describe('DNSSEC', () => { inception, keyTag: 1278, signersName: '.', - signature: new Buffer([]), + signature: Buffer.from([]), }, }, rrs: [ @@ -759,7 +759,7 @@ describe('DNSSEC', () => { keyTag: 1278, // Empty body, flags == 0x0101, algorithm = 253, body = 0x0000 algorithm: 253, digestType: 253, - digest: new Buffer('', 'hex'), + digest: Buffer.from('', 'hex'), }, }, ], @@ -780,7 +780,7 @@ describe('DNSSEC', () => { inception, keyTag: 1278, signersName: 'test', - signature: new Buffer([]), + signature: Buffer.from([]), }, }, rrs: [ diff --git a/test/fixtures/anchors.ts b/test/fixtures/anchors.ts index e5ec7328..c3466217 100644 --- a/test/fixtures/anchors.ts +++ b/test/fixtures/anchors.ts @@ -10,7 +10,7 @@ export const realEntries = [ keyTag: 19036, algorithm: 8, digestType: 2, - digest: new Buffer( + digest: Buffer.from( '49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5', 'hex', ), @@ -25,7 +25,7 @@ export const realEntries = [ keyTag: 20326, algorithm: 8, digestType: 2, - digest: new Buffer( + digest: Buffer.from( 'E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D', 'hex', ), @@ -42,7 +42,7 @@ export const dummyEntry = { keyTag: 1278, // Empty body, flags == 0x0101, algorithm = 253, body = 0x0000 algorithm: 253, digestType: 253, - digest: new Buffer('', 'hex'), + digest: Buffer.from('', 'hex'), }, } as const diff --git a/test/fixtures/dns.ts b/test/fixtures/dns.ts index 91528f70..2584ea10 100644 --- a/test/fixtures/dns.ts +++ b/test/fixtures/dns.ts @@ -42,7 +42,7 @@ export const rrsetWithTexts = ({ inception, keyTag: 1278, signersName: '.', - signature: new Buffer([]), + signature: Buffer.from([]), }, }, rrs: texts.map( @@ -81,7 +81,7 @@ export const testRrset = ({ inception, keyTag: 1278, signersName: '.', - signature: new Buffer([]), + signature: Buffer.from([]), }, }, rrs: [ @@ -118,7 +118,7 @@ export const rootKeys = ({ inception, keyTag: 1278, signersName: '.', - signature: new Buffer([]), + signature: Buffer.from([]), }, } as const From 6bc18af7e6be591e4a81d4bf87fc49097e789d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Mon, 28 Oct 2024 15:08:38 +0100 Subject: [PATCH 11/16] revert redundant name offset --- contracts/dnsregistrar/OffchainDNSResolver.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contracts/dnsregistrar/OffchainDNSResolver.sol b/contracts/dnsregistrar/OffchainDNSResolver.sol index 2431fe4c..a8cff899 100644 --- a/contracts/dnsregistrar/OffchainDNSResolver.sol +++ b/contracts/dnsregistrar/OffchainDNSResolver.sol @@ -86,10 +86,9 @@ contract OffchainDNSResolver is IExtendedResolver, IERC165 { ) { // Ignore records with wrong name, type, or class bytes memory rrname = RRUtils.readName(iter.data, iter.offset); - uint256 nameOffset = 0; if ( - !name.equals(nameOffset, rrname, 0, name.length - nameOffset) || + !rrname.equals(name) || iter.class != CLASS_INET || iter.dnstype != TYPE_TXT ) { From f98f323ef86e42441d27344bc79ef6bf2d7bc2ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Mon, 28 Oct 2024 15:22:12 +0100 Subject: [PATCH 12/16] update lock file --- bun.lockb | Bin 243187 -> 232201 bytes .../reverseRegistrar/ReverseRegistrar.sol | 4 +--- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/bun.lockb b/bun.lockb index cdfe22187446ef1272d45e63dc87e2d88a0287a6..d685ed286ce7d30dfb15f7f6490a87ea5b2a43a8 100755 GIT binary patch delta 58642 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 E54#^YrT_o{ delta 64214 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

3;g_4 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 diff --git a/contracts/reverseRegistrar/ReverseRegistrar.sol b/contracts/reverseRegistrar/ReverseRegistrar.sol index b065bf0b..3dad97bb 100644 --- a/contracts/reverseRegistrar/ReverseRegistrar.sol +++ b/contracts/reverseRegistrar/ReverseRegistrar.sol @@ -165,9 +165,7 @@ contract ReverseRegistrar is Ownable, Controllable, IReverseRegistrar { assembly { for { let i := 40 - } gt(i, 0) { - - } { + } gt(i, 0) {} { i := sub(i, 1) mstore8(i, byte(and(addr, 0xf), lookup)) addr := div(addr, 0x10) From db8a085cf8b18afef68530249da5950fb4d3ed88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Tue, 29 Oct 2024 12:07:37 +0100 Subject: [PATCH 13/16] improve TXT concatenation testing --- test/dnsregistrar/TestOffchainDNSResolver.ts | 57 ++++++++++++++------ test/fixtures/dns.ts | 10 ++-- 2 files changed, 45 insertions(+), 22 deletions(-) diff --git a/test/dnsregistrar/TestOffchainDNSResolver.ts b/test/dnsregistrar/TestOffchainDNSResolver.ts index cfe1fc76..b9f34358 100644 --- a/test/dnsregistrar/TestOffchainDNSResolver.ts +++ b/test/dnsregistrar/TestOffchainDNSResolver.ts @@ -77,7 +77,7 @@ async function fixture() { calldata, }: { name: string - texts: string[] + texts: (string | string[])[] calldata: Hex }) => { const proof = [ @@ -596,14 +596,10 @@ describe('OffchainDNSResolver', () => { const { doDnsResolveCallback, publicResolverAbi } = await loadFixture( fixture, ) - const resolver = await hre.viem.deployContract( 'DummyExtendedDNSSECResolver', [], ) - - const resolver2 = await hre.viem.deployContract('ExtendedDNSResolver', []) - const name = 'test.test' const COIN_TYPE_ETH = 60 const testAddress = '0xfefeFEFeFEFEFEFEFeFefefefefeFEfEfefefEfe' @@ -613,39 +609,66 @@ describe('OffchainDNSResolver', () => { args: [namehash(name)], }) + const resultTextSingle = encodeFunctionResult({ + abi: publicResolverAbi, + functionName: 'text', + result: `a[60]=${testAddress} t[smth]=smth.eth`, + }) + + // test with single string await expect( doDnsResolveCallback({ name, texts: [ - `ENS1 ${resolver2.address} a[${COIN_TYPE_ETH}]=${testAddress} t[smth]=smth.eth`, + `ENS1 ${resolver.address} a[${COIN_TYPE_ETH}]=${testAddress} t[smth]=smth.eth`, ], calldata: calldataAddr, }), - ).resolves.toEqual( - encodeAbiParameters([{ type: 'address' }], [testAddress as Address]), - ) + ).resolves.toEqual(resultTextSingle) - const callDataText = encodeFunctionData({ + const resultTextSplit = encodeFunctionResult({ abi: publicResolverAbi, functionName: 'text', - args: [namehash(name), 'smth'], + result: `a[${COIN_TYPE_ETH}]=${testAddress} t[smth]=smth.eth`, }) - const resultText = encodeFunctionResult({ + // test with split strings + await expect( + doDnsResolveCallback({ + name, + texts: [ + [ + `ENS1 ${resolver.address}`, + ` a[${COIN_TYPE_ETH}]=${testAddress}`, + ` t[smth]=smth.eth`, + ], + ], + calldata: calldataAddr, + }), + ).resolves.toEqual(resultTextSplit) + + // test with very long string + const longData = 'x'.repeat(300) + + const resultTextLongData = encodeFunctionResult({ abi: publicResolverAbi, functionName: 'text', - result: `a[60]=${testAddress} t[smth]=smth.eth`, + result: `a[${COIN_TYPE_ETH}]=${testAddress} ${longData}`, }) - await expect( doDnsResolveCallback({ name, texts: [ - `ENS1 ${resolver.address} a[60]=${testAddress} t[smth]=smth.eth`, + [ + `ENS1 ${resolver.address} a[${COIN_TYPE_ETH}]=${testAddress}`, + ' ', + longData.slice(0, 255), + longData.slice(255), + ], ], - calldata: callDataText, + calldata: calldataAddr, }), - ).resolves.toEqual(resultText) + ).resolves.toEqual(resultTextLongData) }) it('should correctly do text resolution regardless of order', async function () { diff --git a/test/fixtures/dns.ts b/test/fixtures/dns.ts index 2584ea10..40d1d0c3 100644 --- a/test/fixtures/dns.ts +++ b/test/fixtures/dns.ts @@ -24,7 +24,7 @@ export const rrsetWithTexts = ({ texts, }: { name: string - texts: (string | { name: string; value: string })[] + texts: (string | string[])[] }) => ({ sig: { @@ -48,13 +48,13 @@ export const rrsetWithTexts = ({ rrs: texts.map( (text) => ({ - name: typeof text === 'string' ? name : text.name, + name, type: 'TXT', class: 'IN', ttl: 3600, - data: [ - Buffer.from(typeof text === 'string' ? text : text.value, 'ascii'), - ] as Buffer[], + data: Array.isArray(text) + ? text.map((t) => Buffer.from(t, 'ascii')) + : [Buffer.from(text, 'ascii')], } as const), ), } as const) From 7654735ca2472add8069a3f4c715e68358722fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Fri, 15 Nov 2024 20:26:49 +0100 Subject: [PATCH 14/16] Update contracts/dnsregistrar/OffchainDNSResolver.sol Co-authored-by: Nick Johnson --- contracts/dnsregistrar/OffchainDNSResolver.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/dnsregistrar/OffchainDNSResolver.sol b/contracts/dnsregistrar/OffchainDNSResolver.sol index a8cff899..21875c35 100644 --- a/contracts/dnsregistrar/OffchainDNSResolver.sol +++ b/contracts/dnsregistrar/OffchainDNSResolver.sol @@ -201,11 +201,9 @@ contract OffchainDNSResolver is IExtendedResolver, IERC165 { uint256 lastIdx ) internal pure returns (bytes memory) { uint256 totalLength = 0; - uint256 idx = startIdx; - while (idx < lastIdx) { + for (uint256 idx = startidx; idx < lastIdx; idx += fieldLength + 1) { uint256 fieldLength = data.readUint8(idx); totalLength += fieldLength; - idx += fieldLength + 1; } bytes memory result = new bytes(totalLength); From 4630340150a2734a121305bcd5d0e433f38138e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Fri, 15 Nov 2024 20:27:01 +0100 Subject: [PATCH 15/16] Update contracts/dnsregistrar/OffchainDNSResolver.sol Co-authored-by: Nick Johnson --- contracts/dnsregistrar/OffchainDNSResolver.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/dnsregistrar/OffchainDNSResolver.sol b/contracts/dnsregistrar/OffchainDNSResolver.sol index 21875c35..98f8d51b 100644 --- a/contracts/dnsregistrar/OffchainDNSResolver.sol +++ b/contracts/dnsregistrar/OffchainDNSResolver.sol @@ -207,13 +207,11 @@ contract OffchainDNSResolver is IExtendedResolver, IERC165 { } bytes memory result = new bytes(totalLength); - idx = startIdx; uint256 resultIdx = 0; - while (idx < lastIdx) { + for (uint256 idx = startIdx; idx < lastIdx; idx += fieldLength + 1) { uint256 fieldLength = data.readUint8(idx); result.strcpy(resultIdx, data, idx + 1, fieldLength); resultIdx += fieldLength; - idx += fieldLength + 1; } return result; } From 24fcb62a7f332f859e98e1e80d39770747f66792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Muhammed=20Tanr=C4=B1kulu?= Date: Fri, 15 Nov 2024 20:33:17 +0100 Subject: [PATCH 16/16] update for loops in readTXT method --- contracts/dnsregistrar/OffchainDNSResolver.sol | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/contracts/dnsregistrar/OffchainDNSResolver.sol b/contracts/dnsregistrar/OffchainDNSResolver.sol index 98f8d51b..5c7e5bbc 100644 --- a/contracts/dnsregistrar/OffchainDNSResolver.sol +++ b/contracts/dnsregistrar/OffchainDNSResolver.sol @@ -201,17 +201,21 @@ contract OffchainDNSResolver is IExtendedResolver, IERC165 { uint256 lastIdx ) internal pure returns (bytes memory) { uint256 totalLength = 0; - for (uint256 idx = startidx; idx < lastIdx; idx += fieldLength + 1) { - uint256 fieldLength = data.readUint8(idx); + + for (uint256 i = startIdx; i < lastIdx; ) { + uint256 fieldLength = data.readUint8(i); totalLength += fieldLength; + i += fieldLength + 1; } bytes memory result = new bytes(totalLength); uint256 resultIdx = 0; - for (uint256 idx = startIdx; idx < lastIdx; idx += fieldLength + 1) { - uint256 fieldLength = data.readUint8(idx); - result.strcpy(resultIdx, data, idx + 1, fieldLength); + + for (uint256 i = startIdx; i < lastIdx; ) { + uint256 fieldLength = data.readUint8(i); + result.strcpy(resultIdx, data, i + 1, fieldLength); resultIdx += fieldLength; + i += fieldLength + 1; } return result; }