Skip to content

Commit

Permalink
feat: ENS portal
Browse files Browse the repository at this point in the history
  • Loading branch information
ZzzzHui committed May 6, 2024
1 parent 472eb80 commit cae579d
Show file tree
Hide file tree
Showing 7 changed files with 270 additions and 11 deletions.
6 changes: 6 additions & 0 deletions .changeset/proud-badgers-prove.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@cartesi/rollups": major
---

Added ENS Portal.
Added a new input encoding for ENS inputs.
18 changes: 18 additions & 0 deletions contracts/common/InputEncoding.sol
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,22 @@ library InputEncoding {
data // arbitrary size
);
}

/// @notice Encode an ENS input.
/// @param node The ENS node
/// @param name The ENS name
/// @param execLayerData Additional data to be interpreted by the execution layer
/// @return The encoded input payload
function encodeENSInput(
bytes32 node,
bytes calldata name,
bytes calldata execLayerData
) internal pure returns (bytes memory) {
return
abi.encode(
node, // 32B
name, // arbitrary size
execLayerData // arbitrary size
);
}
}
18 changes: 7 additions & 11 deletions contracts/delegatecall/AssetTransferToENS.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@
pragma solidity ^0.8.20;

import {ENS} from "@ensdomains/ens-contracts/contracts/registry/ENS.sol";
import {AddrResolver} from "@ensdomains/ens-contracts/contracts/resolvers/profiles/AddrResolver.sol";

import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";

import {LibAddress} from "../library/LibAddress.sol";
import {LibENS} from "../library/LibENS.sol";

contract AssetTransferToENS {
using LibAddress for address;
using SafeERC20 for IERC20;
using LibENS for ENS;

ENS immutable _ens;

Expand All @@ -28,7 +29,7 @@ contract AssetTransferToENS {
uint256 value,
bytes memory payload
) external {
address recipient = _resolveENS(node);
address recipient = _ens.resolveToAddress(node);
recipient.safeCall(value, payload);
}

Expand All @@ -37,7 +38,7 @@ contract AssetTransferToENS {
bytes32 node,
uint256 value
) external {
address recipient = _resolveENS(node);
address recipient = _ens.resolveToAddress(node);
token.safeTransfer(recipient, value);
}

Expand All @@ -47,7 +48,7 @@ contract AssetTransferToENS {
uint256 tokenId,
bytes calldata data
) external {
address recipient = _resolveENS(node);
address recipient = _ens.resolveToAddress(node);
token.safeTransferFrom(address(this), recipient, tokenId, data);
}

Expand All @@ -58,7 +59,7 @@ contract AssetTransferToENS {
uint256 value,
bytes calldata data
) external {
address recipient = _resolveENS(node);
address recipient = _ens.resolveToAddress(node);
token.safeTransferFrom(address(this), recipient, id, value, data);
}

Expand All @@ -69,7 +70,7 @@ contract AssetTransferToENS {
uint256[] memory values,
bytes calldata data
) external {
address recipient = _resolveENS(node);
address recipient = _ens.resolveToAddress(node);
token.safeBatchTransferFrom(
address(this),
recipient,
Expand All @@ -78,9 +79,4 @@ contract AssetTransferToENS {
data
);
}

function _resolveENS(bytes32 node) internal view returns (address) {
AddrResolver resolver = AddrResolver(_ens.resolver(node));
return resolver.addr(node);
}
}
21 changes: 21 additions & 0 deletions contracts/library/LibENS.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

pragma solidity ^0.8.8;

import {ENS} from "@ensdomains/ens-contracts/contracts/registry/ENS.sol";
import {AddrResolver} from "@ensdomains/ens-contracts/contracts/resolvers/profiles/AddrResolver.sol";

library LibENS {
/// @notice Resolve ENS node to address
/// @param ens The ENS registry
/// @param node The ENS node
/// @return The address that ENS node resolves to
function resolveToAddress(
ENS ens,
bytes32 node
) internal view returns (address) {
AddrResolver resolver = AddrResolver(ens.resolver(node));
return resolver.addr(node);
}
}
63 changes: 63 additions & 0 deletions contracts/portals/ENSPortal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

pragma solidity ^0.8.8;

import {ENS} from "@ensdomains/ens-contracts/contracts/registry/ENS.sol";

import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

import {IENSPortal} from "./IENSPortal.sol";
import {Portal} from "./Portal.sol";
import {IInputBox} from "../inputs/IInputBox.sol";
import {InputEncoding} from "../common/InputEncoding.sol";
import {LibENS} from "../library/LibENS.sol";

/// @title ENS Portal
///
/// @notice This contract allows anyone to send input to the InputBox with ENS
contract ENSPortal is IENSPortal, Portal {
using LibENS for ENS;

ENS immutable _ens;

/// @notice Constructs the portal.
/// @param inputBox The input box used by the portal
/// @param ens The ENS registry
constructor(IInputBox inputBox, ENS ens) Portal(inputBox) {
_ens = ens;
}

function sendInputWithENS(
address appContract,
bytes32 node,
bytes calldata name,
bytes calldata execLayerData
) external override {
address resolution = _ens.resolveToAddress(node);

if (resolution != msg.sender) {
revert AddressResolutionMismatch(resolution, msg.sender);
}

bytes memory payload = InputEncoding.encodeENSInput(
node,
name,
execLayerData
);

_inputBox.addInput(appContract, payload);
}

function getENS() external view override returns (ENS) {
return _ens;
}

function supportsInterface(
bytes4 interfaceId
) public view virtual override(IERC165, Portal) returns (bool) {
return
interfaceId == type(IENSPortal).interfaceId ||
super.supportsInterface(interfaceId);
}
}
36 changes: 36 additions & 0 deletions contracts/portals/IENSPortal.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

pragma solidity ^0.8.8;

import {IPortal} from "./IPortal.sol";
import {ENS} from "@ensdomains/ens-contracts/contracts/registry/ENS.sol";

/// @title ENS Portal interface
interface IENSPortal is IPortal {
// Errors

/// @notice The provided ENS node does not resolve to the sender address.
/// @param resolution The address that the ENS node resolves to
/// @param sender The sender address
error AddressResolutionMismatch(address resolution, address sender);

// Permissionless functions

/// @notice Send input to InputBox with ENS.
/// @param appContract The application contract address
/// @param node The ENS node
/// @param name The ENS name
/// @param execLayerData Additional data to be interpreted by
/// the execution layer. The data may include the ENS name
function sendInputWithENS(
address appContract,
bytes32 node,
bytes calldata name,
bytes calldata execLayerData
) external;

/// @notice Get the ENS registry used by this portal.
/// @return The ENS registry
function getENS() external view returns (ENS);
}
119 changes: 119 additions & 0 deletions test/foundry/portals/ENSPortal.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// (c) Cartesi and individual authors (see AUTHORS)
// SPDX-License-Identifier: Apache-2.0 (see LICENSE)

pragma solidity ^0.8.22;

import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";

import {ENS} from "@ensdomains/ens-contracts/contracts/registry/ENS.sol";
import {AddrResolver} from "@ensdomains/ens-contracts/contracts/resolvers/profiles/AddrResolver.sol";

import {ENSPortal} from "contracts/portals/ENSPortal.sol";
import {IENSPortal} from "contracts/portals/IENSPortal.sol";
import {IInputBox} from "contracts/inputs/IInputBox.sol";
import {IPortal} from "contracts/portals/IPortal.sol";
import {InputEncoding} from "contracts/common/InputEncoding.sol";

import {ERC165Test} from "../util/ERC165Test.sol";

contract ENSPortalTest is ERC165Test {
IInputBox _inputBox;
IENSPortal _portal;
ENS _ens;
AddrResolver _resolver;

address _inputSender;
address _appContract;
bytes4[] _interfaceIds;

bytes32 constant _node = keccak256("user.eth");

function setUp() public {
_inputSender = _newAddr();
_appContract = _newAddr();
_inputBox = IInputBox(_newAddr());
_ens = ENS(_newAddr());
_portal = new ENSPortal(_inputBox, _ens);
_resolver = AddrResolver(_newAddr());

vm.mockCall(
address(_ens),
abi.encodeCall(ENS.resolver, (_node)),
abi.encode(_resolver)
);
vm.mockCall(
address(_resolver),
abi.encodeWithSignature("addr(bytes32)", (_node)),
abi.encode(_inputSender)
);

_interfaceIds.push(type(IENSPortal).interfaceId);
_interfaceIds.push(type(IPortal).interfaceId);
}

function getERC165Contract() public view override returns (IERC165) {
return _portal;
}

function getSupportedInterfaces()
public
view
override
returns (bytes4[] memory)
{
return _interfaceIds;
}

function testGetInputBox() public view {
assertEq(address(_portal.getInputBox()), address(_inputBox));
}

function testGetENS() public view {
assertEq(address(_portal.getENS()), address(_ens));
}

function testAddressResolutionMismatch(
address incorrectSender,
bytes calldata name,
bytes calldata execLayerData
) public {
vm.assume(incorrectSender != _inputSender);

vm.expectRevert(
abi.encodeWithSelector(
IENSPortal.AddressResolutionMismatch.selector,
_inputSender,
incorrectSender
)
);
vm.prank(incorrectSender);
_portal.sendInputWithENS(_appContract, _node, name, execLayerData);
}

function testSendInput(
bytes calldata name,
bytes calldata execLayerData
) public {
bytes memory payload = _encodePayload(_node, name, execLayerData);
bytes memory addInput = _encodeAddInput(payload);

vm.mockCall(address(_inputBox), addInput, abi.encode(bytes32(0)));
vm.expectCall(address(_inputBox), addInput, 1);
vm.prank(_inputSender);
_portal.sendInputWithENS(_appContract, _node, name, execLayerData);
}

function _encodePayload(
bytes32 node,
bytes calldata name,
bytes calldata execLayerData
) internal pure returns (bytes memory) {
return InputEncoding.encodeENSInput(node, name, execLayerData);
}

function _encodeAddInput(
bytes memory payload
) internal view returns (bytes memory) {
return abi.encodeCall(IInputBox.addInput, (_appContract, payload));
}
}

0 comments on commit cae579d

Please sign in to comment.