Skip to content

Commit

Permalink
Finish Addr Resolver + unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
stevieraykatz committed Nov 14, 2024
1 parent 9f1cf35 commit 359b3fb
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 13 deletions.
4 changes: 2 additions & 2 deletions src/L2/resolver/ABIResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ import {ResolverBase} from "./ResolverBase.sol";
/// @author Coinbase (https://github.com/base-org/basenames)
abstract contract ABIResolver is IABIResolver, ResolverBase {
struct ABIResolverStorage {
/// @notice ABI storage (`bytes`) by content type, node, and version.
/// @notice ABI record (`bytes`) by content type, node, and version.
mapping(uint64 version => mapping(bytes32 node => mapping(uint256 contentType => bytes data))) versionable_abis;
}

/// @notice Thrown when setting an ABI with an invalid content type.
error InvalidContentType();

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

Expand Down
52 changes: 41 additions & 11 deletions src/L2/resolver/AddrResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,45 @@ import {IAddressResolver} from "ens-contracts/resolvers/profiles/IAddressResolve

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

/// @title AddrResolver
///
/// @notice ENSIP-11 compliant Address Resolver. Adaptation of the ENS AddrResolver.sol profile contract, with
/// EIP-7201 storage compliance.
/// https://github.com/ensdomains/ens-contracts/blob/staging/contracts/resolvers/profiles/AddrResolver.sol
///
/// @author Coinbase (https://github.com/base-org/basenames)
abstract contract AddrResolver is IAddrResolver, IAddressResolver, ResolverBase {
struct AddrResolverStorage {
/// @notice Address record per cointype, node and version.
mapping(uint64 version => mapping(bytes32 node => mapping(uint256 cointype => bytes addr)))
versionable_addresses;
}

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

/// @notice Ethereum mainnet network-as-cointype.
uint256 private constant COIN_TYPE_ETH = 60;

/**
* Sets the address associated with an ENS node.
* May only be called by the owner of that node in the ENS registry.
* @param node The node to update.
* @param a The address to set.
*/

/// @notice Sets the address associated with an ENS node.
///
/// @dev May only be called by the owner of that node in the ENS registry.
///
/// @param node The node to update.
/// @param a The address to set.
function setAddr(bytes32 node, address a) external virtual authorised(node) {
setAddr(node, COIN_TYPE_ETH, addressToBytes(a));
}

/**
* Returns the address associated with an ENS node.
* @param node The ENS node to query.
* @return The associated address.
*/
/// @notice Returns the address associated with a specified ENS `node`.
///
/// @dev Returns the `addr` record for the Ethereum Mainnet network-as-cointype.
///
/// @param node The ENS node to query.
///
/// @return The associated address.
function addr(bytes32 node) public view virtual override returns (address payable) {
bytes memory a = addr(node, COIN_TYPE_ETH);
if (a.length == 0) {
Expand All @@ -40,6 +53,11 @@ abstract contract AddrResolver is IAddrResolver, IAddressResolver, ResolverBase
return bytesToAddress(a);
}

/// @notice Set the network or coin-specific address for an ENS `node`.
///
/// @param node The ENS node to update.
/// @param coinType The coinType for this address.
/// @param a The network-agnostic bytes of the address.
function setAddr(bytes32 node, uint256 coinType, bytes memory a) public virtual authorised(node) {
emit AddressChanged(node, coinType, a);
if (coinType == COIN_TYPE_ETH) {
Expand All @@ -49,29 +67,41 @@ abstract contract AddrResolver is IAddrResolver, IAddressResolver, ResolverBase
= a;
}

/// @notice Returns the address of the `node` for a specified `coinType`.
///
/// @dev Complies with ENSIP-9 and ENSIP-11.
///
/// @param node The ENS node to update.
/// @param coinType The coinType to fetch.
///
/// @return The address of the specified `node` for the specified `coinType`.
function addr(bytes32 node, uint256 coinType) public view virtual override returns (bytes memory) {
return _getAddrResolverStorage().versionable_addresses[_getResolverBaseStorage().recordVersions[node]][node][coinType];
}

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

/// @notice Helper to convert bytes into an EVM address object.
function bytesToAddress(bytes memory b) internal pure returns (address payable a) {
require(b.length == 20);
assembly {
a := div(mload(add(b, 32)), exp(256, 12))
}
}

/// @notice Helper to convert an EVM address to a bytes object.
function addressToBytes(address a) internal pure returns (bytes memory b) {
b = new bytes(20);
assembly {
mstore(add(b, 32), mul(a, exp(256, 12)))
}
}

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

import {UpgradeableL2ResolverBase} from "./UpgradeableL2ResolverBase.t.sol";
import {AddrResolver} from "src/L2/resolver/AddrResolver.sol";

contract SetAddr is UpgradeableL2ResolverBase {
uint256 BTC_COINTYPE = 0;
uint256 ETH_COINTYPE = 60;
uint256 BASE_COINTYPE = 2147492101;

function test_setsAnETHAddress_byDefault(address a) public {
vm.prank(user);
resolver.setAddr(node, a);
assertEq(resolver.addr(node), a);
assertEq(bytesToAddress(resolver.addr(node, ETH_COINTYPE)), a);
}

function test_setsAnETHAddress(address a) public {
vm.prank(user);
resolver.setAddr(node, ETH_COINTYPE, addressToBytes(a));
assertEq(resolver.addr(node), a);
assertEq(bytesToAddress(resolver.addr(node, ETH_COINTYPE)), a);
}

function test_setsABaseAddress(address a) public {
vm.prank(user);
resolver.setAddr(node, BASE_COINTYPE, addressToBytes(a));
assertEq(bytesToAddress(resolver.addr(node, BASE_COINTYPE)), a);
}

function test_setsABtcAddress() public {
bytes memory satoshi = "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa";
vm.prank(user);
resolver.setAddr(node, BTC_COINTYPE, satoshi);
assertEq(keccak256(resolver.addr(node, BTC_COINTYPE)), keccak256(satoshi));
}

/// @notice Helper to convert bytes into an EVM address object.
function bytesToAddress(bytes memory b) internal pure returns (address payable a) {
require(b.length == 20);
assembly {
a := div(mload(add(b, 32)), exp(256, 12))
}
}

/// @notice Helper to convert an EVM address to a bytes object.
function addressToBytes(address a) internal pure returns (bytes memory b) {
b = new bytes(20);
assembly {
mstore(add(b, 32), mul(a, exp(256, 12)))
}
}
}

0 comments on commit 359b3fb

Please sign in to comment.