generated from AlexNi245/solidity-typescript-hardhat-template
-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathERC3668Resolver.sol
380 lines (342 loc) · 16 KB
/
ERC3668Resolver.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
import {IExtendedResolver, IResolverService} from "./IExtendedResolver.sol";
import {IMetadataResolver} from "./IMetadataResolver.sol";
import {SupportsInterface} from "./SupportsInterface.sol";
import {CcipResponseVerifier, ICcipResponseVerifier} from "./verifier/CcipResponseVerifier.sol";
import {ENSRegistry} from "@ensdomains/ens-contracts/contracts/registry/ENSRegistry.sol";
import {INameWrapper} from "@ensdomains/ens-contracts/contracts/wrapper/INameWrapper.sol";
import {BytesUtils} from "@ensdomains/ens-contracts/contracts/wrapper/BytesUtils.sol";
import {BytesLib} from "solidity-bytes-utils/contracts/BytesLib.sol";
/*
* Implements an ENS resolver that directs all queries to a CCIP read gateway.
* Callers must implement EIP 3668 and ENSIP 10.
*/
contract ERC3668Resolver is IExtendedResolver, IMetadataResolver, SupportsInterface {
using BytesUtils for bytes;
struct CcipVerifier {
string[] gatewayUrls;
ICcipResponseVerifier verifierAddress;
}
/**
* The idnetifier to store the default verifier
*/
bytes32 private constant DEFAULT_VERIFIER = bytes32(0);
/*
* --------------------------------------------------
* EVENTS
* --------------------------------------------------
*/
event VerifierAdded(bytes32 indexed node, address verifierAddress, string[] gatewayUrls);
/*
* --------------------------------------------------
* Errors
* --------------------------------------------------
*/
error OffchainLookup(address sender, string[] urls, bytes callData, bytes4 callbackFunction, bytes extraData);
/*
* --------------------------------------------------
* State Variables
* --------------------------------------------------
*/
ENSRegistry public immutable ensRegistry;
INameWrapper public immutable nameWrapper;
mapping(bytes32 => CcipVerifier) public ccipVerifier;
/*
* --------------------------------------------------
* Constructor
* --------------------------------------------------
*/
constructor(
// The ENS registry
ENSRegistry _ensRegistry,
// The name wrapper
INameWrapper _nameWrapper,
//Address of the default CCIP Verifier
address _defaultVerifier,
string[] memory _gatewayUrls
) {
ensRegistry = _ensRegistry;
nameWrapper = _nameWrapper;
/**
* If a default verifier is set, that verifier will be used by every child address that doesn't have a specific verifier set.
*
*/
if (_defaultVerifier != address(0)) {
_setVerifierForDomain(DEFAULT_VERIFIER, _defaultVerifier, _gatewayUrls);
}
}
/*
* --------------------------------------------------
* External functions
* --------------------------------------------------
*/
/**
* @notice Sets a Cross-chain Information Protocol (CCIP) Verifier for a specific domain node.
* @param node The domain node for which the CCIP Verifier is set.
* @param verifierAddress The address of the CcipResponseVerifier contract.
* @param urls The gateway url that should handle the OffchainLookup.
*/
function setVerifierForDomain(bytes32 node, address verifierAddress, string[] memory urls) external {
/*
* Only the node owner can set the verifier for a node. NameWrapper profiles are supported too.
*/
require(node != bytes32(0), "node is 0x0");
require(msg.sender == getNodeOwner(node), "only node owner");
_setVerifierForDomain(node, verifierAddress, urls);
}
/**
* Resolves arbitrary data for a particular name, as specified by ENSIP 10.
* @param name The DNS-encoded name to resolve.
* @param data The ABI encoded data for the underlying resolution function (Eg, addr(bytes32), text(bytes32,string), etc).
* @return The return data, ABI encoded identically to the underlying function.
*/
function resolve(bytes calldata name, bytes calldata data) external view override returns (bytes memory) {
/*
* Get the verifier for the given name.
* reverts if no verifier was set in advance
*/
(CcipVerifier memory _verifier, bytes32 node) = getVerifierOfDomain(name);
/*
* Retrieves the owner of the node. NameWrapper profiles are supported too. This will be the context of the request.
*/
address nodeOwner = getNameOwner(name, 0);
bytes memory context = abi.encodePacked(nodeOwner);
/*
* The calldata the gateway has to resolve
*/
bytes memory callData = abi.encodeWithSelector(
IResolverService.resolveWithContext.selector,
name,
data,
context
);
revert OffchainLookup(
address(this),
_verifier.gatewayUrls,
callData,
ERC3668Resolver.resolveWithProof.selector,
callData
);
}
/**
* @dev Function to resolve a domain name with proof using an off-chain callback mechanism.
* @param response The response received from off-chain resolution.
* @param extraData The actual calldata that was called on the gateway.
* @return the result of the offchain lookup
*/
function resolveWithProof(bytes calldata response, bytes calldata extraData) external view returns (bytes memory) {
/*
* decode the calldata that was encoded in the resolve function for IResolverService.resolveWithContext()
* bytes memory callData = abi.encodeWithSelector(IResolverService.resolveWithContext.selector, name, data context);
*/
(bytes memory name, bytes memory data) = abi.decode(extraData[4:], (bytes, bytes));
/*
* Get the verifier for the given name.
* reverts if no verifier was set in advance
*/
(CcipVerifier memory _ccipVerifier, ) = getVerifierOfDomain(name);
/*
* to enable the ERC3668Resolver to return data other than bytes it might be possible to override the
* resolvewithProofCallback function.
*/
bytes4 callBackSelector = ICcipResponseVerifier(_ccipVerifier.verifierAddress).onResolveWithProof(name, data);
/*
* reverts when no callback selector was found. This should normally never happen because setVerifier() checks * that the verifierAddress implements the ICcipResponseVerifier interface. However, it might be possible by
* overriding the onResolveWithProof function and return 0x. In that case, the contract reverts here.
*/
require(callBackSelector != bytes4(0), "No callback selector found");
/*
* staticcall to the callback function on the verifier contract.
* This function always returns bytes even the called function returns a Fixed type due to the return type of staticcall in solidity.
* So you might want to decode the result using abi.decode(resolveWithProofResponse, (bytes))
*/
(bool success, bytes memory resolveWithProofResponse) = address(_ccipVerifier.verifierAddress).staticcall(
abi.encodeWithSelector(callBackSelector, response, extraData)
);
/*
* Reverts if the call is not successful
*/
require(success, "staticcall to verifier failed");
return resolveWithProofResponse;
}
/**
* @notice Get metadata about the CCIP Resolver
* @dev This function provides metadata about the CCIP Resolver, including its name, coin type, GraphQL URL, storage type, and encoded information.
* @param name The domain name in format (dnsEncoded)
* @return name The name of the resolver ("CCIP RESOLVER")
* @return coinType Resolvers coin type (60 for Ethereum)
* @return graphqlUrl The GraphQL URL used by the resolver
* @return storageType Storage Type (0 for EVM)
* @return storageLocation The storage identifier. For EVM chains, this is the address of the resolver contract.
* @return context can be l2 resolver contract address for evm chain but can be any l2 storage identifier for non-evm chain
*/
function metadata(
bytes calldata name
) external view returns (string memory, uint256, string memory, uint8, bytes memory, bytes memory) {
/*
* Get the verifier for the given name.
* reverts if no verifier was set in advance
*/
(CcipVerifier memory _ccipVerifier, ) = getVerifierOfDomain(name);
/*
* Get the metadata from the verifier contract
*/
(
string memory resolverName,
uint256 coinType,
string memory graphqlUrl,
uint8 storageType,
bytes memory storageLocation,
) = ICcipResponseVerifier(_ccipVerifier.verifierAddress).metadata(name);
/*
* To determine the context of the request, we need to get the owner of the node.
*/
address nodeOwner = getNameOwner(name, 0);
bytes memory context = abi.encodePacked(nodeOwner);
return (resolverName, coinType, graphqlUrl, storageType, storageLocation, context);
}
/*
* --------------------------------------------------
* Public Functions
* --------------------------------------------------
*/
/**
* @notice Get the CCIP Verifier and node for a given domain name
* @dev This function allows retrieving the CCIP Verifier and its associated node for a given domain name. For subdomains, it will return the CCIP Verifier of the closest parent.
* @param name The domain name in bytes (dnsEncoded)
* @return _ccipVerifier The CCIP Verifier associated with the given domain name
* @return node The node associated with the given domain name
*/
function getVerifierOfDomain(bytes memory name) public view returns (CcipVerifier memory, bytes32) {
return getVerifierOfSegment(name, 0, name.namehash(0));
}
/**
* @notice Check if the contract supports a specific interface
* @dev Implements the ERC-165 standard to check for interface support.
* @param interfaceID The interface identifier to check
* @return True if the contract supports the given interface, otherwise false
*/
function supportsInterface(bytes4 interfaceID) public pure override returns (bool) {
return interfaceID == type(IExtendedResolver).interfaceId || super.supportsInterface(interfaceID);
}
/*
* --------------------------------------------------
* Internal Functions
* --------------------------------------------------
*/
/**
* @notice Get the owner of the ENS node either from the ENS registry or the NameWrapper contract
* @dev This function adds support for ENS nodes owned by the NameWrapper contract.
* @param node The ENS node to query for the owner
* @return nodeOwner The address of the owner of the ENS node
*/
function getNodeOwner(bytes32 node) internal view returns (address nodeOwner) {
nodeOwner = ensRegistry.owner(node);
if (nodeOwner == address(nameWrapper)) {
nodeOwner = nameWrapper.ownerOf(uint256(node));
}
}
/**
* @notice Get the owner of the ENS name either from the ENS registry or the NameWrapper contract
* @dev This function adds support for ENS nodes owned by the NameWrapper contract.
* @param name The domain name in bytes (dnsEncoded)
* @param offset The current offset in the name being processed
* @return nodeOwner The address of the owner of the ENS node
*/
function getNameOwner(bytes memory name, uint256 offset) internal view returns (address nodeOwner) {
bytes32 node = name.namehash(offset);
(, offset) = name.readLabel(offset);
nodeOwner = ensRegistry.owner(node);
if(offset >= name.length) {
return address(0);
}else if (nodeOwner == address(0)){
return getNameOwner(name, offset);
}else if(nodeOwner == address(nameWrapper)){
return nameWrapper.ownerOf(uint256(node));
}
return nodeOwner;
}
/*
* --------------------------------------------------
* Private Functions
* --------------------------------------------------
*
*/
/**
* @notice Sets a Cross-chain Information Protocol (CCIP) Verifier for a specific domain node.
* @param node The domain node for which the CCIP Verifier is set.
* @param verifierAddress The address of the CcipResponseVerifier contract.
* @param urls The gateway url that should handle the OffchainLookup.
*/
function _setVerifierForDomain(bytes32 node, address verifierAddress, string[] memory urls) private {
require(verifierAddress != address(0), "verifierAddress is 0x0");
/*
* We're doing a staticcall here to check if the verifierAddress implements the ICcipResponseVerifier interface.
* This is done to prevent the user from setting an arbitrary address as the verifierAddress.
*/
(bool success, bytes memory response) = verifierAddress.staticcall(
abi.encodeWithSignature("supportsInterface(bytes4)", type(ICcipResponseVerifier).interfaceId)
);
/*
* A successful static call will return 0x0000000000000000000000000000000000000000000000000000000000000001
* Hence we've to check that the last bit is set.
*/
require(
success && response.length == 32 && (response[response.length - 1] & 0x01) == 0x01,
"verifierAddress is not a CCIP Verifier"
);
/*
* Check that the url is non-null.
* Although it may not be a sufficient url check, it prevents users from passing undefined or empty strings.
*/
require(urls.length > 0, "at least one gateway url has to be provided");
/*
* Set the new verifier for the given node.
*/
CcipVerifier memory _ccipVerifier = CcipVerifier(urls, ICcipResponseVerifier(verifierAddress));
ccipVerifier[node] = _ccipVerifier;
emit VerifierAdded(node, verifierAddress, urls);
}
/**
* @dev Recursively searches for a verifier associated with a segment of the given domain name.
* If a verifier is found, it returns the verifier and the corresponding node.
*
* @param name The domain name in bytes
* @param offset The current offset in the name being processed
* @param node The current node being processed
* @return The CcipVerifier associated with the domain segment, and the corresponding node
*
* @notice This function searches for a verifier starting from the given offset in the domain name.
* It checks if a verifier is set for the current node, and if not, it continues with the next label.
* If the end of the name is reached and no verifier is found, it reverts with an UnknownVerifier error.
*/
function getVerifierOfSegment(
bytes memory name,
uint256 offset,
bytes32 node
) private view returns (CcipVerifier memory, bytes32) {
/*
* If we reached the root node and there is no verifier set, we revert with UnknownVerifier
*/
if (offset >= name.length - 1) {
/*
*If no specific verifier is set for the given node, we return the default verifier
*/
CcipVerifier memory defaultCcipVerifier = ccipVerifier[DEFAULT_VERIFIER];
return (defaultCcipVerifier, name.namehash(0));
}
CcipVerifier memory _ccipVerifier = ccipVerifier[node];
/*
* If the verifier is set for the given node, we return it and break the recursion
*/
if (address(_ccipVerifier.verifierAddress) != address(0)) {
return (_ccipVerifier, name.namehash(0));
}
/*
* Otherwise, continue with the next label
*/
(, offset) = name.readLabel(offset);
return getVerifierOfSegment(name, offset, name.namehash(offset));
}
}