Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.26"
remappings = [
"@ensdomains/buffer/=lib/buffer",
"solady/=lib/solady/src/",
Expand Down
48 changes: 44 additions & 4 deletions src/L2/resolver/AddrResolver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,21 @@ abstract contract AddrResolver is IAddrResolver, IAddressResolver, ResolverBase
// keccak256(abi.encode(uint256(keccak256("addr.resolver.storage")) - 1)) & ~bytes32(uint256(0xff));
bytes32 constant ADDR_RESOLVER_STORAGE = 0x1871a91a9a944f867849820431bb11c2d1625edae573523bceb5b38b8b8a7500;

/// @notice Ethereum chain id.
uint32 constant CHAIN_ID_ETH = 1;

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

/// @notice EVM default cointype per ENSIP-19.
uint256 constant COIN_TYPE_DEFAULT = 1 << 31; // 0x8000_0000

/// @notice Thrown when an invalid bytes length is detected.
error InvalidBytesLength();

/// @notice Thrown when setting an invalid EVM address for a valid EVM cointype.
error InvalidEVMAddress(bytes a);

/// @notice Sets the address associated with an ENS node.
///
/// @dev May only be called by the owner of that node in the ENS registry.
Expand All @@ -46,6 +55,9 @@ abstract contract AddrResolver is IAddrResolver, IAddressResolver, ResolverBase
/// @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 authorized(node) {
if (a.length != 0 && a.length != 20 && isEVMCoinType(coinType)) {
revert InvalidEVMAddress(a);
}
emit AddressChanged(node, coinType, a);
if (coinType == COIN_TYPE_ETH) {
emit AddrChanged(node, bytesToAddress(a));
Expand All @@ -71,14 +83,20 @@ abstract contract AddrResolver is IAddrResolver, IAddressResolver, ResolverBase

/// @notice Returns the address of the `node` for a specified `coinType`.
///
/// @dev Complies with ENSIP-9 and ENSIP-11.
/// @dev Complies with ENSIP-9, ENSIP-11 and ENSIP-19.
/// Will return `default` address if there is no address set for a specific EVM cointype.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm assuming this behavior is defined in one of these ENSIPs, since returning something based on the default cointype v.s. returning nothing is definitely a meaningful change in behavior.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the addr get set for the default coinType when registered?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default was a late addition to the ENSIP-19 spec.

///
/// @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];
/// @return addressBytes The address of the specified `node` for the specified `coinType`.
function addr(bytes32 node, uint256 coinType) public view virtual override returns (bytes memory addressBytes) {
mapping(uint256 coinType => bytes addr) storage addrs =
_getAddrResolverStorage().versionable_addresses[_getResolverBaseStorage().recordVersions[node]][node];
addressBytes = addrs[coinType];
if (addressBytes.length == 0 && chainFromCoinType(coinType) > 0) {
addressBytes = addrs[COIN_TYPE_DEFAULT];
}
}

/// @notice ERC-165 compliance.
Expand All @@ -103,6 +121,28 @@ abstract contract AddrResolver is IAddrResolver, IAddressResolver, ResolverBase
}
}

/// @notice Fetch the uint32 coinType from an EVM coinType.
///
/// @dev Extract Chain ID from `coinType`.
///
/// @param coinType The coin type.
///
/// @return The Chain ID or 0 if non-EVM Chain.
function chainFromCoinType(uint256 coinType) internal pure returns (uint32) {
if (coinType == COIN_TYPE_ETH) return CHAIN_ID_ETH;
coinType ^= COIN_TYPE_DEFAULT;
return uint32(coinType < COIN_TYPE_DEFAULT ? coinType : 0);
}

/// @notice Determine if `coinType` is for an EVM address.
///
/// @param coinType The network-as-coin type.
///
/// @return `true` if coin type represents an EVM address, else `false`.
function isEVMCoinType(uint256 coinType) internal pure returns (bool) {
return coinType == COIN_TYPE_DEFAULT || chainFromCoinType(coinType) > 0;
}

/// @notice EIP-7201 storage pointer fetch helper.
function _getAddrResolverStorage() internal pure returns (AddrResolverStorage storage $) {
assembly {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/SignatureVerifier.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
pragma solidity ^0.8.23;

import {ECDSA} from "solady/utils/ECDSA.sol";

Expand Down
2 changes: 1 addition & 1 deletion test/L1Resolver/AdminMethods.t.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
pragma solidity ^0.8.23;

import {Test, console} from "forge-std/Test.sol";
import {L1ResolverTestBase} from "./L1ResolverBase.t.sol";
Expand Down
2 changes: 1 addition & 1 deletion test/L1Resolver/Fallback.t.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
pragma solidity ^0.8.23;

import {Test, console} from "forge-std/Test.sol";
import {L1ResolverTestBase} from "./L1ResolverBase.t.sol";
Expand Down
2 changes: 1 addition & 1 deletion test/L1Resolver/MakeSignatureHash.t.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
pragma solidity ^0.8.23;

import {Test, console} from "forge-std/Test.sol";
import {L1ResolverTestBase} from "./L1ResolverBase.t.sol";
Expand Down
2 changes: 1 addition & 1 deletion test/L1Resolver/Resolve.t.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
pragma solidity ^0.8.23;

import {Test, console} from "forge-std/Test.sol";
import {L1ResolverTestBase} from "./L1ResolverBase.t.sol";
Expand Down
2 changes: 1 addition & 1 deletion test/L1Resolver/ResolveWithProof.t.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
pragma solidity ^0.8.23;

import {Test, console} from "forge-std/Test.sol";
import {L1ResolverTestBase} from "./L1ResolverBase.t.sol";
Expand Down
2 changes: 1 addition & 1 deletion test/L1Resolver/SupportsInterface.t.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
pragma solidity ^0.8.23;

import {Test, console} from "forge-std/Test.sol";
import {L1ResolverTestBase} from "./L1ResolverBase.t.sol";
Expand Down
20 changes: 20 additions & 0 deletions test/UpgradeableL2Resolver/SetAddr.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ contract SetAddr is UpgradeableL2ResolverBase {
uint256 BTC_COINTYPE = 0;
uint256 ETH_COINTYPE = 60;
uint256 BASE_COINTYPE = 2147492101;
uint256 COIN_TYPE_DEFAULT = 1 << 31; // 0x8000_0000

function test_reverts_forUnauthorizedUser() public {
vm.expectRevert(abi.encodeWithSelector(ResolverBase.NotAuthorized.selector, node, notUser));
Expand All @@ -22,6 +23,13 @@ contract SetAddr is UpgradeableL2ResolverBase {
resolver.setAddr(node, 60, "");
}

function test_reverts_whenInvalidEVMAddress() public {
bytes memory badAddr = bytes("1234");
vm.expectRevert(abi.encodeWithSelector(AddrResolver.InvalidEVMAddress.selector, badAddr));
vm.prank(user);
resolver.setAddr(node, BASE_COINTYPE, badAddr);
}

function test_setsAnETHAddress_byDefault(address a) public {
vm.prank(user);
resolver.setAddr(node, a);
Expand All @@ -42,6 +50,18 @@ contract SetAddr is UpgradeableL2ResolverBase {
assertEq(bytesToAddress(resolver.addr(node, BASE_COINTYPE)), a);
}

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

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

function test_setsABtcAddress() public {
bytes memory satoshi = hex"76a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1888ac";
vm.prank(user);
Expand Down
2 changes: 1 addition & 1 deletion test/mocks/MockPublicResolver.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
pragma solidity ^0.8.23;

import {BASE_ETH_NODE} from "src/util/Constants.sol";
import {ExtendedResolver} from "ens-contracts/resolvers/profiles/ExtendedResolver.sol";
Expand Down