diff --git a/contracts/core/04-channel/IBCChannelPacketSendRecv.sol b/contracts/core/04-channel/IBCChannelPacketSendRecv.sol index 97423355..ba265ef4 100644 --- a/contracts/core/04-channel/IBCChannelPacketSendRecv.sol +++ b/contracts/core/04-channel/IBCChannelPacketSendRecv.sol @@ -3,12 +3,12 @@ pragma solidity ^0.8.20; import {Height} from "../../proto/Client.sol"; import {ConnectionEnd} from "../../proto/Connection.sol"; -import {Channel} from "../../proto/Channel.sol"; +import {Channel, Timeout} from "../../proto/Channel.sol"; import {ILightClient} from "../02-client/ILightClient.sol"; import {IIBCClientErrors} from "../02-client/IIBCClientErrors.sol"; import {IBCHeight} from "../02-client/IBCHeight.sol"; +import {IBCChannelUpgradeBase} from "./IBCChannelUpgrade.sol"; import {IBCCommitment} from "../24-host/IBCCommitment.sol"; -import {IBCModuleManager} from "../26-router/IBCModuleManager.sol"; import {IBCChannelLib} from "./IBCChannelLib.sol"; import {IIBCChannelPacketSendRecv} from "./IIBCChannel.sol"; import {IIBCChannelErrors} from "./IIBCChannelErrors.sol"; @@ -17,7 +17,7 @@ import {IIBCChannelErrors} from "./IIBCChannelErrors.sol"; * @dev IBCChannelPacketSendRecv is a contract that implements [ICS-4](https://github.com/cosmos/ibc/tree/main/spec/core/ics-004-channel-and-packet-semantics). */ contract IBCChannelPacketSendRecv is - IBCModuleManager, + IBCChannelUpgradeBase, IIBCChannelPacketSendRecv, IIBCChannelErrors, IIBCClientErrors @@ -276,6 +276,26 @@ contract IBCChannelPacketSendRecv is } delete commitments[packetCommitmentKey]; + + if (channel.state == Channel.State.STATE_FLUSHING) { + Timeout.Data memory timeout = channelStorage.counterpartyUpgradeTimeout; + if (!timeout.height.isZero() || timeout.timestamp != 0) { + if ( + !timeout.height.isZero() && hostHeight().gte(timeout.height) + || timeout.timestamp != 0 && hostTimestamp() >= timeout.timestamp + ) { + restoreChannel(msg_.packet.sourcePort, msg_.packet.sourceChannel, UpgradeHandshakeError.Timeout); + } else if ( + canTransitionToFlushComplete( + channel.ordering, msg_.packet.sourcePort, msg_.packet.sourceChannel, channel.upgrade_sequence + ) + ) { + channel.state = Channel.State.STATE_FLUSHCOMPLETE; + updateChannelCommitment(msg_.packet.sourcePort, msg_.packet.sourceChannel, channel); + } + } + } + emit AcknowledgePacket(msg_.packet, msg_.acknowledgement); lookupModuleByChannel(msg_.packet.sourcePort, msg_.packet.sourceChannel).onAcknowledgementPacket( msg_.packet, msg_.acknowledgement, _msgSender() diff --git a/contracts/core/04-channel/IBCChannelPacketTimeout.sol b/contracts/core/04-channel/IBCChannelPacketTimeout.sol index 9e7a04ab..677ed578 100644 --- a/contracts/core/04-channel/IBCChannelPacketTimeout.sol +++ b/contracts/core/04-channel/IBCChannelPacketTimeout.sol @@ -3,20 +3,21 @@ pragma solidity ^0.8.20; import {Height} from "../../proto/Client.sol"; import {ConnectionEnd} from "../../proto/Connection.sol"; -import {Channel, ChannelCounterparty} from "../../proto/Channel.sol"; +import {Channel, ChannelCounterparty, Timeout} from "../../proto/Channel.sol"; import {ILightClient} from "../02-client/ILightClient.sol"; import {IBCHeight} from "../02-client/IBCHeight.sol"; import {IBCChannelLib} from "./IBCChannelLib.sol"; +import {IBCChannelUpgradeBase} from "./IBCChannelUpgrade.sol"; import {IBCCommitment} from "../24-host/IBCCommitment.sol"; -import {IBCModuleManager} from "../26-router/IBCModuleManager.sol"; import {IIBCChannelPacketTimeout} from "./IIBCChannel.sol"; import {IIBCChannelErrors} from "./IIBCChannelErrors.sol"; -contract IBCChannelPacketTimeout is IBCModuleManager, IIBCChannelPacketTimeout, IIBCChannelErrors { +contract IBCChannelPacketTimeout is IBCChannelUpgradeBase, IIBCChannelPacketTimeout, IIBCChannelErrors { using IBCHeight for Height.Data; function timeoutPacket(MsgTimeoutPacket calldata msg_) external { - Channel.Data storage channel = getChannelStorage()[msg_.packet.sourcePort][msg_.packet.sourceChannel].channel; + ChannelStorage storage channelStorage = getChannelStorage()[msg_.packet.sourcePort][msg_.packet.sourceChannel]; + Channel.Data storage channel = channelStorage.channel; if (channel.state == Channel.State.STATE_UNINITIALIZED_UNSPECIFIED) { revert IBCChannelUnexpectedChannelState(channel.state); } @@ -99,8 +100,6 @@ contract IBCChannelPacketTimeout is IBCModuleManager, IIBCChannelPacketTimeout, msg_.proofHeight ); } - channel.state = Channel.State.STATE_CLOSED; - updateChannelCommitment(msg_.packet.sourcePort, msg_.packet.sourceChannel); } else if (channel.ordering == Channel.Order.ORDER_UNORDERED) { bytes memory path = IBCCommitment.packetReceiptCommitmentPathCalldata( msg_.packet.destinationPort, msg_.packet.destinationChannel, msg_.packet.sequence @@ -129,6 +128,35 @@ contract IBCChannelPacketTimeout is IBCModuleManager, IIBCChannelPacketTimeout, msg_.packet.sourcePort, msg_.packet.sourceChannel, msg_.packet.sequence )]; + if (channel.state == Channel.State.STATE_FLUSHING) { + Timeout.Data memory timeout = channelStorage.counterpartyUpgradeTimeout; + if (!timeout.height.isZero() || timeout.timestamp != 0) { + if ( + !timeout.height.isZero() && hostHeight().gte(timeout.height) + || timeout.timestamp != 0 && hostTimestamp() >= timeout.timestamp + ) { + restoreChannel(msg_.packet.sourcePort, msg_.packet.sourceChannel, UpgradeHandshakeError.Timeout); + } else if ( + canTransitionToFlushComplete( + channel.ordering, msg_.packet.sourcePort, msg_.packet.sourceChannel, channel.upgrade_sequence + ) + ) { + channel.state = Channel.State.STATE_FLUSHCOMPLETE; + updateChannelCommitment(msg_.packet.sourcePort, msg_.packet.sourceChannel, channel); + } + } + } + + if (channel.ordering == Channel.Order.ORDER_ORDERED) { + if (channel.state == Channel.State.STATE_FLUSHING) { + delete channelStorage.upgrade; + deleteUpgradeCommitment(msg_.packet.sourcePort, msg_.packet.sourceChannel); + revertCounterpartyUpgrade(channelStorage); + } + channel.state = Channel.State.STATE_CLOSED; + updateChannelCommitment(msg_.packet.sourcePort, msg_.packet.sourceChannel, channel); + } + lookupModuleByChannel(msg_.packet.sourcePort, msg_.packet.sourceChannel).onTimeoutPacket( msg_.packet, _msgSender() ); @@ -290,12 +318,4 @@ contract IBCChannelPacketTimeout is IBCModuleManager, IIBCChannelPacketTimeout, return (timeDelay + hostStorage.expectedTimePerBlock - 1) / hostStorage.expectedTimePerBlock; } } - - /** - * @dev updateChannelCommitment updates the channel commitment for the given port and channel - */ - function updateChannelCommitment(string memory portId, string memory channelId) private { - getCommitments()[IBCCommitment.channelCommitmentKey(portId, channelId)] = - keccak256(Channel.encode(getChannelStorage()[portId][channelId].channel)); - } } diff --git a/contracts/core/04-channel/IBCChannelUpgrade.sol b/contracts/core/04-channel/IBCChannelUpgrade.sol index d4a0409c..faf5044e 100644 --- a/contracts/core/04-channel/IBCChannelUpgrade.sol +++ b/contracts/core/04-channel/IBCChannelUpgrade.sol @@ -36,7 +36,6 @@ abstract contract IBCChannelUpgradeBase is IBCModuleManager, IIBCChannelUpgradeB delete channelStorage.upgrade; revertCounterpartyUpgrade(channelStorage); - delete channelStorage.counterpartyUpgradeTimeout; deleteUpgradeCommitment(portId, channelId); updateChannelCommitment(portId, channelId, channel); diff --git a/contracts/core/24-host/IBCHost.sol b/contracts/core/24-host/IBCHost.sol index 7f2c57b8..db80fe01 100644 --- a/contracts/core/24-host/IBCHost.sol +++ b/contracts/core/24-host/IBCHost.sol @@ -1,23 +1,33 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.20; +import {Height} from "../../proto/Client.sol"; import {ILightClient} from "../02-client/ILightClient.sol"; import {IIBCClientErrors} from "../02-client/IIBCClientErrors.sol"; import {IBCStore} from "./IBCStore.sol"; import {IIBCHostErrors} from "./IIBCHostErrors.sol"; contract IBCHost is IBCStore, IIBCHostErrors { - // It represents the prefix of the commitment proof(https://github.com/cosmos/ibc/tree/main/spec/core/ics-023-vector-commitments#prefix). - // In ibc-solidity, the prefix is not required, but for compatibility with ibc-go this must be a non-empty value. + /// @dev It represents the prefix of the commitment proof(https://github.com/cosmos/ibc/tree/main/spec/core/ics-023-vector-commitments#prefix). + /// In ibc-solidity, the prefix is not required, but for compatibility with ibc-go this must be a non-empty value. bytes internal constant DEFAULT_COMMITMENT_PREFIX = bytes("ibc"); + /// @dev It represents the default revision number. + uint64 internal constant DEFAULT_REVISION_NUMBER = 0; /** * @dev hostTimestamp returns the current timestamp(Unix time in nanoseconds) of the host chain. */ - function hostTimestamp() internal view virtual returns (uint64) { + function hostTimestamp() internal view returns (uint64) { return uint64(block.timestamp) * 1e9; } + /** + * @dev hostHeight returns the current height of the host chain. + */ + function hostHeight() internal view returns (Height.Data memory) { + return Height.Data({revision_number: _getRevisionNumber(), revision_height: uint64(block.number)}); + } + /** * @dev checkAndGetClient returns the client implementation for the given client ID. */ @@ -35,4 +45,11 @@ contract IBCHost is IBCStore, IIBCHostErrors { function _getCommitmentPrefix() internal view virtual returns (bytes memory) { return DEFAULT_COMMITMENT_PREFIX; } + + /** + * @dev _getRevisionNumber returns the revision number of the host chain. + */ + function _getRevisionNumber() internal view virtual returns (uint64) { + return DEFAULT_REVISION_NUMBER; + } } diff --git a/tests/foundry/src/ICS04Upgrade.t.sol b/tests/foundry/src/ICS04Upgrade.t.sol index 073bd9a0..c8d8ff07 100644 --- a/tests/foundry/src/ICS04Upgrade.t.sol +++ b/tests/foundry/src/ICS04Upgrade.t.sol @@ -861,8 +861,8 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper callbacks.flushingAndFlushing.callback = _testUpgradeRelaySuccessAtFlushing; (ChannelInfo memory channel0, ChannelInfo memory channel1) = createMockAppLocalhostChannel(Channel.Order.ORDER_UNORDERED); - mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.None); - mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.None); + mockApp.sendPacket(IBCMockLib.MOCK_PACKET_DATA, channel0.portId, channel0.channelId, H(uint64(getBlockNumber(1))), 0); + mockApp.sendPacket(IBCMockLib.MOCK_PACKET_DATA, channel1.portId, channel1.channelId, H(uint64(getBlockNumber(1))), 0); handshakeUpgradeWithCallbacks( channel0, channel1, @@ -921,7 +921,7 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper callbacks.flushingAndComplete.callback = _testUpgradeRelaySuccessAtCounterpartyFlushComplete; (ChannelInfo memory channel0, ChannelInfo memory channel1) = createMockAppLocalhostChannel(Channel.Order.ORDER_UNORDERED); - mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.None); + mockApp.sendPacket(IBCMockLib.MOCK_PACKET_DATA, channel0.portId, channel0.channelId, H(uint64(getBlockNumber(1))), 0); handshakeUpgradeWithCallbacks( channel0, channel1, @@ -980,14 +980,13 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper ChannelInfo memory channel0, ChannelInfo memory channel1 ) internal returns (bool) { - uint64 seq = mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.None); - Packet memory p0 = getLastSentPacket(handler, channel0.portId, channel0.channelId, vm.getRecordedLogs()); + MockAppRelayResult memory result = mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.None); vm.expectRevert( - abi.encodeWithSelector(IIBCChannelErrors.IBCChannelCannotRecvNextUpgradePacket.selector, seq, uint64(1)) + abi.encodeWithSelector(IIBCChannelErrors.IBCChannelCannotRecvNextUpgradePacket.selector, result.packet.sequence, uint64(1)) ); handler.recvPacket( IIBCChannelRecvPacket.MsgPacketRecv({ - packet: p0, + packet: result.packet, proof: LocalhostClientLib.sentinelProof(), proofHeight: H(getBlockNumber()) }) @@ -1005,14 +1004,14 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper ensureChannelState(handler, channel1, Channel.State.STATE_OPEN); handler.recvPacket( IIBCChannelRecvPacket.MsgPacketRecv({ - packet: p0, + packet: result.packet, proof: LocalhostClientLib.sentinelProof(), proofHeight: H(getBlockNumber()) }) ); ibcHandler.acknowledgePacket( IIBCChannelAcknowledgePacket.MsgPacketAcknowledgement({ - packet: p0, + packet: result.packet, acknowledgement: getLastWrittenAcknowledgement(handler, vm.getRecordedLogs()).acknowledgement, proof: LocalhostClientLib.sentinelProof(), proofHeight: H(getBlockNumber()) @@ -1021,6 +1020,222 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper return false; } + function testFlushCompletePacketAcknowledgementOrdered() public { + vm.recordLogs(); + HandshakeCallbacks memory callbacks = emptyCallbacks(); + callbacks.flushingAndComplete.callback = _breakCallback; + (ChannelInfo memory channel0, ChannelInfo memory channel1) = + createMockAppLocalhostChannel(Channel.Order.ORDER_ORDERED); + + MockAppRelayResult memory res1 = mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.RecvPacket); + handshakeUpgradeWithCallbacks( + channel0, + channel1, + validProposals( + Channel.Order.ORDER_ORDERED, channel0.connectionId, channel1.connectionId, MOCK_APP_VERSION_2 + ), + HandshakeFlow(false, false), + callbacks + ); + ensureChannelState(ibcHandler, channel1, Channel.State.STATE_FLUSHING); + ibcHandler.channelUpgradeConfirm( + IIBCChannelUpgradeBase.MsgChannelUpgradeConfirm({ + portId: channel1.portId, + channelId: channel1.channelId, + counterpartyChannelState: Channel.State.STATE_FLUSHCOMPLETE, + counterpartyUpgrade: getCounterpartyUpgrade(channel0.portId, channel0.channelId), + proofs: upgradeLocalhostProofs() + }) + ); + ensureChannelState(ibcHandler, channel1, Channel.State.STATE_FLUSHING); + ibcHandler.acknowledgePacket( + IIBCChannelAcknowledgePacket.MsgPacketAcknowledgement({ + packet: res1.packet, + acknowledgement: res1.ack, + proof: LocalhostClientLib.sentinelProof(), + proofHeight: H(getBlockNumber()) + }) + ); + ensureChannelState(ibcHandler, channel1, Channel.State.STATE_FLUSHCOMPLETE); + } + + function testFlushCompletePacketAcknowledgementUnordered() public { + vm.recordLogs(); + HandshakeCallbacks memory callbacks = emptyCallbacks(); + callbacks.flushingAndComplete.callback = _breakCallback; + (ChannelInfo memory channel0, ChannelInfo memory channel1) = + createMockAppLocalhostChannel(Channel.Order.ORDER_UNORDERED); + + MockAppRelayResult memory res0 = mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.RecvPacket); + uint64 upgradeSequence = handshakeUpgradeWithCallbacks( + channel0, + channel1, + validProposals( + Channel.Order.ORDER_UNORDERED, channel0.connectionId, channel1.connectionId, MOCK_APP_VERSION_2 + ), + HandshakeFlow(false, false), + callbacks + ); + ensureChannelState(ibcHandler, channel0, Channel.State.STATE_FLUSHING); + IIBCChannelUpgradableModule(address(ibcHandler.getIBCModuleByPort(channel0.portId))).allowTransitionToFlushComplete(channel0.portId, channel0.channelId, upgradeSequence); + ibcHandler.acknowledgePacket( + IIBCChannelAcknowledgePacket.MsgPacketAcknowledgement({ + packet: res0.packet, + acknowledgement: res0.ack, + proof: LocalhostClientLib.sentinelProof(), + proofHeight: H(getBlockNumber()) + }) + ); + ensureChannelState(ibcHandler, channel0, Channel.State.STATE_FLUSHCOMPLETE); + } + + function testFlushingClosePacketTimeoutOrdered() public { + vm.recordLogs(); + HandshakeCallbacks memory callbacks = emptyCallbacks(); + callbacks.flushingAndComplete.callback = _breakCallback; + (ChannelInfo memory channel0, ChannelInfo memory channel1) = + createMockAppLocalhostChannel(Channel.Order.ORDER_ORDERED); + + MockAppRelayResult memory res1 = mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.None); + handshakeUpgradeWithCallbacks( + channel0, + channel1, + validProposals( + Channel.Order.ORDER_ORDERED, channel0.connectionId, channel1.connectionId, MOCK_APP_VERSION_2, + Timeout.Data({height: H(10), timestamp: 0}), + Timeout.Data({height: H(10), timestamp: 0}) + ), + HandshakeFlow(false, false), + callbacks + ); + vm.roll(getBlockNumber() + 1); + ibcHandler.timeoutPacket( + IIBCChannelPacketTimeout.MsgTimeoutPacket({ + packet: res1.packet, + proof: LocalhostClientLib.sentinelProof(), + proofHeight: H(getBlockNumber()), + nextSequenceRecv: 1 + }) + ); + ensureChannelState(ibcHandler, channel1, Channel.State.STATE_CLOSED); + (, bool found) = ibcHandler.getChannelUpgrade(channel1.portId, channel1.channelId); + assertFalse(found); + assertEq(ibcHandler.getCommitment(IBCCommitment.channelUpgradeCommitmentKey(channel1.portId, channel1.channelId)), bytes32(0)); + } + + function testFlushCompletePacketTimeoutUnordered() public { + vm.recordLogs(); + HandshakeCallbacks memory callbacks = emptyCallbacks(); + callbacks.flushingAndComplete.callback = _breakCallback; + (ChannelInfo memory channel0, ChannelInfo memory channel1) = + createMockAppLocalhostChannel(Channel.Order.ORDER_UNORDERED); + + MockAppRelayResult memory res0 = mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.None); + uint64 upgradeSequence = handshakeUpgradeWithCallbacks( + channel0, + channel1, + validProposals( + Channel.Order.ORDER_UNORDERED, channel0.connectionId, channel1.connectionId, MOCK_APP_VERSION_2, + Timeout.Data({height: H(10), timestamp: 0}), + Timeout.Data({height: H(10), timestamp: 0}) + ), + HandshakeFlow(false, false), + callbacks + ); + vm.roll(getBlockNumber() + 1); + ensureChannelState(ibcHandler, channel0, Channel.State.STATE_FLUSHING); + IIBCChannelUpgradableModule(address(ibcHandler.getIBCModuleByPort(channel0.portId))).allowTransitionToFlushComplete(channel0.portId, channel0.channelId, upgradeSequence); + ibcHandler.timeoutPacket( + IIBCChannelPacketTimeout.MsgTimeoutPacket({ + packet: res0.packet, + proof: LocalhostClientLib.sentinelProof(), + proofHeight: H(getBlockNumber()), + nextSequenceRecv: 0 + }) + ); + ensureChannelState(ibcHandler, channel0, Channel.State.STATE_FLUSHCOMPLETE); + (, bool found) = ibcHandler.getChannelUpgrade(channel1.portId, channel1.channelId); + assertTrue(found); + } + + function testFlushingClosedCounterpartyUpgradeTimeoutOrdered() public { + vm.recordLogs(); + HandshakeCallbacks memory callbacks = emptyCallbacks(); + callbacks.flushingAndComplete.callback = _breakCallback; + (ChannelInfo memory channel0, ChannelInfo memory channel1) = + createMockAppLocalhostChannel(Channel.Order.ORDER_ORDERED); + + MockAppRelayResult memory res1 = mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.None); + handshakeUpgradeWithCallbacks( + channel0, + channel1, + validProposals( + Channel.Order.ORDER_ORDERED, channel0.connectionId, channel1.connectionId, MOCK_APP_VERSION_2, + Timeout.Data({height: H(getBlockNumber(1)), timestamp: 0}), + Timeout.Data({height: H(getBlockNumber(1)), timestamp: 0}) + ), + HandshakeFlow(false, false), + callbacks + ); + ensureChannelState(ibcHandler, channel1, Channel.State.STATE_FLUSHING); + vm.roll(getBlockNumber() + 1); + ibcHandler.timeoutPacket( + IIBCChannelPacketTimeout.MsgTimeoutPacket({ + packet: res1.packet, + proof: LocalhostClientLib.sentinelProof(), + proofHeight: H(getBlockNumber()), + nextSequenceRecv: 1 + }) + ); + ensureChannelState(ibcHandler, channel1, Channel.State.STATE_CLOSED); + (, bool found) = ibcHandler.getChannelUpgrade(channel1.portId, channel1.channelId); + assertFalse(found); + assertEq(ibcHandler.getCommitment(IBCCommitment.channelUpgradeCommitmentKey(channel1.portId, channel1.channelId)), bytes32(0)); + } + + function testFlushingRestoreCounterpartyUpgradeTimeoutUnordered() public { + vm.recordLogs(); + HandshakeCallbacks memory callbacks = emptyCallbacks(); + callbacks.flushingAndComplete.callback = _breakCallback; + (ChannelInfo memory channel0, ChannelInfo memory channel1) = + createMockAppLocalhostChannel(Channel.Order.ORDER_UNORDERED); + + MockAppRelayResult memory res0 = mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.None); + handshakeUpgradeWithCallbacks( + channel0, + channel1, + validProposals( + Channel.Order.ORDER_UNORDERED, channel0.connectionId, channel1.connectionId, MOCK_APP_VERSION_2, + Timeout.Data({height: H(getBlockNumber(1)), timestamp: 0}), + Timeout.Data({height: H(getBlockNumber(1)), timestamp: 0}) + ), + HandshakeFlow(false, false), + callbacks + ); + vm.roll(getBlockNumber() + 1); + ensureChannelState(ibcHandler, channel0, Channel.State.STATE_FLUSHING); + ibcHandler.timeoutPacket( + IIBCChannelPacketTimeout.MsgTimeoutPacket({ + packet: res0.packet, + proof: LocalhostClientLib.sentinelProof(), + proofHeight: H(getBlockNumber()), + nextSequenceRecv: 0 + }) + ); + ensureChannelState(ibcHandler, channel0, Channel.State.STATE_OPEN); + (, bool found) = ibcHandler.getChannelUpgrade(channel0.portId, channel0.channelId); + assertFalse(found); + assertEq(ibcHandler.getCommitment(IBCCommitment.channelUpgradeCommitmentKey(channel0.portId, channel0.channelId)), bytes32(0)); + } + + function _breakCallback( + IIBCHandler, + ChannelInfo memory, + ChannelInfo memory + ) internal returns (bool) { + return false; + } + function testUpgradeToUnordered() public { vm.recordLogs(); Channel.Order[2] memory orders = [Channel.Order.ORDER_ORDERED, Channel.Order.ORDER_UNORDERED]; @@ -1040,9 +1255,9 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper } { (ChannelInfo memory channel0, ChannelInfo memory channel1) = createMockAppLocalhostChannel(orders[i]); - assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 1); - assertEq(mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 1); - assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 2); + assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 1); + assertEq(mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 1); + assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 2); handshakeUpgrade( channel0, channel1, @@ -1056,9 +1271,9 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper } { (ChannelInfo memory channel0, ChannelInfo memory channel1) = createMockAppLocalhostChannel(orders[i]); - assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 1); - assertEq(mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 1); - assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.None), 2); + assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 1); + assertEq(mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 1); + assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.None).packet.sequence, 2); handshakeUpgrade( channel0, channel1, @@ -1072,9 +1287,9 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper } { (ChannelInfo memory channel0, ChannelInfo memory channel1) = createMockAppLocalhostChannel(orders[i]); - assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 1); - assertEq(mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 1); - assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.RecvPacket), 2); + assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 1); + assertEq(mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 1); + assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.RecvPacket).packet.sequence, 2); handshakeUpgrade( channel0, channel1, @@ -1108,8 +1323,8 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper } { (ChannelInfo memory channel0, ChannelInfo memory channel1) = createMockAppLocalhostChannel(orders[i]); - assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 1); - assertEq(mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 1); + assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 1); + assertEq(mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 1); handshakeUpgrade( channel0, channel1, @@ -1123,9 +1338,9 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper } { (ChannelInfo memory channel0, ChannelInfo memory channel1) = createMockAppLocalhostChannel(orders[i]); - assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 1); - assertEq(mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 1); - assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 2); + assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 1); + assertEq(mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 1); + assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 2); handshakeUpgrade( channel0, channel1, @@ -1139,9 +1354,9 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper } { (ChannelInfo memory channel0, ChannelInfo memory channel1) = createMockAppLocalhostChannel(orders[i]); - assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 1); - assertEq(mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 1); - assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.None), 2); + assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 1); + assertEq(mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 1); + assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.None).packet.sequence, 2); handshakeUpgrade( channel0, channel1, @@ -1155,9 +1370,9 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper } { (ChannelInfo memory channel0, ChannelInfo memory channel1) = createMockAppLocalhostChannel(orders[i]); - assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 1); - assertEq(mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket), 1); - assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.RecvPacket), 2); + assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 1); + assertEq(mockAppRelay(channel1, channel0, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.AckPacket).packet.sequence, 1); + assertEq(mockAppRelay(channel0, channel1, IBCMockLib.MOCK_PACKET_DATA, RelayPhase.RecvPacket).packet.sequence, 2); handshakeUpgrade( channel0, channel1, @@ -1208,6 +1423,30 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper }); } + function validProposals( + Channel.Order order, + string memory channel0ConnectionId, + string memory channel1ConnectionId, + string memory appVersion, + Timeout.Data memory timeout0, + Timeout.Data memory timeout1 + ) internal view returns (UpgradeProposals memory) { + return UpgradeProposals({ + p0: UpgradeProposal({ + order: order, + connectionId: channel0ConnectionId, + version: appVersion, + timeout: timeout0 + }), + p1: UpgradeProposal({ + order: order, + connectionId: channel1ConnectionId, + version: appVersion, + timeout: timeout1 + }) + }); + } + struct HandshakeFlow { bool crossingHello; bool fastPath; @@ -1437,6 +1676,16 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper ); ensureChannelState(ibcHandler, channel0, Channel.State.STATE_FLUSHCOMPLETE); + if (!callbacks.flushingAndComplete.reverse) { + if (!callbacks.flushingAndComplete.callback(ibcHandler, channel1, channel0)) { + return upgradeSequence; + } + } else { + if (!callbacks.flushingAndComplete.callback(ibcHandler, channel0, channel1)) { + return upgradeSequence; + } + } + // Confirm@channel1: FLUSHING -> OPEN assertTrue( ibcHandler.channelUpgradeConfirm( @@ -1481,6 +1730,16 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper ); ensureChannelState(ibcHandler, channel0, Channel.State.STATE_FLUSHCOMPLETE); + if (!callbacks.flushingAndComplete.reverse) { + if (!callbacks.flushingAndComplete.callback(ibcHandler, channel1, channel0)) { + return upgradeSequence; + } + } else { + if (!callbacks.flushingAndComplete.callback(ibcHandler, channel0, channel1)) { + return upgradeSequence; + } + } + assertFalse(ibcHandler.getCanTransitionToFlushComplete(channel1.portId, channel1.channelId)); IIBCChannelUpgradableModule(address(ibcHandler.getIBCModuleByPort(channel1.portId))).allowTransitionToFlushComplete(channel1.portId, channel1.channelId, upgradeSequence); assertTrue(ibcHandler.getCanTransitionToFlushComplete(channel1.portId, channel1.channelId)); @@ -1852,44 +2111,56 @@ contract TestICS04Upgrade is ICS04UpgradeTestHelper, ICS04PacketEventTestHelper AckPacket } + struct MockAppRelayResult { + Packet packet; + bytes ack; + } + function mockAppRelay(ChannelInfo memory ca, ChannelInfo memory cb, bytes memory packetData, RelayPhase phase) private - returns (uint64) + returns (MockAppRelayResult memory result) + { + return mockAppRelay(ca, cb, packetData, phase, Timeout.Data({height: H(getBlockNumber(1)), timestamp: 0})); + } + + function mockAppRelay(ChannelInfo memory ca, ChannelInfo memory cb, bytes memory packetData, RelayPhase phase, Timeout.Data memory timeout) + private + returns (MockAppRelayResult memory result) { - uint64 sequence = mockApp.sendPacket(packetData, ca.portId, ca.channelId, H(uint64(getBlockNumber(1))), 0); + uint64 sequence = mockApp.sendPacket(packetData, ca.portId, ca.channelId, timeout.height, timeout.timestamp); + result.packet = getLastSentPacket(ibcHandler, ca.portId, ca.channelId, vm.getRecordedLogs()); if (phase == RelayPhase.None) { - return sequence; + return result; } - Packet memory packet = getLastSentPacket(ibcHandler, ca.portId, ca.channelId, vm.getRecordedLogs()); ibcHandler.recvPacket( IIBCChannelRecvPacket.MsgPacketRecv({ - packet: packet, + packet: result.packet, proof: LocalhostClientLib.sentinelProof(), proofHeight: Height.nil() }) ); Vm.Log[] memory logs = vm.getRecordedLogs(); - assertEq(abi.encode(packet), abi.encode(getLastRecvPacket(ibcHandler, logs))); + assertEq(abi.encode(result.packet), abi.encode(getLastRecvPacket(ibcHandler, logs))); if (keccak256(packetData) == keccak256(IBCMockLib.MOCK_ASYNC_PACKET_DATA)) { mockApp.writeAcknowledgement(cb.portId, cb.channelId, sequence); logs = vm.getRecordedLogs(); } - if (phase == RelayPhase.RecvPacket) { - return sequence; - } WriteAcknolwedgement memory ack = getLastWrittenAcknowledgement(ibcHandler, logs); assertEq(ack.sequence, sequence); - + result.ack = ack.acknowledgement; + if (phase == RelayPhase.RecvPacket) { + return result; + } ibcHandler.acknowledgePacket( IIBCChannelAcknowledgePacket.MsgPacketAcknowledgement({ - packet: packet, + packet: result.packet, acknowledgement: ack.acknowledgement, proof: LocalhostClientLib.sentinelProof(), proofHeight: Height.nil() }) ); assertTrue(ibcHandler.getPacketCommitment(ca.portId, ca.channelId, sequence) == bytes32(0)); - return sequence; + return result; } // ------------------------------ Handshake Callbacks ------------------------------ //