Skip to content

Commit

Permalink
sync testing with feature/better-offchain-dns
Browse files Browse the repository at this point in the history
  • Loading branch information
mdtanrikulu committed Mar 26, 2024
1 parent 2880879 commit 2be5088
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 70 deletions.
228 changes: 173 additions & 55 deletions contracts/dnsregistrar/mocks/DummyExtendedDNSSECResolver2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 "";
Expand Down
24 changes: 21 additions & 3 deletions contracts/utils/HexUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions contracts/utils/TestHexUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
23 changes: 11 additions & 12 deletions test/dnsregistrar/TestOffchainDNSResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,31 +468,30 @@ 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),
'smth',
).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')
})

Expand All @@ -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')
})

Expand All @@ -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')
})
})

0 comments on commit 2be5088

Please sign in to comment.