diff --git a/onchain/rollups/.changeset/sharp-actors-brake.md b/onchain/rollups/.changeset/sharp-actors-brake.md new file mode 100644 index 00000000..0528d4ae --- /dev/null +++ b/onchain/rollups/.changeset/sharp-actors-brake.md @@ -0,0 +1,6 @@ +--- +"@cartesi/rollups": major +--- + +Implemented EIP-165 for input relays. +This is because `CartesiDApp` can return an array of input relays. EIP-165 helps to tell which interfaces the relay implements. diff --git a/onchain/rollups/contracts/inputs/IInputRelay.sol b/onchain/rollups/contracts/inputs/IInputRelay.sol index ed607d2c..c8115bed 100644 --- a/onchain/rollups/contracts/inputs/IInputRelay.sol +++ b/onchain/rollups/contracts/inputs/IInputRelay.sol @@ -4,9 +4,10 @@ pragma solidity ^0.8.8; import {IInputBox} from "./IInputBox.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; /// @title Input Relay interface -interface IInputRelay { +interface IInputRelay is IERC165 { // Permissionless functions /// @notice Get the input box used by this input relay. diff --git a/onchain/rollups/contracts/inputs/InputRelay.sol b/onchain/rollups/contracts/inputs/InputRelay.sol index b2eabd81..b5002a0b 100644 --- a/onchain/rollups/contracts/inputs/InputRelay.sol +++ b/onchain/rollups/contracts/inputs/InputRelay.sol @@ -5,10 +5,11 @@ pragma solidity ^0.8.8; import {IInputRelay} from "./IInputRelay.sol"; import {IInputBox} from "./IInputBox.sol"; +import {ERC165, IERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; /// @title Input Relay /// @notice This contract serves as a base for all the other input relays. -contract InputRelay is IInputRelay { +contract InputRelay is IInputRelay, ERC165 { /// @notice The input box used by the input relay. IInputBox internal immutable inputBox; @@ -18,6 +19,14 @@ contract InputRelay is IInputRelay { inputBox = _inputBox; } + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IInputRelay).interfaceId || + super.supportsInterface(interfaceId); + } + function getInputBox() external view override returns (IInputBox) { return inputBox; } diff --git a/onchain/rollups/contracts/portals/ERC1155BatchPortal.sol b/onchain/rollups/contracts/portals/ERC1155BatchPortal.sol index 1aeee7c7..fa404713 100644 --- a/onchain/rollups/contracts/portals/ERC1155BatchPortal.sol +++ b/onchain/rollups/contracts/portals/ERC1155BatchPortal.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.8; import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IERC1155BatchPortal} from "./IERC1155BatchPortal.sol"; import {InputRelay} from "../inputs/InputRelay.sol"; @@ -14,11 +15,19 @@ import {InputEncoding} from "../common/InputEncoding.sol"; /// /// @notice This contract allows anyone to perform batch transfers of /// ERC-1155 tokens to a DApp while informing the off-chain machine. -contract ERC1155BatchPortal is InputRelay, IERC1155BatchPortal { +contract ERC1155BatchPortal is IERC1155BatchPortal, InputRelay { /// @notice Constructs the portal. /// @param _inputBox The input box used by the portal constructor(IInputBox _inputBox) InputRelay(_inputBox) {} + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(IERC165, InputRelay) returns (bool) { + return + interfaceId == type(IERC1155BatchPortal).interfaceId || + super.supportsInterface(interfaceId); + } + function depositBatchERC1155Token( IERC1155 _token, address _dapp, diff --git a/onchain/rollups/contracts/portals/ERC1155SinglePortal.sol b/onchain/rollups/contracts/portals/ERC1155SinglePortal.sol index 747a2b62..4d31ca9c 100644 --- a/onchain/rollups/contracts/portals/ERC1155SinglePortal.sol +++ b/onchain/rollups/contracts/portals/ERC1155SinglePortal.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.8; import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IERC1155SinglePortal} from "./IERC1155SinglePortal.sol"; import {InputRelay} from "../inputs/InputRelay.sol"; @@ -14,11 +15,19 @@ import {InputEncoding} from "../common/InputEncoding.sol"; /// /// @notice This contract allows anyone to perform single transfers of /// ERC-1155 tokens to a DApp while informing the off-chain machine. -contract ERC1155SinglePortal is InputRelay, IERC1155SinglePortal { +contract ERC1155SinglePortal is IERC1155SinglePortal, InputRelay { /// @notice Constructs the portal. /// @param _inputBox The input box used by the portal constructor(IInputBox _inputBox) InputRelay(_inputBox) {} + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(IERC165, InputRelay) returns (bool) { + return + interfaceId == type(IERC1155SinglePortal).interfaceId || + super.supportsInterface(interfaceId); + } + function depositSingleERC1155Token( IERC1155 _token, address _dapp, diff --git a/onchain/rollups/contracts/portals/ERC20Portal.sol b/onchain/rollups/contracts/portals/ERC20Portal.sol index 98876571..aa55f35d 100644 --- a/onchain/rollups/contracts/portals/ERC20Portal.sol +++ b/onchain/rollups/contracts/portals/ERC20Portal.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.8; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IERC20Portal} from "./IERC20Portal.sol"; import {InputRelay} from "../inputs/InputRelay.sol"; @@ -15,13 +16,21 @@ import {InputEncoding} from "../common/InputEncoding.sol"; /// /// @notice This contract allows anyone to perform transfers of /// ERC-20 tokens to a DApp while informing the off-chain machine. -contract ERC20Portal is InputRelay, IERC20Portal { +contract ERC20Portal is IERC20Portal, InputRelay { using SafeERC20 for IERC20; /// @notice Constructs the portal. /// @param _inputBox The input box used by the portal constructor(IInputBox _inputBox) InputRelay(_inputBox) {} + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(IERC165, InputRelay) returns (bool) { + return + interfaceId == type(IERC20Portal).interfaceId || + super.supportsInterface(interfaceId); + } + function depositERC20Tokens( IERC20 _token, address _dapp, diff --git a/onchain/rollups/contracts/portals/ERC721Portal.sol b/onchain/rollups/contracts/portals/ERC721Portal.sol index 0e36bbe5..6080ff09 100644 --- a/onchain/rollups/contracts/portals/ERC721Portal.sol +++ b/onchain/rollups/contracts/portals/ERC721Portal.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.8; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IERC721Portal} from "./IERC721Portal.sol"; import {InputRelay} from "../inputs/InputRelay.sol"; @@ -14,11 +15,19 @@ import {InputEncoding} from "../common/InputEncoding.sol"; /// /// @notice This contract allows anyone to perform transfers of /// ERC-721 tokens to a DApp while informing the off-chain machine. -contract ERC721Portal is InputRelay, IERC721Portal { +contract ERC721Portal is IERC721Portal, InputRelay { /// @notice Constructs the portal. /// @param _inputBox The input box used by the portal constructor(IInputBox _inputBox) InputRelay(_inputBox) {} + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(IERC165, InputRelay) returns (bool) { + return + interfaceId == type(IERC721Portal).interfaceId || + super.supportsInterface(interfaceId); + } + function depositERC721Token( IERC721 _token, address _dapp, diff --git a/onchain/rollups/contracts/portals/EtherPortal.sol b/onchain/rollups/contracts/portals/EtherPortal.sol index 46a9a9ba..801d4f3b 100644 --- a/onchain/rollups/contracts/portals/EtherPortal.sol +++ b/onchain/rollups/contracts/portals/EtherPortal.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.8; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + import {IEtherPortal} from "./IEtherPortal.sol"; import {InputRelay} from "../inputs/InputRelay.sol"; import {IInputBox} from "../inputs/IInputBox.sol"; @@ -12,7 +14,7 @@ import {InputEncoding} from "../common/InputEncoding.sol"; /// /// @notice This contract allows anyone to perform transfers of /// Ether to a DApp while informing the off-chain machine. -contract EtherPortal is InputRelay, IEtherPortal { +contract EtherPortal is IEtherPortal, InputRelay { /// @notice Raised when the Ether transfer fails. error EtherTransferFailed(); @@ -20,6 +22,14 @@ contract EtherPortal is InputRelay, IEtherPortal { /// @param _inputBox The input box used by the portal constructor(IInputBox _inputBox) InputRelay(_inputBox) {} + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(IERC165, InputRelay) returns (bool) { + return + interfaceId == type(IEtherPortal).interfaceId || + super.supportsInterface(interfaceId); + } + function depositEther( address _dapp, bytes calldata _execLayerData diff --git a/onchain/rollups/contracts/relays/DAppAddressRelay.sol b/onchain/rollups/contracts/relays/DAppAddressRelay.sol index ff3520e3..cfb2ed2a 100644 --- a/onchain/rollups/contracts/relays/DAppAddressRelay.sol +++ b/onchain/rollups/contracts/relays/DAppAddressRelay.sol @@ -3,6 +3,8 @@ pragma solidity ^0.8.8; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; + import {IDAppAddressRelay} from "./IDAppAddressRelay.sol"; import {InputRelay} from "../inputs/InputRelay.sol"; import {IInputBox} from "../inputs/IInputBox.sol"; @@ -12,11 +14,19 @@ import {InputEncoding} from "../common/InputEncoding.sol"; /// /// @notice This contract allows anyone to inform the off-chain machine /// of the address of the DApp contract in a trustless and permissionless way. -contract DAppAddressRelay is InputRelay, IDAppAddressRelay { +contract DAppAddressRelay is IDAppAddressRelay, InputRelay { /// @notice Constructs the relay. /// @param _inputBox The input box used by the relay constructor(IInputBox _inputBox) InputRelay(_inputBox) {} + function supportsInterface( + bytes4 interfaceId + ) public view virtual override(IERC165, InputRelay) returns (bool) { + return + interfaceId == type(IDAppAddressRelay).interfaceId || + super.supportsInterface(interfaceId); + } + function relayDAppAddress(address _dapp) external override { bytes memory input = InputEncoding.encodeDAppAddressRelay(_dapp); inputBox.addInput(_dapp, input); diff --git a/onchain/rollups/test/foundry/portals/ERC1155BatchPortal.t.sol b/onchain/rollups/test/foundry/portals/ERC1155BatchPortal.t.sol index c16bd3bd..93e26c59 100644 --- a/onchain/rollups/test/foundry/portals/ERC1155BatchPortal.t.sol +++ b/onchain/rollups/test/foundry/portals/ERC1155BatchPortal.t.sol @@ -10,8 +10,10 @@ import {IERC1155BatchPortal} from "contracts/portals/IERC1155BatchPortal.sol"; import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IInputBox} from "contracts/inputs/IInputBox.sol"; import {InputBox} from "contracts/inputs/InputBox.sol"; +import {IInputRelay} from "contracts/inputs/IInputRelay.sol"; contract BatchToken is ERC1155 { constructor( @@ -80,6 +82,21 @@ contract ERC1155BatchPortalTest is Test { bob = vm.addr(3); } + function testSupportsInterface(bytes4 _randomInterfaceId) public { + assertTrue( + portal.supportsInterface(type(IERC1155BatchPortal).interfaceId) + ); + assertTrue(portal.supportsInterface(type(IInputRelay).interfaceId)); + assertTrue(portal.supportsInterface(type(IERC165).interfaceId)); + + assertFalse(portal.supportsInterface(bytes4(0xffffffff))); + + vm.assume(_randomInterfaceId != type(IERC1155BatchPortal).interfaceId); + vm.assume(_randomInterfaceId != type(IInputRelay).interfaceId); + vm.assume(_randomInterfaceId != type(IERC165).interfaceId); + assertFalse(portal.supportsInterface(_randomInterfaceId)); + } + function testGetInputBoxBatch() public { assertEq(address(portal.getInputBox()), address(inputBox)); } diff --git a/onchain/rollups/test/foundry/portals/ERC1155SinglePortal.t.sol b/onchain/rollups/test/foundry/portals/ERC1155SinglePortal.t.sol index 08318bdf..1a802b2e 100644 --- a/onchain/rollups/test/foundry/portals/ERC1155SinglePortal.t.sol +++ b/onchain/rollups/test/foundry/portals/ERC1155SinglePortal.t.sol @@ -10,8 +10,10 @@ import {IERC1155SinglePortal} from "contracts/portals/IERC1155SinglePortal.sol"; import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IInputBox} from "contracts/inputs/IInputBox.sol"; import {InputBox} from "contracts/inputs/InputBox.sol"; +import {IInputRelay} from "contracts/inputs/IInputRelay.sol"; contract NormalToken is ERC1155 { constructor( @@ -90,6 +92,21 @@ contract ERC1155SinglePortalTest is Test { bob = vm.addr(3); } + function testSupportsInterface(bytes4 _randomInterfaceId) public { + assertTrue( + portal.supportsInterface(type(IERC1155SinglePortal).interfaceId) + ); + assertTrue(portal.supportsInterface(type(IInputRelay).interfaceId)); + assertTrue(portal.supportsInterface(type(IERC165).interfaceId)); + + assertFalse(portal.supportsInterface(bytes4(0xffffffff))); + + vm.assume(_randomInterfaceId != type(IERC1155SinglePortal).interfaceId); + vm.assume(_randomInterfaceId != type(IInputRelay).interfaceId); + vm.assume(_randomInterfaceId != type(IERC165).interfaceId); + assertFalse(portal.supportsInterface(_randomInterfaceId)); + } + function testGetInputBox() public { assertEq(address(portal.getInputBox()), address(inputBox)); } diff --git a/onchain/rollups/test/foundry/portals/ERC20Portal.t.sol b/onchain/rollups/test/foundry/portals/ERC20Portal.t.sol index ba18ad16..b0611fc4 100644 --- a/onchain/rollups/test/foundry/portals/ERC20Portal.t.sol +++ b/onchain/rollups/test/foundry/portals/ERC20Portal.t.sol @@ -9,8 +9,10 @@ import {ERC20Portal} from "contracts/portals/ERC20Portal.sol"; import {IERC20Portal} from "contracts/portals/IERC20Portal.sol"; import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IInputBox} from "contracts/inputs/IInputBox.sol"; import {InputBox} from "contracts/inputs/InputBox.sol"; +import {IInputRelay} from "contracts/inputs/IInputRelay.sol"; contract NormalToken is ERC20 { constructor( @@ -137,6 +139,19 @@ contract ERC20PortalTest is Test { dapp = vm.addr(2); } + function testSupportsInterface(bytes4 _randomInterfaceId) public { + assertTrue(portal.supportsInterface(type(IERC20Portal).interfaceId)); + assertTrue(portal.supportsInterface(type(IInputRelay).interfaceId)); + assertTrue(portal.supportsInterface(type(IERC165).interfaceId)); + + assertFalse(portal.supportsInterface(bytes4(0xffffffff))); + + vm.assume(_randomInterfaceId != type(IERC20Portal).interfaceId); + vm.assume(_randomInterfaceId != type(IInputRelay).interfaceId); + vm.assume(_randomInterfaceId != type(IERC165).interfaceId); + assertFalse(portal.supportsInterface(_randomInterfaceId)); + } + function testGetInputBox() public { assertEq(address(portal.getInputBox()), address(inputBox)); } diff --git a/onchain/rollups/test/foundry/portals/ERC721Portal.t.sol b/onchain/rollups/test/foundry/portals/ERC721Portal.t.sol index 3cd25216..49527ad8 100644 --- a/onchain/rollups/test/foundry/portals/ERC721Portal.t.sol +++ b/onchain/rollups/test/foundry/portals/ERC721Portal.t.sol @@ -10,9 +10,11 @@ import {IERC721Portal} from "contracts/portals/IERC721Portal.sol"; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import {IERC721Receiver} from "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IInputBox} from "contracts/inputs/IInputBox.sol"; import {InputBox} from "contracts/inputs/InputBox.sol"; import {InputEncoding} from "contracts/common/InputEncoding.sol"; +import {IInputRelay} from "contracts/inputs/IInputRelay.sol"; contract NormalToken is ERC721 { constructor( @@ -114,6 +116,19 @@ contract ERC721PortalTest is Test { alice = vm.addr(1); } + function testSupportsInterface(bytes4 _randomInterfaceId) public { + assertTrue(portal.supportsInterface(type(IERC721Portal).interfaceId)); + assertTrue(portal.supportsInterface(type(IInputRelay).interfaceId)); + assertTrue(portal.supportsInterface(type(IERC165).interfaceId)); + + assertFalse(portal.supportsInterface(bytes4(0xffffffff))); + + vm.assume(_randomInterfaceId != type(IERC721Portal).interfaceId); + vm.assume(_randomInterfaceId != type(IInputRelay).interfaceId); + vm.assume(_randomInterfaceId != type(IERC165).interfaceId); + assertFalse(portal.supportsInterface(_randomInterfaceId)); + } + function testGetInputBox() public { assertEq(address(portal.getInputBox()), address(inputBox)); } diff --git a/onchain/rollups/test/foundry/portals/EtherPortal.t.sol b/onchain/rollups/test/foundry/portals/EtherPortal.t.sol index d77c74e4..2d27d33e 100644 --- a/onchain/rollups/test/foundry/portals/EtherPortal.t.sol +++ b/onchain/rollups/test/foundry/portals/EtherPortal.t.sol @@ -5,11 +5,13 @@ pragma solidity ^0.8.8; import {Test} from "forge-std/Test.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {EtherPortal} from "contracts/portals/EtherPortal.sol"; import {IEtherPortal} from "contracts/portals/IEtherPortal.sol"; import {IInputBox} from "contracts/inputs/IInputBox.sol"; import {InputBox} from "contracts/inputs/InputBox.sol"; import {InputEncoding} from "contracts/common/InputEncoding.sol"; +import {IInputRelay} from "contracts/inputs/IInputRelay.sol"; contract BadEtherReceiver { receive() external payable { @@ -65,6 +67,23 @@ contract EtherPortalTest is Test { dapp = address(0x12345678); } + function testSupportsInterface(bytes4 _randomInterfaceId) public { + assertTrue( + etherPortal.supportsInterface(type(IEtherPortal).interfaceId) + ); + assertTrue( + etherPortal.supportsInterface(type(IInputRelay).interfaceId) + ); + assertTrue(etherPortal.supportsInterface(type(IERC165).interfaceId)); + + assertFalse(etherPortal.supportsInterface(bytes4(0xffffffff))); + + vm.assume(_randomInterfaceId != type(IEtherPortal).interfaceId); + vm.assume(_randomInterfaceId != type(IInputRelay).interfaceId); + vm.assume(_randomInterfaceId != type(IERC165).interfaceId); + assertFalse(etherPortal.supportsInterface(_randomInterfaceId)); + } + function testGetInputBox() public { assertEq(address(etherPortal.getInputBox()), address(inputBox)); } diff --git a/onchain/rollups/test/foundry/relays/DAppAddressRelay.t.sol b/onchain/rollups/test/foundry/relays/DAppAddressRelay.t.sol index b2f70209..20ebefca 100644 --- a/onchain/rollups/test/foundry/relays/DAppAddressRelay.t.sol +++ b/onchain/rollups/test/foundry/relays/DAppAddressRelay.t.sol @@ -5,11 +5,13 @@ pragma solidity ^0.8.8; import {Test} from "forge-std/Test.sol"; +import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IDAppAddressRelay} from "contracts/relays/IDAppAddressRelay.sol"; import {DAppAddressRelay} from "contracts/relays/DAppAddressRelay.sol"; import {IInputBox} from "contracts/inputs/IInputBox.sol"; import {InputBox} from "contracts/inputs/InputBox.sol"; import {InputEncoding} from "contracts/common/InputEncoding.sol"; +import {IInputRelay} from "contracts/inputs/IInputRelay.sol"; contract DAppAddressRelayTest is Test { IInputBox inputBox; @@ -27,6 +29,21 @@ contract DAppAddressRelayTest is Test { relay = new DAppAddressRelay(inputBox); } + function testSupportsInterface(bytes4 _randomInterfaceId) public { + assertTrue( + relay.supportsInterface(type(IDAppAddressRelay).interfaceId) + ); + assertTrue(relay.supportsInterface(type(IInputRelay).interfaceId)); + assertTrue(relay.supportsInterface(type(IERC165).interfaceId)); + + assertFalse(relay.supportsInterface(bytes4(0xffffffff))); + + vm.assume(_randomInterfaceId != type(IDAppAddressRelay).interfaceId); + vm.assume(_randomInterfaceId != type(IInputRelay).interfaceId); + vm.assume(_randomInterfaceId != type(IERC165).interfaceId); + assertFalse(relay.supportsInterface(_randomInterfaceId)); + } + function testGetInputBox() public { assertEq(address(relay.getInputBox()), address(inputBox)); }