Skip to content

Commit 242c27e

Browse files
authored
Merge pull request #292 from ensdomains/feat/universal-resolver-result
feat: universal resolver safe calls + success var return
2 parents 5ae0cd7 + 6117475 commit 242c27e

File tree

8 files changed

+682
-130
lines changed

8 files changed

+682
-130
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,8 @@ yarn pub
176176
5. Create a "Release Candidate" [release](https://docs.github.com/en/repositories/releasing-projects-on-github/about-releases) on GitHub. This will be of the form `v1.2.3-RC0`. This tagged commit is now subject to our bug bounty.
177177
6. Have the tagged commit audited if necessary
178178
7. If changes are required, make the changes and then once ready for review create another GitHub release with an incremented RC value `v1.2.3-RC0` -> `v.1.2.3-RC1`. Repeat as necessary.
179-
8. Deploy to testnet. Open a pull request to merge the deploy artifacts into
180-
the `feature` branch. Get someone to review and approve the deployment and then merge. You now MUST merge this branch into `staging` branch.
179+
8. Deploy to testnet. Open a pull request to merge the deploy artifacts into
180+
the `feature` branch. Get someone to review and approve the deployment and then merge. You now MUST merge this branch into `staging` branch.
181181
9. Create GitHub release of the form `v1.2.3-testnet` from the commit that has the new deployment artifacts.
182182
10. If any further changes are needed, you can either make them on the existing feature branch that is in sync or create a new branch, and follow steps 1 -> 9. Repeat as necessary.
183183
11. Make Deployment to mainnet from `staging`. Commit build artifacts. You now MUST merge this branch into `main`.

contracts/utils/LowLevelCallUtils.sol

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,30 @@ library LowLevelCallUtils {
1717
function functionStaticCall(
1818
address target,
1919
bytes memory data
20+
) internal view returns (bool success) {
21+
return functionStaticCall(target, data, gasleft());
22+
}
23+
24+
/**
25+
* @dev Makes a static call to the specified `target` with `data` using `gasLimit`. Return data can be fetched with
26+
* `returnDataSize` and `readReturnData`.
27+
* @param target The address to staticcall.
28+
* @param data The data to pass to the call.
29+
* @param gasLimit The gas limit to use for the call.
30+
* @return success True if the call succeeded, or false if it reverts.
31+
*/
32+
function functionStaticCall(
33+
address target,
34+
bytes memory data,
35+
uint256 gasLimit
2036
) internal view returns (bool success) {
2137
require(
2238
target.isContract(),
2339
"LowLevelCallUtils: static call to non-contract"
2440
);
2541
assembly {
2642
success := staticcall(
27-
gas(),
43+
gasLimit,
2844
target,
2945
add(data, 32),
3046
mload(data),

contracts/utils/UniversalResolver.sol

Lines changed: 134 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ error ResolverNotFound();
2424

2525
error ResolverWildcardNotSupported();
2626

27+
error ResolverNotContract();
28+
29+
error ResolverError(bytes returnData);
30+
31+
error HttpError(HttpErrorItem[] errors);
32+
33+
struct HttpErrorItem {
34+
uint16 status;
35+
string message;
36+
}
37+
2738
struct MulticallData {
2839
bytes name;
2940
bytes[] data;
@@ -35,6 +46,11 @@ struct MulticallData {
3546
bool[] failures;
3647
}
3748

49+
struct MulticallChecks {
50+
bool isCallback;
51+
bool hasExtendedResolver;
52+
}
53+
3854
struct OffchainLookupCallData {
3955
address sender;
4056
string[] urls;
@@ -46,6 +62,11 @@ struct OffchainLookupExtraData {
4662
bytes data;
4763
}
4864

65+
struct Result {
66+
bool success;
67+
bytes returnData;
68+
}
69+
4970
interface BatchGateway {
5071
function query(
5172
OffchainLookupCallData[] memory data
@@ -97,7 +118,7 @@ contract UniversalResolver is ERC165, Ownable {
97118
function resolve(
98119
bytes calldata name,
99120
bytes[] memory data
100-
) external view returns (bytes[] memory, address) {
121+
) external view returns (Result[] memory, address) {
101122
return resolve(name, data, batchGatewayURLs);
102123
}
103124

@@ -120,7 +141,7 @@ contract UniversalResolver is ERC165, Ownable {
120141
bytes calldata name,
121142
bytes[] memory data,
122143
string[] memory gateways
123-
) public view returns (bytes[] memory, address) {
144+
) public view returns (Result[] memory, address) {
124145
return
125146
_resolve(name, data, gateways, this.resolveCallback.selector, "");
126147
}
@@ -134,14 +155,19 @@ contract UniversalResolver is ERC165, Ownable {
134155
) public view returns (bytes memory, address) {
135156
bytes[] memory dataArr = new bytes[](1);
136157
dataArr[0] = data;
137-
(bytes[] memory results, address resolver) = _resolve(
158+
(Result[] memory results, address resolver) = _resolve(
138159
name,
139160
dataArr,
140161
gateways,
141162
callbackFunction,
142163
metaData
143164
);
144-
return (results[0], resolver);
165+
166+
Result memory result = results[0];
167+
168+
_checkResolveSingle(result);
169+
170+
return (result.returnData, resolver);
145171
}
146172

147173
function _resolve(
@@ -150,13 +176,17 @@ contract UniversalResolver is ERC165, Ownable {
150176
string[] memory gateways,
151177
bytes4 callbackFunction,
152178
bytes memory metaData
153-
) internal view returns (bytes[] memory results, address resolverAddress) {
179+
) internal view returns (Result[] memory results, address resolverAddress) {
154180
(Resolver resolver, , uint256 finalOffset) = findResolver(name);
155181
resolverAddress = address(resolver);
156182
if (resolverAddress == address(0)) {
157183
revert ResolverNotFound();
158184
}
159185

186+
if (!resolverAddress.isContract()) {
187+
revert ResolverNotContract();
188+
}
189+
160190
bool isWildcard = finalOffset != 0;
161191

162192
results = _multicall(
@@ -193,7 +223,7 @@ contract UniversalResolver is ERC165, Ownable {
193223
reverseName.namehash(0)
194224
);
195225
(
196-
bytes memory resolvedReverseData,
226+
bytes memory reverseResolvedData,
197227
address reverseResolverAddress
198228
) = _resolveSingle(
199229
reverseName,
@@ -205,7 +235,7 @@ contract UniversalResolver is ERC165, Ownable {
205235

206236
return
207237
getForwardDataFromReverse(
208-
resolvedReverseData,
238+
reverseResolvedData,
209239
reverseResolverAddress,
210240
gateways
211241
);
@@ -249,19 +279,23 @@ contract UniversalResolver is ERC165, Ownable {
249279
bytes calldata response,
250280
bytes calldata extraData
251281
) external view returns (bytes memory, address) {
252-
(bytes[] memory results, address resolver, , ) = _resolveCallback(
282+
(Result[] memory results, address resolver, , ) = _resolveCallback(
253283
response,
254284
extraData,
255285
this.resolveSingleCallback.selector
256286
);
257-
return (results[0], resolver);
287+
Result memory result = results[0];
288+
289+
_checkResolveSingle(result);
290+
291+
return (result.returnData, resolver);
258292
}
259293

260294
function resolveCallback(
261295
bytes calldata response,
262296
bytes calldata extraData
263-
) external view returns (bytes[] memory, address) {
264-
(bytes[] memory results, address resolver, , ) = _resolveCallback(
297+
) external view returns (Result[] memory, address) {
298+
(Result[] memory results, address resolver, , ) = _resolveCallback(
265299
response,
266300
extraData,
267301
this.resolveCallback.selector
@@ -274,7 +308,7 @@ contract UniversalResolver is ERC165, Ownable {
274308
bytes calldata extraData
275309
) external view returns (string memory, address, address, address) {
276310
(
277-
bytes[] memory resolvedData,
311+
Result[] memory results,
278312
address resolverAddress,
279313
string[] memory gateways,
280314
bytes memory metaData
@@ -284,10 +318,16 @@ contract UniversalResolver is ERC165, Ownable {
284318
this.reverseCallback.selector
285319
);
286320

321+
Result memory result = results[0];
322+
323+
if (!result.success) {
324+
revert ResolverError(result.returnData);
325+
}
326+
287327
if (metaData.length > 0) {
288328
(string memory resolvedName, address reverseResolverAddress) = abi
289329
.decode(metaData, (string, address));
290-
address resolvedAddress = abi.decode(resolvedData[0], (address));
330+
address resolvedAddress = abi.decode(result.returnData, (address));
291331
return (
292332
resolvedName,
293333
resolvedAddress,
@@ -298,7 +338,7 @@ contract UniversalResolver is ERC165, Ownable {
298338

299339
return
300340
getForwardDataFromReverse(
301-
resolvedData[0],
341+
result.returnData,
302342
resolverAddress,
303343
gateways
304344
);
@@ -319,7 +359,7 @@ contract UniversalResolver is ERC165, Ownable {
319359
)
320360
internal
321361
view
322-
returns (bytes[] memory, address, string[] memory, bytes memory)
362+
returns (Result[] memory, address, string[] memory, bytes memory)
323363
{
324364
MulticallData memory multicallData;
325365
multicallData.callbackFunction = callbackFunction;
@@ -381,7 +421,8 @@ contract UniversalResolver is ERC165, Ownable {
381421
*/
382422
function callWithOffchainLookupPropagation(
383423
address target,
384-
bytes memory data
424+
bytes memory data,
425+
bool isSafe
385426
)
386427
internal
387428
view
@@ -392,7 +433,11 @@ contract UniversalResolver is ERC165, Ownable {
392433
bool result
393434
)
394435
{
395-
result = LowLevelCallUtils.functionStaticCall(address(target), data);
436+
if (isSafe) {
437+
result = LowLevelCallUtils.functionStaticCall(target, data);
438+
} else {
439+
result = LowLevelCallUtils.functionStaticCall(target, data, 50000);
440+
}
396441
uint256 size = LowLevelCallUtils.returnDataSize();
397442

398443
if (result) {
@@ -499,56 +544,105 @@ contract UniversalResolver is ERC165, Ownable {
499544
return (parentresolver, node, parentoffset);
500545
}
501546

502-
function _hasExtendedResolver(
503-
address resolver
547+
function _checkInterface(
548+
address resolver,
549+
bytes4 interfaceId
504550
) internal view returns (bool) {
505551
try
506-
Resolver(resolver).supportsInterface{gas: 50000}(
507-
type(IExtendedResolver).interfaceId
508-
)
552+
Resolver(resolver).supportsInterface{gas: 50000}(interfaceId)
509553
returns (bool supported) {
510554
return supported;
511555
} catch {
512556
return false;
513557
}
514558
}
515559

560+
function _checkSafetyAndItem(
561+
bytes memory name,
562+
bytes memory item,
563+
address resolver,
564+
MulticallChecks memory multicallChecks
565+
) internal view returns (bool, bytes memory) {
566+
if (!multicallChecks.isCallback) {
567+
if (multicallChecks.hasExtendedResolver) {
568+
return (
569+
true,
570+
abi.encodeCall(IExtendedResolver.resolve, (name, item))
571+
);
572+
}
573+
return (_checkInterface(resolver, bytes4(item)), item);
574+
}
575+
return (true, item);
576+
}
577+
578+
function _checkMulticall(
579+
MulticallData memory multicallData
580+
) internal view returns (MulticallChecks memory) {
581+
bool isCallback = multicallData.name.length == 0;
582+
bool hasExtendedResolver = _checkInterface(
583+
multicallData.resolver,
584+
type(IExtendedResolver).interfaceId
585+
);
586+
587+
if (multicallData.isWildcard && !hasExtendedResolver) {
588+
revert ResolverWildcardNotSupported();
589+
}
590+
591+
return MulticallChecks(isCallback, hasExtendedResolver);
592+
}
593+
594+
function _checkResolveSingle(Result memory result) internal pure {
595+
if (!result.success) {
596+
if (bytes4(result.returnData) == HttpError.selector) {
597+
(, HttpErrorItem[] memory errors) = abi.decode(
598+
result.returnData,
599+
(bytes4, HttpErrorItem[])
600+
);
601+
revert HttpError(errors);
602+
}
603+
revert ResolverError(result.returnData);
604+
}
605+
}
606+
516607
function _multicall(
517608
MulticallData memory multicallData
518-
) internal view returns (bytes[] memory results) {
609+
) internal view returns (Result[] memory results) {
519610
uint256 length = multicallData.data.length;
520611
uint256 offchainCount = 0;
521612
OffchainLookupCallData[]
522613
memory callDatas = new OffchainLookupCallData[](length);
523614
OffchainLookupExtraData[]
524615
memory extraDatas = new OffchainLookupExtraData[](length);
525-
results = new bytes[](length);
526-
bool isCallback = multicallData.name.length == 0;
527-
bool hasExtendedResolver = _hasExtendedResolver(multicallData.resolver);
528-
529-
if (multicallData.isWildcard && !hasExtendedResolver) {
530-
revert ResolverWildcardNotSupported();
531-
}
616+
results = new Result[](length);
617+
MulticallChecks memory multicallChecks = _checkMulticall(multicallData);
532618

533619
for (uint256 i = 0; i < length; i++) {
534620
bytes memory item = multicallData.data[i];
535621
bool failure = multicallData.failures[i];
622+
536623
if (failure) {
537-
results[i] = item;
624+
results[i] = Result(false, item);
538625
continue;
539626
}
540-
if (!isCallback && hasExtendedResolver) {
541-
item = abi.encodeCall(
542-
IExtendedResolver.resolve,
543-
(multicallData.name, item)
544-
);
545-
}
627+
628+
bool isSafe = false;
629+
(isSafe, item) = _checkSafetyAndItem(
630+
multicallData.name,
631+
item,
632+
multicallData.resolver,
633+
multicallChecks
634+
);
635+
546636
(
547637
bool offchain,
548638
bytes memory returnData,
549639
OffchainLookupExtraData memory extraData,
550640
bool success
551-
) = callWithOffchainLookupPropagation(multicallData.resolver, item);
641+
) = callWithOffchainLookupPropagation(
642+
multicallData.resolver,
643+
item,
644+
isSafe
645+
);
552646

553647
if (offchain) {
554648
callDatas[offchainCount] = abi.decode(
@@ -560,11 +654,11 @@ contract UniversalResolver is ERC165, Ownable {
560654
continue;
561655
}
562656

563-
if (success && hasExtendedResolver) {
657+
if (success && multicallChecks.hasExtendedResolver) {
564658
// if this is a successful resolve() call, unwrap the result
565659
returnData = abi.decode(returnData, (bytes));
566660
}
567-
results[i] = returnData;
661+
results[i] = Result(success, returnData);
568662
extraDatas[i].data = multicallData.data[i];
569663
}
570664

0 commit comments

Comments
 (0)