@@ -3,7 +3,6 @@ pragma solidity >=0.8.13;
33
44import {ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol " ;
55import {ERC165Checker } from "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol " ;
6- import {UniversalResolver} from "../universalResolver/UniversalResolver.sol " ;
76import {CCIPBatcher, OffchainLookup} from "@ens/contracts/ccipRead/CCIPBatcher.sol " ;
87import {DNSSEC} from "@ens/contracts/dnssec-oracle/DNSSEC.sol " ;
98import {RRUtils} from "@ens/contracts/dnssec-oracle/RRUtils.sol " ;
@@ -20,13 +19,15 @@ import {IMulticallable} from "@ens/contracts/resolvers/IMulticallable.sol";
2019import {IFeatureSupporter, isFeatureSupported} from "../common/IFeatureSupporter.sol " ;
2120import {ResolverFeatures} from "../common/ResolverFeatures.sol " ;
2221
22+ /// @dev Gateway interface for DNSSEC oracle.
2323interface 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`.
3031interface IUniversalResolverStub {
3132 function findResolver (
3233 bytes memory
@@ -40,12 +41,19 @@ uint16 constant TYPE_TXT = 16;
4041bytes constant PREFIX = "ENS1 " ;
4142uint256 constant PREFIX_LENGTH = 5 ; // PREFIX.length
4243
44+ /// @title DNSTLDResolver
45+ /// @notice Resolver that performs imported DNS fallback to V1 and gasless DNS resolution.
4346contract 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
0 commit comments