Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

concatenate multiple text fields #332

Open
wants to merge 21 commits into
base: fix/refactor-wrapped-callback
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
89ad8ed
concatenate multiple text fields
mdtanrikulu Feb 20, 2024
2880879
update wildcard check without copying name, concatenate text in O(n)
mdtanrikulu Feb 20, 2024
2be5088
sync testing with feature/better-offchain-dns
mdtanrikulu Mar 26, 2024
e9894f6
update testhexutils
mdtanrikulu Mar 27, 2024
092b087
Merge branch 'fix/refactor-wrapped-callback' into experimental-multi-…
mdtanrikulu Jul 23, 2024
0758752
remove DummyDNSResolver2
mdtanrikulu Aug 22, 2024
c3a0836
update extendeddnsresolver deployments file
mdtanrikulu Aug 22, 2024
1445c3f
merge staging into
mdtanrikulu Aug 22, 2024
d7f5ee6
Merge branch 'fix/refactor-wrapped-callback' into experimental-multi-…
mdtanrikulu Aug 22, 2024
67e3145
fix wildcard check by comparing to it's hex representation
mdtanrikulu Aug 27, 2024
4f59136
Merge branch 'staging' into experimental-multi-field
mdtanrikulu Oct 24, 2024
92d01c6
revert wildcard support
mdtanrikulu Oct 24, 2024
c03968b
update deprecated experimental loader, bump hardhat
mdtanrikulu Oct 24, 2024
7ff6a56
update deprecated buffer use
mdtanrikulu Oct 24, 2024
6bc18af
revert redundant name offset
mdtanrikulu Oct 28, 2024
f98f323
update lock file
mdtanrikulu Oct 28, 2024
69476ed
Merge branch 'fix/refactor-wrapped-callback' into experimental-multi-…
mdtanrikulu Oct 28, 2024
db8a085
improve TXT concatenation testing
mdtanrikulu Oct 29, 2024
7654735
Update contracts/dnsregistrar/OffchainDNSResolver.sol
mdtanrikulu Nov 15, 2024
4630340
Update contracts/dnsregistrar/OffchainDNSResolver.sol
mdtanrikulu Nov 15, 2024
24fcb62
update for loops in readTXT method
mdtanrikulu Nov 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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] == ".") {
mdtanrikulu marked this conversation as resolved.
Show resolved Hide resolved
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);
mdtanrikulu marked this conversation as resolved.
Show resolved Hide resolved
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')
})
})
Loading