Skip to content

Commit df9faa8

Browse files
committed
add comments, some cleanup
1 parent d25edb1 commit df9faa8

3 files changed

Lines changed: 78 additions & 49 deletions

File tree

contracts/src/L1/DNSTLDResolver.sol

Lines changed: 65 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ pragma solidity >=0.8.13;
33

44
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
55
import {ERC165Checker} from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
6-
import {UniversalResolver} from "../universalResolver/UniversalResolver.sol";
76
import {CCIPBatcher, OffchainLookup} from "@ens/contracts/ccipRead/CCIPBatcher.sol";
87
import {DNSSEC} from "@ens/contracts/dnssec-oracle/DNSSEC.sol";
98
import {RRUtils} from "@ens/contracts/dnssec-oracle/RRUtils.sol";
@@ -20,13 +19,15 @@ import {IMulticallable} from "@ens/contracts/resolvers/IMulticallable.sol";
2019
import {IFeatureSupporter, isFeatureSupported} from "../common/IFeatureSupporter.sol";
2120
import {ResolverFeatures} from "../common/ResolverFeatures.sol";
2221

22+
/// @dev Gateway interface for DNSSEC oracle.
2323
interface IDNSGateway {
2424
function resolve(
2525
bytes memory name,
2626
uint16 qtype
2727
) external returns (DNSSEC.RRSetWithSignature[] memory);
2828
}
2929

30+
/// @dev Partial interface for `UniversalResolver`.
3031
interface IUniversalResolverStub {
3132
function findResolver(
3233
bytes memory
@@ -40,12 +41,19 @@ uint16 constant TYPE_TXT = 16;
4041
bytes constant PREFIX = "ENS1 ";
4142
uint256 constant PREFIX_LENGTH = 5; // PREFIX.length
4243

44+
/// @title DNSTLDResolver
45+
/// @notice Resolver that performs imported DNS fallback to V1 and gasless DNS resolution.
4346
contract DNSTLDResolver is
4447
ERC165,
4548
IFeatureSupporter,
4649
IExtendedResolver,
4750
CCIPBatcher
4851
{
52+
IUniversalResolverStub public immutable universalResolverV1;
53+
IUniversalResolverStub public immutable universalResolverV2;
54+
DNSSEC public immutable oracleVerifier;
55+
string[] public oracleGateways;
56+
4957
/// @dev `name` does not exist.
5058
/// Error selector: `0x5fe9a5df`
5159
/// @param name The DNS-encoded ENS name.
@@ -55,11 +63,6 @@ contract DNSTLDResolver is
5563
/// Error selector: `0xf4ba19b7`
5664
error InvalidTXT();
5765

58-
IUniversalResolverStub public immutable universalResolverV1;
59-
IUniversalResolverStub public immutable universalResolverV2;
60-
DNSSEC public immutable oracleVerifier;
61-
string[] public oracleGateways;
62-
6366
constructor(
6467
IUniversalResolverStub _universalResolverV1,
6568
IUniversalResolverStub _universalResolverV2,
@@ -87,38 +90,42 @@ contract DNSTLDResolver is
8790
return ResolverFeatures.RESOLVE_MULTICALL == feature;
8891
}
8992

90-
/// DNS Resolution with Fallback:
91-
/// 0. If there exists a resolver in V1, go to step 3.
92-
/// 1. Query the DNSSEC oracle for TXT records.
93-
/// 2. Verify TXT records, find ENS1 record, parse ENS1 record into resolver and context.
94-
/// 3. Call the resolver and return the records.
95-
/// @notice Callers should enable EIP-3668.
93+
/// @notice Resolve `name` using V1 or DNSSEC.
94+
/// Callers should enable EIP-3668.
9695
/// @dev This function executes over multiple steps (step 1 of 3).
96+
///
97+
/// 1. If there exists a resolver in V1, go to step 4.
98+
/// 2. Query the DNSSEC oracle for TXT records.
99+
/// 3. Verify TXT records, find ENS1 record, parse resolver and context.
100+
/// 4. Call the resolver and return the requested records.
97101
function resolve(
98102
bytes calldata name,
99103
bytes calldata data
100104
) external view returns (bytes memory) {
101105
(address resolver, , uint256 offset) = universalResolverV1.findResolver(
102106
name
103107
);
104-
if (resolver == address(0)) {
105-
revert OffchainLookup(
106-
address(this),
107-
oracleGateways,
108-
abi.encodeCall(IDNSGateway.resolve, (name, TYPE_TXT)),
109-
this.resolveOracleCallback.selector,
110-
abi.encode(name, data)
111-
);
112-
}
113-
if (!_isExtended(resolver) && offset != 0) {
114-
revert UnreachableName(name);
108+
if (
109+
resolver != address(0) &&
110+
(offset == 0 ||
111+
ERC165Checker.supportsERC165InterfaceUnchecked(
112+
resolver,
113+
type(IExtendedResolver).interfaceId
114+
))
115+
) {
116+
_callResolver(resolver, name, data, false, "");
115117
}
116-
_callResolver(resolver, name, data, false, "");
118+
revert OffchainLookup(
119+
address(this),
120+
oracleGateways,
121+
abi.encodeCall(IDNSGateway.resolve, (name, TYPE_TXT)),
122+
this.resolveOracleCallback.selector,
123+
abi.encode(name, data)
124+
);
117125
}
118126

119127
/// @dev CCIP-Read callback for `resolve()` from calling the DNSSEC oracle (step 2 of 3).
120128
/// Reverts `UnreachableName` if no "ENS1" TXT record is found.
121-
/// Reverts `UnreachableName` if resolver is not a contract.
122129
/// @param response The response data.
123130
/// @param extraData The contextual data passed from `resolve()`.
124131
/// @return The abi-encoded result from the resolver.
@@ -156,11 +163,18 @@ contract DNSTLDResolver is
156163
revert UnreachableName(name);
157164
}
158165

166+
/// @dev Efficiently call another resolver.
167+
/// Reverts `UnreachableName` if resolver is not a contract.
168+
/// @param resolver The resolver to call.
169+
/// @param name The name to resolve.
170+
/// @param call The calldata.
171+
/// @param dns True if `IExtendedDNSResolver` should be considered.
172+
/// @param context The context for `IExtendedDNSResolver`.
159173
function _callResolver(
160174
address resolver,
161175
bytes memory name,
162176
bytes memory call,
163-
bool tryContext,
177+
bool dns,
164178
bytes memory context
165179
) internal view {
166180
if (resolver.code.length == 0) {
@@ -183,7 +197,7 @@ contract DNSTLDResolver is
183197
}
184198
bool extended;
185199
if (
186-
tryContext &&
200+
dns &&
187201
ERC165Checker.supportsERC165InterfaceUnchecked(
188202
resolver,
189203
type(IExtendedDNSResolver).interfaceId
@@ -206,7 +220,12 @@ contract DNSTLDResolver is
206220
);
207221
}
208222
}
209-
} else if (_isExtended(resolver)) {
223+
} else if (
224+
ERC165Checker.supportsERC165InterfaceUnchecked(
225+
resolver,
226+
type(IExtendedResolver).interfaceId
227+
)
228+
) {
210229
if (direct) {
211230
ccipRead(
212231
resolver,
@@ -230,10 +249,10 @@ contract DNSTLDResolver is
230249
);
231250
}
232251

233-
/// @dev CCIP-Read callback for `_callResolver()` from calling the DNS resolver (step 3 of 3).
234-
/// @param response The response data.
235-
/// @param extraData The
236-
/// @return result The abi-encoded result.
252+
/// @dev CCIP-Read callback for `_callResolver()` from batch calling the gasless DNS resolver (step 3 of 3).
253+
/// @param response The response data from the batch gateway.
254+
/// @param extraData The abi-encoded properties of the call.
255+
/// @return result The abi-encoded result from the resolver.
237256
function resolveBatchCallback(
238257
bytes calldata response,
239258
bytes calldata extraData
@@ -261,9 +280,9 @@ contract DNSTLDResolver is
261280
}
262281

263282
/// @dev Parse the TXT record into resolver and context.
264-
/// Format: "ENS1 <name or address> <context?>"
283+
/// Format: "ENS1 <name or address> <context?>".
265284
/// @param txt The TXT data.
266-
/// @return resolver The resolver address or null if wrong format.
285+
/// @return resolver The resolver address or null if wrong format or name didn't resolve.
267286
/// @return context The optional context data.
268287
function _parseTXT(
269288
bytes memory txt
@@ -297,12 +316,16 @@ contract DNSTLDResolver is
297316
}
298317
}
299318

300-
/// @dev Parse the value into an address.
301-
/// If the value matches `/^0x[0-9a-f][40]$/`.
319+
/// @dev Parse the value into a resolver address.
320+
/// If the value matches `/^0x[0-9a-f][40]$/`, it's a literal address.
321+
/// Otherwise, it's considered a name and resolved in the registry.
322+
/// Reverts `DNSEncodingFailed` if the name cannot be encoded.
323+
/// @param v The address or name.
324+
/// @return resolver The corresponding resolver address.
302325
function _parseResolver(
303326
bytes memory v
304327
) internal view returns (address resolver) {
305-
if (v[0] == "0" && v[1] == "x") {
328+
if (v[0] == "0" && v[1] == "xow ") {
306329
(address addr, bool valid) = HexUtils.hexToAddress(v, 2, v.length);
307330
if (valid) {
308331
return addr;
@@ -313,11 +336,14 @@ contract DNSTLDResolver is
313336
);
314337
}
315338

339+
// TODO: add memcpy to BytesUtils
316340
/// @dev Decode `data[pos:end]` as raw TXT chunks.
317341
/// Encoding: `[byte(n) + <n bytes>]...`
342+
/// Reverts `InvalidTXT` if the data is malformed.
318343
/// @param data The raw TXT data.
319344
/// @param pos The offset of the record data.
320345
/// @param end The upper bound of the record data.
346+
/// @return txt The decoded TXT value.
321347
function _readTXT(
322348
bytes memory data,
323349
uint256 pos,
@@ -336,15 +362,8 @@ contract DNSTLDResolver is
336362
if (pos != end) revert InvalidTXT();
337363
}
338364

339-
/// @dev Determine if the resolver is `IExtendedResolver`.
340-
function _isExtended(address resolver) internal view returns (bool) {
341-
return
342-
ERC165Checker.supportsERC165InterfaceUnchecked(
343-
resolver,
344-
type(IExtendedResolver).interfaceId
345-
);
346-
}
347-
365+
/// TODO: move this to CCIPBatcher
366+
/// @dev Create a `Batch` for a single target with multiple calls.
348367
function _createBatch(
349368
address target,
350369
bytes[] memory calls

contracts/src/L1/ETHFallbackResolver.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ contract ETHFallbackResolver is
179179
}
180180

181181
/// @notice Resolve `name` using Namechain, Mainnet V2, or Mainnet V1 depending on migration and ejection status.
182-
/// @notice Callers should enable EIP-3668.
182+
/// Callers should enable EIP-3668.
183183
/// @dev This function executes over multiple steps (step 1 of 2).
184184
///
185185
/// `GatewayRequest` walkthrough:

contracts/test/dns/DNSTLDResolver.test.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
} from "../utils/resolutions.js";
1212
import { dnsEncodeName, expectVar } from "../utils/utils.js";
1313
import { encodeRRs, TXT } from "./gasless.js";
14+
import { FEATURES } from "../utils/features.js";
1415

1516
const chain = await hre.network.connect();
1617

@@ -26,7 +27,7 @@ async function fixture() {
2627
mainnetV2.universalResolver.address,
2728
mockDNSSEC.address,
2829
[
29-
// "data"" is sufficient to satisfy: `abi.decode(DNSSEC.RRSetWithSignature[])`
30+
// "data" is sufficient to satisfy: `abi.decode(DNSSEC.RRSetWithSignature[])`
3031
'data:application/json,{"data":"0x0000000000000000000000000000000000000000000000000000000000000000"}',
3132
],
3233
]);
@@ -57,7 +58,16 @@ describe("DNSTLDResolver", () => {
5758
shouldSupportInterfaces({
5859
contract: () =>
5960
chain.networkHelpers.loadFixture(fixture).then((F) => F.dnsTLDResolver),
60-
interfaces: ["IERC165", "IExtendedResolver"],
61+
interfaces: ["IERC165", "IExtendedResolver", "IFeatureSupporter"],
62+
});
63+
64+
it("supportsFeature: resolve(multicall)", async () => {
65+
const F = await chain.networkHelpers.loadFixture(fixture);
66+
await expect(
67+
F.dnsTLDResolver.read.supportsFeature([
68+
FEATURES.RESOLVER.RESOLVE_MULTICALL,
69+
]),
70+
).resolves.toStrictEqual(true);
6171
});
6272

6373
function testProfiles(

0 commit comments

Comments
 (0)