diff --git a/README.md b/README.md index 97b4c078..61e8b785 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,39 @@ # vIBC Core Smart Contracts -This project includes +This project includes the core smart contracts for the vIBC protocol, and a few demo contracts that simulate testing and serve as a template for integrating dapp devs. -- vIBC Core Smart Contracts (CoreSC) -- a few demo contracts that simulate dev users protocol contracts, eg. Mars, etc. +![](./diagrams/vibcContractsOverview.jpg) + + +## Repo Structure + +All contracts internal to this project are in the `contracts`. This directory contains the following subdirectories: +- `core/`: The contracts that are core to the vIBC protocol. These are the contracts that will be deployed and used in production. +- `interfaces/`: Interfaces for core and testing contracts +- `libs/`: Libraries used by the core vIbc protocol. +- The `utils/`, `base/`, and `example/` directories all contain contracts that are not core to the vIBC protocol. These contracts are used only for testing or as templates for dapp contracts that will integrate with the vIBC protocol. + + +# Core Contracts +## Dispatcher +The Dispatcher contract routes packets to dapps by mapping channels to dapp addresses. The dispatcher also calls packet and channel callbacks on sending/receiving dapps. This contract uses a UUPS proxy compliant with ERC1967 for upgrades. The proxy is intended to be managed by a single owner address, which will be replaced by a multisig in production. + +Since the optimistic light client contract is used to prove that events happened on the Polymer chain, all methods in the dispatcher are permissionless, aside from the methods to set the connection to client mapping, upgrading the implementation, and setting the dispatcher's port prefix. + +Due to the nature of ibc callbacks, the dispatcher should safely be able to integrate its handler methods with any arbitrary (i.e. potentially malicious) contracts. + +The dispatcher can integrate with multiple light clients of the peptide chain to fit differing needs of security and ux. Currently, the dispatcher integrates with the DummyLightClient (used for testing to reduce the need for generating proofs), and the OptimisticLightClient - an EVM implementation of the Optimistic proof verification currently used by Optimism. Support for future clients is also possible. + +Any dapps which send/receive Ibc packets can do so directly through the dispatcher, or through a middleware contract like the `UniversalChannelHandler`. Dapps that directly integrate with the dispatcher are expected to implement the `IbcReceiver` interface, and those which use the `UniversalChannelHandler` are assumed to implement the `IbcUniversalPacketReceiver` interface. + +## OptimisticLightClient +The OptimisticLightClient contract abstracts away proof verification from the dispatcher contract. This light client represents a view of the Polymer chain, and is used to prove that channel handshake and packet sending events happened. + +## OptimisticProofVerifier +The optimisticProofVerifier verifies proofs for the optimistic light client. + +## UniversalChannelHandler +The UniversalChannelHandler is a middleware contract that can be used to save dapps from having to go through the 4-step channel handshake to send or receive Ibc packets. ## Quick Start with Forge/Foundry @@ -36,3 +66,4 @@ forge test ```sh forge clean ``` + diff --git a/contracts/base/GeneralMiddleware.sol b/contracts/base/GeneralMiddleware.sol index c7a72bf0..f84c35de 100644 --- a/contracts/base/GeneralMiddleware.sol +++ b/contracts/base/GeneralMiddleware.sol @@ -2,7 +2,8 @@ pragma solidity ^0.8.9; -import {Ibc, IbcUtils, UniversalPacket, AckPacket} from "../libs/Ibc.sol"; +import {Ibc, UniversalPacket, AckPacket} from "../libs/Ibc.sol"; +import {IbcUtils} from "../libs/IbcUtils.sol"; import { IbcUniversalPacketReceiver, IbcMwUser, @@ -12,6 +13,12 @@ import { IbcMwPacketSender } from "../interfaces/IbcMiddleware.sol"; +/** + * @title GeneralMiddleware + * @author Polymer Labs + * @notice GeneralMiddleware is a starting point for developers to implement their own middleware logic. It is not + * intended to be directly deployed, but rather only used for testing and development + */ contract GeneralMiddleware is IbcMwUser, IbcMiddleware, IbcMwEventsEmitter { /** * @dev MW_ID is the ID of MW contract on all supported virtual chains. diff --git a/contracts/core/Dispatcher.sol b/contracts/core/Dispatcher.sol index 77e8a3cc..44f86b37 100644 --- a/contracts/core/Dispatcher.sol +++ b/contracts/core/Dispatcher.sol @@ -1,6 +1,6 @@ //SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.9; +pragma solidity 0.8.15; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; @@ -8,31 +8,24 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeab import {OwnableUpgradeable} from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import {IbcChannelReceiver, IbcPacketReceiver} from "../interfaces/IbcReceiver.sol"; -import {L1Header, OpL2StateProof, Ics23Proof} from "../interfaces/ProofVerifier.sol"; -import {LightClient} from "../interfaces/LightClient.sol"; +import {L1Header, OpL2StateProof, Ics23Proof} from "../interfaces/IProofVerifier.sol"; +import {ILightClient} from "../interfaces/ILightClient.sol"; import {IDispatcher} from "../interfaces/IDispatcher.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; -import { - Channel, - ChannelEnd, - ChannelOrder, - IbcPacket, - ChannelState, - AckPacket, - IBCErrors, - IbcUtils, - Ibc -} from "../libs/Ibc.sol"; +import {Channel, ChannelEnd, ChannelOrder, IbcPacket, ChannelState, AckPacket, IBCErrors, Ibc} from "../libs/Ibc.sol"; +import {IbcUtils} from "../libs/IbcUtils.sol"; /** * @title Dispatcher * @author Polymer Labs - * @notice - * Contract callers call this contract to send IBC-like msg, - * which can be relayed to a rollup module on the Polymerase chain - * @notice - * in addition to directly calling this contract, this can also be called by middlewares (such as UCH ) + * @notice This contract facilitates the 4-step channel opening process and IBC packet sending/receiving. + * @notice Contract callers call this contract to send IBC-like messages, which can be relayed to a rollup module on the + * Polymerase chain + * @notice In addition to directly calling this contract, this contract can also be called by arbitrary middleware + * contracts, such as the UniversalChannelHandler + * @notice This contract is upgradeable and uses the UUPS pattern + * */ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDispatcher { // Gap to allow for additional contract inheritance, similar to OpenZeppelin's Initializable contract @@ -54,18 +47,21 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi mapping(address => mapping(bytes32 => mapping(uint64 => bool))) private _recvPacketReceipt; // keep track of outbound ack packets to prevent replay attack mapping(address => mapping(bytes32 => mapping(uint64 => bool))) private _ackPacketCommitment; - LightClient _UNUSED; + + ILightClient _UNUSED; // From previous dispatcher version mapping(bytes32 => string) private _channelIdToConnection; - mapping(string => LightClient) private _connectionToLightClient; - uint256 private _numClients; + mapping(string => ILightClient) private _connectionToLightClient; - // - // methods - // constructor() { _disableInitializers(); } + /** + * @notice Initializes the Dispatcher contract with the provided port prefix. + * @param initPortPrefix The initial port prefix to be set for the contract. + * @dev This method should be called only once during contract deployment. + * @dev For contract upgarades, which need to reinitialize the contract, use the reinitializer modifier. + */ function initialize(string memory initPortPrefix) public virtual initializer { if (bytes(initPortPrefix).length == 0) { revert IBCErrors.invalidPortPrefix(); @@ -75,9 +71,11 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi portPrefixLen = uint32(bytes(initPortPrefix).length); } - // - // CoreSC maaintainer methods, only invoked by the owner - // + /** + * @notice Sets the port prefix for the Dispatcher contract. + * @param _portPrefix The new port prefix to be set. + * @dev It can only be called by the contract owner. + */ function setPortPrefix(string calldata _portPrefix) external onlyOwner { if (bytes(_portPrefix).length == 0) { revert IBCErrors.invalidPortPrefix(); @@ -90,6 +88,13 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi // with the optimistic consensus state. The optimistic consensus // is accepted and will be open for verify in the fraud proof // window. + /** + * @notice Updates the client with optimistic consensus state. The optimistic consensus is accepted and will be open + * for verification in the fraud proof window + * @dev Calls lightClient.addOpConsensusState; See Lightclient natspec for params information + * @dev This function updates the client with optimistic consensus state. + * It should be called after verifying the optimistic consensus state on the main chain. + */ function updateClientWithOptimisticConsensusState( L1Header calldata l1header, OpL2StateProof calldata proof, @@ -101,14 +106,13 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi } /** - * @notice Sets the specified `LightClient` for the given connection - * @notice Can either be used to either set a new client for a new connection, or to update an existing connection's - * client. - * @dev Only callable by the contract owner. - * @param connection The connection string identifying the connection. - * @param lightClient The address of the `LightClient` contract to be set. + * @notice Adds a new mapping between a light client and a connection string. + * @param connection The string representing the new connection, as per ICS03 (should always be first connection in + * connectionHop array) + * @param lightClient The ILightClient contract address used by the connection + * @dev This method can only be called by the contract owner. */ - function setClientForConnection(string calldata connection, LightClient lightClient) external onlyOwner { + function setClientForConnection(string calldata connection, ILightClient lightClient) external onlyOwner { _setClientForConnection(connection, lightClient); } @@ -127,6 +131,18 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi * onChanOpenInit. If the callback succeeds, the dApp should return the selected version and the emitted event * will be relayed to the IBC/VIBC hub chain. */ + /** + * @notice Initializes the channel opening process with the specified parameters. This is the first step in the channel + * handshake, initiated directly by the dapp which wishes to establish a channel with the receiver. + * @param ordering The ordering of the channel (ORDERED or UNORDERED). + * @param feeEnabled A boolean indicating whether fees are enabled for the channel. + * @param connectionHops The list of connection hops associated with the channel, with the first channel in this + * array always starting from the chain this contract is deployed on + * @param counterpartyPortId The port ID of the counterparty. + * @dev This function initializes the channel opening process by calling the onChanOpenInit function of the + * specified receiver contract. + * It can only be called by authorized parties. + */ function channelOpenInit( string calldata version, ChannelOrder ordering, @@ -149,9 +165,21 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi } /** - * This function is called by a 'relayer' on behalf of a dApp. The dApp should implement IbcChannelHandler's - * onChanOpenTry. If the callback succeeds, the dApp should return the selected version and the emitted event - * will be relayed to the IBC/VIBC hub chain. + * @notice Initializes step 2 of the channel handshake process. This is called by a relayer on behalf of the + * receiving dapp. + * To minimize trust assumptions in proving that the counterparty has indeed initiated step 1, there must be a valid + * proof of the counterparty being in the TRY_PENDING state from the Polymer Client + * @notice The receiving dApp should implement IbcChannelHandler's onChanOpenTry callback. If the callback + * succeeds, the dApp should return the selected version and the emitted even will be relayed to the IBC/VIBC hub + * chain. + * @param local The counterparty information for the receiver. + * @param ordering The ordering of the channel (ORDERED or UNORDERED). + * @param feeEnabled Whether fees are enabled for the channel. + * @param connectionHops The list of connection hops associated with the channel; with the first channel in this + * array always starting from the chain this contract is deployed on + * @param counterparty The counterparty information of the sender + * @param proof The proof that the counterparty is in the TRY_PENDING state (i.e. that it is indeed intending to + * initialize a channel with the given receiver) */ function channelOpenTry( ChannelEnd calldata local, @@ -204,8 +232,22 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi } /** - * This func is called by a 'relayer' after the IBC/VIBC hub chain has processed the ChannelOpenTry event. - * The dApp should implement the onChannelConnect method to handle the third channel handshake method: ChanOpenAck + * @notice Initializes step 3 of the channel handshake process. This method is called by a relayer on behalf of the + * sending, dapp (i.e. the dapp which initiated the channelOpenInit call). This step happens after the IBC/VIBC hub + * chain has processed the ChannelOpenTry event. + * To minimize trust assumptions in proving that the counterparty had indeed responded successfully in step 2 of the + * handshake, there must be a valid proof of the counterparty being in the ACK_PENDING state from the Polymer Client + * @notice Completes the channel opening acknowledge process with the specified parameters. + * @notice The dApp should implement the onChannelOpenAck method to handle the third channel handshake method: + * ChanOpenAck process. + * @param local The counterparty information for the local channel. + * @param connectionHops The list of connection hops associated with the channel, with the first channel in this + * array always starting from the chain this contract is deployed on. + * @param ordering The ordering of the channel (ORDERED or UNORDERED). + * @param feeEnabled A boolean indicating whether fees are enabled for the channel. + * @param counterparty The counterparty information for the channel. + * @param proof The proof that the counterparty is in the ACK_PENDING state (i.e. that it responded with a + * successful channelOpenTry ) */ function channelOpenAck( ChannelEnd calldata local, @@ -215,12 +257,7 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi ChannelEnd calldata counterparty, Ics23Proof calldata proof ) external nonReentrant { - if ( - bytes(local.portId).length == 0 || bytes(counterparty.portId).length == 0 || local.channelId == bytes32(0) - || counterparty.channelId == bytes32(0) - ) { - revert IBCErrors.invalidCounterParty(); - } + _checkInValidCounterParty(local.portId, local.channelId, counterparty.portId, counterparty.channelId); _getLightClientFromConnection(connectionHops[0]).verifyMembership( proof, Ibc.channelProofKey(local.portId, local.channelId), @@ -251,9 +288,25 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi } /** - * This func is called by a 'relayer' after the IBC/VIBC hub chain has processed the ChannelOpenTry event. - * The dApp should implement the onChannelConnect method to handle the last channel handshake method: + * @notice Initializes step 4 of the channel handshake process. This method is called by a relayer on behalf of the + * receiving dapp. This step happens after the IBC/VIBC hub chain has processed the ChannelOpenAck event. + * To minimize trust assumptions in proving that the counterparty had indeed responded successfully in step 2 of the + * handshake, there must be a valid proof of the counterparty being in the CONFIRM_PENDING state from the Polymer + * Client + * + * @notice Initiates the channel opening confirm process with the specified parameters. + * The receiver should implement the onChannelConnect method to handle the last channel handshake method: * ChannelOpenConfirm + * @param local The counterparty information for the local channel. + * @param ordering The ordering of the channel (ORDERED or UNORDERED). + * @param connectionHops The list of connection hops associated with the channel, with the first channel in this + * array always starting from the chain this contract is deployed on. + * @param counterparty The counterparty information for the channel. + * @param proof The proof of channel opening confirm. + * @dev This function initiates the channel opening confirm process by calling the onChanOpenConfirm function of the + * specified + * receiver contract. + * It can only be called by authorized parties. */ function channelOpenConfirm( ChannelEnd calldata local, @@ -263,12 +316,7 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi ChannelEnd calldata counterparty, Ics23Proof calldata proof ) external nonReentrant { - if ( - bytes(local.portId).length == 0 || bytes(counterparty.portId).length == 0 || local.channelId == bytes32(0) - || counterparty.channelId == bytes32(0) - ) { - revert IBCErrors.invalidCounterParty(); - } + _checkInValidCounterParty(local.portId, local.channelId, counterparty.portId, counterparty.channelId); _getLightClientFromConnection(connectionHops[0]).verifyMembership( proof, @@ -300,6 +348,9 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi /** * @notice Initializes a close channel handshake process. It is directly called by the dapp which wants to close * the channel + * @dev Emits a `CloseIbcChannel` event with the given `channelId` and the address of the message sender + * @notice Close the specified IBC channel by channel ID + * @notice Must be called by the channel owner, ie. _portChannelMap[msg.sender][channelId] must exist */ function channelCloseInit(bytes32 channelId) external nonReentrant { Channel memory channel = _portChannelMap[msg.sender][channelId]; @@ -325,9 +376,15 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi } /** - * This func is called by a 'relayer' after the IBC/VIBC hub chain has processed ChanCloseConfirm event. - * The dApp's onChanCloseConfirm callback is invoked. - * dApp should throw an error if the channel should not be closed. + * @notice Confirms a close channel handshake process. It is called by a relayer on behalf of the dapp whhich + * initializes the channel closefter after the IBC/VIBC hub chain has processed ChanCloseConfirm event. + * @dev Emits a `CloseIbcChannel` event with the given `channelId` and the address of the message sender + * The dApp's onChanCloseConfirm callback is invoked. dApp should throw an error if the channel should not be + * closed. + * @notice Close the specified IBC channel by channel ID + * @notice Must be called by the channel owner, ie. _portChannelMap[msg.sender][channelId] must exist + * @param portAddress The address of the receiver port of the channel to close + * @param channelId The ID of the channel to close */ function channelCloseConfirm(address portAddress, bytes32 channelId, Ics23Proof calldata proof) external @@ -374,21 +431,14 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi } } - // - // IBC Packet methods - // - /** * @notice Sends an IBC packet on a existing channel with the specified packet data and timeout block timestamp. - * @notice Data should be encoded in a format defined by the channel version, and the module on the other side - * should know how to parse this. * @dev Emits an `IbcPacketEvent` event containing the sender address, channel ID, packet data, and timeout block * timestamp (formatted as seconds after the unix epoch). * @param channelId The ID of the channel on which to send the packet. * @param packet The packet data to send. * @param timeoutTimestamp The timestamp, in seconds after the unix epoch, after which the packet times out if it - * has not been - * received. + * has not been received. */ function sendPacket(bytes32 channelId, bytes calldata packet, uint64 timeoutTimestamp) external { // ensure port owns channel @@ -398,17 +448,19 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi _sendPacket(msg.sender, channelId, packet, timeoutTimestamp); } + /** - * @notice Handle the acknowledgement of an IBC packet by the counterparty - * @dev Verifies the given proof and calls the `onAcknowledgementPacket` function on the given `receiver` contract, - * ie. the IBC dApp. - * Prerequisite: the original packet is committed and not ack'ed or timed out yet. + * @notice Handle the acknowledgement of an IBC packet by the counterparty, called by a relayer. + * @dev To minimize trust assumptions, an inclusion proof must be given that proves the packet commitment is + * in the IBC/VIBC hub chain. + * @dev This method also calls the `onAcknowledgementPacket` function on + * the given `receiver` contract, + * @dev Prerequisite: The original packet must be committed and not ack'ed or timed out yet. * Note: If the receiving dapp doesn't satisfy the interface, the transaction will be reverted. * @param packet The IbcPacket data for the acknowledged packet * @param ack The acknowledgement receipt for the packet * @param proof The membership proof to verify the packet acknowledgement committed on Polymer chain */ - function acknowledgement(IbcPacket calldata packet, bytes calldata ack, Ics23Proof calldata proof) external nonReentrant @@ -483,8 +535,9 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi /** * @notice Receive an IBC packet and then pass it to the IBC-dApp for processing if verification succeeds. - * @dev Verifies the given proof and calls the `onRecvPacket` function on the given `receiver` contract - * If the address doesn't satisfy the interface, the transaction will be reverted. + * @dev To minimize trust assumptions, verifies the given proof + * @dev calls the `onRecvPacket` function on the given receiver contract + * If the address doesn't satisfy the interface, the nextSequenceRecv will not be updated * The receiver must be the intended packet destination, which is the same as packet.dest.portId. * @param packet The IbcPacket data for the received packet * @param proof The proof data needed to verify the packet receipt @@ -546,7 +599,13 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi } /** - * Generate a timeout packet for the given packet + * @notice Writes a timeout packet to the specified receiver. + * @param packet The timeout packet data. + * @dev Requirements: + * - The `receiver` address must not be the zero address. + * - The `receiver` must be the original packet sender. + * - The packet must not have a receipt. + * - The packet must have timed out. */ function writeTimeoutPacket(IbcPacket calldata packet, Ics23Proof calldata proof) external { _getLightClientFromChannelId(packet.dest.channelId).verifyMembership( @@ -575,14 +634,20 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi * @param portAddress EVM address of the IBC port * @param channelId IBC channel ID from the port perspective * @return channel A channel struct is always returned. If it doesn't exists, the channel struct is populated with - * default - * values per EVM. + * default values per EVM. */ function getChannel(address portAddress, bytes32 channelId) external view returns (Channel memory channel) { channel = _portChannelMap[portAddress][channelId]; } - // getOptimisticConsensusState + /** + * @notice Retrieves the optimistic consensus state for the specified height and client ID. + * @param height The height of the consensus state. + * @param connection The connection the client corresponds to; used to find the light client. + * @return appHash The application hash of the consensus state. + * @return fraudProofEndTime The end time of the fraud proof. + * @return ended A boolean indicating whether the consensus state has ended. + */ function getOptimisticConsensusState(uint256 height, string calldata connection) external view @@ -591,7 +656,7 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi return _getLightClientFromConnection(connection).getState(height); } - function _setClientForConnection(string calldata connection, LightClient lightClient) internal { + function _setClientForConnection(string calldata connection, ILightClient lightClient) internal { if (bytes(connection).length == 0) { revert IBCErrors.invalidConnection(""); } @@ -602,7 +667,13 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi _connectionToLightClient[connection] = lightClient; } - // Prerequisite: must verify sender is authorized to send packet on the channel + /** + * @notice Sends a packet on the specified channel with the provided details. + * @param sender The address of the sender. + * @param channelId The ID of the channel. + * @param packet The packet data to be sent. + * @param timeoutTimestamp The timeout timestamp for the packet. + */ function _sendPacket(address sender, bytes32 channelId, bytes memory packet, uint64 timeoutTimestamp) internal { // current packet sequence uint64 sequence = _nextSequenceSend[sender][channelId]; @@ -618,6 +689,16 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi emit SendPacket(sender, channelId, packet, sequence, timeoutTimestamp); } + /** + * @notice Connects a channel with the provided parameters. + * @param portAddress The address of the IbcChannelReceiver contract. + * @param local The details of the local counterparty. + * @param connectionHops The connection hops associated with the channel, with the first channel in this + * array always starting from the chain this contract is deployed on. + * @param ordering The ordering of the channel. + * @param feeEnabled A boolean indicating whether fees are enabled for the channel. + * @param counterparty The details of the counterparty. + */ function _connectChannel( IbcChannelReceiver portAddress, ChannelEnd calldata local, @@ -648,7 +729,20 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi // light client } - // Returns the result of the call if no revert, otherwise returns the error if thrown. + /** + * @notice Calls the specified contract with the provided arguments and returns the success status and + * message. + * @notice This functions similar to a try/catch statement - if the low-level call reverts, this transaction will + * not revert, but instead the error message will be returned + * @param receiver The address of the contract to call. + * @param args The abi-encoded low-level call + * @return success A boolean indicating whether the call was successful and made to a contract address. + * @return message The return message from the contract call; or error for a failed call + * @dev Requirements for the success return value to be true: + * - The `receiver` address must correspond to a contract. + * - The receiver contract must have the called implemented + * - The receiver contract must not revert in the low-level call + */ function _callIfContract(address receiver, bytes memory args) internal returns (bool success, bytes memory message) @@ -673,10 +767,18 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi } } + /** + * @inheritdoc UUPSUpgradeable + */ function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} - // _isPacketTimeout returns true if the given packet has timed out acoording to host chain's block height and - // timestamp, in seconds after the unix epoch. + /** + * @notice Checks if the given packet has timed out according to the host chain's block height and timestamp. + * @param timeoutTimestamp The timeout timestamp of the packet. + * @param revisionHeight The block height of the packet's timeout revision. + * @return isTimeOut Returns true if the given packet has timed out acoording to host chain's block height and + * timestamp, in seconds after the unix epoch. + */ function _isPacketTimeout(uint64 timeoutTimestamp, uint64 revisionHeight) internal view returns (bool isTimeOut) { return ( isTimeOut = (timeoutTimestamp != 0 && block.timestamp >= timeoutTimestamp) @@ -689,7 +791,12 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi addr = Ibc._hexStrToAddress(port[portPrefixLen:]); } - function _getLightClientFromChannelId(bytes32 channelId) internal view returns (LightClient lightClient) { + /** + * @notice Retrieves the light client associated with the specified channel ID. + * @param channelId The ID of the channel. + * @return lightClient The light client associated with the channel. + */ + function _getLightClientFromChannelId(bytes32 channelId) internal view returns (ILightClient lightClient) { string memory connection = _channelIdToConnection[channelId]; if (bytes(connection).length == 0) { revert IBCErrors.channelIdNotFound(channelId); @@ -697,10 +804,29 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuard, IDi return _getLightClientFromConnection(connection); } - function _getLightClientFromConnection(string memory connection) internal view returns (LightClient lightClient) { + /** + * @notice Retrieves the light client associated with the specified connection. + * @param connection The connection string(i.e. the first connection in the connectionHops array) + * @return lightClient The light client associated with the connection. + */ + function _getLightClientFromConnection(string memory connection) internal view returns (ILightClient lightClient) { lightClient = _connectionToLightClient[connection]; if (address(lightClient) == address(0)) { revert IBCErrors.lightClientNotFound(connection); } } + + function _checkInValidCounterParty( + string calldata localPortId, + bytes32 localChannelId, + string calldata counterPartyPortId, + bytes32 counterPartyChannelId + ) internal pure { + if ( + bytes(localPortId).length == 0 || bytes(counterPartyPortId).length == 0 || localChannelId == bytes32(0) + || counterPartyChannelId == bytes32(0) + ) { + revert IBCErrors.invalidCounterParty(); + } + } } diff --git a/contracts/core/OpLightClient.sol b/contracts/core/OptimisticLightClient.sol similarity index 76% rename from contracts/core/OpLightClient.sol rename to contracts/core/OptimisticLightClient.sol index 5e760bb8..c910dd7e 100644 --- a/contracts/core/OpLightClient.sol +++ b/contracts/core/OptimisticLightClient.sol @@ -1,35 +1,37 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; +pragma solidity 0.8.15; -import {L1Header, ProofVerifier, OpL2StateProof, Ics23Proof} from "../interfaces/ProofVerifier.sol"; -import {LightClient} from "../interfaces/LightClient.sol"; +import {L1Header, IProofVerifier, OpL2StateProof, Ics23Proof} from "../interfaces/IProofVerifier.sol"; +import {ILightClient} from "../interfaces/ILightClient.sol"; import {L1Block} from "optimism/L2/L1Block.sol"; -// OptimisticLightClient manages the appHash at different -// heights and track the fraud proof end time for them. -contract OptimisticLightClient is LightClient { +/** + * @title OptimisticLightClient + * @author Polymer Labs + * @dev This specific light client implementation uses the same client that is used in the op-stack + */ +contract OptimisticLightClient is ILightClient { // consensusStates maps from the height to the appHash. mapping(uint256 => uint256) public consensusStates; // fraudProofEndtime maps from the appHash to the fraud proof end time. mapping(uint256 => uint256) public fraudProofEndtime; uint256 public fraudProofWindowSeconds; - ProofVerifier public verifier; + IProofVerifier public verifier; L1Block public l1BlockProvider; error CannotUpdatePendingOptimisticConsensusState(); error AppHashHasNotPassedFraudProofWindow(); - constructor(uint32 fraudProofWindowSeconds_, ProofVerifier verifier_, L1Block _l1BlockProvider) { + constructor(uint32 fraudProofWindowSeconds_, IProofVerifier verifier_, L1Block _l1BlockProvider) { fraudProofWindowSeconds = fraudProofWindowSeconds_; verifier = verifier_; l1BlockProvider = _l1BlockProvider; } - // addOpConsensusState adds an appHash to internal store and - // returns the fraud proof end time, and a bool flag indicating if - // the fraud proof window has passed according to the block's - // timestamp. + /** + * @inheritdoc ILightClient + */ function addOpConsensusState( L1Header calldata l1header, OpL2StateProof calldata proof, @@ -60,17 +62,13 @@ contract OptimisticLightClient is LightClient { } /** - * getState returns the appHash at the given height, and the fraud - * proof end time. - * 0 is returned if there isn't an appHash with the given height. + * @inheritdoc ILightClient */ function getState(uint256 height) external view returns (uint256 appHash, uint256 fraudProofEndTime, bool ended) { return getInternalState(height); } /** - * verifyMembership checks if the current trustedOptimisticConsensusState state - * can be used to perform the membership test and if so, it uses - * the verifier to perform membership check. + * @inheritdoc ILightClient */ function verifyMembership(Ics23Proof calldata proof, bytes calldata key, bytes calldata expectedValue) @@ -85,17 +83,26 @@ contract OptimisticLightClient is LightClient { verifier.verifyMembership(bytes32(appHash), key, expectedValue, proof); } + /** + * @inheritdoc ILightClient + */ function verifyNonMembership(Ics23Proof calldata proof, bytes calldata key) external view { (uint256 appHash,, bool ended) = getInternalState(proof.height - 1); if (!ended) revert AppHashHasNotPassedFraudProofWindow(); verifier.verifyNonMembership(bytes32(appHash), key, proof); } + /** + * @inheritdoc ILightClient + */ function getFraudProofEndtime(uint256 height) external view returns (uint256 fraudProofEndTime) { uint256 hash = consensusStates[height]; return fraudProofEndtime[hash]; } + /** + * @dev Returns the internal state of the light client at a given height. + */ function getInternalState(uint256 height) public view diff --git a/contracts/core/OpProofVerifier.sol b/contracts/core/OptimisticProofVerifier.sol similarity index 85% rename from contracts/core/OpProofVerifier.sol rename to contracts/core/OptimisticProofVerifier.sol index e8ee9b48..3a687f46 100644 --- a/contracts/core/OpProofVerifier.sol +++ b/contracts/core/OptimisticProofVerifier.sol @@ -1,12 +1,17 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; +pragma solidity 0.8.15; import {SecureMerkleTrie} from "optimism/libraries/trie/SecureMerkleTrie.sol"; import {RLPReader} from "optimism/libraries/rlp/RLPReader.sol"; import {RLPWriter} from "optimism/libraries/rlp/RLPWriter.sol"; -import {ProofVerifier, L1Header, OpL2StateProof, Ics23Proof, OpIcs23Proof} from "../interfaces/ProofVerifier.sol"; - -contract OpProofVerifier is ProofVerifier { +import {IProofVerifier, L1Header, OpL2StateProof, Ics23Proof, OpIcs23Proof} from "../interfaces/IProofVerifier.sol"; + +/** + * @title OptimisticProofVerifier + * @notice Verifies proofs related to Optimistic Rollup state updates + * @author Polymer Labs + */ +contract OptimisticProofVerifier is IProofVerifier { using RLPReader for RLPReader.RLPItem; using RLPReader for bytes; @@ -62,12 +67,12 @@ contract OpProofVerifier is ProofVerifier { revert InvalidL1BlockNumber(); } - // this computes the L1 header hash + // This computes the L1 header hash if (trustedL1BlockHash != keccak256(RLPWriter.writeList(l1header.header))) { revert InvalidL1BlockHash(); } - // these two checks are here to verify that the "plain" (i.e. not RLP encoded) values in the l1header are + // These two checks are here to verify that the "plain" (i.e. not RLP encoded) values in the l1header are // the same ones found in l1header.header (i.e. RLP encoded). This is because it is cheaper to RLP // encode that decode if (keccak256(RLPWriter.writeUint(l1header.number)) != keccak256(l1header.header[_L1_NUMBER_INDEX])) { @@ -95,7 +100,7 @@ contract OpProofVerifier is ProofVerifier { abi.encode(proof.l2OutputProposalKey), proof.outputRootProof, bytes32(bytes(stateAccount[2].readBytes())) ); - // now that the output root is verified, we need to verify the app hash. To do so we try to derive the + // Now that the output root is verified, we need to verify the app hash. To do so we try to derive the // the output root the same way the proposer did. // See https://github.com/polymerdao/optimism/blob/polymer/v1.2.0/op-service/eth/output.go#L44 if ( @@ -112,11 +117,16 @@ contract OpProofVerifier is ProofVerifier { } } + /** + * @dev Prove that a given state is not part of a proof + * @dev this method is mainly used for packet timeouts, which is currently not implemented + */ function verifyNonMembership(bytes32, bytes calldata, Ics23Proof calldata) external pure { revert MethodNotImplemented(); } /** + * @inheritdoc IProofVerifier * @dev verifies a chain of ICS23 proofs * Each computed subroot starting from index 0 must match the value of the next proof (hence chained proofs). * The cosmos SDK and ics23 support chained proofs to switch between different proof specs. @@ -138,8 +148,13 @@ contract OpProofVerifier is ProofVerifier { if (appHash != _verify(proofs.proof[1])) revert InvalidIbcStateProof(); } - // this code was adapted from the ICS23 membership verification found here: - // https://github.com/cosmos/ics23/blob/go/v0.10.0/go/ics23.go#L36 + /** + * @dev Verifies an ICS23 proof through the root hash based on the provided proof. + * @dev This code was adapted from the ICS23 membership verification found here: + * https://github.com/cosmos/ics23/blob/go/v0.10.0/go/ics23.go#L36 + * @param proof The ICS23 proof to be verified. + * @return computed The computed root hash. + */ function _verify(OpIcs23Proof calldata proof) internal pure returns (bytes32 computed) { bytes32 hashedData = sha256(proof.value); computed = sha256( @@ -153,6 +168,11 @@ contract OpProofVerifier is ProofVerifier { } } + /** + * @dev Encodes an integer value into a variable-length integer format. + * @param value The integer value to be encoded. + * @return encoded The encoded bytes array. + */ function _encodeVarint(uint256 value) internal pure returns (bytes memory encoded) { bytes memory result; while (value >= 0x80) { diff --git a/contracts/core/UniversalChannelHandler.sol b/contracts/core/UniversalChannelHandler.sol index 6576eea3..3eb7dd79 100644 --- a/contracts/core/UniversalChannelHandler.sol +++ b/contracts/core/UniversalChannelHandler.sol @@ -1,6 +1,6 @@ //SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.9; +pragma solidity 0.8.15; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IbcDispatcher} from "../interfaces/IbcDispatcher.sol"; @@ -13,9 +13,17 @@ import { } from "../interfaces/IbcMiddleware.sol"; import {IbcReceiver} from "../interfaces/IbcReceiver.sol"; import {IbcReceiverBaseUpgradeable} from "../interfaces/IbcReceiverUpgradeable.sol"; -import {ChannelOrder, ChannelEnd, IbcPacket, AckPacket, UniversalPacket, IbcUtils} from "../libs/Ibc.sol"; +import {ChannelOrder, ChannelEnd, IbcPacket, AckPacket, UniversalPacket} from "../libs/Ibc.sol"; +import {IbcUtils} from "../libs/IbcUtils.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +/** + * @title Universal Channel Handler + * @author Polymer Labs + * @notice Implements universal channels for virtual IBC. Universal channels prevent dapps from needing to do a 4-step + * channel handshake to establish a channel. + * @dev This contract can integrate directly with dapps, or a middleware stack for packet routing. + */ contract UniversalChannelHandler is IbcReceiverBaseUpgradeable, UUPSUpgradeable, IbcUniversalChannelMW { uint256[49] private __gap; @@ -60,6 +68,13 @@ contract UniversalChannelHandler is IbcReceiverBaseUpgradeable, UUPSUpgradeable, dispatcher.channelOpenInit(version, ordering, feeEnabled, connectionHops, counterpartyPortIdentifier); } + /** + * @notice Sends a universal packet over an IBC channel + * @param channelId The channel ID through which the packet is sent on the dispatcher + * @param destPortAddr The destination port address + * @param appData The packet data to be sent + * @param timeoutTimestamp of when the packet can timeout + */ function sendUniversalPacket( bytes32 channelId, bytes32 destPortAddr, @@ -73,7 +88,15 @@ contract UniversalChannelHandler is IbcReceiverBaseUpgradeable, UUPSUpgradeable, dispatcher.sendPacket(channelId, packetData, timeoutTimestamp); } - // called by another IBC middleware; pack packet and send over to Dispatcher + /** + * @notice Sends a middleware packet over an IBC channel. This is intended to be called by another middleware + * contract, rather than an end Dapp itself. + * @param channelId The channel ID through which the packet is sent on the dispatcher + * @param destPortAddr The destination port address + * @param srcMwIds The mwId bitmap of the middleware stack + * @param appData The packet data to be sent + * @param timeoutTimestamp of when the packet can timeout + */ function sendMWPacket( bytes32 channelId, // original source address of the packet @@ -89,6 +112,11 @@ contract UniversalChannelHandler is IbcReceiverBaseUpgradeable, UUPSUpgradeable, dispatcher.sendPacket(channelId, packetData, timeoutTimestamp); } + /** + * @notice Handles the reception of an IBC packet from the counterparty + * @param packet The received IBC packet + * @return ackPacket The packet acknowledgement + */ function onRecvPacket(IbcPacket calldata packet) external override @@ -108,6 +136,11 @@ contract UniversalChannelHandler is IbcReceiverBaseUpgradeable, UUPSUpgradeable, } } + /** + * @notice Handles acknowledging the reception of an acknowledgement packet by the counterparty + * @param packet The IBC packet to be acknowledged + * @param ack The packet acknowledgement + */ function onAcknowledgementPacket(IbcPacket calldata packet, AckPacket calldata ack) external override @@ -126,6 +159,10 @@ contract UniversalChannelHandler is IbcReceiverBaseUpgradeable, UUPSUpgradeable, } } + /** + * @notice Handles the timeout event for an IBC packet + * @param packet The IBC packet that has timed out + */ function onTimeoutPacket(IbcPacket calldata packet) external override onlyIbcDispatcher { UniversalPacket memory ucPacketData = IbcUtils.fromUniversalPacketBytes(packet.data); address[] storage mwAddrs = mwStackAddrs[ucPacketData.mwBitmap]; @@ -185,6 +222,12 @@ contract UniversalChannelHandler is IbcReceiverBaseUpgradeable, UUPSUpgradeable, } // IBC callback functions + /** + * @notice Handles the acknowledgment of channel opening. + * This function is accessible only by the IBC dispatcher. + * @param channelId The channel ID of the opened channel + * @param counterpartyVersion The version string provided by the counterparty + */ function onChanOpenAck(bytes32 channelId, bytes32, string calldata counterpartyVersion) external view @@ -206,6 +249,11 @@ contract UniversalChannelHandler is IbcReceiverBaseUpgradeable, UUPSUpgradeable, return version; } + /** + * @dev Internal function to open a channel only if the version matches what is expected. + * @param version The version string provided by the counterparty + * @return selectedVersion The selected version string + */ function _openChannel(string calldata version) internal pure returns (string memory selectedVersion) { if (keccak256(abi.encodePacked(version)) != keccak256(abi.encodePacked(VERSION))) { revert UnsupportedVersion(); diff --git a/contracts/examples/Earth.sol b/contracts/examples/Earth.sol index 6dd1424f..04344f45 100644 --- a/contracts/examples/Earth.sol +++ b/contracts/examples/Earth.sol @@ -2,9 +2,16 @@ pragma solidity ^0.8.9; -import {IbcUtils, UniversalPacket, AckPacket} from "../libs/Ibc.sol"; +import {UniversalPacket, AckPacket} from "../libs/Ibc.sol"; +import {IbcUtils} from "../libs/IbcUtils.sol"; import {IbcMwUser, IbcUniversalPacketReceiver, IbcUniversalPacketSender} from "../interfaces/IbcMiddleware.sol"; +/** + * @title Earth + * @notice Earth is a simple IBC receiver contract that receives packets and sends acks. + * @dev This contract is used for only testing IBC functionality and as an example for dapp developers on how to + * integrate with the vibc protocol. + */ contract Earth is IbcMwUser, IbcUniversalPacketReceiver { struct UcPacketWithChannel { bytes32 channelId; diff --git a/contracts/examples/Mars.sol b/contracts/examples/Mars.sol index 638c940e..bd5d08fe 100644 --- a/contracts/examples/Mars.sol +++ b/contracts/examples/Mars.sol @@ -6,6 +6,12 @@ import {IBCErrors, AckPacket, ChannelOrder, ChannelEnd} from "../libs/Ibc.sol"; import {IbcReceiverBase, IbcReceiver, IbcPacket} from "../interfaces/IbcReceiver.sol"; import {IbcDispatcher} from "../interfaces/IbcDispatcher.sol"; +/** + * @title Mars + * @notice Mars is a simple IBC receiver contract that receives packets and sends acks. + * @dev This contract is used for only testing IBC functionality and as an example for dapp developers on how to + * integrate with the vibc protocol. + */ contract Mars is IbcReceiverBase, IbcReceiver { // received packet as chain B IbcPacket[] public recvedPackets; diff --git a/contracts/interfaces/IDispatcher.sol b/contracts/interfaces/IDispatcher.sol index b14bc8fd..690560ac 100644 --- a/contracts/interfaces/IDispatcher.sol +++ b/contracts/interfaces/IDispatcher.sol @@ -3,20 +3,11 @@ pragma solidity ^0.8.0; import {IbcDispatcher, IbcEventsEmitter} from "./IbcDispatcher.sol"; -import {L1Header, OpL2StateProof, Ics23Proof} from "./ProofVerifier.sol"; +import {L1Header, OpL2StateProof, Ics23Proof} from "./IProofVerifier.sol"; import {IbcChannelReceiver, IbcPacketReceiver} from "./IbcReceiver.sol"; -import { - Channel, - ChannelEnd, - ChannelOrder, - IbcPacket, - ChannelState, - AckPacket, - IBCErrors, - IbcUtils, - Ibc -} from "../libs/Ibc.sol"; -import {LightClient} from "./LightClient.sol"; +import {Channel, ChannelEnd, ChannelOrder, IbcPacket, ChannelState, AckPacket, IBCErrors, Ibc} from "../libs/Ibc.sol"; +import {IbcUtils} from "../libs/IbcUtils.sol"; +import {ILightClient} from "./ILightClient.sol"; interface IDispatcher is IbcDispatcher, IbcEventsEmitter { function setPortPrefix(string calldata _portPrefix) external; @@ -42,7 +33,7 @@ interface IDispatcher is IbcDispatcher, IbcEventsEmitter { string calldata counterpartyPortId ) external; - function setClientForConnection(string calldata connection, LightClient lightClient) external; + function setClientForConnection(string calldata connection, ILightClient lightClient) external; function removeConnection(string calldata connection) external; diff --git a/contracts/interfaces/ILightClient.sol b/contracts/interfaces/ILightClient.sol new file mode 100644 index 00000000..a49f21fb --- /dev/null +++ b/contracts/interfaces/ILightClient.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {Ics23Proof, L1Header, OpL2StateProof} from "./IProofVerifier.sol"; + +/** + * @title ILightClient + * @author Polymer Labs + * @notice A contract that manages the merkle root(appHash) at different block heights of a chain and tracks the fraud + * proof end time for them. + * @notice This is used for state inclusion proofs + */ +interface ILightClient { + /** + * @dev Adds an appHash to the internal store and returns the fraud proof end time, and a bool flag indicating if + * the fraud proof window has passed according to the block's time stamp. + * @param l1header RLP "encoded" version of the L1 header that matches with the trusted hash and number + * @param proof l2 state proof. It includes the keys, hashes and storage proofs required to verify the app hash + * @param appHash l2 app hash (state root) to be verified + * @return fraudProofEndTime The fraud proof end time. + * @return ended A boolean indicating if the fraud proof window has passed. + */ + function addOpConsensusState( + L1Header calldata l1header, + OpL2StateProof calldata proof, + uint256 height, + uint256 appHash + ) external returns (uint256 fraudProofEndTime, bool ended); + + /** + * @dev Returns the fraud proof end time at a giben block + * 0 is returned if there isn't an appHash with the given height. + */ + function getFraudProofEndtime(uint256 height) external returns (uint256 endTime); + + /** + * @dev Checks if the current trusted optimistic consensus state + * can be used to perform the membership test and if so, verifies the proof + * @dev reverts if the proof is not valid (i.e. if the key is not included in the proof) + */ + function verifyMembership(Ics23Proof calldata proof, bytes memory key, bytes memory expectedValue) external; + + /** + * @dev Verifies that the given key is not included in the proof + */ + function verifyNonMembership(Ics23Proof calldata proof, bytes memory key) external; + + /** + * @dev Returns the apphash at a given block height + */ + function getState(uint256 height) external view returns (uint256 appHash, uint256 fraudProofEndTime, bool ended); +} diff --git a/contracts/interfaces/ProofVerifier.sol b/contracts/interfaces/IProofVerifier.sol similarity index 90% rename from contracts/interfaces/ProofVerifier.sol rename to contracts/interfaces/IProofVerifier.sol index 675a8031..313b2629 100644 --- a/contracts/interfaces/ProofVerifier.sol +++ b/contracts/interfaces/IProofVerifier.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; +// OpIcs23ProofPath represents a commitment path in an ICS23 proof, which consists of a commitment prefix and a suffix. struct OpIcs23ProofPath { bytes prefix; bytes suffix; } +// OpIcs23Proof represents an ICS23 proof struct OpIcs23Proof { OpIcs23ProofPath[] path; bytes key; @@ -36,7 +38,12 @@ struct L1Header { uint64 number; } -interface ProofVerifier { +/** + * @title IProofVerifier + * @author Polymer Labs + * @notice An interface that abstracts away proof verification logic for light clients + */ +interface IProofVerifier { error InvalidL1BlockNumber(); error InvalidL1BlockHash(); error InvalidRLPEncodedL1BlockNumber(); diff --git a/contracts/interfaces/IUniversalChannelHandler.sol b/contracts/interfaces/IUniversalChannelHandler.sol index 7a1d4c91..a0ae6359 100644 --- a/contracts/interfaces/IUniversalChannelHandler.sol +++ b/contracts/interfaces/IUniversalChannelHandler.sol @@ -3,19 +3,10 @@ pragma solidity ^0.8.0; import {IbcDispatcher, IbcEventsEmitter} from "./IbcDispatcher.sol"; -import {L1Header, OpL2StateProof, Ics23Proof} from "./ProofVerifier.sol"; +import {L1Header, OpL2StateProof, Ics23Proof} from "./IProofVerifier.sol"; import {IbcUniversalChannelMW} from "./IbcMiddleware.sol"; -import { - Channel, - ChannelEnd, - ChannelOrder, - IbcPacket, - ChannelState, - AckPacket, - IBCErrors, - IbcUtils, - Ibc -} from "../libs/Ibc.sol"; +import {Channel, ChannelEnd, ChannelOrder, IbcPacket, ChannelState, AckPacket, IBCErrors, Ibc} from "../libs/Ibc.sol"; +import {IbcUtils} from "../libs/IbcUtils.sol"; interface IUniversalChannelHandler is IbcUniversalChannelMW { function registerMwStack(uint256 mwBitmap, address[] calldata mwAddrs) external; diff --git a/contracts/interfaces/IbcDispatcher.sol b/contracts/interfaces/IbcDispatcher.sol index 8b40446a..0855bf9d 100644 --- a/contracts/interfaces/IbcDispatcher.sol +++ b/contracts/interfaces/IbcDispatcher.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.9; import {Height, ChannelEnd, ChannelOrder, AckPacket} from "../libs/Ibc.sol"; import {IbcChannelReceiver} from "./IbcReceiver.sol"; -import {Ics23Proof} from "./ProofVerifier.sol"; +import {Ics23Proof} from "./IProofVerifier.sol"; /** * @title IbcPacketSender diff --git a/contracts/interfaces/IbcReceiver.sol b/contracts/interfaces/IbcReceiver.sol index fdfe6e3f..c5822cbc 100644 --- a/contracts/interfaces/IbcReceiver.sol +++ b/contracts/interfaces/IbcReceiver.sol @@ -12,6 +12,13 @@ import {ChannelOrder, ChannelEnd, IbcPacket, AckPacket} from "../libs/Ibc.sol"; * handshake callbacks. */ interface IbcChannelReceiver { + /** + * @notice Handles the channel open initialization event (step 1 of the open channel handshake) + * @dev Make sure to validate that the channel version is indeed one supported by the dapp; this callback should + * revert if not. + * @param version The version string provided by the counterparty + * @return selectedVersion The selected version string + */ function onChanOpenInit( ChannelOrder order, string[] calldata connectionHops, @@ -19,6 +26,14 @@ interface IbcChannelReceiver { string calldata version ) external returns (string memory selectedVersion); + /** + * @notice Handles the channel open try event (step 2 of the open channel handshake) + * @dev Make sure to validate that the counterparty version is indeed one supported by the dapp; this callback + * should + * revert if not. + * @param counterpartyVersion The version string provided by the counterparty + * @return selectedVersion The selected version string + */ function onChanOpenTry( ChannelOrder order, string[] memory connectionHops, @@ -28,9 +43,14 @@ interface IbcChannelReceiver { string memory counterpartyVersion ) external returns (string memory selectedVersion); + /** + * @notice Handles the channel open acknowledgment event (step 3 of the open channel handshake) + * @dev Make sure to validate channelId and counterpartyVersion + * @param channelId The unique identifier of the channel + * @param counterpartyVersion The version string provided by the counterparty + */ function onChanOpenAck(bytes32 channelId, bytes32 counterpartychannelId, string calldata counterpartyVersion) external; - /** * @notice Handles the channel close init event * @param channelId The unique identifier of the channel @@ -51,6 +71,11 @@ interface IbcChannelReceiver { function onChanCloseConfirm(bytes32 channelId, string calldata counterpartyPortId, bytes32 counterpartyChannelId) external; + /** + * @notice Handles the channel open confirmation event (step 4 of the open channel handshake) + * @dev Make sure to validate channelId and counterpartyVersion + * @param channelId The unique identifier of the channel + */ function onChanOpenConfirm(bytes32 channelId) external; } @@ -60,10 +85,27 @@ interface IbcChannelReceiver { * @dev Packet handling callback methods are invoked by the IBC dispatcher. */ interface IbcPacketReceiver { + /** + * @notice Callback for receiving a packet; triggered when a counterparty sends an an IBC packet + * @param packet The IBC packet received + * @return ackPacket The acknowledgement packet generated in response + * @dev Make sure to validate packet's source and destiation channels and ports. + */ function onRecvPacket(IbcPacket calldata packet) external returns (AckPacket memory ackPacket); + /** + * @notice Callback for acknowledging a packet; triggered on reciept of an IBC packet by the counterparty + * @param packet The IBC packet for which acknowledgement is received + * @param ack The acknowledgement packet received + * @dev Make sure to validate packet's source and destiation channels and ports. + */ function onAcknowledgementPacket(IbcPacket calldata packet, AckPacket calldata ack) external; + /** + * @notice Callback for handling a packet timeout + * @param packet The IBC packet that has timed out + * @dev Make sure to validate packet's source and destiation channels and ports. + */ function onTimeoutPacket(IbcPacket calldata packet) external; } @@ -72,6 +114,13 @@ interface IbcPacketReceiver { * @author Polymer Labs * @notice IBC receiver interface must be implemented by a IBC-enabled contract. * The implementer, aka. dApp devs, should implement channel handshake and packet handling methods. + * @notice : Make sure to integrate carefully. Anyone can open a channel with your dapp. It's important to + * follow best practices and to note: + * - Always validte all given arguments (e.g. channels, ports) in the channel handsakes + * - It's recommended to use the onlyIbcDispatcher modifier to restrict access control to only the Dispatcher + * contract. + * - Dispatcher callbacks inherit from nonReentrant, so multiple calls to the Dispatcher cannot be made within the + * same transaction . */ interface IbcReceiver is IbcChannelReceiver, IbcPacketReceiver {} diff --git a/contracts/interfaces/LightClient.sol b/contracts/interfaces/LightClient.sol deleted file mode 100644 index e93e4123..00000000 --- a/contracts/interfaces/LightClient.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -import {Ics23Proof, L1Header, OpL2StateProof} from "./ProofVerifier.sol"; - -interface LightClient { - /** - * addOpConsensusState adds an appHash to internal store and - * returns the fraud proof end time, and a bool flag indicating if - * the fraud proof window has passed according to the block's time stamp. - */ - function addOpConsensusState( - L1Header calldata l1header, - OpL2StateProof calldata proof, - uint256 height, - uint256 appHash - ) external returns (uint256 endTime, bool ended); - - /** - * - */ - function getFraudProofEndtime(uint256 height) external returns (uint256 endTime); - - /** - * verifyMembership checks if the current state - * can be used to perform the membership test and if so, it uses - * the verifier to perform membership check. - */ - function verifyMembership(Ics23Proof calldata proof, bytes memory key, bytes memory expectedValue) external; - - /** - * - */ - function verifyNonMembership(Ics23Proof calldata proof, bytes memory key) external; - - /** - * - */ - function getState(uint256 height) external view returns (uint256 appHash, uint256 fraudProofEndTime, bool ended); -} diff --git a/contracts/libs/Ibc.sol b/contracts/libs/Ibc.sol index 4bb43df6..68d9a146 100644 --- a/contracts/libs/Ibc.sol +++ b/contracts/libs/Ibc.sol @@ -6,6 +6,7 @@ import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {ProtoChannel, ProtoCounterparty} from "proto/channel.sol"; import {Base64} from "base64/base64.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; +import {IBCErrors} from "./IbcErrors.sol"; /** * Ibc.sol @@ -134,159 +135,6 @@ struct Proof { bytes proof; } -// misc errors. -library IBCErrors { - error invalidCounterParty(); - error invalidCounterPartyPortId(); - error invalidHexStringLength(); - error invalidRelayerAddress(); - error consensusStateVerificationFailed(); - error packetNotTimedOut(); - error invalidAddress(); - error notEnoughGas(); - error invalidPortPrefix(); - - // packet sequence related errors. - error invalidPacketSequence(); - error unexpectedPacketSequence(); - - // channel related errors. - error channelNotOwnedBySender(); - error channelNotOwnedByPortAddress(); - - // client related errors. - error clientAlreadyCreated(); - error clientNotCreated(); - - // packet commitment related errors. - error packetCommitmentNotFound(); - error ackPacketCommitmentAlreadyExists(); - error packetReceiptAlreadyExists(); - - // receiver related errors. - error receiverNotIntendedPacketDestination(); - error receiverNotOriginPacketSender(); - - error invalidChannelType(string channelType); - - // related to clients - error lightClientNotFound(string connection); - error channelIdNotFound(bytes32 channelId); - error invalidConnection(string connection); -} - -// define a library of Ibc utility functions -library IbcUtils { - error StringTooLong(); - - // fromUniversalPacketBytes converts UniversalPacketDataBytes to UniversalPacketData, per how its packed into bytes - function fromUniversalPacketBytes(bytes calldata data) - external - pure - returns (UniversalPacket memory universalPacketData) - { - bytes32 srcPortAddr; - uint256 mwBitmap; - bytes32 destPortAddr; - assembly { - // Keep reusing 0x0 to move from calldata to return vars - calldatacopy(0x0, data.offset, 32) - srcPortAddr := mload(0x0) - calldatacopy(0x0, add(data.offset, 32), 32) - mwBitmap := mload(0x0) - calldatacopy(0x0, add(data.offset, 64), 32) - destPortAddr := mload(0x0) - } - universalPacketData = UniversalPacket(srcPortAddr, uint256(mwBitmap), destPortAddr, data[96:data.length]); - } - - /** - * Convert a non-0x-prefixed hex string to an address - * @param hexStr hex string to convert to address. Note that the hex string must not include a 0x prefix. - * hexStr is case-insensitive. - */ - function hexStrToAddress(string memory hexStr) public pure returns (address addr) { - if (bytes(hexStr).length != 40) { - revert IBCErrors.invalidHexStringLength(); - } - - bytes memory strBytes = bytes(hexStr); - bytes memory addrBytes = new bytes(20); - - for (uint256 i = 0; i < 20; i++) { - uint8 high = uint8(strBytes[i * 2]); - uint8 low = uint8(strBytes[1 + i * 2]); - // Convert to lowercase if the character is in uppercase - if (high >= 65 && high <= 90) { - high += 32; - } - if (low >= 65 && low <= 90) { - low += 32; - } - uint8 digit = (high - (high >= 97 ? 87 : 48)) * 16 + (low - (low >= 97 ? 87 : 48)); - addrBytes[i] = bytes1(digit); - } - - assembly { - addr := mload(add(addrBytes, 20)) - } - - return addr; - } - - // For XXXX => vIBC direction, SC needs to verify the proof of membership of TRY_PENDING - // For vIBC initiated channel, SC doesn't need to verify any proof, and these should be all empty - function isChannelOpenTry(ChannelEnd calldata counterparty) public pure returns (bool open) { - if (counterparty.channelId == bytes32(0) && bytes(counterparty.version).length == 0) { - return false; - // ChanOpenInit with unknow conterparty - } else if (counterparty.channelId != bytes32(0) && bytes(counterparty.version).length != 0) { - // this is the ChanOpenTry; counterparty must not be zero-value - return true; - } else { - revert IBCErrors.invalidCounterParty(); - } - } - - function toUniversalPacketBytes(UniversalPacket memory data) internal pure returns (bytes memory packetBytes) { - packetBytes = bytes.concat(data.srcPortAddr, bytes32(data.mwBitmap), data.destPortAddr, data.appData); - } - - // addressToPortId converts an address to a port ID - function addressToPortId(string memory portPrefix, address addr) internal pure returns (string memory portId) { - portId = string(abi.encodePacked(portPrefix, toHexStr(addr))); - } - - // convert an address to its hex string, but without 0x prefix - function toHexStr(address addr) internal pure returns (bytes memory hexStr) { - bytes memory addrWithPrefix = abi.encodePacked(Strings.toHexString(addr)); - bytes memory addrWithoutPrefix = new bytes(addrWithPrefix.length - 2); - for (uint256 i = 0; i < addrWithoutPrefix.length; i++) { - addrWithoutPrefix[i] = addrWithPrefix[i + 2]; - } - hexStr = addrWithoutPrefix; - } - - // toAddress converts a bytes32 to an address - function toAddress(bytes32 b) internal pure returns (address out) { - out = address(uint160(uint256(b))); - } - - // toBytes32 converts an address to a bytes32 - function toBytes32(address a) internal pure returns (bytes32 out) { - out = bytes32(uint256(uint160(a))); - } - - function toBytes32(string memory s) internal pure returns (bytes32 result) { - bytes memory b = bytes(s); - if (b.length > 32) revert StringTooLong(); - - assembly { - result := mload(add(b, 32)) - } - } -} - library Ibc { /** * Convert a non-0x-prefixed hex string to an address diff --git a/contracts/libs/IbcErrors.sol b/contracts/libs/IbcErrors.sol new file mode 100644 index 00000000..5d7a7427 --- /dev/null +++ b/contracts/libs/IbcErrors.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +library IBCErrors { + // Misc errors + error invalidCounterParty(); + error invalidCounterPartyPortId(); + error invalidHexStringLength(); + error invalidRelayerAddress(); + error consensusStateVerificationFailed(); + error packetNotTimedOut(); + error invalidAddress(); + error invalidPortPrefix(); + error notEnoughGas(); + + // packet sequence related errors. + error invalidPacketSequence(); + error unexpectedPacketSequence(); + + // channel related errors. + error channelNotOwnedBySender(); + error channelNotOwnedByPortAddress(); + + // client related errors. + error clientAlreadyCreated(); + error clientNotCreated(); + + // packet commitment related errors. + error packetCommitmentNotFound(); + error ackPacketCommitmentAlreadyExists(); + error packetReceiptAlreadyExists(); + + // receiver related errors. + error receiverNotIntendedPacketDestination(); + error receiverNotOriginPacketSender(); + + error invalidChannelType(string channelType); + + // related to clients + error lightClientNotFound(string connection); + error channelIdNotFound(bytes32 channelId); + error invalidConnection(string connection); +} diff --git a/contracts/libs/IbcUtils.sol b/contracts/libs/IbcUtils.sol new file mode 100644 index 00000000..746c2729 --- /dev/null +++ b/contracts/libs/IbcUtils.sol @@ -0,0 +1,103 @@ +//SPDX-License-Identifier: UNLICENSED + +pragma solidity 0.8.15; + +import {UniversalPacket, ChannelEnd, Strings, IBCErrors} from "./Ibc.sol"; + +// define a library of Ibc utility functions +library IbcUtils { + error StringTooLong(); + + // fromUniversalPacketBytes converts UniversalPacketDataBytes to UniversalPacketData, per how its packed into bytes + function fromUniversalPacketBytes(bytes calldata data) + external + pure + returns (UniversalPacket memory universalPacketData) + { + bytes32 srcPortAddr; + uint256 mwBitmap; + bytes32 destPortAddr; + assembly { + // Keep reusing 0x0 to move from calldata to return vars + calldatacopy(0x0, data.offset, 32) + srcPortAddr := mload(0x0) + calldatacopy(0x0, add(data.offset, 32), 32) + mwBitmap := mload(0x0) + calldatacopy(0x0, add(data.offset, 64), 32) + destPortAddr := mload(0x0) + } + universalPacketData = UniversalPacket(srcPortAddr, uint256(mwBitmap), destPortAddr, data[96:data.length]); + } + + /** + * Convert a non-0x-prefixed hex string to an address + * @param hexStr hex string to convert to address. Note that the hex string must not include a 0x prefix. + * hexStr is case-insensitive. + */ + function hexStrToAddress(string memory hexStr) public pure returns (address addr) { + if (bytes(hexStr).length != 40) { + revert IBCErrors.invalidHexStringLength(); + } + + bytes memory strBytes = bytes(hexStr); + bytes memory addrBytes = new bytes(20); + + for (uint256 i = 0; i < 20; i++) { + uint8 high = uint8(strBytes[i * 2]); + uint8 low = uint8(strBytes[1 + i * 2]); + // Convert to lowercase if the character is in uppercase + if (high >= 65 && high <= 90) { + high += 32; + } + if (low >= 65 && low <= 90) { + low += 32; + } + uint8 digit = (high - (high >= 97 ? 87 : 48)) * 16 + (low - (low >= 97 ? 87 : 48)); + addrBytes[i] = bytes1(digit); + } + + assembly { + addr := mload(add(addrBytes, 20)) + } + + return addr; + } + + function toUniversalPacketBytes(UniversalPacket memory data) internal pure returns (bytes memory packetBytes) { + packetBytes = bytes.concat(data.srcPortAddr, bytes32(data.mwBitmap), data.destPortAddr, data.appData); + } + + // addressToPortId converts an address to a port ID + function addressToPortId(string memory portPrefix, address addr) internal pure returns (string memory portId) { + portId = string(abi.encodePacked(portPrefix, toHexStr(addr))); + } + + // convert an address to its hex string, but without 0x prefix + function toHexStr(address addr) internal pure returns (bytes memory hexStr) { + bytes memory addrWithPrefix = abi.encodePacked(Strings.toHexString(addr)); + bytes memory addrWithoutPrefix = new bytes(addrWithPrefix.length - 2); + for (uint256 i = 0; i < addrWithoutPrefix.length; i++) { + addrWithoutPrefix[i] = addrWithPrefix[i + 2]; + } + hexStr = addrWithoutPrefix; + } + + // toAddress converts a bytes32 to an address + function toAddress(bytes32 b) internal pure returns (address out) { + out = address(uint160(uint256(b))); + } + + // toBytes32 converts an address to a bytes32 + function toBytes32(address a) internal pure returns (bytes32 out) { + out = bytes32(uint256(uint160(a))); + } + + function toBytes32(string memory s) internal pure returns (bytes32 result) { + bytes memory b = bytes(s); + if (b.length > 32) revert StringTooLong(); + + assembly { + result := mload(add(b, 32)) + } + } +} diff --git a/contracts/utils/DummyLightClient.sol b/contracts/utils/DummyLightClient.sol index daf657d1..0d833373 100644 --- a/contracts/utils/DummyLightClient.sol +++ b/contracts/utils/DummyLightClient.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import {LightClient, L1Header, OpL2StateProof, Ics23Proof} from "../interfaces/LightClient.sol"; +import {ILightClient, L1Header, OpL2StateProof, Ics23Proof} from "../interfaces/ILightClient.sol"; /** * @title DummyLightClient @@ -9,7 +9,7 @@ import {LightClient, L1Header, OpL2StateProof, Ics23Proof} from "../interfaces/L * It should only be used for testing purposes. * The logic for checking if the proof length is greater than zero is naive. */ -contract DummyLightClient is LightClient { +contract DummyLightClient is ILightClient { error InvalidDummyMembershipProof(); error InvalidDummyNonMembershipProof(); diff --git a/contracts/utils/DummyProofVerifier.sol b/contracts/utils/DummyProofVerifier.sol index c1b0056c..2ee99554 100644 --- a/contracts/utils/DummyProofVerifier.sol +++ b/contracts/utils/DummyProofVerifier.sol @@ -1,9 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import {ProofVerifier, L1Header, OpL2StateProof, Ics23Proof} from "../interfaces/ProofVerifier.sol"; +import {IProofVerifier, L1Header, OpL2StateProof, Ics23Proof} from "../interfaces/IProofVerifier.sol"; -contract DummyProofVerifier is ProofVerifier { +/** + * @title DummyProofVerifier + * @dev A dummy implementation of the IProofVerifier interface for testing purposes. Does not actually verify any + * proofs. + */ +contract DummyProofVerifier is IProofVerifier { function verifyStateUpdate(L1Header calldata, OpL2StateProof calldata, bytes32, bytes32, uint64) external pure {} function verifyMembership(bytes32, bytes calldata, bytes calldata, Ics23Proof calldata) external pure {} diff --git a/diagrams/vibcContractsOverview.jpg b/diagrams/vibcContractsOverview.jpg new file mode 100644 index 00000000..348c25f6 Binary files /dev/null and b/diagrams/vibcContractsOverview.jpg differ diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 9fa794ed..8dacfd78 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -9,8 +9,8 @@ import {Dispatcher} from "../contracts/core/Dispatcher.sol"; import {IDispatcher} from "../contracts/core/Dispatcher.sol"; import {IUniversalChannelHandler} from "../contracts/interfaces/IUniversalChannelHandler.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import "../contracts/core/OpProofVerifier.sol"; -import "../contracts/core/OpLightClient.sol"; +import "../contracts/core/OptimisticProofVerifier.sol"; +import "../contracts/core/OptimisticLightClient.sol"; import "../contracts/core/UniversalChannelHandler.sol"; import "../contracts/examples/Earth.sol"; @@ -89,8 +89,8 @@ contract Deploy is Script { } function deployOpProofVerifier(address l2OutputOracleAddress) public broadcast returns (address addr_) { - OpProofVerifier verifier = new OpProofVerifier{salt: _implSalt()}(l2OutputOracleAddress); - console.log("OpProofVerifier deployed at %s", address(verifier)); + OptimisticProofVerifier verifier = new OptimisticProofVerifier{salt: _implSalt()}(l2OutputOracleAddress); + console.log("OptimisticProofVerifier deployed at %s", address(verifier)); return address(verifier); } @@ -100,7 +100,7 @@ contract Deploy is Script { returns (address addr_) { OptimisticLightClient manager = new OptimisticLightClient{salt: _implSalt()}( - fraudProofWindowSecs, ProofVerifier(proofVerifierAddr), L1Block(l1BlockProvider) + fraudProofWindowSecs, IProofVerifier(proofVerifierAddr), L1Block(l1BlockProvider) ); console.log("OptimisticLightClient deployed at %s", address(manager)); return address(manager); diff --git a/test/Dispatcher.gasGriefing.t.sol b/test/Dispatcher.gasGriefing.t.sol index b41028d0..685d0c11 100644 --- a/test/Dispatcher.gasGriefing.t.sol +++ b/test/Dispatcher.gasGriefing.t.sol @@ -1,10 +1,12 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {Base} from "./Dispatcher.base.t.sol"; +import {Base} from "./utils/Dispatcher.base.t.sol"; import {GasUsingMars} from "./mocks/GasUsingMars.sol"; -import {IbcEndpoint, ChannelEnd, IbcUtils, IbcPacket, IBCErrors} from "../contracts/libs/Ibc.sol"; -import {TestUtilsTest} from "./TestUtils.t.sol"; +import {IbcEndpoint, ChannelEnd, IbcPacket, IBCErrors} from "../contracts/libs/Ibc.sol"; +import {IbcUtils} from "../contracts/libs/IbcUtils.sol"; + +import {TestUtilsTest} from "./utils/TestUtils.t.sol"; contract DispatcherGasGriefing is Base { IbcEndpoint src = IbcEndpoint("polyibc.bsc.58b604DB8886656695442374D8E940D814F2eDd4", "channel-99"); diff --git a/test/Dispatcher/Dispatcher.ack.sol b/test/Dispatcher/Dispatcher.ack.sol new file mode 100644 index 00000000..007a461f --- /dev/null +++ b/test/Dispatcher/Dispatcher.ack.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.15; + +import "../utils/Dispatcher.base.t.sol"; +import "../../contracts/examples/Mars.sol"; +import "../../contracts/examples/Earth.sol"; +import "../../contracts/libs/Ibc.sol"; +import "../../contracts/libs/IbcUtils.sol"; +import {IbcReceiver} from "../../contracts/interfaces/IbcReceiver.sol"; +import {PacketSenderTestBase} from "./Dispatcher.t.sol"; + +// Test Chain A receives an acknowledgement packet from Chain B +contract DispatcherAckPacketTestSuite is PacketSenderTestBase { + function test_success() public { + for (uint64 index = 0; index < 3; index++) { + sendPacket(); + + vm.expectEmit(true, true, false, true, address(dispatcherProxy)); + emit Acknowledgement(address(mars), channelId, sentPacket.sequence); + dispatcherProxy.acknowledgement(sentPacket, ackPacket, validProof); + // confirm dapp recieved the ack + (bool success, bytes memory data) = mars.ackPackets(sentPacket.sequence - 1); + AckPacket memory parsed = Ibc.parseAckData(ackPacket); + assertEq(success, parsed.success); + assertEq(data, parsed.data); + } + } + + // cannot ack packets if packet commitment is missing + function test_missingPacket() public { + vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); + dispatcherProxy.acknowledgement(genPacket(1), genAckPacket("1"), validProof); + + sendPacket(); + dispatcherProxy.acknowledgement(sentPacket, ackPacket, validProof); + + // packet commitment is removed after ack + vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); + dispatcherProxy.acknowledgement(sentPacket, ackPacket, validProof); + } + + // cannot recieve ack packets out of order for ordered channel + function test_outOfOrder() public { + for (uint64 index = 0; index < 3; index++) { + sendPacket(); + } + // 1st ack is ok + dispatcherProxy.acknowledgement(genPacket(1), genAckPacket("1"), validProof); + + // only 2nd ack is allowed; so the 3rd ack fails + vm.expectRevert(abi.encodeWithSelector(IBCErrors.unexpectedPacketSequence.selector)); + + dispatcherProxy.acknowledgement(genPacket(3), genAckPacket("3"), validProof); + } + + function test_invalidPort() public { + Mars earth = new Mars(dispatcherProxy); + string memory earthPort = string(abi.encodePacked(portPrefix, getHexBytes(address(earth)))); + IbcEndpoint memory earthEnd = IbcEndpoint(earthPort, channelId); + + sendPacket(); + + // another valid packet but not the same port + IbcPacket memory packetEarth = sentPacket; + packetEarth.src = earthEnd; + + vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); + dispatcherProxy.acknowledgement(packetEarth, ackPacket, validProof); + } + + // ackPacket fails if channel doesn't match + function test_invalidChannel() public { + sendPacket(); + + IbcEndpoint memory invalidSrc = IbcEndpoint(src.portId, "channel-invalid"); + IbcPacket memory packet = sentPacket; + packet.src = invalidSrc; + + vm.expectRevert(abi.encodeWithSelector(IBCErrors.channelIdNotFound.selector, packet.src.channelId)); + + dispatcherProxy.acknowledgement(packet, ackPacket, validProof); + } +} diff --git a/test/Dispatcher.client.t.sol b/test/Dispatcher/Dispatcher.client.t.sol similarity index 73% rename from test/Dispatcher.client.t.sol rename to test/Dispatcher/Dispatcher.client.t.sol index 89118152..ee02dcc7 100644 --- a/test/Dispatcher.client.t.sol +++ b/test/Dispatcher/Dispatcher.client.t.sol @@ -1,14 +1,15 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "../contracts/libs/Ibc.sol"; -import {Dispatcher} from "../contracts/core/Dispatcher.sol"; -import {IDispatcher} from "../contracts/interfaces/IDispatcher.sol"; -import {IbcEventsEmitter} from "../contracts/interfaces/IbcDispatcher.sol"; -import {IbcReceiver} from "../contracts/interfaces/IbcReceiver.sol"; +import "../../contracts/libs/Ibc.sol"; +import {Dispatcher} from "../../contracts/core/Dispatcher.sol"; +import {IDispatcher} from "../../contracts/interfaces/IDispatcher.sol"; +import {IbcEventsEmitter} from "../../contracts/interfaces/IbcDispatcher.sol"; +import {IbcReceiver} from "../../contracts/interfaces/IbcReceiver.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import "../contracts/core/OpLightClient.sol"; -import "./Dispatcher.base.t.sol"; +import "../../contracts/examples/Mars.sol"; +import "../../contracts/core/OptimisticLightClient.sol"; +import "../utils/Dispatcher.base.t.sol"; abstract contract DispatcherUpdateClientTestSuite is Base { function test_updateOptimisticConsensusState_success() public { diff --git a/test/Dispatcher.closeChannel.t.sol b/test/Dispatcher/Dispatcher.closeChannel.t.sol similarity index 91% rename from test/Dispatcher.closeChannel.t.sol rename to test/Dispatcher/Dispatcher.closeChannel.t.sol index d394a89c..839ca665 100644 --- a/test/Dispatcher.closeChannel.t.sol +++ b/test/Dispatcher/Dispatcher.closeChannel.t.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import {ChannelOpenTestBaseSetup, DappRevertTests, PacketSenderTestBase} from "./Dispatcher.t.sol"; -import {RevertingStringCloseChannelMars, Mars} from "../contracts/examples/Mars.sol"; -import {DummyLightClient} from "../contracts/utils/DummyLightClient.sol"; -import "./Dispatcher.base.t.sol"; +import {ChannelOpenTestBaseSetup, PacketSenderTestBase} from "./Dispatcher.t.sol"; +import {DappHandlerRevertTests} from "./Dispatcher.dappHandlerRevert.t.sol"; +import {RevertingStringCloseChannelMars, Mars} from "../../contracts/examples/Mars.sol"; +import {DummyLightClient} from "../../contracts/utils/DummyLightClient.sol"; +import "../utils/Dispatcher.base.t.sol"; import { Channel, ChannelEnd, @@ -13,9 +14,9 @@ import { ChannelState, AckPacket, IBCErrors, - IbcUtils, Ibc -} from "../contracts/libs/Ibc.sol"; +} from "../../contracts/libs/Ibc.sol"; +import {IbcUtils} from "../../contracts/libs/IbcUtils.sol"; contract DispatcherCloseChannelTest is PacketSenderTestBase { Channel defaultChannel; // Uninitialized struct to compare that structs are deleted @@ -77,7 +78,7 @@ contract DispatcherCloseChannelTest is PacketSenderTestBase { } } -contract DappRevertTestsCloseChannel is DappRevertTests { +contract DappRevertTestsCloseChannel is DappHandlerRevertTests { Channel defaultChannel; // Uninitialized struct to compare that structs are deleted RevertingStringCloseChannelMars revertingStringCloseMars; diff --git a/test/Dispatcher/Dispatcher.dappHandlerRevert.t.sol b/test/Dispatcher/Dispatcher.dappHandlerRevert.t.sol new file mode 100644 index 00000000..ef8b9b9a --- /dev/null +++ b/test/Dispatcher/Dispatcher.dappHandlerRevert.t.sol @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "../utils/Dispatcher.base.t.sol"; +import "../../contracts/examples/Mars.sol"; +import "../../contracts/examples/Earth.sol"; +import "../../contracts/libs/Ibc.sol"; +import "../../contracts/libs/IbcUtils.sol"; +import {IbcReceiver} from "../../contracts/interfaces/IbcReceiver.sol"; + +contract DappHandlerRevertTests is Base { + RevertingBytesMars revertingBytesMars; + PanickingMars panickingMars; + RevertingEmptyMars revertingEmptyMars; + RevertingStringMars revertingStringMars; + string[] connectionHops0 = ["connection-0", "connection-3"]; + string[] connectionHops1 = ["connection-2", "connection-1"]; + ChannelEnd ch0 = + ChannelEnd("polyibc.eth1.71C95911E9a5D330f4D621842EC243EE1343292e", IbcUtils.toBytes32("channel-0"), "1.0"); + ChannelEnd ch1 = + ChannelEnd("polyibc.eth2.71C95911E9a5D330f4D621842EC243EE1343292e", IbcUtils.toBytes32("channel-1"), "1.0"); + + function setUp() public virtual override { + (dispatcherProxy, dispatcherImplementation) = TestUtilsTest.deployDispatcherProxyAndImpl(portPrefix); + dispatcherProxy.setClientForConnection(connectionHops0[0], dummyLightClient); + dispatcherProxy.setClientForConnection(connectionHops1[0], dummyLightClient); + dispatcherProxy.setClientForConnection(connectionHops[0], dummyLightClient); + revertingBytesMars = new RevertingBytesMars(dispatcherProxy); + panickingMars = new PanickingMars(dispatcherProxy); + revertingEmptyMars = new RevertingEmptyMars(dispatcherProxy); + revertingStringMars = new RevertingStringMars(dispatcherProxy); + } + + function test_ibc_channel_open_non_dapp_call() public { + address nonDappAddr = vm.addr(1); + + emit ChannelOpenInitError(nonDappAddr, bytes("call to non-contract")); + dispatcherProxy.channelOpenInit(ch1.version, ChannelOrder.NONE, false, connectionHops1, ch0.portId); + } + + function test_ibc_channel_open_dapp_without_handler() public { + Earth earth = new Earth(vm.addr(1)); + emit ChannelOpenInitError(address(earth), ""); + dispatcherProxy.channelOpenInit(ch1.version, ChannelOrder.NONE, false, connectionHops1, ch0.portId); + } + + function test_recv_packet_callback_revert_and_panic() public { + // this data is taken from polymerase/tests/e2e/tests/evm.events.test.ts MarsDappPair.createSentPacket() + IbcPacket memory packet; + packet.data = bytes("packet-1"); + packet.timeoutTimestamp = 15_566_401_733_896_437_760; + packet.dest.channelId = ch1.channelId; + packet.dest.portId = string(abi.encodePacked(portPrefix, IbcUtils.toHexStr(address(revertingBytesMars)))); + packet.src.portId = ch0.portId; + packet.src.channelId = ch0.channelId; + packet.sequence = 1; + + // Ack packet will check for an existing channel + bytes32 connectionStr = bytes32(0x636f6e6e656374696f6e2d300000000000000000000000000000000000000018); // Connection-0 + _storeChannelidToConnectionMapping(ch1.channelId, connectionStr); + + // Test Revert Memory + vm.expectEmit(true, true, true, true); + emit WriteAckPacket( + address(revertingBytesMars), + packet.dest.channelId, + packet.sequence, + AckPacket(false, abi.encodeWithSelector(RevertingBytesMars.OnRecvPacketRevert.selector)) + ); + dispatcherProxy.recvPacket(packet, validProof); + + // Test Revert String + packet.dest.portId = string(abi.encodePacked(portPrefix, IbcUtils.toHexStr(address(revertingStringMars)))); + vm.expectEmit(true, true, true, true); + emit WriteAckPacket( + address(revertingStringMars), + packet.dest.channelId, + packet.sequence, + AckPacket(false, abi.encodeWithSignature("Error(string)", "on recv packet is reverting")) + ); + dispatcherProxy.recvPacket(packet, validProof); + + // Test Revert empty + packet.dest.portId = string(abi.encodePacked(portPrefix, IbcUtils.toHexStr(address(revertingEmptyMars)))); + vm.expectEmit(true, true, true, true); + emit WriteAckPacket(address(revertingEmptyMars), packet.dest.channelId, packet.sequence, AckPacket(false, "")); + dispatcherProxy.recvPacket(packet, validProof); + + // Test Panic + packet.dest.portId = string(abi.encodePacked(portPrefix, IbcUtils.toHexStr(address(panickingMars)))); + vm.expectEmit(true, true, true, true); + emit WriteAckPacket( + address(panickingMars), + packet.dest.channelId, + packet.sequence, + AckPacket(false, abi.encodeWithSignature("Panic(uint256)", uint256(1))) + ); + dispatcherProxy.recvPacket(packet, validProof); + } + + function test_ack_packet_dapp_revert() public { + // plant a fake packet commitment so the ack checks go through + // use "forge inspect --storage" to find the slot + bytes32 slot1 = keccak256(abi.encode(address(revertingStringMars), uint32(156))); // current nested mapping + // slot: + bytes32 slot2 = keccak256(abi.encode(ch0.channelId, slot1)); + bytes32 slot3 = keccak256(abi.encode(uint256(1), slot2)); + vm.store(address(dispatcherProxy), slot3, bytes32(uint256(1))); + + bytes32 connectionStr = bytes32(0x636f6e6e656374696f6e2d300000000000000000000000000000000000000018); // Connection-0 + _storeChannelidToConnectionMapping(ch0.channelId, connectionStr); + + IbcPacket memory packet; + packet.data = bytes("packet-1"); + packet.timeoutTimestamp = 15_566_401_733_896_437_760; + packet.src.channelId = ch0.channelId; + packet.src.portId = string(abi.encodePacked(portPrefix, IbcUtils.toHexStr(address(revertingStringMars)))); + packet.dest.portId = ch1.portId; + packet.dest.channelId = ch1.channelId; + packet.sequence = 1; + + // this data is taken from the write_acknowledgement event emitted by polymer + bytes memory ack = + bytes('{"result":"eyAiYWNjb3VudCI6ICJhY2NvdW50IiwgInJlcGx5IjogImdvdCB0aGUgbWVzc2FnZSIgfQ=="}'); + + vm.expectEmit(true, true, true, true); + emit AcknowledgementError( + address(revertingStringMars), + abi.encodeWithSignature("Error(string)", "acknowledgement packet is reverting") + ); + dispatcherProxy.acknowledgement(packet, ack, validProof); + } + + function test_ibc_channel_open_dapp_revert() public { + vm.expectEmit(true, true, true, true); + vm.prank(address(revertingStringMars)); + emit ChannelOpenInitError( + address(revertingStringMars), abi.encodeWithSignature("Error(string)", "open ibc channel is reverting") + ); + dispatcherProxy.channelOpenInit(ch1.version, ChannelOrder.NONE, false, connectionHops1, ch0.portId); + } + + function test_ibc_channel_ack_dapp_revert() public { + vm.expectEmit(true, true, true, true); + ch0.portId = IbcUtils.addressToPortId(portPrefix, address(revertingStringMars)); + emit ChannelOpenAckError( + address(revertingStringMars), abi.encodeWithSignature("Error(string)", "connect ibc channel is reverting") + ); + dispatcherProxy.channelOpenAck(ch0, connectionHops0, ChannelOrder.NONE, false, ch1, validProof); + } +} diff --git a/test/Dispatcher.multiclient.sol b/test/Dispatcher/Dispatcher.multiclient.sol similarity index 89% rename from test/Dispatcher.multiclient.sol rename to test/Dispatcher/Dispatcher.multiclient.sol index 8702869f..d8c90006 100644 --- a/test/Dispatcher.multiclient.sol +++ b/test/Dispatcher/Dispatcher.multiclient.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.15; -import "../contracts/libs/Ibc.sol"; -import {Mars} from "../contracts/examples/Mars.sol"; +import "../../contracts/libs/Ibc.sol"; +import {IbcUtils} from "../../contracts/libs/IbcUtils.sol"; +import {Mars} from "../../contracts/examples/Mars.sol"; import {DispatcherProofTestUtils} from "./Dispatcher.proof.t.sol"; -import {DummyLightClient} from "../contracts/utils/DummyLightClient.sol"; -import {OptimisticLightClient} from "../contracts/core/OpLightClient.sol"; -import {LightClient} from "../contracts/interfaces/LightClient.sol"; -import "../contracts/interfaces/ProofVerifier.sol"; +import {DummyLightClient} from "../../contracts/utils/DummyLightClient.sol"; +import {OptimisticLightClient} from "../../contracts/core/OptimisticLightClient.sol"; +import {ILightClient} from "../../contracts/interfaces/ILightClient.sol"; +import "../../contracts/interfaces/IProofVerifier.sol"; contract DispatcherRealProofMultiClient is DispatcherProofTestUtils { string[] connectionHops0 = ["dummy-connection-1", "dummy-connection-2"]; @@ -41,7 +42,7 @@ contract DispatcherRealProofMultiClient is DispatcherProofTestUtils { dispatcherProxy.channelOpenTry(ch0, ChannelOrder.NONE, false, connectionHops0, ch1, dummyProof); // Having a dummy light client shouldn't impact opLightClient's ability to reject invalid proofs - vm.expectRevert(abi.encodeWithSelector(ProofVerifier.InvalidProofValue.selector)); + vm.expectRevert(abi.encodeWithSelector(IProofVerifier.InvalidProofValue.selector)); dispatcherProxy.channelOpenTry(ch1, ChannelOrder.NONE, false, connectionHops1, ch0, dummyProof); } @@ -62,7 +63,7 @@ contract DispatcherRealProofMultiClient is DispatcherProofTestUtils { bytes memory ackData = bytes.concat(keccak256(hex"22726573756c7422")); // OpProofverifier will cause acknowledgement to fail - vm.expectRevert(abi.encodeWithSelector(ProofVerifier.InvalidProofKey.selector)); + vm.expectRevert(abi.encodeWithSelector(IProofVerifier.InvalidProofKey.selector)); dispatcherProxy.acknowledgement(packet, ackData, invalidProof); // Now we change the client to the dummy client and the packet should go through since it circumvents the proof @@ -84,7 +85,7 @@ contract DispatcherRealProofMultiClient is DispatcherProofTestUtils { function test_addr0_channels_cannot_be_added() public { vm.expectRevert(abi.encodeWithSelector(IBCErrors.invalidAddress.selector)); - dispatcherProxy.setClientForConnection(connectionHops0[0], LightClient(address(0))); + dispatcherProxy.setClientForConnection(connectionHops0[0], ILightClient(address(0))); } function test_Dispatcher_removeConnection() public { diff --git a/test/Dispatcher.proof.t.sol b/test/Dispatcher/Dispatcher.proof.t.sol similarity index 91% rename from test/Dispatcher.proof.t.sol rename to test/Dispatcher/Dispatcher.proof.t.sol index 749df599..a93d11ea 100644 --- a/test/Dispatcher.proof.t.sol +++ b/test/Dispatcher/Dispatcher.proof.t.sol @@ -1,17 +1,18 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.15; -import "../contracts/libs/Ibc.sol"; -import {Base} from "./Dispatcher.base.t.sol"; -import {Dispatcher} from "../contracts/core/Dispatcher.sol"; -import {IDispatcher} from "../contracts/interfaces/IDispatcher.sol"; -import {Mars} from "../contracts/examples/Mars.sol"; -import {IbcDispatcher, IbcEventsEmitter} from "../contracts/interfaces/IbcDispatcher.sol"; -import "../contracts/core/OpLightClient.sol"; -import "./Proof.base.t.sol"; +import "../../contracts/libs/Ibc.sol"; +import "../../contracts/libs/IbcUtils.sol"; +import {Base} from "../utils/Dispatcher.base.t.sol"; +import {Dispatcher} from "../../contracts/core/Dispatcher.sol"; +import {IDispatcher} from "../../contracts/interfaces/IDispatcher.sol"; +import "../../contracts/examples/Mars.sol"; +import {IbcDispatcher, IbcEventsEmitter} from "../../contracts/interfaces/IbcDispatcher.sol"; +import "../../contracts/core/OptimisticLightClient.sol"; +import "../utils/Proof.base.t.sol"; import {stdStorage, StdStorage} from "forge-std/Test.sol"; -import {ChannelHandshakeSetting} from "./Dispatcher.base.t.sol"; -import {DummyLightClient} from "../contracts/utils/DummyLightClient.sol"; +import {ChannelHandshakeSetting} from "../utils/Dispatcher.base.t.sol"; +import {DummyLightClient} from "../../contracts/utils/DummyLightClient.sol"; import {DispatcherSendPacketTestSuite, ChannelOpenTestBaseSetup} from "./Dispatcher.t.sol"; contract DispatcherProofTestUtils is Base { @@ -145,7 +146,7 @@ abstract contract DispatcherIbcWithRealProofsSuite is IbcEventsEmitter, Dispatch packet.src.channelId = ch1.channelId; packet.sequence = 1; - vm.expectRevert(abi.encodeWithSelector(ProofVerifier.MethodNotImplemented.selector)); + vm.expectRevert(abi.encodeWithSelector(IProofVerifier.MethodNotImplemented.selector)); dispatcherProxy.timeout(packet, proof); } } @@ -155,7 +156,6 @@ contract DispatcherIbcWithRealProofs is DispatcherIbcWithRealProofsSuite { super.setUp(); string memory portPrefix1 = "polyibc.eth1."; - string memory portPrefix2 = "polyibc.eth2."; opLightClient = new OptimisticLightClient(1, opProofVerifier, l1BlockProvider); (dispatcherProxy, dispatcherImplementation) = deployDispatcherProxyAndImpl(portPrefix1); dispatcherProxy.setClientForConnection(connectionHops0[0], opLightClient); diff --git a/test/Dispatcher.t.sol b/test/Dispatcher/Dispatcher.t.sol similarity index 52% rename from test/Dispatcher.t.sol rename to test/Dispatcher/Dispatcher.t.sol index ce238da2..c4812dbc 100644 --- a/test/Dispatcher.t.sol +++ b/test/Dispatcher/Dispatcher.t.sol @@ -1,21 +1,16 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -import "../contracts/libs/Ibc.sol"; -import {Dispatcher} from "../contracts/core/Dispatcher.sol"; -import {IbcEventsEmitter} from "../contracts/interfaces/IbcDispatcher.sol"; -import {IbcReceiver, IbcReceiverBase} from "../contracts/interfaces/IbcReceiver.sol"; -import {DummyLightClient} from "../contracts/utils/DummyLightClient.sol"; -import { - Mars, - RevertingBytesMars, - PanickingMars, - RevertingEmptyMars, - RevertingStringMars -} from "../contracts/examples/Mars.sol"; -import "../contracts/core/OpLightClient.sol"; -import "./Dispatcher.base.t.sol"; -import {Earth} from "../contracts/examples/Earth.sol"; +import "../../contracts/libs/Ibc.sol"; +import "../../contracts/libs/IbcUtils.sol"; +import {Dispatcher} from "../../contracts/core/Dispatcher.sol"; +import {IbcEventsEmitter} from "../../contracts/interfaces/IbcDispatcher.sol"; +import {IbcReceiver} from "../../contracts/interfaces/IbcReceiver.sol"; +import {DummyLightClient} from "../../contracts/utils/DummyLightClient.sol"; +import "../../contracts/examples/Mars.sol"; +import "../../contracts/core/OptimisticLightClient.sol"; +import "../utils/Dispatcher.base.t.sol"; +import {Earth} from "../../contracts/examples/Earth.sol"; abstract contract ChannelHandshakeUtils is Base { string portId; @@ -370,319 +365,11 @@ contract DispatcherRecvPacketTestSuite is ChannelOpenTestBaseSetup { Ibc.packetCommitmentProofKey(spoofedPacket), abi.encode(Ibc.packetCommitmentProofValue(spoofedPacket)) ), - abi.encodeWithSelector(ProofVerifier.InvalidProofValue.selector) + abi.encodeWithSelector(IProofVerifier.InvalidProofValue.selector) ); - vm.expectRevert(abi.encodeWithSelector(ProofVerifier.InvalidProofValue.selector)); + vm.expectRevert(abi.encodeWithSelector(IProofVerifier.InvalidProofValue.selector)); dispatcherProxy.writeTimeoutPacket(spoofedPacket, validProof); } // TODO: add tests for unordered channel, wrong port, and invalid proof } - -// Test Chain A receives an acknowledgement packet from Chain B -contract DispatcherAckPacketTestSuite is PacketSenderTestBase { - function test_success() public { - for (uint64 index = 0; index < 3; index++) { - sendPacket(); - - vm.expectEmit(true, true, false, true, address(dispatcherProxy)); - emit Acknowledgement(address(mars), channelId, sentPacket.sequence); - dispatcherProxy.acknowledgement(sentPacket, ackPacket, validProof); - // confirm dapp recieved the ack - (bool success, bytes memory data) = mars.ackPackets(sentPacket.sequence - 1); - AckPacket memory parsed = Ibc.parseAckData(ackPacket); - assertEq(success, parsed.success); - assertEq(data, parsed.data); - } - } - - // cannot ack packets if packet commitment is missing - function test_missingPacket() public { - vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); - dispatcherProxy.acknowledgement(genPacket(1), genAckPacket("1"), validProof); - - sendPacket(); - dispatcherProxy.acknowledgement(sentPacket, ackPacket, validProof); - - // packet commitment is removed after ack - vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); - dispatcherProxy.acknowledgement(sentPacket, ackPacket, validProof); - } - - // cannot recieve ack packets out of order for ordered channel - function test_outOfOrder() public { - for (uint64 index = 0; index < 3; index++) { - sendPacket(); - } - // 1st ack is ok - dispatcherProxy.acknowledgement(genPacket(1), genAckPacket("1"), validProof); - - // only 2nd ack is allowed; so the 3rd ack fails - vm.expectRevert(abi.encodeWithSelector(IBCErrors.unexpectedPacketSequence.selector)); - - dispatcherProxy.acknowledgement(genPacket(3), genAckPacket("3"), validProof); - } - - function test_invalidPort() public { - Mars earth = new Mars(dispatcherProxy); - string memory earthPort = string(abi.encodePacked(portPrefix, getHexBytes(address(earth)))); - IbcEndpoint memory earthEnd = IbcEndpoint(earthPort, channelId); - - sendPacket(); - - // another valid packet but not the same port - IbcPacket memory packetEarth = sentPacket; - packetEarth.src = earthEnd; - - vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); - dispatcherProxy.acknowledgement(packetEarth, ackPacket, validProof); - } - - function test_misformattedPort() public { - Mars earth = new Mars(dispatcherProxy); - string memory earthPort = string(abi.encodePacked(portPrefix, getHexBytes(address(earth)))); - IbcEndpoint memory earthEnd = IbcEndpoint(earthPort, channelId); - - sendPacket(); - - // another valid packet but not the same port - IbcPacket memory packetEarth = sentPacket; - packetEarth.src = earthEnd; - packetEarth.src.portId = string(bytes.concat(bytes(portPrefix), bytes32(uint256(uint160(vm.addr(1)))))); - - vm.expectRevert(abi.encodeWithSelector(IBCErrors.invalidHexStringLength.selector)); - dispatcherProxy.acknowledgement(packetEarth, ackPacket, validProof); - } - - // ackPacket fails if channel doesn't match - function test_invalidChannel() public { - sendPacket(); - - IbcEndpoint memory invalidSrc = IbcEndpoint(src.portId, "channel-invalid"); - IbcPacket memory packet = sentPacket; - packet.src = invalidSrc; - - vm.expectRevert(abi.encodeWithSelector(IBCErrors.channelIdNotFound.selector, packet.src.channelId)); - dispatcherProxy.acknowledgement(packet, ackPacket, validProof); - } -} - -// Test Chain A receives a timeout packet from Chain B -contract DispatcherTimeoutPacketTestSuite is PacketSenderTestBase { - // preconditions for timeout packet - // - packet commitment exists - // - packet timeout is verified by Polymer client - function test_success() public { - for (uint64 index = 0; index < 3; index++) { - sendPacket(); - - vm.expectEmit(true, true, true, true, address(dispatcherProxy)); - emit Timeout(address(mars), channelId, sentPacket.sequence); - dispatcherProxy.timeout(sentPacket, validProof); - } - } - - function test_timeout_dapp_revert() public { - sentPacket = IbcPacket(srcRevertingMars, dest, 1, payload, ZERO_HEIGHT, maxTimeout); - revertingBytesMars.greet(payloadStr, channelId, maxTimeout); - nextSendSeq += 1; - vm.expectEmit(true, true, true, true, address(dispatcherProxy)); - emit TimeoutError( - address(revertingBytesMars), abi.encodeWithSelector(RevertingBytesMars.OnTimeoutPacket.selector) - ); - dispatcherProxy.timeout(sentPacket, validProof); - } - - // cannot timeout packets if packet commitment is missing - function test_missingPacket() public { - vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); - dispatcherProxy.timeout(genPacket(1), validProof); - - sendPacket(); - dispatcherProxy.timeout(sentPacket, validProof); - - // packet commitment is removed after timeout - vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); - dispatcherProxy.timeout(sentPacket, validProof); - } - - // cannot timeout packets if original packet port doesn't match current port - function test_invalidPort_123a() public { - Mars earth = new Mars(dispatcherProxy); - string memory earthPort = string(abi.encodePacked(portPrefix, getHexBytes(address(earth)))); - IbcEndpoint memory earthEnd = IbcEndpoint(earthPort, channelId); - - sendPacket(); - - // another valid packet but not the same port - IbcPacket memory packetEarth = sentPacket; - packetEarth.src = earthEnd; - - vm.expectRevert(IBCErrors.packetCommitmentNotFound.selector); - dispatcherProxy.timeout(packetEarth, validProof); - } - - // cannot timeout packetsfails if channel doesn't match - function test_invalidChannel() public { - bytes32 connectionStr = bytes32(0x636f6e6e656374696f6e2d310000000000000000000000000000000000000018); // - // Connection-1 - IbcPacket memory packet = sentPacket; - _storeChannelidToConnectionMapping(packet.src.channelId, connectionStr); - sendPacket(); - - IbcEndpoint memory invalidSrc = IbcEndpoint(src.portId, "channel-invalid"); - packet.src = invalidSrc; - vm.expectRevert(abi.encodeWithSelector(IBCErrors.channelIdNotFound.selector, packet.src.channelId)); - dispatcherProxy.timeout(packet, validProof); - } - - // cannot timeout packets if proof from Polymer is invalid - - function test_invalidProof() public { - sendPacket(); - vm.expectRevert(DummyLightClient.InvalidDummyNonMembershipProof.selector); - dispatcherProxy.timeout(sentPacket, invalidProof); - } -} - -contract DappRevertTests is Base { - RevertingBytesMars revertingBytesMars; - PanickingMars panickingMars; - RevertingEmptyMars revertingEmptyMars; - RevertingStringMars revertingStringMars; - string[] connectionHops0 = ["connection-0", "connection-3"]; - string[] connectionHops1 = ["connection-2", "connection-1"]; - ChannelEnd ch0 = - ChannelEnd("polyibc.eth.71C95911E9a5D330f4D621842EC243EE1343292e", IbcUtils.toBytes32("channel-0"), "1.0"); - ChannelEnd ch1 = - ChannelEnd("polyibc.eth.71C95911E9a5D330f4D621842EC243EE1343292e", IbcUtils.toBytes32("channel-1"), "1.0"); - - function setUp() public virtual override { - (dispatcherProxy, dispatcherImplementation) = TestUtilsTest.deployDispatcherProxyAndImpl(portPrefix); - dispatcherProxy.setClientForConnection(connectionHops0[0], dummyLightClient); - dispatcherProxy.setClientForConnection(connectionHops1[0], dummyLightClient); - dispatcherProxy.setClientForConnection(connectionHops[0], dummyLightClient); - revertingBytesMars = new RevertingBytesMars(dispatcherProxy); - panickingMars = new PanickingMars(dispatcherProxy); - revertingEmptyMars = new RevertingEmptyMars(dispatcherProxy); - revertingStringMars = new RevertingStringMars(dispatcherProxy); - } - - function test_ibc_channel_open_non_dapp_call() public { - address nonDappAddr = vm.addr(1); - - emit ChannelOpenInitError(nonDappAddr, bytes("call to non-contract")); - dispatcherProxy.channelOpenInit(ch1.version, ChannelOrder.NONE, false, connectionHops1, ch0.portId); - } - - function test_ibc_channel_open_dapp_without_handler() public { - Earth earth = new Earth(vm.addr(1)); - emit ChannelOpenInitError(address(earth), ""); - dispatcherProxy.channelOpenInit(ch1.version, ChannelOrder.NONE, false, connectionHops1, ch0.portId); - } - - function test_recv_packet_callback_revert_and_panic() public { - // this data is taken from polymerase/tests/e2e/tests/evm.events.test.ts MarsDappPair.createSentPacket() - IbcPacket memory packet; - packet.data = bytes("packet-1"); - packet.timeoutTimestamp = 15_566_401_733_896_437_760; - packet.dest.channelId = ch1.channelId; - packet.dest.portId = string(abi.encodePacked(portPrefix, IbcUtils.toHexStr(address(revertingBytesMars)))); - packet.src.portId = ch0.portId; - packet.src.channelId = ch0.channelId; - packet.sequence = 1; - - // Ack packet will check for an existing channel - bytes32 connectionStr = bytes32(0x636f6e6e656374696f6e2d300000000000000000000000000000000000000018); // Connection-0 - _storeChannelidToConnectionMapping(ch1.channelId, connectionStr); - - // Test Revert Memory - vm.expectEmit(true, true, true, true); - emit WriteAckPacket( - address(revertingBytesMars), - packet.dest.channelId, - packet.sequence, - AckPacket(false, abi.encodeWithSelector(RevertingBytesMars.OnRecvPacketRevert.selector)) - ); - dispatcherProxy.recvPacket(packet, validProof); - - // Test Revert String - packet.dest.portId = string(abi.encodePacked(portPrefix, IbcUtils.toHexStr(address(revertingStringMars)))); - vm.expectEmit(true, true, true, true); - emit WriteAckPacket( - address(revertingStringMars), - packet.dest.channelId, - packet.sequence, - AckPacket(false, abi.encodeWithSignature("Error(string)", "on recv packet is reverting")) - ); - dispatcherProxy.recvPacket(packet, validProof); - - // Test Revert empty - packet.dest.portId = string(abi.encodePacked(portPrefix, IbcUtils.toHexStr(address(revertingEmptyMars)))); - vm.expectEmit(true, true, true, true); - emit WriteAckPacket(address(revertingEmptyMars), packet.dest.channelId, packet.sequence, AckPacket(false, "")); - dispatcherProxy.recvPacket(packet, validProof); - - // Test Panic - packet.dest.portId = string(abi.encodePacked(portPrefix, IbcUtils.toHexStr(address(panickingMars)))); - vm.expectEmit(true, true, true, true); - emit WriteAckPacket( - address(panickingMars), - packet.dest.channelId, - packet.sequence, - AckPacket(false, abi.encodeWithSignature("Panic(uint256)", uint256(1))) - ); - dispatcherProxy.recvPacket(packet, validProof); - } - - function test_ack_packet_dapp_revert() public { - // plant a fake packet commitment so the ack checks go through - // use "forge inspect --storage" to find the slot - bytes32 slot1 = keccak256(abi.encode(address(revertingStringMars), uint32(156))); // current nested mapping - // slot: - bytes32 slot2 = keccak256(abi.encode(ch0.channelId, slot1)); - bytes32 slot3 = keccak256(abi.encode(uint256(1), slot2)); - vm.store(address(dispatcherProxy), slot3, bytes32(uint256(1))); - - bytes32 connectionStr = bytes32(0x636f6e6e656374696f6e2d300000000000000000000000000000000000000018); // Connection-0 - _storeChannelidToConnectionMapping(ch0.channelId, connectionStr); - - IbcPacket memory packet; - packet.data = bytes("packet-1"); - packet.timeoutTimestamp = 15_566_401_733_896_437_760; - packet.src.channelId = ch0.channelId; - packet.src.portId = string(abi.encodePacked(portPrefix, IbcUtils.toHexStr(address(revertingStringMars)))); - packet.dest.portId = ch1.portId; - packet.dest.channelId = ch1.channelId; - packet.sequence = 1; - - // this data is taken from the write_acknowledgement event emitted by polymer - bytes memory ack = - bytes('{"result":"eyAiYWNjb3VudCI6ICJhY2NvdW50IiwgInJlcGx5IjogImdvdCB0aGUgbWVzc2FnZSIgfQ=="}'); - - vm.expectEmit(true, true, true, true); - emit AcknowledgementError( - address(revertingStringMars), - abi.encodeWithSignature("Error(string)", "acknowledgement packet is reverting") - ); - dispatcherProxy.acknowledgement(packet, ack, validProof); - } - - function test_ibc_channel_open_dapp_revert() public { - vm.expectEmit(true, true, true, true); - vm.prank(address(revertingStringMars)); - emit ChannelOpenInitError( - address(revertingStringMars), abi.encodeWithSignature("Error(string)", "open ibc channel is reverting") - ); - dispatcherProxy.channelOpenInit(ch1.version, ChannelOrder.NONE, false, connectionHops1, ch0.portId); - } - - function test_ibc_channel_ack_dapp_revert() public { - vm.expectEmit(true, true, true, true); - ch0.portId = IbcUtils.addressToPortId(portPrefix, address(revertingStringMars)); - emit ChannelOpenAckError( - address(revertingStringMars), abi.encodeWithSignature("Error(string)", "connect ibc channel is reverting") - ); - dispatcherProxy.channelOpenAck(ch0, connectionHops0, ChannelOrder.NONE, false, ch1, validProof); - } -} diff --git a/test/Dispatcher/Dispatcher.timeout.t.sol b/test/Dispatcher/Dispatcher.timeout.t.sol new file mode 100644 index 00000000..586b0941 --- /dev/null +++ b/test/Dispatcher/Dispatcher.timeout.t.sol @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "../utils/Dispatcher.base.t.sol"; +import "../../contracts/examples/Mars.sol"; +import "../../contracts/examples/Earth.sol"; +import "../../contracts/libs/Ibc.sol"; +import "../../contracts/libs/IbcUtils.sol"; +import {IbcReceiver} from "../../contracts/interfaces/IbcReceiver.sol"; +import {PacketSenderTestBase} from "./Dispatcher.t.sol"; + +// Test Chain A receives a timeout packet from Chain B +contract DispatcherTimeoutPacketTestSuite is PacketSenderTestBase { + // preconditions for timeout packet + // - packet commitment exists + // - packet timeout is verified by Polymer client + function test_success() public { + for (uint64 index = 0; index < 3; index++) { + sendPacket(); + + vm.expectEmit(true, true, true, true, address(dispatcherProxy)); + emit Timeout(address(mars), channelId, sentPacket.sequence); + dispatcherProxy.timeout(sentPacket, validProof); + } + } + + function test_timeout_dapp_revert() public { + sentPacket = IbcPacket(srcRevertingMars, dest, 1, payload, ZERO_HEIGHT, maxTimeout); + revertingBytesMars.greet(payloadStr, channelId, maxTimeout); + nextSendSeq += 1; + vm.expectEmit(true, true, true, true, address(dispatcherProxy)); + emit TimeoutError( + address(revertingBytesMars), abi.encodeWithSelector(RevertingBytesMars.OnTimeoutPacket.selector) + ); + dispatcherProxy.timeout(sentPacket, validProof); + } + + // cannot timeout packets if packet commitment is missing + function test_missingPacket() public { + vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); + dispatcherProxy.timeout(genPacket(1), validProof); + + sendPacket(); + dispatcherProxy.timeout(sentPacket, validProof); + + // packet commitment is removed after timeout + vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); + dispatcherProxy.timeout(sentPacket, validProof); + } + + // cannot timeout packets if original packet port doesn't match current port + function test_invalidPort() public { + Mars earth = new Mars(dispatcherProxy); + string memory earthPort = string(abi.encodePacked(portPrefix, getHexBytes(address(earth)))); + IbcEndpoint memory earthEnd = IbcEndpoint(earthPort, channelId); + + sendPacket(); + + // another valid packet but not the same port + IbcPacket memory packetEarth = sentPacket; + packetEarth.src = earthEnd; + + vm.expectRevert(IBCErrors.packetCommitmentNotFound.selector); + dispatcherProxy.timeout(packetEarth, validProof); + } + + // cannot timeout packetsfails if channel doesn't match + function test_invalidChannel() public { + sendPacket(); + + IbcEndpoint memory invalidSrc = IbcEndpoint(src.portId, "channel-invalid"); + IbcPacket memory packet = sentPacket; + packet.src = invalidSrc; + + bytes32 connectionStr = bytes32(0x636f6e6e656374696f6e2d310000000000000000000000000000000000000018); // + // Connection-1 + _storeChannelidToConnectionMapping(packet.src.channelId, connectionStr); + + vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); + /* vm.expectRevert('Packet commitment not found'); */ + dispatcherProxy.timeout(packet, validProof); + } + + // cannot timeout packets if proof from Polymer is invalid + function test_invalidProof() public { + sendPacket(); + vm.expectRevert(DummyLightClient.InvalidDummyNonMembershipProof.selector); + dispatcherProxy.timeout(sentPacket, invalidProof); + } +} diff --git a/test/Ibc.t.sol b/test/Ibc.t.sol index 8adb1e99..6930a329 100644 --- a/test/Ibc.t.sol +++ b/test/Ibc.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; import "../contracts/libs/Ibc.sol"; +import "../contracts/libs/IbcUtils.sol"; import "forge-std/Test.sol"; import {IbcChannelReceiver} from "../contracts/interfaces/IbcReceiver.sol"; diff --git a/test/OpConsensusStateManager.t.sol b/test/OpConsensusStateManager.t.sol index f9197322..86242968 100644 --- a/test/OpConsensusStateManager.t.sol +++ b/test/OpConsensusStateManager.t.sol @@ -2,13 +2,13 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; -import "../contracts/core/OpLightClient.sol"; +import "../contracts/core/OptimisticLightClient.sol"; import "../contracts/utils/DummyProofVerifier.sol"; -import "./Proof.base.t.sol"; +import "./utils/Proof.base.t.sol"; contract OptimisticLightClientTest is ProofBase { OptimisticLightClient manager; - ProofVerifier verifier; + IProofVerifier verifier; constructor() { verifier = new DummyProofVerifier(); diff --git a/test/Verifier.t.sol b/test/Verifier.t.sol index 7761eb33..9ba712e0 100644 --- a/test/Verifier.t.sol +++ b/test/Verifier.t.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import "../contracts/core/OpProofVerifier.sol"; +import "../contracts/core/OptimisticProofVerifier.sol"; import "../contracts/libs/Ibc.sol"; +import "../contracts/libs/IbcUtils.sol"; import "forge-std/Test.sol"; -import "./Proof.base.t.sol"; +import "./utils/Proof.base.t.sol"; contract OpProofVerifierStateUpdateTest is ProofBase { function test_verify_state_update_sucess() public view { @@ -20,25 +21,26 @@ contract OpProofVerifierStateUpdateTest is ProofBase { bytes32 trustedL1BlockHash = keccak256(RLPWriter.writeList(l1header.header)); uint64 trustedL1BlockNumber = l1header.number; - OpProofVerifier verifier = new OpProofVerifier(address(0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990)); + OptimisticProofVerifier verifier = + new OptimisticProofVerifier(address(0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990)); vm.expectRevert("MerkleTrie: invalid large internal hash"); verifier.verifyStateUpdate(l1header, validStateProof, apphash, trustedL1BlockHash, trustedL1BlockNumber); } function test_verify_state_update_invalid_l1_number() public { - vm.expectRevert(ProofVerifier.InvalidL1BlockNumber.selector); + vm.expectRevert(IProofVerifier.InvalidL1BlockNumber.selector); opProofVerifier.verifyStateUpdate(emptyl1header, invalidStateProof, bytes32(0), bytes32(0), 42); } function test_verify_state_update_invalid_l1_hash() public { - vm.expectRevert(ProofVerifier.InvalidL1BlockHash.selector); + vm.expectRevert(IProofVerifier.InvalidL1BlockHash.selector); opProofVerifier.verifyStateUpdate(emptyl1header, invalidStateProof, bytes32(0), bytes32(0), 0); } function test_verify_state_update_invalid_rlp_computed_hash() public { // just so the verifier can reach item at index 8 emptyl1header.header = new bytes[](9); - vm.expectRevert(ProofVerifier.InvalidRLPEncodedL1BlockNumber.selector); + vm.expectRevert(IProofVerifier.InvalidRLPEncodedL1BlockNumber.selector); opProofVerifier.verifyStateUpdate( emptyl1header, invalidStateProof, bytes32(0), keccak256(RLPWriter.writeList(emptyl1header.header)), 0 ); @@ -48,7 +50,7 @@ contract OpProofVerifierStateUpdateTest is ProofBase { // just so the verifier can reach item at index 8 emptyl1header.header = new bytes[](9); emptyl1header.header[8] = RLPWriter.writeUint(0); - vm.expectRevert(ProofVerifier.InvalidRLPEncodedL1StateRoot.selector); + vm.expectRevert(IProofVerifier.InvalidRLPEncodedL1StateRoot.selector); opProofVerifier.verifyStateUpdate( emptyl1header, invalidStateProof, bytes32(0), keccak256(RLPWriter.writeList(emptyl1header.header)), 0 ); @@ -65,7 +67,7 @@ contract OpProofVerifierStateUpdateTest is ProofBase { } function test_verify_state_update_invalid_apphash() public { - vm.expectRevert(ProofVerifier.InvalidAppHash.selector); + vm.expectRevert(IProofVerifier.InvalidAppHash.selector); opProofVerifier.verifyStateUpdate( l1header, validStateProof, bytes32(0), keccak256(RLPWriter.writeList(l1header.header)), l1header.number ); diff --git a/test/VirtualChain.sol b/test/VirtualChain.sol index 4dc30077..6d473307 100644 --- a/test/VirtualChain.sol +++ b/test/VirtualChain.sol @@ -7,16 +7,17 @@ import {IbcDispatcher, IbcEventsEmitter} from "../contracts/interfaces/IbcDispat import {IUniversalChannelHandler} from "../contracts/interfaces/IUniversalChannelHandler.sol"; import {IDispatcher} from "../contracts/interfaces/IDispatcher.sol"; import "../contracts/libs/Ibc.sol"; +import {IbcUtils} from "../contracts/libs/IbcUtils.sol"; import {Dispatcher} from "../contracts/core/Dispatcher.sol"; import {IbcChannelReceiver, IbcPacketReceiver} from "../contracts/interfaces/IbcReceiver.sol"; -import "../contracts/interfaces/ProofVerifier.sol"; +import "../contracts/interfaces/IProofVerifier.sol"; import {UniversalChannelHandler} from "../contracts/core/UniversalChannelHandler.sol"; import {Mars} from "../contracts/examples/Mars.sol"; import {Earth} from "../contracts/examples/Earth.sol"; import {IbcMiddleware} from "../contracts/interfaces/IbcMiddleware.sol"; import {GeneralMiddleware} from "../contracts/base/GeneralMiddleware.sol"; import "../contracts/utils/DummyLightClient.sol"; -import {TestUtilsTest} from "./TestUtils.t.sol"; +import {TestUtilsTest} from "./utils/TestUtils.t.sol"; struct ChannelSetting { ChannelOrder ordering; diff --git a/test/universal.channel.t.sol b/test/universal.channel.t.sol index f9384302..bfdd2129 100644 --- a/test/universal.channel.t.sol +++ b/test/universal.channel.t.sol @@ -2,14 +2,15 @@ pragma solidity ^0.8.13; import "../contracts/libs/Ibc.sol"; +import "../contracts/libs/IbcUtils.sol"; import {Dispatcher} from "../contracts/core/Dispatcher.sol"; import {IbcEventsEmitter} from "../contracts/interfaces/IbcDispatcher.sol"; import {IbcReceiver} from "../contracts/interfaces/IbcReceiver.sol"; import "../contracts/core/UniversalChannelHandler.sol"; import {Mars} from "../contracts/examples/Mars.sol"; import "../contracts/interfaces/IbcMiddleware.sol"; -import "../contracts/core/OpLightClient.sol"; -import "./Dispatcher.base.t.sol"; +import "../contracts/core/OptimisticLightClient.sol"; +import "./utils/Dispatcher.base.t.sol"; import "./VirtualChain.sol"; contract UniversalChannelTest is Base { diff --git a/test/upgradeableProxy/Dispatcher.upgrade.t.sol b/test/upgradeableProxy/Dispatcher.upgrade.t.sol index 54cc6c4c..30577517 100644 --- a/test/upgradeableProxy/Dispatcher.upgrade.t.sol +++ b/test/upgradeableProxy/Dispatcher.upgrade.t.sol @@ -2,27 +2,19 @@ pragma solidity ^0.8.13; import "forge-std/console2.sol"; -import {DispatcherUpdateClientTestSuite} from "../Dispatcher.client.t.sol"; -import {DispatcherIbcWithRealProofsSuite} from "../Dispatcher.proof.t.sol"; +import {DispatcherUpdateClientTestSuite} from "../Dispatcher/Dispatcher.client.t.sol"; +import {DispatcherIbcWithRealProofsSuite} from "../Dispatcher/Dispatcher.proof.t.sol"; import {Mars} from "../../contracts/examples/Mars.sol"; -import "../../contracts/core/OpLightClient.sol"; -import {ChannelHandshakeTestSuite, ChannelHandshakeTest, ChannelHandshakeUtils} from "../Dispatcher.t.sol"; -import {LocalEnd} from "../Dispatcher.base.t.sol"; -import {Base, ChannelHandshakeSetting} from "../Dispatcher.base.t.sol"; -import { - ChannelEnd, - ChannelOrder, - IbcEndpoint, - IbcPacket, - AckPacket, - Ibc, - IbcUtils, - Height -} from "../../contracts/libs/Ibc.sol"; +import "../../contracts/core/OptimisticLightClient.sol"; +import {ChannelHandshakeTestSuite, ChannelHandshakeTest, ChannelHandshakeUtils} from "../Dispatcher/Dispatcher.t.sol"; +import {LocalEnd} from "../utils/Dispatcher.base.t.sol"; +import {Base, ChannelHandshakeSetting} from "../utils/Dispatcher.base.t.sol"; +import {ChannelEnd, ChannelOrder, IbcEndpoint, IbcPacket, AckPacket, Ibc, Height} from "../../contracts/libs/Ibc.sol"; +import {IbcUtils} from "../../contracts/libs/IbcUtils.sol"; import {IbcReceiver} from "../../contracts/interfaces/IbcReceiver.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import {OptimisticLightClient} from "../../contracts/core/OpLightClient.sol"; -import {ProofVerifier} from "../../contracts/core/OpProofVerifier.sol"; +import {OptimisticLightClient} from "../../contracts/core/OptimisticLightClient.sol"; +import {IProofVerifier} from "../../contracts/core/OptimisticProofVerifier.sol"; import {DummyLightClient} from "../../contracts/utils/DummyLightClient.sol"; import {IDispatcher} from "../../contracts/interfaces/IDispatcher.sol"; diff --git a/test/upgradeableProxy/DispatcherUUPS.accessControl.t.sol b/test/upgradeableProxy/DispatcherUUPS.accessControl.t.sol index d5fbe6a0..ba7736ac 100644 --- a/test/upgradeableProxy/DispatcherUUPS.accessControl.t.sol +++ b/test/upgradeableProxy/DispatcherUUPS.accessControl.t.sol @@ -6,8 +6,8 @@ import {Dispatcher} from "../../contracts/core/Dispatcher.sol"; import {IbcEventsEmitter} from "../../contracts/interfaces/IbcDispatcher.sol"; import {IbcReceiver} from "../../contracts/interfaces/IbcReceiver.sol"; import {Mars} from "../../contracts/examples/Mars.sol"; -import "../../contracts/core/OpLightClient.sol"; -import "../Dispatcher.base.t.sol"; +import "../../contracts/core/OptimisticLightClient.sol"; +import "../utils/Dispatcher.base.t.sol"; import {DispatcherV2} from "./upgrades/DispatcherV2.sol"; import {DispatcherV2Initializable} from "./upgrades/DispatcherV2Initializable.sol"; import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; @@ -15,7 +15,7 @@ import {DummyLightClient} from "../../contracts/utils/DummyLightClient.sol"; contract DispatcherUUPSAccessControl is Base { string public portPrefix2 = "IIpolyibc.eth."; - LightClient lightClient2 = new DummyLightClient(); + ILightClient lightClient2 = new DummyLightClient(); address public notOwner = vm.addr(1); DispatcherV2 dispatcherImplementation2; DispatcherV2Initializable dispatcherImplementation3; diff --git a/test/upgradeableProxy/upgrades/DispatcherV2.sol b/test/upgradeableProxy/upgrades/DispatcherV2.sol index f1678284..abc93168 100644 --- a/test/upgradeableProxy/upgrades/DispatcherV2.sol +++ b/test/upgradeableProxy/upgrades/DispatcherV2.sol @@ -7,8 +7,8 @@ import {UUPSUpgradeable} from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeab import {OwnableUpgradeable} from "@openzeppelin-upgradeable/contracts/access/OwnableUpgradeable.sol"; import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import {IbcChannelReceiver, IbcPacketReceiver} from "../../../contracts/interfaces/IbcReceiver.sol"; -import {L1Header, OpL2StateProof, Ics23Proof} from "../../../contracts/interfaces/ProofVerifier.sol"; -import {LightClient} from "../../../contracts/interfaces/LightClient.sol"; +import {L1Header, OpL2StateProof, Ics23Proof} from "../../../contracts/interfaces/IProofVerifier.sol"; +import {ILightClient} from "../../../contracts/interfaces/ILightClient.sol"; import {IDispatcher} from "../../../contracts/interfaces/IDispatcher.sol"; import {Dispatcher} from "../../../contracts/core/Dispatcher.sol"; import { @@ -19,9 +19,9 @@ import { ChannelState, AckPacket, IBCErrors, - IbcUtils, Ibc } from "../../../contracts/libs/Ibc.sol"; +import {IbcUtils} from "../../../contracts/libs/IbcUtils.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; /** diff --git a/test/upgradeableProxy/upgrades/DispatcherV2Initializable.sol b/test/upgradeableProxy/upgrades/DispatcherV2Initializable.sol index 39de1a73..d4977ba8 100644 --- a/test/upgradeableProxy/upgrades/DispatcherV2Initializable.sol +++ b/test/upgradeableProxy/upgrades/DispatcherV2Initializable.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.9; import {DispatcherV2} from "./DispatcherV2.sol"; -import {LightClient} from "../../../contracts/interfaces/LightClient.sol"; +import {ILightClient} from "../../../contracts/interfaces/ILightClient.sol"; import {IBCErrors} from "../../../contracts/libs/Ibc.sol"; /** * @title Dispatcher diff --git a/test/Dispatcher.base.t.sol b/test/utils/Dispatcher.base.t.sol similarity index 91% rename from test/Dispatcher.base.t.sol rename to test/utils/Dispatcher.base.t.sol index c978f585..00f162c2 100644 --- a/test/Dispatcher.base.t.sol +++ b/test/utils/Dispatcher.base.t.sol @@ -3,14 +3,15 @@ pragma solidity ^0.8.13; import "forge-std/Test.sol"; import {ProofBase} from "./Proof.base.t.sol"; -import "../contracts/libs/Ibc.sol"; -import {Dispatcher} from "../contracts/core/Dispatcher.sol"; -import {IDispatcher} from "../contracts/interfaces/IDispatcher.sol"; -import {IbcEventsEmitter} from "../contracts/interfaces/IbcDispatcher.sol"; -import {IbcChannelReceiver} from "../contracts/interfaces/IbcReceiver.sol"; -import "../contracts/core/OpLightClient.sol"; -import "../contracts/utils/DummyLightClient.sol"; -import "../contracts/core/OpProofVerifier.sol"; +import "../../contracts/libs/Ibc.sol"; +import {Dispatcher} from "../../contracts/core/Dispatcher.sol"; +import {IDispatcher} from "../../contracts/interfaces/IDispatcher.sol"; +import {IbcEventsEmitter} from "../../contracts/interfaces/IbcDispatcher.sol"; +import {IbcChannelReceiver} from "../../contracts/interfaces/IbcReceiver.sol"; +import "../../contracts/examples/Mars.sol"; +import "../../contracts/core/OptimisticLightClient.sol"; +import "../../contracts/utils/DummyLightClient.sol"; +import "../../contracts/core/OptimisticProofVerifier.sol"; import {TestUtilsTest} from "./TestUtils.t.sol"; struct LocalEnd { @@ -39,8 +40,8 @@ contract Base is IbcEventsEmitter, ProofBase, TestUtilsTest { Height ZERO_HEIGHT = Height(0, 0); uint64 maxTimeout = UINT64_MAX; - LightClient opLightClient = new OptimisticLightClient(1800, opProofVerifier, l1BlockProvider); - LightClient dummyLightClient = new DummyLightClient(); + ILightClient opLightClient = new OptimisticLightClient(1800, opProofVerifier, l1BlockProvider); + ILightClient dummyLightClient = new DummyLightClient(); IDispatcher public dispatcherProxy; Dispatcher public dispatcherImplementation; @@ -206,7 +207,7 @@ contract Base is IbcEventsEmitter, ProofBase, TestUtilsTest { } // Store connection in channelid to connection mapping using store - function _getConnectiontoClientIdMapping(string memory connection) internal returns (uint256 clientId) { + function _getConnectiontoClientIdMapping(string memory connection) internal view returns (uint256 clientId) { bytes32 clientIdSlot = keccak256(abi.encode(connection, CONNECTION_TO_CLIENT_ID_STARTING_SLOT)); clientId = uint256(vm.load(address(dispatcherProxy), clientIdSlot)); } diff --git a/test/Proof.base.t.sol b/test/utils/Proof.base.t.sol similarity index 92% rename from test/Proof.base.t.sol rename to test/utils/Proof.base.t.sol index c5cf3fa3..22752bcf 100644 --- a/test/Proof.base.t.sol +++ b/test/utils/Proof.base.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.0; import "forge-std/Test.sol"; -import "../contracts/libs/Ibc.sol"; -import "../contracts/core/OpProofVerifier.sol"; +import "../../contracts/libs/Ibc.sol"; +import "../../contracts/core/OptimisticProofVerifier.sol"; import {L1Block} from "optimism/L2/L1Block.sol"; contract ProofBase is Test { @@ -25,7 +25,8 @@ contract ProofBase is Test { bytes32 apphash; L1Block l1BlockProvider = new L1Block(); - OpProofVerifier opProofVerifier = new OpProofVerifier(address(0x5cA3f8919DF7d82Bf51a672B7D73Ec13a2705dDb)); + OptimisticProofVerifier opProofVerifier = + new OptimisticProofVerifier(address(0x5cA3f8919DF7d82Bf51a672B7D73Ec13a2705dDb)); constructor() { // generate the channel_proof.hex file with the following command: diff --git a/test/TestUtils.t.sol b/test/utils/TestUtils.t.sol similarity index 82% rename from test/TestUtils.t.sol rename to test/utils/TestUtils.t.sol index 48705544..eba10445 100644 --- a/test/TestUtils.t.sol +++ b/test/utils/TestUtils.t.sol @@ -1,10 +1,9 @@ -import {IDispatcher} from "../contracts/interfaces/IDispatcher.sol"; -import {IUniversalChannelHandler} from "../contracts/interfaces/IUniversalChannelHandler.sol"; -import {LightClient} from "../contracts/interfaces/LightClient.sol"; +import {IDispatcher} from "../../contracts/interfaces/IDispatcher.sol"; +import {IUniversalChannelHandler} from "../../contracts/interfaces/IUniversalChannelHandler.sol"; +import {UniversalChannelHandler} from "../../contracts/core/UniversalChannelHandler.sol"; +import {ILightClient} from "../../contracts/interfaces/ILightClient.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {Dispatcher} from "../contracts/core/Dispatcher.sol"; -import {UniversalChannelHandler} from "../contracts/core/UniversalChannelHandler.sol"; - +import {Dispatcher} from "../../contracts/core/Dispatcher.sol"; import {Test} from "forge-std/Test.sol"; import {Vm} from "forge-std/Vm.sol"; diff --git a/test/extract.ts b/test/utils/extract.ts similarity index 100% rename from test/extract.ts rename to test/utils/extract.ts diff --git a/test/query-contract.sh b/test/utils/query-contract.sh similarity index 100% rename from test/query-contract.sh rename to test/utils/query-contract.sh