diff --git a/.gitignore b/.gitignore index 9a1fa0ec..d47d7a74 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,7 @@ typechain-types cache artifacts +# Coverage Report +report/ +lcov.info + diff --git a/contracts/core/Dispatcher.sol b/contracts/core/Dispatcher.sol index d0302891..6b0fb403 100644 --- a/contracts/core/Dispatcher.sol +++ b/contracts/core/Dispatcher.sol @@ -11,6 +11,7 @@ import {IbcChannelReceiver, IbcPacketReceiver} from "../interfaces/IbcReceiver.s import {L1Header, OpL2StateProof, Ics23Proof} from "../interfaces/ProofVerifier.sol"; import {LightClient} from "../interfaces/LightClient.sol"; import {IDispatcher} from "../interfaces/IDispatcher.sol"; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import { Channel, CounterParty, @@ -22,7 +23,6 @@ import { IbcUtils, Ibc } from "../libs/Ibc.sol"; -import {Address} from "@openzeppelin/contracts/utils/Address.sol"; /** * @title Dispatcher @@ -36,6 +36,7 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { // fields // // IBC_PortID = portPrefix + address (hex string without 0x prefix, case insensitive) + string public portPrefix; uint32 public portPrefixLen; @@ -53,7 +54,7 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { // keep track of outbound ack packets to prevent replay attack mapping(address => mapping(bytes32 => mapping(uint64 => bool))) private _ackPacketCommitment; - LightClient _lightClient; + LightClient public lightClient; // // methods @@ -62,11 +63,11 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { _disableInitializers(); } - function initialize(string memory initPortPrefix, LightClient lightClient) public initializer { + function initialize(string memory initPortPrefix, LightClient _lightClient) public virtual initializer { __Ownable_init(); portPrefix = initPortPrefix; portPrefixLen = uint32(bytes(initPortPrefix).length); - _lightClient = lightClient; + lightClient = _lightClient; } // @@ -87,7 +88,7 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { uint256 height, uint256 appHash ) external returns (uint256 fraudProofEndTime, bool ended) { - return _lightClient.addOpConsensusState(l1header, proof, height, appHash); + return lightClient.addOpConsensusState(l1header, proof, height, appHash); } /** @@ -138,10 +139,17 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { revert IBCErrors.invalidCounterPartyPortId(); } - _lightClient.verifyMembership( + lightClient.verifyMembership( proof, Ibc.channelProofKey(local.portId, local.channelId), - Ibc.channelProofValue(ChannelState.TRY_PENDING, ordering, local.version, connectionHops, counterparty) + Ibc.channelProofValue( + ChannelState.TRY_PENDING, + ordering, + local.version, + connectionHops, + counterparty.portId, + counterparty.channelId + ) ); (bool success, bytes memory data) = _callIfContract( @@ -176,10 +184,17 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { CounterParty calldata counterparty, Ics23Proof calldata proof ) external { - _lightClient.verifyMembership( + lightClient.verifyMembership( proof, Ibc.channelProofKey(local.portId, local.channelId), - Ibc.channelProofValue(ChannelState.ACK_PENDING, ordering, local.version, connectionHops, counterparty) + Ibc.channelProofValue( + ChannelState.ACK_PENDING, + ordering, + local.version, + connectionHops, + counterparty.portId, + counterparty.channelId + ) ); (bool success, bytes memory data) = _callIfContract( @@ -209,10 +224,17 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { CounterParty calldata counterparty, Ics23Proof calldata proof ) external { - _lightClient.verifyMembership( + lightClient.verifyMembership( proof, Ibc.channelProofKey(local.portId, local.channelId), - Ibc.channelProofValue(ChannelState.CONFIRM_PENDING, ordering, local.version, connectionHops, counterparty) + Ibc.channelProofValue( + ChannelState.CONFIRM_PENDING, + ordering, + local.version, + connectionHops, + counterparty.portId, + counterparty.channelId + ) ); (bool success, bytes memory data) = _callIfContract( @@ -233,54 +255,72 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { * @notice Close the specified IBC channel by channel ID * Must be called by the channel owner, ie. _portChannelMap[msg.sender][channelId] must exist */ - function closeIbcChannel(bytes32 channelId) external { + function channelCloseInit(bytes32 channelId) external { Channel memory channel = _portChannelMap[msg.sender][channelId]; if (channel.counterpartyChannelId == bytes32(0)) { revert IBCErrors.channelNotOwnedBySender(); } - (bool success, bytes memory data) = _callIfContract( msg.sender, abi.encodeWithSelector( - IbcChannelReceiver.onCloseIbcChannel.selector, + IbcChannelReceiver.onChanCloseInit.selector, channelId, channel.counterpartyPortId, channel.counterpartyChannelId ) ); + + delete _portChannelMap[msg.sender][channelId]; if (success) { - emit CloseIbcChannel(msg.sender, channelId); + emit ChannelCloseInit(msg.sender, channelId); } else { - emit CloseIbcChannelError(address(msg.sender), data); + emit ChannelCloseInitError(address(msg.sender), data); } } /** * This func is called by a 'relayer' after the IBC/VIBC hub chain has processed ChanCloseConfirm event. - * The dApp's onCloseIbcChannel callback is invoked. + * The dApp's onChanCloseConfirm callback is invoked. * dApp should throw an error if the channel should not be closed. */ - // FIXME this is commented out to make the contract size smaller. We need to optimise for size - // function onCloseIbcChannel(address portAddress, bytes32 channelId, Ics23Proof calldata proof) external { - // // verify VIBC/IBC hub chain has processed ChanCloseConfirm event - // _lightClient.verifyMembership( - // proof, - // bytes('channel/path/to/be/added/here'), - // bytes('expected channel bytes constructed from params. Channel.State = {Closed(_Pending?)}') - // ); - // - // // ensure port owns channel - // Channel memory channel = _portChannelMap[portAddress][channelId]; - // if (channel.counterpartyChannelId == bytes32(0)) { - // revert channelNotOwnedByPortAddress(); - // } - // - // // confirm with dApp by calling its callback - // IbcChannelReceiver reciever = IbcChannelReceiver(portAddress); - // reciever.onCloseIbcChannel(channelId, channel.counterpartyPortId, channel.counterpartyChannelId); - // delete _portChannelMap[portAddress][channelId]; - // emit CloseIbcChannel(portAddress, channelId); - // } + function channelCloseConfirm(address portAddress, bytes32 channelId, Ics23Proof calldata proof) external { + // ensure port owns channel + Channel memory channel = _portChannelMap[portAddress][channelId]; + if (channel.counterpartyChannelId == bytes32(0)) { + revert IBCErrors.channelNotOwnedByPortAddress(); + } + + // verify VIBC/IBC hub chain has processed ChanCloseConfirm event + lightClient.verifyMembership( + proof, + Ibc.channelProofKeyMemory(channel.portId, channelId), + Ibc.channelProofValueMemory( + ChannelState.CLOSE_CONFIRM_PENDING, + channel.ordering, + channel.version, + channel.connectionHops, + channel.counterpartyPortId, + channel.counterpartyChannelId + ) + ); + + (bool success, bytes memory data) = _callIfContract( + portAddress, + abi.encodeWithSelector( + IbcChannelReceiver.onChanCloseConfirm.selector, + channelId, + channel.counterpartyPortId, + channel.counterpartyChannelId + ) + ); + + delete _portChannelMap[portAddress][channelId]; + if (success) { + emit ChannelCloseConfirm(portAddress, channelId); + } else { + emit ChannelCloseConfirmError(address(portAddress), data); + } + } // // IBC Packet methods @@ -291,16 +331,16 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { * @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. + * 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 nanoseconds after which the packet times out if it has not been + * @param timeoutTimestamp The timestamp, in seconds after the unix epoch, after which the packet times out if it + * has not been * received. */ function sendPacket(bytes32 channelId, bytes calldata packet, uint64 timeoutTimestamp) external { // ensure port owns channel - Channel memory channel = _portChannelMap[msg.sender][channelId]; - if (channel.counterpartyChannelId == bytes32(0)) { + if (_portChannelMap[msg.sender][channelId].counterpartyChannelId == bytes32(0)) { revert IBCErrors.channelNotOwnedBySender(); } @@ -330,7 +370,7 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { } // prove ack packet is on Polymer chain - _lightClient.verifyMembership(proof, Ibc.ackProofKey(packet), abi.encode(Ibc.ackProofValue(ack))); + lightClient.verifyMembership(proof, Ibc.ackProofKey(packet), abi.encode(Ibc.ackProofValue(ack))); // verify packet has been committed and not yet ack'ed or timed out bool hasCommitment = _sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; if (!hasCommitment) { @@ -338,14 +378,13 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { } // enforce ack'ed packet sequences always increment by 1 for ordered channels - Channel memory channel = _portChannelMap[address(receiver)][packet.src.channelId]; (bool success, bytes memory data) = _callIfContract( address(receiver), abi.encodeWithSelector(IbcPacketReceiver.onAcknowledgementPacket.selector, packet, Ibc.parseAckData(ack)) ); if (success) { - if (channel.ordering == ChannelOrder.ORDERED) { + if (_portChannelMap[address(receiver)][packet.src.channelId].ordering == ChannelOrder.ORDERED) { if (packet.sequence != _nextSequenceAck[address(receiver)][packet.src.channelId]) { revert IBCErrors.unexpectedPacketSequence(); } @@ -379,7 +418,7 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { // prove absence of packet receipt on Polymer chain // TODO: add non membership support - _lightClient.verifyNonMembership(proof, "packet/receipt/path"); + lightClient.verifyNonMembership(proof, Ibc.packetCommitmentProofKey(packet)); // verify packet has been committed and not yet ack'ed or timed out bool hasCommitment = _sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; @@ -415,7 +454,7 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { if (!portIdAddressMatch(address(receiver), packet.dest.portId)) { revert IBCErrors.receiverNotIntendedPacketDestination(); } - _lightClient.verifyMembership( + lightClient.verifyMembership( proof, Ibc.packetCommitmentProofKey(packet), abi.encode(Ibc.packetCommitmentProofValue(packet)) ); @@ -428,8 +467,7 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { _recvPacketReceipt[address(receiver)][packet.dest.channelId][packet.sequence] = true; // enforce recv'ed packet sequences always increment by 1 for ordered channels - Channel memory channel = _portChannelMap[address(receiver)][packet.dest.channelId]; - if (channel.ordering == ChannelOrder.ORDERED) { + if (_portChannelMap[address(receiver)][packet.dest.channelId].ordering == ChannelOrder.ORDERED) { if (packet.sequence != _nextSequenceRecv[address(receiver)][packet.dest.channelId]) { revert IBCErrors.unexpectedPacketSequence(); } @@ -441,7 +479,7 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { emit RecvPacket(address(receiver), packet.dest.channelId, packet.sequence); // If pkt is already timed out, then return early so dApps won't receive it. - if (_isPacketTimeout(packet)) { + if (_isPacketTimeout(packet.timeoutTimestamp, packet.timeoutHeight.revision_height)) { address writerPortAddress = address(receiver); emit WriteTimeoutPacket( writerPortAddress, packet.dest.channelId, packet.sequence, packet.timeoutHeight, packet.timeoutTimestamp @@ -450,10 +488,9 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { } // Not timeout yet, then do normal handling - IbcPacket memory pkt = packet; AckPacket memory ack; (bool success, bytes memory data) = - _callIfContract(address(receiver), abi.encodeWithSelector(IbcPacketReceiver.onRecvPacket.selector, pkt)); + _callIfContract(address(receiver), abi.encodeWithSelector(IbcPacketReceiver.onRecvPacket.selector, packet)); if (success) { (ack) = abi.decode(data, (AckPacket)); } else { @@ -503,7 +540,7 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { } // verify packet has timed out; zero-value in packet.timeout means no timeout set - if (!_isPacketTimeout(packet)) { + if (!_isPacketTimeout(packet.timeoutTimestamp, packet.timeoutHeight.revision_height)) { revert IBCErrors.packetNotTimedOut(); } @@ -530,7 +567,7 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { view returns (uint256 appHash, uint256 fraudProofEndTime, bool ended) { - return _lightClient.getState(height); + return lightClient.getState(height); } // verify an EVM address matches an IBC portId. @@ -539,8 +576,7 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { if (keccak256(abi.encodePacked(portPrefix)) != keccak256(abi.encodePacked(portId[0:portPrefixLen]))) { return false; } - string memory portSuffix = portId[portPrefixLen:]; - isMatch = Ibc._hexStrToAddress(portSuffix) == addr; + isMatch = Ibc._hexStrToAddress(portId[portPrefixLen:]) == addr; } // Prerequisite: must verify sender is authorized to send packet on the channel @@ -572,6 +608,7 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { // TODO: The call to `Channel` constructor MUST be move to `openIbcChannel` phase // Then `connectIbcChannel` phase can use the `version` as part of `require` condition. _portChannelMap[address(portAddress)][local.channelId] = Channel( + local.portId, counterparty.version, // TODO: this should be self version instead of counterparty version ordering, feeEnabled, @@ -603,12 +640,12 @@ contract Dispatcher is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { 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 - function _isPacketTimeout(IbcPacket calldata packet) internal view returns (bool isTimeOut) { + // timestamp, in seconds after the unix epoch. + function _isPacketTimeout(uint64 timeoutTimestamp, uint64 revisionHeight) internal view returns (bool isTimeOut) { return ( - isTimeOut = (packet.timeoutTimestamp != 0 && block.timestamp >= packet.timeoutTimestamp) + isTimeOut = (timeoutTimestamp != 0 && block.timestamp >= timeoutTimestamp) // TODO: check timeoutHeight.revision_number? - || (packet.timeoutHeight.revision_height != 0 && block.number >= packet.timeoutHeight.revision_height) + || (revisionHeight != 0 && block.number >= revisionHeight) ); } } diff --git a/contracts/core/UniversalChannelHandler.sol b/contracts/core/UniversalChannelHandler.sol index 55ff58db..33554dd7 100644 --- a/contracts/core/UniversalChannelHandler.sol +++ b/contracts/core/UniversalChannelHandler.sol @@ -15,7 +15,6 @@ import {IbcReceiver, IbcReceiverBase} from "../interfaces/IbcReceiver.sol"; import {ChannelOrder, CounterParty, IbcPacket, AckPacket, UniversalPacket, IbcUtils} from "../libs/Ibc.sol"; contract UniversalChannelHandler is IbcReceiverBase, IbcUniversalChannelMW { - bytes32[] public connectedChannels; string public constant VERSION = "1.0"; uint256 public constant MW_ID = 1; @@ -32,21 +31,12 @@ contract UniversalChannelHandler is IbcReceiverBase, IbcUniversalChannelMW { */ function closeChannel(bytes32 channelId) external onlyOwner { - dispatcher.closeIbcChannel(channelId); + dispatcher.channelCloseInit(channelId); } - function onCloseIbcChannel(bytes32 channelId, string calldata, bytes32) external onlyIbcDispatcher { - // logic to determin if the channel should be closed - bool channelFound = false; - for (uint256 i = 0; i < connectedChannels.length; i++) { - if (connectedChannels[i] == channelId) { - delete connectedChannels[i]; - channelFound = true; - break; - } - } - if (!channelFound) revert ChannelNotFound(); - } + function onChanCloseInit(bytes32 channelId, string calldata, bytes32) external onlyIbcDispatcher {} + + function onChanCloseConfirm(bytes32 channelId, string calldata, bytes32) external onlyIbcDispatcher {} function sendUniversalPacket( bytes32 channelId, @@ -172,7 +162,6 @@ contract UniversalChannelHandler is IbcReceiverBase, IbcUniversalChannelMW { if (keccak256(abi.encodePacked(version)) != keccak256(abi.encodePacked(VERSION))) { revert UnsupportedVersion(); } - connectedChannels.push(channelId); } function _openChannel(string calldata version) private pure returns (string memory selectedVersion) { diff --git a/contracts/examples/Mars.sol b/contracts/examples/Mars.sol index 2a0b50c1..949aa82c 100644 --- a/contracts/examples/Mars.sol +++ b/contracts/examples/Mars.sol @@ -39,8 +39,14 @@ contract Mars is IbcReceiverBase, IbcReceiver { timeoutPackets.push(packet); } - function onCloseIbcChannel(bytes32 channelId, string calldata, bytes32) external virtual onlyIbcDispatcher { - // logic to determin if the channel should be closed + function onChanCloseInit(bytes32 channelId, string calldata counterpartyPortId, bytes32 counterpartyChannelId) + external + virtual + onlyIbcDispatcher + {} + + function onChanCloseConfirm(bytes32 channelId, string calldata, bytes32) external virtual onlyIbcDispatcher { + // logic to determine if the channel should be closed bool channelFound = false; for (uint256 i = 0; i < connectedChannels.length; i++) { if (connectedChannels[i] == channelId) { @@ -54,10 +60,10 @@ contract Mars is IbcReceiverBase, IbcReceiver { /** * This func triggers channel closure from the dApp. - * Func args can be arbitary, as long as dispatcher.closeIbcChannel is invoked propperly. + * Func args can be arbitary, as long as dispatcher.channelCloseInit is invoked propperly. */ function triggerChannelClose(bytes32 channelId) external onlyOwner { - dispatcher.closeIbcChannel(channelId); + dispatcher.channelCloseInit(channelId); } /** @@ -147,15 +153,26 @@ contract RevertingStringMars is Mars { } // solhint-disable-next-line - function onCloseIbcChannel(bytes32, string calldata, bytes32) external view override onlyIbcDispatcher { + function onAcknowledgementPacket(IbcPacket calldata, AckPacket calldata) external view override onlyIbcDispatcher { // solhint-disable-next-line - require(false, "close ibc channel is reverting"); + require(false, "acknowledgement packet is reverting"); } +} +/// Used to only test reverts for close channel (seperate from RevertingStringMars to avoid reverts on setting up the +/// channel) +contract RevertingStringCloseChannelMars is Mars { + constructor(IbcDispatcher _dispatcher) Mars(_dispatcher) {} // solhint-disable-next-line - function onAcknowledgementPacket(IbcPacket calldata, AckPacket calldata) external view override onlyIbcDispatcher { + + function onChanCloseInit(bytes32, string calldata, bytes32) external view override onlyIbcDispatcher { // solhint-disable-next-line - require(false, "acknowledgement packet is reverting"); + require(false, "close ibc channel is reverting"); + } + + function onChanCloseConfirm(bytes32, string calldata, bytes32) external view override onlyIbcDispatcher { + // solhint-disable-next-line + require(false, "close ibc channel is reverting"); } } diff --git a/contracts/interfaces/IDispatcher.sol b/contracts/interfaces/IDispatcher.sol index f89666f2..28cc261b 100644 --- a/contracts/interfaces/IDispatcher.sol +++ b/contracts/interfaces/IDispatcher.sol @@ -85,9 +85,8 @@ interface IDispatcher is IbcDispatcher, IbcEventsEmitter { Ics23Proof calldata proof ) external; - function closeIbcChannel(bytes32 channelId) external; - - function sendPacket(bytes32 channelId, bytes calldata packet, uint64 timeoutTimestamp) external; + function channelCloseConfirm(address portAddress, bytes32 channelId, Ics23Proof calldata proof) external; + function channelCloseInit(bytes32 channelId) external; function acknowledgement( IbcPacketReceiver receiver, diff --git a/contracts/interfaces/IbcDispatcher.sol b/contracts/interfaces/IbcDispatcher.sol index 122e301f..bdcb1146 100644 --- a/contracts/interfaces/IbcDispatcher.sol +++ b/contracts/interfaces/IbcDispatcher.sol @@ -32,7 +32,7 @@ interface IbcDispatcher is IbcPacketSender { string calldata counterpartyPortId ) external; - function closeIbcChannel(bytes32 channelId) external; + function channelCloseInit(bytes32 channelId) external; function portPrefix() external view returns (string memory portPrefix); } @@ -72,9 +72,11 @@ interface IbcEventsEmitter { event ChannelOpenConfirm(address indexed receiver, bytes32 channelId); event ChannelOpenConfirmError(address indexed receiver, bytes error); - event CloseIbcChannel(address indexed portAddress, bytes32 indexed channelId); + event ChannelCloseInit(address indexed portAddress, bytes32 indexed channelId); + event ChannelCloseConfirm(address indexed portAddress, bytes32 indexed channelId); - event CloseIbcChannelError(address indexed receiver, bytes error); + event ChannelCloseInitError(address indexed receiver, bytes error); + event ChannelCloseConfirmError(address indexed receiver, bytes error); event AcknowledgementError(address indexed receiver, bytes error); event TimeoutError(address indexed receiver, bytes error); diff --git a/contracts/interfaces/IbcReceiver.sol b/contracts/interfaces/IbcReceiver.sol index 1e458aef..02b0b724 100644 --- a/contracts/interfaces/IbcReceiver.sol +++ b/contracts/interfaces/IbcReceiver.sol @@ -20,7 +20,10 @@ interface IbcChannelReceiver { function onChanOpenConfirm(bytes32 channelId, string calldata counterpartyVersion) external; - function onCloseIbcChannel(bytes32 channelId, string calldata counterpartyPortId, bytes32 counterpartyChannelId) + function onChanCloseInit(bytes32 channelId, string calldata counterpartyPortId, bytes32 counterpartyChannelId) + external; + + function onChanCloseConfirm(bytes32 channelId, string calldata counterpartyPortId, bytes32 counterpartyChannelId) external; } diff --git a/contracts/libs/Ibc.sol b/contracts/libs/Ibc.sol index f04e5b34..bc055418 100644 --- a/contracts/libs/Ibc.sol +++ b/contracts/libs/Ibc.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.9; 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"; /** * Ibc.sol @@ -23,7 +24,7 @@ struct IbcPacket { bytes data; /// block height after which the packet times out Height timeoutHeight; - /// block timestamp (in nanoseconds) after which the packet times out + /// block timestamp (in seconds after the unix epoch) after which the packet times out uint64 timeoutTimestamp; } @@ -106,6 +107,7 @@ enum ChannelState { } struct Channel { + string portId; string version; ChannelOrder ordering; bool feeEnabled; @@ -311,59 +313,16 @@ library Ibc { } } - // 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(CounterParty calldata counterparty) external pure returns (bool open) { - if (counterparty.channelId == bytes32(0) && bytes(counterparty.version).length == 0) { - open = 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 - open = true; - } else { - revert IBCErrors.invalidCounterParty(); - } - } - - function toStr(bytes32 b) public pure returns (string memory outStr) { - uint8 i = 0; - while (i < 32 && b[i] != 0) { - i++; - } - bytes memory bytesArray = new bytes(i); - for (uint8 j = 0; j < i; j++) { - bytesArray[j] = b[j]; - } - outStr = string(bytesArray); - } - - function toStr(uint256 _number) public pure returns (string memory outStr) { - if (_number == 0) { - return "0"; - } - - uint256 length; - uint256 number = _number; - - // Determine the length of the string - while (number != 0) { - length++; - number /= 10; - } - - bytes memory buffer = new bytes(length); - - // Convert each digit to its ASCII representation - for (uint256 i = length; i > 0; i--) { - buffer[i - 1] = bytes1(uint8(48 + (_number % 10))); - _number /= 10; - } - - outStr = string(buffer); + // https://github.com/open-ibc/ibcx-go/blob/ef80dd6784fd/modules/core/24-host/keys.go#L135 + function channelProofKey(string calldata portId, bytes32 channelId) external pure returns (bytes memory proofKey) { + proofKey = abi.encodePacked("channelEnds/ports/", portId, "/channels/", toStr(channelId)); } - // https://github.com/open-ibc/ibcx-go/blob/ef80dd6784fd/modules/core/24-host/keys.go#L135 - function channelProofKey(string calldata portId, bytes32 channelId) public pure returns (bytes memory proofKey) { + function channelProofKeyMemory(string memory portId, bytes32 channelId) + external + pure + returns (bytes memory proofKey) + { proofKey = abi.encodePacked("channelEnds/ports/", portId, "/channels/", toStr(channelId)); } @@ -374,13 +333,33 @@ library Ibc { ChannelOrder ordering, string calldata version, string[] calldata connectionHops, - CounterParty calldata counterparty - ) public pure returns (bytes memory proofValue) { + string calldata counterpartyPortId, + bytes32 counterpartyChannelId + ) external pure returns (bytes memory proofValue) { + proofValue = ProtoChannel.encode( + ProtoChannel.Data( + int32(uint32(state)), + int32(uint32(ordering)), + ProtoCounterparty.Data(counterpartyPortId, toStr(counterpartyChannelId)), + connectionHops, + version + ) + ); + } + + function channelProofValueMemory( + ChannelState state, + ChannelOrder ordering, + string memory version, + string[] memory connectionHops, + string memory counterpartyPortId, + bytes32 counterpartyChannelId + ) external pure returns (bytes memory proofValue) { proofValue = ProtoChannel.encode( ProtoChannel.Data( int32(uint32(state)), int32(uint32(ordering)), - ProtoCounterparty.Data(counterparty.portId, toStr(counterparty.channelId)), + ProtoCounterparty.Data(counterpartyPortId, toStr(counterpartyChannelId)), connectionHops, version ) @@ -388,7 +367,7 @@ library Ibc { } // https://github.com/open-ibc/ibcx-go/blob/ef80dd6784fd/modules/core/24-host/keys.go#L185 - function packetCommitmentProofKey(IbcPacket calldata packet) public pure returns (bytes memory proofKey) { + function packetCommitmentProofKey(IbcPacket calldata packet) external pure returns (bytes memory proofKey) { proofKey = abi.encodePacked( "commitments/ports/", packet.src.portId, @@ -400,7 +379,7 @@ library Ibc { } // https://github.com/open-ibc/ibcx-go/blob/ef80dd6784fd/modules/core/04-channel/types/packet.go#L19 - function packetCommitmentProofValue(IbcPacket calldata packet) public pure returns (bytes32 proofValue) { + function packetCommitmentProofValue(IbcPacket calldata packet) external pure returns (bytes32 proofValue) { proofValue = sha256( abi.encodePacked( packet.timeoutTimestamp, @@ -412,7 +391,7 @@ library Ibc { } // https://github.com/open-ibc/ibcx-go/blob/ef80dd6784fd/modules/core/24-host/keys.go#L201 - function ackProofKey(IbcPacket calldata packet) public pure returns (bytes memory proofKey) { + function ackProofKey(IbcPacket calldata packet) external pure returns (bytes memory proofKey) { proofKey = abi.encodePacked( "acks/ports/", packet.dest.portId, @@ -424,14 +403,51 @@ library Ibc { } // https://github.com/open-ibc/ibcx-go/blob/ef80dd6784fd/modules/core/04-channel/types/packet.go#L38 - function ackProofValue(bytes calldata ack) public pure returns (bytes32 proofValue) { + function ackProofValue(bytes calldata ack) external pure returns (bytes32 proofValue) { proofValue = sha256(ack); } - function parseAckData(bytes calldata ack) public pure returns (AckPacket memory ackData) { + function parseAckData(bytes calldata ack) external pure returns (AckPacket memory ackData) { // this hex value is '"result"' ackData = (keccak256(ack[1:9]) == keccak256(hex"22726573756c7422")) ? AckPacket(true, Base64.decode(string(ack[11:ack.length - 2]))) // result success : AckPacket(false, ack[10:ack.length - 2]); // this is an error } + + function toStr(bytes32 b) public pure returns (string memory outStr) { + uint8 i = 0; + while (i < 32 && b[i] != 0) { + i++; + } + bytes memory bytesArray = new bytes(i); + for (uint8 j = 0; j < i; j++) { + bytesArray[j] = b[j]; + } + outStr = string(bytesArray); + } + + function toStr(uint256 _number) public pure returns (string memory outStr) { + if (_number == 0) { + return "0"; + } + + uint256 length; + uint256 number = _number; + + // Determine the length of the string + while (number != 0) { + length++; + number /= 10; + } + + bytes memory buffer = new bytes(length); + + // Convert each digit to its ASCII representation + for (uint256 i = length; i > 0; i--) { + buffer[i - 1] = bytes1(uint8(48 + (_number % 10))); + _number /= 10; + } + + outStr = string(buffer); + } } diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 033f6bcd..096df6ed 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.0; import "forge-std/Script.sol"; import "../contracts/utils/DummyProofVerifier.sol"; import "../contracts/utils/DummyLightClient.sol"; -import "../contracts/core/Dispatcher.sol"; +import {Dispatcher} from "../contracts/core/Dispatcher.sol"; import "../contracts/examples/Mars.sol"; import {IDispatcher} from "../contracts/core/Dispatcher.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; diff --git a/test/Dispatcher.closeChannel.t.sol b/test/Dispatcher.closeChannel.t.sol new file mode 100644 index 00000000..bc6911db --- /dev/null +++ b/test/Dispatcher.closeChannel.t.sol @@ -0,0 +1,131 @@ +// 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 { + Channel, + CounterParty, + ChannelOrder, + IbcPacket, + ChannelState, + AckPacket, + IBCErrors, + IbcUtils, + Ibc +} from "../contracts/libs/Ibc.sol"; + +contract DispatcherCloseChannelTest is PacketSenderTestBase { + Channel defaultChannel; // Uninitialized struct to compare that structs are deleted + + function setUp() public override { + super.setUp(); + sendPacket(); + } + + function test_closeChannelInit_success() public { + assertNotEq0(abi.encode(dispatcherProxy.getChannel(address(mars), channelId)), abi.encode(defaultChannel)); + vm.expectEmit(true, true, true, true); + emit ChannelCloseInit(address(mars), channelId); + mars.triggerChannelClose(channelId); + assertEq(abi.encode(dispatcherProxy.getChannel(address(mars), channelId)), abi.encode(defaultChannel)); + } + + function test_closeChannelInit_mustOwner() public { + Mars earth = new Mars(dispatcherProxy); + vm.expectRevert(abi.encodeWithSelector(IBCErrors.channelNotOwnedBySender.selector)); + earth.triggerChannelClose(channelId); + assertNotEq0(abi.encode(dispatcherProxy.getChannel(address(mars), channelId)), abi.encode(defaultChannel)); + } + + function test_closeChannelConfirm_success() public { + assertNotEq0(abi.encode(dispatcherProxy.getChannel(address(mars), channelId)), abi.encode(defaultChannel)); + vm.expectEmit(true, true, true, true); + emit ChannelCloseConfirm(address(mars), channelId); + dispatcherProxy.channelCloseConfirm(address(mars), channelId, validProof); + assertEq(abi.encode(dispatcherProxy.getChannel(address(mars), channelId)), abi.encode(defaultChannel)); + } + + function test_closeChannelConfirm_mustOwner() public { + vm.expectRevert(abi.encodeWithSelector(IBCErrors.channelNotOwnedByPortAddress.selector)); + dispatcherProxy.channelCloseConfirm(address(mars), "channel-999", validProof); + assertNotEq0(abi.encode(dispatcherProxy.getChannel(address(mars), channelId)), abi.encode(defaultChannel)); + } + + function test_closeChannelConfirm_invalidProof() public { + vm.expectRevert(DummyLightClient.InvalidDummyMembershipProof.selector); + dispatcherProxy.channelCloseConfirm(address(mars), channelId, invalidProof); + assertNotEq0(abi.encode(dispatcherProxy.getChannel(address(mars), channelId)), abi.encode(defaultChannel)); + } + + function test_sendPacket_afterChannelCloseInit() public { + mars.triggerChannelClose(channelId); + sentPacket = genPacket(nextSendSeq); + ackPacket = genAckPacket(Ibc.toStr(nextSendSeq)); + vm.expectRevert(IBCErrors.channelNotOwnedBySender.selector); + mars.greet(payloadStr, channelId, maxTimeout); + } + + function test_sendPacket_afterChannelCloseConfirm() public { + dispatcherProxy.channelCloseConfirm(address(mars), channelId, validProof); + sentPacket = genPacket(nextSendSeq); + ackPacket = genAckPacket(Ibc.toStr(nextSendSeq)); + vm.expectRevert(IBCErrors.channelNotOwnedBySender.selector); + mars.greet(payloadStr, channelId, maxTimeout); + } +} + +contract DappRevertTestsCloseChannel is DappRevertTests { + Channel defaultChannel; // Uninitialized struct to compare that structs are deleted + + RevertingStringCloseChannelMars revertingStringCloseMars; + string portId = "eth1.7E5F4552091A69125d5DfCb7b8C2659029395Bdf"; + LocalEnd _local; + CounterParty _remote; + + function setUp() public override { + super.setUp(); + revertingStringCloseMars = new RevertingStringCloseChannelMars(dispatcherProxy); + ChannelHandshakeSetting memory setting = ChannelHandshakeSetting(ChannelOrder.ORDERED, false, true, validProof); + _local = LocalEnd(revertingStringCloseMars, portId, ch0.channelId, connectionHops, "1.0", "1.0"); + _remote = CounterParty("eth2.7E5F4552091A69125d5DfCb7b8C2659029395Bdf", "channel-2", "1.0"); + + channelOpenInit(_local, _remote, setting, true); + channelOpenTry(_local, _remote, setting, true); + channelOpenAck(_local, _remote, setting, true); + channelOpenConfirm(_local, _remote, setting, true); + } + + function test_ibc_channel_close_init_dapp_revert() public { + // We need to actually setup channelhandshake since foundry doesn't support packed slots + vm.expectEmit(true, true, true, true); + emit ChannelCloseInitError( + address(revertingStringCloseMars), + abi.encodeWithSignature("Error(string)", "close ibc channel is reverting") + ); + revertingStringCloseMars.triggerChannelClose(ch0.channelId); + + // Channels should still be deleted on dapp revert + assertEq( + abi.encode(defaultChannel), + abi.encode(dispatcherProxy.getChannel(address(revertingStringCloseMars), ch0.channelId)) + ); + } + + function test_ibc_channel_close_confirm_dapp_revert() public { + vm.expectEmit(true, true, true, true); + emit ChannelCloseConfirmError( + address(revertingStringCloseMars), + abi.encodeWithSignature("Error(string)", "close ibc channel is reverting") + ); + + dispatcherProxy.channelCloseConfirm(address(revertingStringCloseMars), ch0.channelId, validProof); + // Channels should still be deleted on dapp revert + assertEq( + abi.encode(defaultChannel), + abi.encode(dispatcherProxy.getChannel(address(revertingStringCloseMars), ch0.channelId)) + ); + } +} diff --git a/test/Dispatcher.proof.t.sol b/test/Dispatcher.proof.t.sol index f0caca6d..7f98aea6 100644 --- a/test/Dispatcher.proof.t.sol +++ b/test/Dispatcher.proof.t.sol @@ -62,10 +62,10 @@ abstract contract DispatcherIbcWithRealProofsSuite is IbcEventsEmitter, Base { function test_ack_packet() public { Ics23Proof memory proof = load_proof("/test/payload/packet_ack_proof.hex"); - // plant a fake packet commitment so the ack checks go through + // Plant a fake packet commitment so the ack checks go through // Stdstore doesn't work for proxies so we have to use store - // use "forge inspect --storage" to find the nested mapping slot - bytes32 slot1 = keccak256(abi.encode(address(mars), uint32(107))); // current nested mapping slot: 107 + // use "forge inspect Dispatcher storage" to find the nested mapping slot + bytes32 slot1 = keccak256(abi.encode(address(mars), uint32(107))); bytes32 slot2 = keccak256(abi.encode(ch0.channelId, slot1)); bytes32 slot3 = keccak256(abi.encode(uint256(1), slot2)); vm.store(address(dispatcherProxy), slot3, bytes32(uint256(1))); @@ -112,8 +112,20 @@ abstract contract DispatcherIbcWithRealProofsSuite is IbcEventsEmitter, Base { dispatcherProxy.recvPacket(mars, packet, proof); } - function test_timeout_packet() public { - vm.skip(true); // not implemented + function test_timeout_packet_revert() public { + // Timeout reverts since it is not yet implemented + Ics23Proof memory proof = load_proof("/test/payload/packet_commitment_proof.hex"); + 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("polyibc.eth1.", IbcUtils.toHexStr(address(mars)))); + packet.src.portId = string(abi.encodePacked("polyibc.eth1.", IbcUtils.toHexStr(address(mars)))); + packet.src.channelId = ch0.channelId; + packet.sequence = 1; + + vm.expectRevert(abi.encodeWithSelector(ProofVerifier.MethodNotImplemented.selector)); + dispatcherProxy.timeout(mars, packet, proof); } function load_proof(string memory filepath) internal returns (Ics23Proof memory) { diff --git a/test/Dispatcher.t.sol b/test/Dispatcher.t.sol index 7d563e27..d4eb401c 100644 --- a/test/Dispatcher.t.sol +++ b/test/Dispatcher.t.sol @@ -224,37 +224,6 @@ contract ChannelOpenTestBaseSetup is Base { } } -// FIXME this is commented out to make the contract size smaller. We need to optimise for size -// contract DispatcherCloseChannelTest is ChannelOpenTestBase { -// function test_closeChannelInit_success() public { -// vm.expectEmit(true, true, true, true); -// emit CloseIbcChannel(address(mars), channelId); -// mars.triggerChannelClose(channelId); -// } -// -// function test_closeChannelInit_mustOwner() public { -// Mars earth = new Mars(dispatcherProxy); -// vm.expectRevert(abi.encodeWithSignature('channelNotOwnedBySender()')); -// earth.triggerChannelClose(channelId); -// } -// -// function test_closeChannelConfirm_success() public { -// vm.expectEmit(true, true, true, true); -// emit CloseIbcChannel(address(mars), channelId); -// dispatcherProxy.onCloseIbcChannel(address(mars), channelId, validProof); -// } -// -// function test_closeChannelConfirm_mustOwner() public { -// vm.expectRevert(abi.encodeWithSignature('channelNotOwnedByPortAddress()')); -// dispatcherProxy.onCloseIbcChannel(address(mars), 'channel-999', validProof); -// } -// -// function test_closeChannelConfirm_invalidProof() public { -// vm.expectRevert('Invalid dummy membership proof'); -// dispatcherProxy.onCloseIbcChannel(address(mars), channelId, invalidProof); -// } -// } - contract DispatcherSendPacketTestSuite is ChannelOpenTestBaseSetup { // default params string payload = "msgPayload"; @@ -273,7 +242,7 @@ contract DispatcherSendPacketTestSuite is ChannelOpenTestBaseSetup { // sendPacket fails if calling dApp doesn't own the channel function test_mustOwner() public { Mars earth = new Mars(dispatcherProxy); - vm.expectRevert(abi.encodeWithSignature("channelNotOwnedBySender()")); + vm.expectRevert(abi.encodeWithSelector(IBCErrors.channelNotOwnedBySender.selector)); earth.greet(payload, channelId, timeoutTimestamp); } } @@ -374,7 +343,7 @@ contract DispatcherRecvPacketTestSuite is ChannelOpenTestBaseSetup { dispatcherProxy.recvPacket( IbcReceiver(mars), IbcPacket(src, dest, 1, payload, ZERO_HEIGHT, maxTimeout), validProof ); - vm.expectRevert(abi.encodeWithSignature("unexpectedPacketSequence()")); + vm.expectRevert(abi.encodeWithSelector(IBCErrors.unexpectedPacketSequence.selector)); dispatcherProxy.recvPacket( IbcReceiver(mars), IbcPacket(src, dest, 3, payload, ZERO_HEIGHT, maxTimeout), validProof ); @@ -402,14 +371,14 @@ contract DispatcherAckPacketTestSuite is PacketSenderTestBase { // cannot ack packets if packet commitment is missing function test_missingPacket() public { - vm.expectRevert(abi.encodeWithSignature("packetCommitmentNotFound()")); + vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); dispatcherProxy.acknowledgement(IbcReceiver(mars), genPacket(1), genAckPacket("1"), validProof); sendPacket(); dispatcherProxy.acknowledgement(IbcReceiver(mars), sentPacket, ackPacket, validProof); // packet commitment is removed after ack - vm.expectRevert(abi.encodeWithSignature("packetCommitmentNotFound()")); + vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); dispatcherProxy.acknowledgement(IbcReceiver(mars), sentPacket, ackPacket, validProof); } @@ -422,7 +391,7 @@ contract DispatcherAckPacketTestSuite is PacketSenderTestBase { dispatcherProxy.acknowledgement(IbcReceiver(mars), genPacket(1), genAckPacket("1"), validProof); // only 2nd ack is allowed; so the 3rd ack fails - vm.expectRevert(abi.encodeWithSignature("unexpectedPacketSequence()")); + vm.expectRevert(abi.encodeWithSelector(IBCErrors.unexpectedPacketSequence.selector)); dispatcherProxy.acknowledgement(IbcReceiver(mars), genPacket(3), genAckPacket("3"), validProof); } @@ -438,7 +407,7 @@ contract DispatcherAckPacketTestSuite is PacketSenderTestBase { IbcPacket memory packetEarth = sentPacket; packetEarth.src = earthEnd; - vm.expectRevert(abi.encodeWithSignature("receiverNotOriginPacketSender()")); + vm.expectRevert(abi.encodeWithSelector(IBCErrors.receiverNotOriginPacketSender.selector)); dispatcherProxy.acknowledgement(IbcReceiver(mars), packetEarth, ackPacket, validProof); } @@ -450,7 +419,7 @@ contract DispatcherAckPacketTestSuite is PacketSenderTestBase { IbcPacket memory packet = sentPacket; packet.src = invalidSrc; - vm.expectRevert(abi.encodeWithSignature("packetCommitmentNotFound()")); + vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); dispatcherProxy.acknowledgement(IbcReceiver(mars), packet, ackPacket, validProof); } } @@ -483,14 +452,14 @@ contract DispatcherTimeoutPacketTestSuite is PacketSenderTestBase { // cannot timeout packets if packet commitment is missing function test_missingPacket() public { - vm.expectRevert(abi.encodeWithSignature("packetCommitmentNotFound()")); + vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); dispatcherProxy.timeout(IbcReceiver(mars), genPacket(1), validProof); sendPacket(); dispatcherProxy.timeout(IbcReceiver(mars), sentPacket, validProof); // packet commitment is removed after timeout - vm.expectRevert(abi.encodeWithSignature("packetCommitmentNotFound()")); + vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); dispatcherProxy.timeout(IbcReceiver(mars), sentPacket, validProof); } @@ -518,8 +487,7 @@ contract DispatcherTimeoutPacketTestSuite is PacketSenderTestBase { IbcPacket memory packet = sentPacket; packet.src = invalidSrc; - vm.expectRevert(abi.encodeWithSignature("packetCommitmentNotFound()")); - /* vm.expectRevert('Packet commitment not found'); */ + vm.expectRevert(abi.encodeWithSelector(IBCErrors.packetCommitmentNotFound.selector)); dispatcherProxy.timeout(IbcReceiver(mars), packet, validProof); } @@ -543,7 +511,7 @@ contract DappRevertTests is Base { CounterParty ch1 = CounterParty("polyibc.eth2.71C95911E9a5D330f4D621842EC243EE1343292e", IbcUtils.toBytes32("channel-1"), "1.0"); - function setUp() public override { + function setUp() public virtual override { (dispatcherProxy, dispatcherImplementation) = TestUtilsTest.deployDispatcherProxyAndImpl(portPrefix, dummyConsStateManager); revertingBytesMars = new RevertingBytesMars(dispatcherProxy); diff --git a/test/Verifier.t.sol b/test/Verifier.t.sol index be2c4131..f1c05a4c 100644 --- a/test/Verifier.t.sol +++ b/test/Verifier.t.sol @@ -80,17 +80,25 @@ contract OpProofVerifierMembershipVerificationTest is ProofBase { string[] memory connectionHops = new string[](2); connectionHops[0] = "connection-2"; - connectionHops[1] = "connection-1"; - CounterParty memory counterparty = CounterParty( "polyibc.eth1.71C95911E9a5D330f4D621842EC243EE1343292e", IbcUtils.toBytes32("channel-0"), "1.0" ); + + connectionHops[1] = "connection-1"; + this.run_packet_proof_verification( input, Ibc.channelProofKey( "polyibc.eth2.71C95911E9a5D330f4D621842EC243EE1343292e", IbcUtils.toBytes32("channel-1") ), - Ibc.channelProofValue(ChannelState.TRY_PENDING, ChannelOrder.NONE, "1.0", connectionHops, counterparty) + Ibc.channelProofValue( + ChannelState.TRY_PENDING, + ChannelOrder.NONE, + "1.0", + connectionHops, + counterparty.portId, + counterparty.channelId + ) ); } @@ -105,12 +113,20 @@ contract OpProofVerifierMembershipVerificationTest is ProofBase { CounterParty memory counterparty = CounterParty("polyibc.eth2.71C95911E9a5D330f4D621842EC243EE1343292e", IbcUtils.toBytes32("channel-1"), ""); + this.run_packet_proof_verification( input, Ibc.channelProofKey( "polyibc.eth1.71C95911E9a5D330f4D621842EC243EE1343292e", IbcUtils.toBytes32("channel-0") ), - Ibc.channelProofValue(ChannelState.ACK_PENDING, ChannelOrder.NONE, "1.0", connectionHops, counterparty) + Ibc.channelProofValue( + ChannelState.ACK_PENDING, + ChannelOrder.NONE, + "1.0", + connectionHops, + counterparty.portId, + counterparty.channelId + ) ); } @@ -126,12 +142,20 @@ contract OpProofVerifierMembershipVerificationTest is ProofBase { CounterParty memory counterparty = CounterParty( "polyibc.eth1.71C95911E9a5D330f4D621842EC243EE1343292e", IbcUtils.toBytes32("channel-0"), "1.0" ); + this.run_packet_proof_verification( input, Ibc.channelProofKey( "polyibc.eth2.71C95911E9a5D330f4D621842EC243EE1343292e", IbcUtils.toBytes32("channel-1") ), - Ibc.channelProofValue(ChannelState.CONFIRM_PENDING, ChannelOrder.NONE, "1.0", connectionHops, counterparty) + Ibc.channelProofValue( + ChannelState.CONFIRM_PENDING, + ChannelOrder.NONE, + "1.0", + connectionHops, + counterparty.portId, + counterparty.channelId + ) ); } diff --git a/test/VirtualChain.sol b/test/VirtualChain.sol index 4b3802e7..4820ad89 100644 --- a/test/VirtualChain.sol +++ b/test/VirtualChain.sol @@ -6,7 +6,8 @@ import "@openzeppelin/contracts/utils/Strings.sol"; import {IbcDispatcher, IbcEventsEmitter} from "../contracts/interfaces/IbcDispatcher.sol"; import {IDispatcher} from "../contracts/interfaces/IDispatcher.sol"; import "../contracts/libs/Ibc.sol"; -import "../contracts/core/Dispatcher.sol"; +import {Dispatcher} from "../contracts/core/Dispatcher.sol"; +import {IbcChannelReceiver, IbcPacketReceiver} from "../contracts/interfaces/IbcReceiver.sol"; import "../contracts/interfaces/ProofVerifier.sol"; import {UniversalChannelHandler} from "../contracts/core/UniversalChannelHandler.sol"; import {Mars} from "../contracts/examples/Mars.sol"; @@ -91,6 +92,7 @@ contract VirtualChain is Test, IbcEventsEmitter, TestUtilsTest { ChannelSetting memory setting ) public view returns (Channel memory) { return Channel( + setting.portId, setting.version, setting.ordering, setting.feeEnabled, diff --git a/test/universal.channel.t.sol b/test/universal.channel.t.sol index bf3b4ed9..817ed989 100644 --- a/test/universal.channel.t.sol +++ b/test/universal.channel.t.sol @@ -39,8 +39,6 @@ contract UniversalChannelTest is Base { function assert_channel(VirtualChain vc1, VirtualChain vc2, ChannelSetting memory setting) internal { bytes32 channelId1 = vc1.channelIds(address(vc1.ucHandler()), address(vc2.ucHandler())); bytes32 channelId2 = vc2.channelIds(address(vc2.ucHandler()), address(vc1.ucHandler())); - assertEq(vc1.ucHandler().connectedChannels(0), channelId1); - assertEq(vc2.ucHandler().connectedChannels(0), channelId2); Channel memory channel1 = vc1.dispatcherProxy().getChannel(address(vc1.ucHandler()), channelId1); Channel memory channel2 = vc2.dispatcherProxy().getChannel(address(vc2.ucHandler()), channelId2); diff --git a/test/upgradeableProxy/Dispatcher.upgrade.t.sol b/test/upgradeableProxy/Dispatcher.upgrade.t.sol index dfe9df67..a7dff73c 100644 --- a/test/upgradeableProxy/Dispatcher.upgrade.t.sol +++ b/test/upgradeableProxy/Dispatcher.upgrade.t.sol @@ -138,7 +138,7 @@ contract DispatcherUpgradeTest is ChannelHandShakeUpgradeUtil, UpgradeTestUtils assert(vm.load(address(dispatcherProxy), findPacketCommitmentSlot(address(mars), _local.channelId, 2)) > 0); assert(vm.load(address(dispatcherProxy), findPacketCommitmentSlot(address(mars), _local.channelId, 3)) > 0); - // test sending packet with the updated contract + // Test sending packet with the updated contract sendOnePacket(_local.channelId, 4); assert(vm.load(address(dispatcherProxy), findPacketCommitmentSlot(address(mars), _local.channelId, 4)) > 0); uint64 nextSequenceSendAfterSending = uint64( diff --git a/test/upgradeableProxy/upgrades/DispatcherV2.sol b/test/upgradeableProxy/upgrades/DispatcherV2.sol index de7e7298..6843dda9 100644 --- a/test/upgradeableProxy/upgrades/DispatcherV2.sol +++ b/test/upgradeableProxy/upgrades/DispatcherV2.sol @@ -10,6 +10,7 @@ import {IbcChannelReceiver, IbcPacketReceiver} from "../../../contracts/interfac import {L1Header, OpL2StateProof, Ics23Proof} from "../../../contracts/interfaces/ProofVerifier.sol"; import {LightClient} from "../../../contracts/interfaces/LightClient.sol"; import {IDispatcher} from "../../../contracts/interfaces/IDispatcher.sol"; +import {Dispatcher} from "../../../contracts/core/Dispatcher.sol"; import { Channel, CounterParty, @@ -30,584 +31,4 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol"; * Contract callers call this contract to send IBC-like msg, * which can be relayed to a rollup module on the Polymerase chain */ -contract DispatcherV2 is OwnableUpgradeable, UUPSUpgradeable, IDispatcher { - // - // fields - // - // IBC_PortID = portPrefix + address (hex string without 0x prefix, case insensitive) - string public portPrefix; - uint32 public portPrefixLen; - - mapping(address => mapping(bytes32 => Channel)) public portChannelMap; - mapping(address => mapping(bytes32 => uint64)) public nextSequenceSend; - // keep track of received packets' sequences to ensure channel ordering is enforced for ordered channels - mapping(address => mapping(bytes32 => uint64)) public nextSequenceRecv; - mapping(address => mapping(bytes32 => uint64)) public nextSequenceAck; - // only stores a bit to mark packet has not been ack'ed or timed out yet; actual IBC packet verification is done on - // Polymer chain. - // Keep track of sent packets - mapping(address => mapping(bytes32 => mapping(uint64 => bool))) public sendPacketCommitment; - // keep track of received packets to prevent replay attack - mapping(address => mapping(bytes32 => mapping(uint64 => bool))) public recvPacketReceipt; - // keep track of outbound ack packets to prevent replay attack - mapping(address => mapping(bytes32 => mapping(uint64 => bool))) public ackPacketCommitment; - - LightClient public lightClient; - - // - // methods - // - constructor() { - _disableInitializers(); - } - - function initialize(string memory initPortPrefix, LightClient _lightClient) public virtual initializer { - __Ownable_init(); - portPrefix = initPortPrefix; - portPrefixLen = uint32(bytes(initPortPrefix).length); - lightClient = _lightClient; - } - - // - // CoreSC maaintainer methods, only invoked by the owner - // - function setPortPrefix(string calldata _portPrefix) external onlyOwner { - portPrefix = _portPrefix; - portPrefixLen = uint32(bytes(_portPrefix).length); - } - - // updateClientWithOptimisticConsensusState updates the client - // with the optimistic consensus state. The optimistic consensus - // is accepted and will be open for verify in the fraud proof - // window. - function updateClientWithOptimisticConsensusState( - L1Header calldata l1header, - OpL2StateProof calldata proof, - uint256 height, - uint256 appHash - ) external returns (uint256 fraudProofEndTime, bool ended) { - return lightClient.addOpConsensusState(l1header, proof, height, appHash); - } - - /** - * This function is called by a 'relayer' on behalf of a dApp. The dApp should implement IbcChannelHandler's - * 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. - */ - function channelOpenInit( - IbcChannelReceiver receiver, - string calldata version, - ChannelOrder ordering, - bool feeEnabled, - string[] calldata connectionHops, - string calldata counterpartyPortId - ) external { - if (bytes(counterpartyPortId).length == 0) { - revert IBCErrors.invalidCounterPartyPortId(); - } - - (bool success, bytes memory data) = _callIfContract( - address(receiver), abi.encodeWithSelector(IbcChannelReceiver.onChanOpenInit.selector, version) - ); - - if (success) { - emit ChannelOpenInit( - address(receiver), abi.decode(data, (string)), ordering, feeEnabled, connectionHops, counterpartyPortId - ); - } else { - emit ChannelOpenInitError(address(receiver), data); - } - } - - /** - * 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. - */ - function channelOpenTry( - IbcChannelReceiver receiver, - CounterParty calldata local, - ChannelOrder ordering, - bool feeEnabled, - string[] calldata connectionHops, - CounterParty calldata counterparty, - Ics23Proof calldata proof - ) external { - if (bytes(counterparty.portId).length == 0) { - revert IBCErrors.invalidCounterPartyPortId(); - } - - lightClient.verifyMembership( - proof, - Ibc.channelProofKey(local.portId, local.channelId), - Ibc.channelProofValue(ChannelState.TRY_PENDING, ordering, local.version, connectionHops, counterparty) - ); - - (bool success, bytes memory data) = _callIfContract( - address(receiver), abi.encodeWithSelector(IbcChannelReceiver.onChanOpenTry.selector, counterparty.version) - ); - - if (success) { - emit ChannelOpenTry( - address(receiver), - abi.decode(data, (string)), - ordering, - feeEnabled, - connectionHops, - counterparty.portId, - counterparty.channelId - ); - } else { - emit ChannelOpenTryError(address(receiver), data); - } - } - - /** - * 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 - */ - function channelOpenAck( - IbcChannelReceiver receiver, - CounterParty calldata local, - string[] calldata connectionHops, - ChannelOrder ordering, - bool feeEnabled, - CounterParty calldata counterparty, - Ics23Proof calldata proof - ) external { - lightClient.verifyMembership( - proof, - Ibc.channelProofKey(local.portId, local.channelId), - Ibc.channelProofValue(ChannelState.ACK_PENDING, ordering, local.version, connectionHops, counterparty) - ); - - (bool success, bytes memory data) = _callIfContract( - address(receiver), - abi.encodeWithSelector(IbcChannelReceiver.onChanOpenAck.selector, local.channelId, counterparty.version) - ); - - if (success) { - _connectChannel(receiver, local, connectionHops, ordering, feeEnabled, counterparty); - emit ChannelOpenAck(address(receiver), local.channelId); - } else { - emit ChannelOpenAckError(address(receiver), data); - } - } - - /** - * 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: - * ChannelOpenConfirm - */ - function channelOpenConfirm( - IbcChannelReceiver receiver, - CounterParty calldata local, - string[] calldata connectionHops, - ChannelOrder ordering, - bool feeEnabled, - CounterParty calldata counterparty, - Ics23Proof calldata proof - ) external { - lightClient.verifyMembership( - proof, - Ibc.channelProofKey(local.portId, local.channelId), - Ibc.channelProofValue(ChannelState.CONFIRM_PENDING, ordering, local.version, connectionHops, counterparty) - ); - - (bool success, bytes memory data) = _callIfContract( - address(receiver), - abi.encodeWithSelector(IbcChannelReceiver.onChanOpenConfirm.selector, local.channelId, counterparty.version) - ); - - if (success) { - _connectChannel(receiver, local, connectionHops, ordering, feeEnabled, counterparty); - emit ChannelOpenConfirm(address(receiver), local.channelId); - } else { - emit ChannelOpenConfirmError(address(receiver), data); - } - } - - /** - * @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 - * Must be called by the channel owner, ie. portChannelMap[msg.sender][channelId] must exist - */ - function closeIbcChannel(bytes32 channelId) external { - Channel memory channel = portChannelMap[msg.sender][channelId]; - if (channel.counterpartyChannelId == bytes32(0)) { - revert IBCErrors.channelNotOwnedBySender(); - } - - (bool success, bytes memory data) = _callIfContract( - msg.sender, - abi.encodeWithSelector( - IbcChannelReceiver.onCloseIbcChannel.selector, - channelId, - channel.counterpartyPortId, - channel.counterpartyChannelId - ) - ); - if (success) { - emit CloseIbcChannel(msg.sender, channelId); - } else { - emit CloseIbcChannelError(address(msg.sender), data); - } - } - - /** - * This func is called by a 'relayer' after the IBC/VIBC hub chain has processed ChanCloseConfirm event. - * The dApp's onCloseIbcChannel callback is invoked. - * dApp should throw an error if the channel should not be closed. - */ - // FIXME this is commented out to make the contract size smaller. We need to optimise for size - // function onCloseIbcChannel(address portAddress, bytes32 channelId, Ics23Proof calldata proof) external { - // // verify VIBC/IBC hub chain has processed ChanCloseConfirm event - // lightClient.verifyMembership( - // proof, - // bytes('channel/path/to/be/added/here'), - // bytes('expected channel bytes constructed from params. Channel.State = {Closed(_Pending?)}') - // ); - // - // // ensure port owns channel - // Channel memory channel = portChannelMap[portAddress][channelId]; - // if (channel.counterpartyChannelId == bytes32(0)) { - // revert channelNotOwnedByPortAddress(); - // } - // - // // confirm with dApp by calling its callback - // IbcChannelReceiver reciever = IbcChannelReceiver(portAddress); - // reciever.onCloseIbcChannel(channelId, channel.counterpartyPortId, channel.counterpartyChannelId); - // delete portChannelMap[portAddress][channelId]; - // emit CloseIbcChannel(portAddress, channelId); - // } - - // - // 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. - * @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 nanoseconds after which the packet times out if it has not been - * received. - */ - function sendPacket(bytes32 channelId, bytes calldata packet, uint64 timeoutTimestamp) external { - // ensure port owns channel - Channel memory channel = portChannelMap[msg.sender][channelId]; - if (channel.counterpartyChannelId == bytes32(0)) { - revert IBCErrors.channelNotOwnedBySender(); - } - - _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. - * @param receiver The IbcPacketHandler contract that should handle the packet acknowledgement event - * If the address 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( - IbcPacketReceiver receiver, - IbcPacket calldata packet, - bytes calldata ack, - Ics23Proof calldata proof - ) external { - // verify `receiver` is the original packet sender - if (!portIdAddressMatch(address(receiver), packet.src.portId)) { - revert IBCErrors.receiverNotOriginPacketSender(); - } - - // prove ack packet is on Polymer chain - lightClient.verifyMembership(proof, Ibc.ackProofKey(packet), abi.encode(Ibc.ackProofValue(ack))); - // verify packet has been committed and not yet ack'ed or timed out - bool hasCommitment = sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; - if (!hasCommitment) { - revert IBCErrors.packetCommitmentNotFound(); - } - - // enforce ack'ed packet sequences always increment by 1 for ordered channels - Channel memory channel = portChannelMap[address(receiver)][packet.src.channelId]; - (bool success, bytes memory data) = _callIfContract( - address(receiver), - abi.encodeWithSelector(IbcPacketReceiver.onAcknowledgementPacket.selector, packet, Ibc.parseAckData(ack)) - ); - - if (success) { - if (channel.ordering == ChannelOrder.ORDERED) { - if (packet.sequence != nextSequenceAck[address(receiver)][packet.src.channelId]) { - revert IBCErrors.unexpectedPacketSequence(); - } - - nextSequenceAck[address(receiver)][packet.src.channelId] = packet.sequence + 1; - } - - // delete packet commitment to avoid double ack - delete sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; - emit Acknowledgement(address(receiver), packet.src.channelId, packet.sequence); - } else { - emit AcknowledgementError(address(receiver), data); - } - } - - /** - * @notice Timeout of an IBC packet - * @dev Verifies the given proof and calls the `onTimeoutPacket` function on the given `receiver` contract, ie. the - * IBC-dApp. - * Prerequisite: the original packet is committed and not ack'ed or timed out yet. - * @param receiver The IbcPacketHandler contract that should handle the packet timeout event - * If the address doesn't satisfy the interface, the transaction will be reverted. - * @param packet The IbcPacket data for the timed-out packet - * @param proof The non-membership proof data needed to verify the packet timeout - */ - function timeout(IbcPacketReceiver receiver, IbcPacket calldata packet, Ics23Proof calldata proof) external { - // verify `receiver` is the original packet sender - if (!portIdAddressMatch(address(receiver), packet.src.portId)) { - revert IBCErrors.receiverNotIntendedPacketDestination(); - } - - // prove absence of packet receipt on Polymer chain - // TODO: add non membership support - lightClient.verifyNonMembership(proof, "packet/receipt/path"); - - // verify packet has been committed and not yet ack'ed or timed out - bool hasCommitment = sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; - if (!hasCommitment) { - revert IBCErrors.packetCommitmentNotFound(); - } - - (bool success, bytes memory data) = _callIfContract( - address(receiver), abi.encodeWithSelector(IbcPacketReceiver.onTimeoutPacket.selector, packet) - ); - if (success) { - // delete packet commitment to avoid double timeout - delete sendPacketCommitment[address(receiver)][packet.src.channelId][packet.sequence]; - emit Timeout(address(receiver), packet.src.channelId, packet.sequence); - } else { - emit TimeoutError(address(receiver), data); - } - } - - /** - * @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 - * @param receiver The IbcPacketHandler contract that should handle the packet receipt event - * If the address doesn't satisfy the interface, the transaction will be reverted. - * 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 - * @dev Emit an `RecvPacket` event with the details of the received packet; - * Also emit a WriteAckPacket event, which can be relayed to Polymer chain by relayers - */ - function recvPacket(IbcPacketReceiver receiver, IbcPacket calldata packet, Ics23Proof calldata proof) external { - // verify `receiver` is the intended packet destination - if (!portIdAddressMatch(address(receiver), packet.dest.portId)) { - revert IBCErrors.receiverNotIntendedPacketDestination(); - } - lightClient.verifyMembership( - proof, Ibc.packetCommitmentProofKey(packet), abi.encode(Ibc.packetCommitmentProofValue(packet)) - ); - - // verify packet has not been received yet - bool hasReceipt = recvPacketReceipt[address(receiver)][packet.dest.channelId][packet.sequence]; - if (hasReceipt) { - revert IBCErrors.packetReceiptAlreadyExists(); - } - - recvPacketReceipt[address(receiver)][packet.dest.channelId][packet.sequence] = true; - - // enforce recv'ed packet sequences always increment by 1 for ordered channels - Channel memory channel = portChannelMap[address(receiver)][packet.dest.channelId]; - if (channel.ordering == ChannelOrder.ORDERED) { - if (packet.sequence != nextSequenceRecv[address(receiver)][packet.dest.channelId]) { - revert IBCErrors.unexpectedPacketSequence(); - } - - nextSequenceRecv[address(receiver)][packet.dest.channelId] = packet.sequence + 1; - } - - // Emit recv packet event to prove the relayer did the correct job, and pkt is received. - emit RecvPacket(address(receiver), packet.dest.channelId, packet.sequence); - - // If pkt is already timed out, then return early so dApps won't receive it. - if (_isPacketTimeout(packet)) { - address writerPortAddress = address(receiver); - emit WriteTimeoutPacket( - writerPortAddress, packet.dest.channelId, packet.sequence, packet.timeoutHeight, packet.timeoutTimestamp - ); - return; - } - - // Not timeout yet, then do normal handling - IbcPacket memory pkt = packet; - AckPacket memory ack; - (bool success, bytes memory data) = - _callIfContract(address(receiver), abi.encodeWithSelector(IbcPacketReceiver.onRecvPacket.selector, pkt)); - if (success) { - (ack) = abi.decode(data, (AckPacket)); - } else { - ack = AckPacket(false, data); - } - bool hasAckPacketCommitment = ackPacketCommitment[address(receiver)][packet.dest.channelId][packet.sequence]; - // check is not necessary for sync-acks - if (hasAckPacketCommitment) { - revert IBCErrors.ackPacketCommitmentAlreadyExists(); - } - - ackPacketCommitment[address(receiver)][packet.dest.channelId][packet.sequence] = true; - - emit WriteAckPacket(address(receiver), packet.dest.channelId, packet.sequence, ack); - } - - // TODO: add async writeAckPacket - // // this can be invoked sync or async by the IBC-dApp - // function writeAckPacket(IbcPacket calldata packet, AckPacket calldata ackPacket) external { - // // verify `receiver` is the original packet sender - // require( - // portIdAddressMatch(address(msg.sender), packet.src.portId), - // 'Receiver is not the original packet sender' - // ); - // } - - // TODO: remove below writeTimeoutPacket() function - // 1. core SC is responsible to generate timeout packet - // 2. user contract are not free to generate timeout with different criteria - // 3. [optional]: we may wish relayer to trigger timeout process, but in this case, below function won't do - // the job, as it doesn't have proofs. - // There is no strong reason to do this, as relayer can always do the regular `recvPacket` flow, which will - // do proper timeout generation. - /** - * Generate a timeout packet for the given packet - */ - function writeTimeoutPacket(address receiver, IbcPacket calldata packet) external { - // verify `receiver` is the original packet sender - if (!portIdAddressMatch(receiver, packet.src.portId)) { - revert IBCErrors.receiverNotIntendedPacketDestination(); - } - - // verify packet does not have a receipt - bool hasReceipt = recvPacketReceipt[receiver][packet.dest.channelId][packet.sequence]; - if (hasReceipt) { - revert IBCErrors.packetReceiptAlreadyExists(); - } - - // verify packet has timed out; zero-value in packet.timeout means no timeout set - if (!_isPacketTimeout(packet)) { - revert IBCErrors.packetNotTimedOut(); - } - - emit WriteTimeoutPacket( - receiver, packet.dest.channelId, packet.sequence, packet.timeoutHeight, packet.timeoutTimestamp - ); - } - - /** - * @notice Get the IBC channel with the specified port and channel ID - * @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. - */ - function getChannel(address portAddress, bytes32 channelId) external view returns (Channel memory channel) { - channel = portChannelMap[portAddress][channelId]; - } - - // getOptimisticConsensusState - function getOptimisticConsensusState(uint256 height) - external - view - returns (uint256 appHash, uint256 fraudProofEndTime, bool ended) - { - return lightClient.getState(height); - } - - // verify an EVM address matches an IBC portId. - // IBC_PortID = portPrefix + address (hex string without 0x prefix, case-insensitive) - function portIdAddressMatch(address addr, string calldata portId) public view returns (bool isMatch) { - if (keccak256(abi.encodePacked(portPrefix)) != keccak256(abi.encodePacked(portId[0:portPrefixLen]))) { - return false; - } - string memory portSuffix = portId[portPrefixLen:]; - isMatch = Ibc._hexStrToAddress(portSuffix) == addr; - } - - // Prerequisite: must verify sender is authorized to send packet on the channel - function _sendPacket(address sender, bytes32 channelId, bytes memory packet, uint64 timeoutTimestamp) internal { - // current packet sequence - uint64 sequence = nextSequenceSend[sender][channelId]; - if (sequence == 0) { - revert IBCErrors.invalidPacketSequence(); - } - - // packet commitment - sendPacketCommitment[sender][channelId][sequence] = true; - // increment nextSendPacketSequence - nextSequenceSend[sender][channelId] = sequence + 1; - - emit SendPacket(sender, channelId, packet, sequence, timeoutTimestamp); - } - - function _connectChannel( - IbcChannelReceiver portAddress, - CounterParty calldata local, - string[] calldata connectionHops, - ChannelOrder ordering, - bool feeEnabled, - CounterParty calldata counterparty - ) internal { - // Register port and channel mapping - // TODO: check duplicated channel registration? - // TODO: The call to `Channel` constructor MUST be move to `openIbcChannel` phase - // Then `connectIbcChannel` phase can use the `version` as part of `require` condition. - portChannelMap[address(portAddress)][local.channelId] = Channel( - counterparty.version, // TODO: this should be self version instead of counterparty version - ordering, - feeEnabled, - connectionHops, - counterparty.portId, - counterparty.channelId - ); - - // initialize channel sequences - nextSequenceSend[address(portAddress)][local.channelId] = 1; - nextSequenceRecv[address(portAddress)][local.channelId] = 1; - nextSequenceAck[address(portAddress)][local.channelId] = 1; - } - - // Returns the result of the call if no revert, otherwise returns the error if thrown. - function _callIfContract(address receiver, bytes memory args) - internal - returns (bool success, bytes memory message) - { - if (!Address.isContract(receiver)) { - return (false, bytes("call to non-contract")); - } - // Only call if we are sure receiver is a contract - // Note: This tx won't revert if the low-level call fails, see - // https://docs.soliditylang.org/en/latest/cheatsheet.html#members-of-address - (success, message) = receiver.call(args); - } - - 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 - function _isPacketTimeout(IbcPacket calldata packet) internal view returns (bool isTimeOut) { - return ( - isTimeOut = (packet.timeoutTimestamp != 0 && block.timestamp >= packet.timeoutTimestamp) - // TODO: check timeoutHeight.revision_number? - || (packet.timeoutHeight.revision_height != 0 && block.number >= packet.timeoutHeight.revision_height) - ); - } -} +contract DispatcherV2 is Dispatcher {}