Skip to content

Commit

Permalink
concatenate multiple text fields
Browse files Browse the repository at this point in the history
  • Loading branch information
mdtanrikulu committed Feb 20, 2024
1 parent fa44cdb commit 89ad8ed
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 7 deletions.
29 changes: 24 additions & 5 deletions contracts/dnsregistrar/OffchainDNSResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
103 changes: 103 additions & 0 deletions contracts/dnsregistrar/mocks/DummyExtendedDNSSECResolver2.sol
Original file line number Diff line number Diff line change
@@ -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 "";
}
}
84 changes: 82 additions & 2 deletions test/dnsregistrar/TestOffchainDNSResolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
)
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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')
})
})

0 comments on commit 89ad8ed

Please sign in to comment.