Skip to content

Commit

Permalink
Cleanup natspec in InterfaceResolver, add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
stevieraykatz committed Nov 15, 2024
1 parent 953172f commit 4b12718
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 22 deletions.
2 changes: 1 addition & 1 deletion src/L2/resolver/AddrResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {IAddressResolver} from "ens-contracts/resolvers/profiles/IAddressResolve

import {ResolverBase} from "./ResolverBase.sol";

/// @title AddrResolver
/// @title Address Resolver
///
/// @notice ENSIP-11 compliant Address Resolver. Adaptation of the ENS AddrResolver.sol profile contract, with
/// EIP-7201 storage compliance.
Expand Down
51 changes: 31 additions & 20 deletions src/L2/resolver/InterfaceResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,59 @@ pragma solidity ^0.8.23;

import {IERC165} from "lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol";
import {IInterfaceResolver} from "ens-contracts/resolvers/profiles/IInterfaceResolver.sol";
import {IAddrResolver} from "ens-contracts/resolvers/profiles/IAddrResolver.sol";

import {AddrResolver} from "./AddrResolver.sol";
import {ResolverBase} from "./ResolverBase.sol";

abstract contract InterfaceResolver is IInterfaceResolver, AddrResolver {
/// @title Interface Resolver
///
/// @notice ENSIP-8 compliant Interface Discovery resolver. Adaptation of the ENS InterfaceResolver.sol profile contract, with
/// EIP-7201 storage compliance.
/// https://github.com/ensdomains/ens-contracts/blob/staging/contracts/resolvers/profiles/InterfaceResolver.sol
///
/// @author Coinbase (https://github.com/base-org/basenames)
abstract contract InterfaceResolver is IInterfaceResolver, ResolverBase {
struct InterfaceResolverStorage {
/// @notice Interface implementer address by interface id, node and version.
mapping(uint64 version => mapping(bytes32 node => mapping(bytes4 interfaceId => address implemenentor)))
versionable_interfaces;
}

/// @notice EIP-7201 storage location.
// keccak256(abi.encode(uint256(keccak256("interface.resolver.storage")) - 1)) & ~bytes32(uint256(0xff));
bytes32 constant INTERFACE_RESOLVER_STORAGE = 0x933ab330cd660334bb219a68b3bfaf86387ecd49e4e53a39e8310a5bd6910c00;

/**
* Sets an interface associated with a name.
* Setting the address to 0 restores the default behaviour of querying the contract at `addr()` for interface support.
* @param node The node to update.
* @param interfaceID The EIP 165 interface ID.
* @param implementer The address of a contract that implements this interface for this node.
*/
/// @notice Sets an interface implementer address.
///
/// @dev Setting the address to 0 restores the default behaviour of querying the contract at `addr()` for interface support.
///
/// @param node The node to update.
/// @param interfaceID The EIP-165 interface ID.
/// @param implementer The address of a contract that implements this interface for this node.
function setInterface(bytes32 node, bytes4 interfaceID, address implementer) external virtual authorised(node) {
_getInterfaceResolverStorage().versionable_interfaces[_getResolverBaseStorage().recordVersions[node]][node][interfaceID]
= implementer;
emit InterfaceChanged(node, interfaceID, implementer);
}

/**
* Returns the address of a contract that implements the specified interface for this name.
* If an implementer has not been set for this interfaceID and name, the resolver will query
* the contract at `addr()`. If `addr()` is set, a contract exists at that address, and that
* contract implements EIP165 and returns `true` for the specified interfaceID, its address
* will be returned.
* @param node The ENS node to query.
* @param interfaceID The EIP 165 interface ID to check for.
* @return The address that implements this interface, or 0 if the interface is unsupported.
*/
/// @notice Returns the address of a contract that implements the specified interface for this name.
///
/// @dev If an implementer has not been set for this interfaceID and name, this resolver will query
/// itself at `addr(node)`. If `addr()` is set, a contract exists at that address, and that
/// contract implements EIP165 and returns `true` for the specified interfaceID, its address
/// will be returned.
/// @param node The ENS node to query.
/// @param interfaceID The EIP 165 interface ID to check for.
///
/// @return The address that implements this interface, or address(0) if the interface is unsupported.
function interfaceImplementer(bytes32 node, bytes4 interfaceID) external view virtual override returns (address) {
address implementer = _getInterfaceResolverStorage().versionable_interfaces[_getResolverBaseStorage()
.recordVersions[node]][node][interfaceID];
if (implementer != address(0)) {
return implementer;
}

address a = addr(node);
address a = IAddrResolver(address(this)).addr(node);
if (a == address(0)) {
return address(0);
}
Expand All @@ -67,10 +76,12 @@ abstract contract InterfaceResolver is IInterfaceResolver, AddrResolver {
return a;
}

/// @notice ERC-165 compliance.
function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) {
return interfaceID == type(IInterfaceResolver).interfaceId || super.supportsInterface(interfaceID);
}

/// @notice EIP-7201 storage pointer fetch helper.
function _getInterfaceResolverStorage() internal pure returns (InterfaceResolverStorage storage $) {
assembly {
$.slot := INTERFACE_RESOLVER_STORAGE
Expand Down
49 changes: 49 additions & 0 deletions test/UpgradeableL2Resolver/SetInterface.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {ERC165} from "lib/openzeppelin-contracts/contracts/utils/introspection/ERC165.sol";
import {UpgradeableL2ResolverBase} from "./UpgradeableL2ResolverBase.t.sol";
import {ResolverBase} from "src/L2/resolver/ResolverBase.sol";
import {InterfaceResolver} from "src/L2/resolver/InterfaceResolver.sol";

contract SetInterface is UpgradeableL2ResolverBase {
Counter counter;

function setUp() public override {
super.setUp();
counter = new Counter();
}

function test_reverts_forUnauthorizedUser() public {
vm.expectRevert(abi.encodeWithSelector(ResolverBase.NotAuthorized.selector, node, notUser));
vm.prank(notUser);
resolver.setInterface(node, type(ICounter).interfaceId, address(counter));
}

function test_setsTheInterface_whenTheAddressIsSpecifiedExplicitly() public {
vm.prank(user);
resolver.setInterface(node, type(ICounter).interfaceId, address(counter));
assertEq(resolver.interfaceImplementer(node, type(ICounter).interfaceId), address(counter));
}

function test_returnsTheInterface_whenTheAddressIsSetToTheAddrProfile() public {
vm.prank(user);
resolver.setAddr(node, address(counter));
assertEq(resolver.interfaceImplementer(node, type(ICounter).interfaceId), address(counter));
}
}

interface ICounter {
function set(uint x) external;
}

contract Counter is ICounter, ERC165 {
uint public x;
function set(uint x_) external {
x = x_;
}
/// @notice ERC-165 compliance.
function supportsInterface(bytes4 interfaceID) public view virtual override returns (bool) {
return interfaceID == type(ICounter).interfaceId || super.supportsInterface(interfaceID);
}
}
2 changes: 1 addition & 1 deletion test/UpgradeableL2Resolver/UpgradeableL2ResolverBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ contract UpgradeableL2ResolverBase is Test {
bytes32 label = keccak256("test");
bytes32 node;

function setUp() public {
function setUp() public virtual {
registry = new Registry(owner);
reverse = address(new MockReverseRegistrar());
resolverImpl = new UpgradeableL2Resolver();
Expand Down

0 comments on commit 4b12718

Please sign in to comment.