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

feat: universalresolver v3 #350

Open
wants to merge 24 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Binary file modified bun.lockb
Binary file not shown.
61 changes: 20 additions & 41 deletions contracts/dnsregistrar/OffchainDNSResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,9 @@ import "../utils/BytesUtils.sol";

import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {LowLevelCallUtils} from "../utils/LowLevelCallUtils.sol";
import {ERC3668Utils, OffchainLookup, OffchainLookupData} from "../utils/ERC3668Utils.sol";

error InvalidOperation();
error OffchainLookup(
address sender,
string[] urls,
bytes callData,
bytes4 callbackFunction,
bytes extraData
);

interface IDNSGateway {
function resolve(
Expand Down Expand Up @@ -268,28 +262,14 @@ contract OffchainDNSResolver is IExtendedResolver, IERC165 {
revertWithDefaultOffchainLookup(name, innerdata);
}

bool result = LowLevelCallUtils.functionStaticCall(
address(target),
data
);
uint256 size = LowLevelCallUtils.returnDataSize();
if (result) {
bytes memory returnData = LowLevelCallUtils.readReturnData(0, size);
return abi.decode(returnData, (bytes));
}
// Failure
if (size >= 4) {
bytes memory errorId = LowLevelCallUtils.readReturnData(0, 4);
if (bytes4(errorId) == OffchainLookup.selector) {
// Offchain lookup. Decode the revert message and create our own that nests it.
bytes memory revertData = LowLevelCallUtils.readReturnData(
4,
size - 4
);
handleOffchainLookupError(revertData, target, name);
}
}
LowLevelCallUtils.propagateRevert();
(bool success, bytes4 errorId, bytes memory result) = ERC3668Utils
.callWithNormalisedResult(target, data);
if (success) return abi.decode(result, (bytes));

if (errorId == OffchainLookup.selector)
handleOffchainLookupError(result, target, name);

LowLevelCallUtils.propagateRevert(bytes.concat(errorId, result));
}

function revertWithDefaultOffchainLookup(
Expand All @@ -313,24 +293,23 @@ contract OffchainDNSResolver is IExtendedResolver, IERC165 {
address target,
bytes memory name
) internal view {
(
address sender,
string[] memory urls,
bytes memory callData,
bytes4 innerCallbackFunction,
bytes memory extraData
) = abi.decode(returnData, (address, string[], bytes, bytes4, bytes));

if (sender != target) {
OffchainLookupData memory caughtLookup = ERC3668Utils
.getOffchainLookupData(returnData);

if (caughtLookup.sender != target) {
revert InvalidOperation();
}

revert OffchainLookup(
address(this),
urls,
callData,
caughtLookup.urls,
caughtLookup.callData,
OffchainDNSResolver.resolveCallback.selector,
abi.encode(name, extraData, innerCallbackFunction)
abi.encode(
name,
caughtLookup.extraData,
caughtLookup.callbackFunction
)
);
}
}
49 changes: 49 additions & 0 deletions contracts/test/mocks/DummyAddrOffchainResolver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "../../../contracts/resolvers/profiles/IAddrResolver.sol";

error OffchainLookup(
address sender,
string[] urls,
bytes callData,
bytes4 callbackFunction,
bytes extraData
);

contract DummyAddrOffchainResolver is IAddrResolver, ERC165 {
function supportsInterface(
bytes4 interfaceId
) public view virtual override returns (bool) {
return
interfaceId == type(IAddrResolver).interfaceId ||
super.supportsInterface(interfaceId);
}

function addr(bytes32) external view returns (address payable) {
string[] memory urls = new string[](1);
urls[0] = "https://example.com/";

bytes memory data = abi.encode(address(this));

revert OffchainLookup(
address(this),
urls,
data,
this.addrCallback.selector,
data
);
}

function addrOnchain(bytes32) external view returns (address) {
return address(this);
}

function addrCallback(
bytes calldata response,
bytes calldata /* extraData */
) external pure returns (address) {
return abi.decode(response, (address));
}
}
52 changes: 52 additions & 0 deletions contracts/test/mocks/DummyAddressOffchainResolver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "../../../contracts/resolvers/profiles/IAddressResolver.sol";

error OffchainLookup(
address sender,
string[] urls,
bytes callData,
bytes4 callbackFunction,
bytes extraData
);

contract DummyAddressOffchainResolver is IAddressResolver, ERC165 {
function supportsInterface(
bytes4 interfaceId
) public view virtual override returns (bool) {
return
interfaceId == type(IAddressResolver).interfaceId ||
super.supportsInterface(interfaceId);
}

function addr(
bytes32 node,
uint256 coinType
) external view returns (bytes memory) {
string[] memory urls = new string[](1);
urls[0] = "https://example.com/";

bytes memory data = abi.encodeWithSelector(
this.addr.selector,
node,
coinType
);

revert OffchainLookup(
address(this),
urls,
data,
this.addrCallback.selector,
data
);
}

function addrCallback(
bytes calldata response,
bytes calldata /* extraData */
) external pure returns (bytes memory) {
return abi.decode(response, (bytes));
}
}
10 changes: 10 additions & 0 deletions contracts/test/mocks/DummyENSIP10ResolverFinderImplementer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.13;

import {ENS} from "../../registry/ENS.sol";
import {ENSIP10ResolverFinder} from "../../universalResolver/ENSIP10ResolverFinder.sol";

contract DummyENSIP10ResolverFinderImplementer is ENSIP10ResolverFinder {
constructor(ENS ensRegistry) ENSIP10ResolverFinder(ensRegistry) {}
}
45 changes: 45 additions & 0 deletions contracts/test/mocks/DummyNameOffchainResolver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import "../../../contracts/resolvers/profiles/INameResolver.sol";

error OffchainLookup(
address sender,
string[] urls,
bytes callData,
bytes4 callbackFunction,
bytes extraData
);

contract DummyNameOffchainResolver is INameResolver, ERC165 {
function supportsInterface(
bytes4 interfaceId
) public view virtual override returns (bool) {
return
interfaceId == type(INameResolver).interfaceId ||
super.supportsInterface(interfaceId);
}

function name(bytes32) external view returns (string memory) {
string[] memory urls = new string[](1);
urls[0] = "https://example.com/";

bytes memory data = abi.encode("name-offchain.eth");

revert OffchainLookup(
address(this),
urls,
data,
this.nameCallback.selector,
data
);
}

function nameCallback(
bytes calldata response,
bytes calldata /* extraData */
) external pure returns (string memory) {
return abi.decode(response, (string));
}
}
38 changes: 30 additions & 8 deletions contracts/test/mocks/DummyOffchainResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,36 @@ contract DummyOffchainResolver is IExtendedResolver, ERC165 {
function resolveCallback(
bytes calldata response,
bytes calldata extraData
) external view returns (bytes memory) {
require(
keccak256(response) == keccak256(extraData),
"Response data error"
);
if (bytes4(extraData) == bytes4(keccak256("name(bytes32)"))) {
return abi.encode("offchain.test.eth");
) external pure returns (bytes memory) {
if (bytes4(extraData) == bytes4(keccak256("emptyResponse()"))) {
revert();
}
if (bytes4(extraData) == bytes4(keccak256("revertWithData()"))) {
revert("Unsupported call");
}
if (bytes4(extraData) != bytes4(keccak256("multicall(bytes[])"))) {
return response;
}

bytes[] memory results = abi.decode(response, (bytes[]));
bytes[] memory calls = abi.decode(extraData[4:], (bytes[]));
for (uint256 i = 0; i < calls.length; i++) {
if (
bytes4(calls[i]) == bytes4(keccak256("text(bytes32,string)")) ||
bytes4(calls[i]) == bytes4(keccak256("addr(bytes32)")) ||
bytes4(calls[i]) ==
bytes4(keccak256("addr(bytes32,uint256)")) ||
bytes4(calls[i]) == bytes4(keccak256("name(bytes32)"))
) {
calls[i] = results[i];
} else if (
bytes4(calls[i]) == bytes4(keccak256("emptyResponse()"))
) {
calls[i] = "";
} else {
revert("Unsupported call");
}
}
return abi.encode(address(this));
return abi.encode(calls);
}
}
75 changes: 75 additions & 0 deletions contracts/universalResolver/ENSIP10ResolverFinder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {ENS} from "../registry/ENS.sol";
import {HexUtils} from "../utils/HexUtils.sol";

/// @notice ENSIP10 implementation for finding the resolver of a given name.
/// https://docs.ens.domains/ensip/10
abstract contract ENSIP10ResolverFinder {
using HexUtils for bytes;

/// @notice The ENS registry.
ENS internal immutable _registry;

/// @notice The name is not encoded correctly.
error InvalidNameEncoding();

/// @notice Sets the ENS registry.
/// @param registry_ The ENS registry.
constructor(ENS registry_) {
_registry = registry_;
}

/// @notice Finds a resolver by recursively querying the registry, starting at the longest name and progressively
/// removing labels until it finds a result.
/// @param name The name to resolve, in DNS-encoded and normalised form.
/// @return resolver The Resolver responsible for this name.
/// @return namehash The namehash of the full name.
/// @return finalOffset The offset of the first label with a resolver.
function findResolver(
bytes calldata name
) public view returns (address resolver, bytes32 namehash, uint256 finalOffset) {
return _findResolver(name, 0);
}

/// @dev Finds a resolver recursively based on the offset input.
function _findResolver(
bytes calldata name,
uint256 offset
) internal view returns (address, bytes32, uint256) {
uint256 labelLength = uint256(uint8(name[offset]));
if (labelLength == 0) {
return (address(0), bytes32(0), offset);
}
uint256 nextLabel = offset + labelLength + 1;
if (nextLabel > name.length) revert InvalidNameEncoding();

bytes32 labelHash;
// Check if the label is encoded
if (
labelLength == 66 &&
// 0x5b == '['
name[offset + 1] == 0x5b &&
// 0x5d == ']'
name[nextLabel - 1] == 0x5d
) {
// Use the data within the square brackets as the labelhash (i.e. `[...labelhash]`)
(labelHash, ) = bytes(name[offset + 2:nextLabel - 1])
.hexStringToBytes32(0, 64);
} else {
labelHash = keccak256(name[offset + 1:nextLabel]);
}
(
address parentresolver,
bytes32 parentnode,
uint256 parentoffset
) = _findResolver(name, nextLabel);
bytes32 node = keccak256(abi.encodePacked(parentnode, labelHash));
address resolver = _registry.resolver(node);
if (resolver != address(0)) {
return (resolver, node, offset);
}
return (parentresolver, node, parentoffset);
}
}
Loading