From fa091a58e226503007c9c5a81d0f21ca8380771c Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Tue, 6 Aug 2024 19:05:58 +0200 Subject: [PATCH] lint and cleanup --- abi/ICS26Router.json | 16 +++ e2e/artifacts/genesis.json | 2 +- e2e/interchaintestv8/e2esuite/grpc_query.go | 3 +- e2e/interchaintestv8/ibc_eureka_test.go | 19 +-- .../types/ics26router/contract.go | 2 +- script/E2ETestDeploy.s.sol | 6 +- src/ICS20Transfer.sol | 8 +- src/ICS26Router.sol | 17 ++- src/errors/IICS26RouterErrors.sol | 8 +- src/utils/ICS24Host.sol | 26 ++-- test/ICS20TransferTest.t.sol | 41 +++++-- test/ICS24Host.t.sol | 54 +++++++++ test/ICS26RouterTest.t.sol | 10 +- test/IntegrationTest.t.sol | 113 +++++++++++++++--- 14 files changed, 263 insertions(+), 62 deletions(-) create mode 100644 test/ICS24Host.t.sol diff --git a/abi/ICS26Router.json b/abi/ICS26Router.json index 3f169182..b0c81943 100644 --- a/abi/ICS26Router.json +++ b/abi/ICS26Router.json @@ -1073,6 +1073,22 @@ "name": "ReentrancyGuardReentrantCall", "inputs": [] }, + { + "type": "error", + "name": "SafeCastOverflowedUintDowncast", + "inputs": [ + { + "name": "bits", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + } + ] + }, { "type": "error", "name": "StringsInsufficientHexLength", diff --git a/e2e/artifacts/genesis.json b/e2e/artifacts/genesis.json index aa50f724..7173e3eb 100644 --- a/e2e/artifacts/genesis.json +++ b/e2e/artifacts/genesis.json @@ -1,6 +1,6 @@ { "trustedClientState": "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000012754500000000000000000000000000000000000000000000000000000000001baf800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000673696d642d310000000000000000000000000000000000000000000000000000", - "trustedConsensusState": "0000000000000000000000000000000000000000000000000000000066b0b92862093024debc13c1fab2cce33d04895624033edca5cc22f7266085eca4c6461606c79e13ccd4e467cae775cbc073ccca2735947069c0255e78337c50dcdee823", + "trustedConsensusState": "0000000000000000000000000000000000000000000000000000000066b24dfc0a376dcfbf32aa896d68958779c5d6ef4c73b5f0eb8c55e74eaeb1399a9f8250b29f00b95808b8b596d78d77bbb1f263250537c5561baf2dc879ae5226c9ccd7", "updateClientVkey": "0x0068b9d316aced51c5923b2d50692f4a6a9bfefcd89392914b90e77545727fbe", "membershipVkey": "0x00a4245d249b5c35c9782cc899c8e370a35d5d928187dc9e7acbab7096764b72", "ucAndMembershipVkey": "0x00cea834e3408d45d29080a3146e4fb1fd0c06503d655bd787219caac86cf59c" diff --git a/e2e/interchaintestv8/e2esuite/grpc_query.go b/e2e/interchaintestv8/e2esuite/grpc_query.go index 032948d2..ed7630c2 100644 --- a/e2e/interchaintestv8/e2esuite/grpc_query.go +++ b/e2e/interchaintestv8/e2esuite/grpc_query.go @@ -3,7 +3,6 @@ package e2esuite import ( "context" "fmt" - abci "github.com/cometbft/cometbft/abci/types" "github.com/cosmos/gogoproto/proto" "google.golang.org/grpc" @@ -13,6 +12,8 @@ import ( msgv1 "cosmossdk.io/api/cosmos/msg/v1" reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1" + abci "github.com/cometbft/cometbft/abci/types" + "github.com/strangelove-ventures/interchaintest/v8/chain/cosmos" ) diff --git a/e2e/interchaintestv8/ibc_eureka_test.go b/e2e/interchaintestv8/ibc_eureka_test.go index 34c49776..dad2fe5a 100644 --- a/e2e/interchaintestv8/ibc_eureka_test.go +++ b/e2e/interchaintestv8/ibc_eureka_test.go @@ -2,13 +2,10 @@ package main import ( "context" - sdkmath "cosmossdk.io/math" "crypto/ecdsa" "encoding/hex" "encoding/json" "fmt" - abci "github.com/cometbft/cometbft/abci/types" - sdk "github.com/cosmos/cosmos-sdk/types" "math/big" "os" "strconv" @@ -23,8 +20,13 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" + sdkmath "cosmossdk.io/math" + + sdk "github.com/cosmos/cosmos-sdk/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + abci "github.com/cometbft/cometbft/abci/types" + transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types" channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" @@ -357,7 +359,6 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { s.Require().NoError(err) s.Require().Equal(transferAmount, ics20TransferBalance) })) - })) // TODO: When using a non-mock light client on the cosmos side, the client there needs to be updated at this point @@ -511,8 +512,8 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { Data: []byte(packetCommitmentPath), Prove: true, }) - _ = err - _ = storeProofResp + s.Require().NoError(err) + s.Require().Equal(uint32(0), storeProofResp.Code) proofHeight, ucAndMemProof, err := operator.UpdateClientAndMembershipProof( uint64(trustedHeight), uint64(latestHeight), packetCommitmentPath, "--trust-level", testvalues.DefaultTrustLevel.String(), @@ -523,7 +524,7 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { msg := ics26router.IICS26RouterMsgsMsgRecvPacket{ Packet: ics26router.IICS26RouterMsgsPacket{ Sequence: uint32(returnPacket.Sequence), - TimeoutTimestamp: returnPacket.TimeoutTimestamp / 1_000_000_000, // Nano to seconds + TimeoutTimestamp: returnPacket.TimeoutTimestamp, SourcePort: returnPacket.SourcePort, SourceChannel: returnPacket.SourceChannel, DestPort: returnPacket.DestinationPort, @@ -569,12 +570,12 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { txResp, err := s.BroadcastMessages(ctx, simd, s.UserB, 200_000, &channeltypes.MsgAcknowledgement{ Packet: returnPacket, Acknowledgement: returnWriteAckEvent.Acknowledgement, - ProofAcked: []byte("doesn't matter"), + ProofAcked: []byte("doesn't matter"), // Because mock light client ProofHeight: clienttypes.Height{}, Signer: s.UserB.FormattedAddress(), }) s.Require().NoError(err) - s.Require().Equal(0, txResp.Code) + s.Require().Equal(uint32(0), txResp.Code) })) } diff --git a/e2e/interchaintestv8/types/ics26router/contract.go b/e2e/interchaintestv8/types/ics26router/contract.go index 39a4b298..74abab31 100644 --- a/e2e/interchaintestv8/types/ics26router/contract.go +++ b/e2e/interchaintestv8/types/ics26router/contract.go @@ -81,7 +81,7 @@ type IICS26RouterMsgsPacket struct { // ContractMetaData contains all meta data concerning the Contract contract. var ContractMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"ics02Client_\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"ackPacket\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.MsgAckPacket\",\"components\":[{\"name\":\"packet\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"acknowledgement\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"proofAcked\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"proofHeight\",\"type\":\"tuple\",\"internalType\":\"structIICS02ClientMsgs.Height\",\"components\":[{\"name\":\"revisionNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"revisionHeight\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"addIBCApp\",\"inputs\":[{\"name\":\"portId\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"getCommitment\",\"inputs\":[{\"name\":\"hashedPath\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getIBCApp\",\"inputs\":[{\"name\":\"portId\",\"type\":\"string\",\"internalType\":\"string\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIIBCApp\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNextSequenceSend\",\"inputs\":[{\"name\":\"portId\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"channelId\",\"type\":\"string\",\"internalType\":\"string\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"recvPacket\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.MsgRecvPacket\",\"components\":[{\"name\":\"packet\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"proofCommitment\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"proofHeight\",\"type\":\"tuple\",\"internalType\":\"structIICS02ClientMsgs.Height\",\"components\":[{\"name\":\"revisionNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"revisionHeight\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"sendPacket\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.MsgSendPacket\",\"components\":[{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"}]}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"timeoutPacket\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.MsgTimeoutPacket\",\"components\":[{\"name\":\"packet\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"proofTimeout\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"proofHeight\",\"type\":\"tuple\",\"internalType\":\"structIICS02ClientMsgs.Height\",\"components\":[{\"name\":\"revisionNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"revisionHeight\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"AckPacket\",\"inputs\":[{\"name\":\"packet\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"acknowledgement\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"IBCAppAdded\",\"inputs\":[{\"name\":\"portId\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"},{\"name\":\"app\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RecvPacket\",\"inputs\":[{\"name\":\"packet\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"SendPacket\",\"inputs\":[{\"name\":\"packet\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"TimeoutPacket\",\"inputs\":[{\"name\":\"packet\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"WriteAcknowledgement\",\"inputs\":[{\"name\":\"packet\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"acknowledgement\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"IBCAppNotFound\",\"inputs\":[{\"name\":\"portId\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"type\":\"error\",\"name\":\"IBCAsyncAcknowledgementNotSupported\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IBCInvalidCounterparty\",\"inputs\":[{\"name\":\"expected\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"actual\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"type\":\"error\",\"name\":\"IBCInvalidPortIdentifier\",\"inputs\":[{\"name\":\"portId\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"type\":\"error\",\"name\":\"IBCInvalidTimeoutTimestamp\",\"inputs\":[{\"name\":\"timeoutTimestamp\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"comparedTimestamp\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"IBCMembershipProofVerificationFailed\",\"inputs\":[{\"name\":\"packet\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"membershipMsg\",\"type\":\"tuple\",\"internalType\":\"structILightClientMsgs.MsgMembership\",\"components\":[{\"name\":\"proof\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"proofHeight\",\"type\":\"tuple\",\"internalType\":\"structIICS02ClientMsgs.Height\",\"components\":[{\"name\":\"revisionNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"revisionHeight\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"path\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"value\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"reason\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"IBCPacketAcknowledgementAlreadyExists\",\"inputs\":[{\"name\":\"path\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"IBCPacketCommitmentAlreadyExists\",\"inputs\":[{\"name\":\"path\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"IBCPacketCommitmentMismatch\",\"inputs\":[{\"name\":\"expected\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"actual\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"IBCPacketCommitmentNotFound\",\"inputs\":[{\"name\":\"path\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"IBCPacketHandlingFailed\",\"inputs\":[{\"name\":\"packet\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"reason\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"IBCPacketReceiptAlreadyExists\",\"inputs\":[{\"name\":\"path\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"IBCPortAlreadyExists\",\"inputs\":[{\"name\":\"portId\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"type\":\"error\",\"name\":\"OwnableInvalidOwner\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"OwnableUnauthorizedAccount\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ReentrancyGuardReentrantCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"StringsInsufficientHexLength\",\"inputs\":[{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"length\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}]", + ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"ics02Client_\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"ackPacket\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.MsgAckPacket\",\"components\":[{\"name\":\"packet\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"acknowledgement\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"proofAcked\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"proofHeight\",\"type\":\"tuple\",\"internalType\":\"structIICS02ClientMsgs.Height\",\"components\":[{\"name\":\"revisionNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"revisionHeight\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"addIBCApp\",\"inputs\":[{\"name\":\"portId\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"app\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"getCommitment\",\"inputs\":[{\"name\":\"hashedPath\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getIBCApp\",\"inputs\":[{\"name\":\"portId\",\"type\":\"string\",\"internalType\":\"string\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"contractIIBCApp\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getNextSequenceSend\",\"inputs\":[{\"name\":\"portId\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"channelId\",\"type\":\"string\",\"internalType\":\"string\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"recvPacket\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.MsgRecvPacket\",\"components\":[{\"name\":\"packet\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"proofCommitment\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"proofHeight\",\"type\":\"tuple\",\"internalType\":\"structIICS02ClientMsgs.Height\",\"components\":[{\"name\":\"revisionNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"revisionHeight\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"sendPacket\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.MsgSendPacket\",\"components\":[{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"}]}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"timeoutPacket\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.MsgTimeoutPacket\",\"components\":[{\"name\":\"packet\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"proofTimeout\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"proofHeight\",\"type\":\"tuple\",\"internalType\":\"structIICS02ClientMsgs.Height\",\"components\":[{\"name\":\"revisionNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"revisionHeight\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"AckPacket\",\"inputs\":[{\"name\":\"packet\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"acknowledgement\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"IBCAppAdded\",\"inputs\":[{\"name\":\"portId\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"},{\"name\":\"app\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"inputs\":[{\"name\":\"previousOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"newOwner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"RecvPacket\",\"inputs\":[{\"name\":\"packet\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"SendPacket\",\"inputs\":[{\"name\":\"packet\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"TimeoutPacket\",\"inputs\":[{\"name\":\"packet\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"WriteAcknowledgement\",\"inputs\":[{\"name\":\"packet\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"acknowledgement\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"type\":\"error\",\"name\":\"IBCAppNotFound\",\"inputs\":[{\"name\":\"portId\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"type\":\"error\",\"name\":\"IBCAsyncAcknowledgementNotSupported\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"IBCInvalidCounterparty\",\"inputs\":[{\"name\":\"expected\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"actual\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"type\":\"error\",\"name\":\"IBCInvalidPortIdentifier\",\"inputs\":[{\"name\":\"portId\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"type\":\"error\",\"name\":\"IBCInvalidTimeoutTimestamp\",\"inputs\":[{\"name\":\"timeoutTimestamp\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"comparedTimestamp\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"IBCMembershipProofVerificationFailed\",\"inputs\":[{\"name\":\"packet\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"membershipMsg\",\"type\":\"tuple\",\"internalType\":\"structILightClientMsgs.MsgMembership\",\"components\":[{\"name\":\"proof\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"proofHeight\",\"type\":\"tuple\",\"internalType\":\"structIICS02ClientMsgs.Height\",\"components\":[{\"name\":\"revisionNumber\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"revisionHeight\",\"type\":\"uint32\",\"internalType\":\"uint32\"}]},{\"name\":\"path\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"value\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"reason\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"IBCPacketAcknowledgementAlreadyExists\",\"inputs\":[{\"name\":\"path\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"IBCPacketCommitmentAlreadyExists\",\"inputs\":[{\"name\":\"path\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"IBCPacketCommitmentMismatch\",\"inputs\":[{\"name\":\"expected\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"actual\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"IBCPacketCommitmentNotFound\",\"inputs\":[{\"name\":\"path\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"IBCPacketHandlingFailed\",\"inputs\":[{\"name\":\"packet\",\"type\":\"tuple\",\"internalType\":\"structIICS26RouterMsgs.Packet\",\"components\":[{\"name\":\"sequence\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"sourcePort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"version\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"name\":\"reason\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"IBCPacketReceiptAlreadyExists\",\"inputs\":[{\"name\":\"path\",\"type\":\"bytes\",\"internalType\":\"bytes\"}]},{\"type\":\"error\",\"name\":\"IBCPortAlreadyExists\",\"inputs\":[{\"name\":\"portId\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"type\":\"error\",\"name\":\"OwnableInvalidOwner\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"OwnableUnauthorizedAccount\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ReentrancyGuardReentrantCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"SafeCastOverflowedUintDowncast\",\"inputs\":[{\"name\":\"bits\",\"type\":\"uint8\",\"internalType\":\"uint8\"},{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"StringsInsufficientHexLength\",\"inputs\":[{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"length\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}]", } // ContractABI is the input ABI used to generate the binding from. diff --git a/script/E2ETestDeploy.s.sol b/script/E2ETestDeploy.s.sol index 83b42165..9051e9ca 100644 --- a/script/E2ETestDeploy.s.sol +++ b/script/E2ETestDeploy.s.sol @@ -25,6 +25,8 @@ struct SP1ICS07TendermintGenesisJson { contract E2ETestDeploy is Script { using stdJson for string; + error E2EInvalidAddress(string addr); + string public constant E2E_FAUCET = "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"; function run() public returns (string memory) { @@ -59,7 +61,9 @@ contract E2ETestDeploy is Script { // Mint some tokens (address addr, bool ok) = ICS20Lib.hexStringToAddress(E2E_FAUCET); - require(ok, "invalid address"); + if (!ok) { + revert E2EInvalidAddress(E2E_FAUCET); + } erc20.mint(addr, 100_000_000_000); vm.stopBroadcast(); diff --git a/src/ICS20Transfer.sol b/src/ICS20Transfer.sol index b7900e3f..dca04ac0 100644 --- a/src/ICS20Transfer.sol +++ b/src/ICS20Transfer.sol @@ -104,12 +104,13 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr bytes memory denom = bytes(packetData.denom); if ( denom.length >= denomPrefix.length - && ICS20Lib.equal(ICS20Lib.slice(denom, 0, denomPrefix.length), denomPrefix) + && ICS20Lib.equal(ICS20Lib.slice(denom, 0, denomPrefix.length), denomPrefix) ) { // sender chain is not the source, unescrow tokens // TODO: Implement escrow balance tracking - string memory unprefixedDenom = string(ICS20Lib.slice(denom, denomPrefix.length, denom.length - denomPrefix.length)); + string memory unprefixedDenom = + string(ICS20Lib.slice(denom, denomPrefix.length, denom.length - denomPrefix.length)); (address tokenContract, bool tokenContractConvertSuccess) = ICS20Lib.hexStringToAddress(unprefixedDenom); if (!tokenContractConvertSuccess) { return ICS20Lib.errorAck(abi.encodePacked("invalid token contract: ", unprefixedDenom)); @@ -120,7 +121,8 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr // sender chain is the source, mint vouchers // TODO: Implement escrow balance tracking // TODO: Implement creating (new erc20 contracts), looking up and minting of vouchers - revert(string(denomPrefix)); + // solhint-disable-next-line + revert("not supported: sender denom is source"); } emit ICS20ReceiveTransfer(packetData); diff --git a/src/ICS26Router.sol b/src/ICS26Router.sol index 77c525b4..b84321db 100644 --- a/src/ICS26Router.sol +++ b/src/ICS26Router.sol @@ -123,21 +123,28 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors, Ree value: abi.encodePacked(commitmentBz) }); - try ics02Client.getClient(msg_.packet.destChannel).membership(membershipMsg) { - } catch (bytes memory reason) { + // solhint-disable-next-line no-empty-blocks + try ics02Client.getClient(msg_.packet.destChannel).membership(membershipMsg) { } + catch (bytes memory reason) { revert IBCMembershipProofVerificationFailed(msg_.packet, membershipMsg, reason); } - if (msg_.packet.timeoutTimestamp <= block.timestamp) { + + uint64 timeoutTimestamp = msg_.packet.timeoutTimestamp; + if (!ICS24Host.isTimestampInSeconds(timeoutTimestamp)) { + timeoutTimestamp = uint64(timeoutTimestamp / 1_000_000_000); + } + if (timeoutTimestamp <= block.timestamp) { revert IBCInvalidTimeoutTimestamp(msg_.packet.timeoutTimestamp, block.timestamp); } - try app.onRecvPacket(IIBCAppCallbacks.OnRecvPacketCallback({ packet: msg_.packet, relayer: msg.sender })) returns (bytes memory ack) { + try app.onRecvPacket(IIBCAppCallbacks.OnRecvPacketCallback({ packet: msg_.packet, relayer: msg.sender })) + returns (bytes memory ack) { if (ack.length == 0) { revert IBCAsyncAcknowledgementNotSupported(); } writeAcknowledgement(msg_.packet, ack); - } catch (bytes memory reason) { + } catch (bytes memory reason) { revert IBCPacketHandlingFailed(msg_.packet, reason); } diff --git a/src/errors/IICS26RouterErrors.sol b/src/errors/IICS26RouterErrors.sol index 50e12150..817691cd 100644 --- a/src/errors/IICS26RouterErrors.sol +++ b/src/errors/IICS26RouterErrors.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.25; -import {IICS26RouterMsgs} from "../msgs/IICS26RouterMsgs.sol"; -import {ILightClientMsgs} from "../msgs/ILightClientMsgs.sol"; +import { IICS26RouterMsgs } from "../msgs/IICS26RouterMsgs.sol"; +import { ILightClientMsgs } from "../msgs/ILightClientMsgs.sol"; interface IICS26RouterErrors { /// @param portId port identifier @@ -24,7 +24,9 @@ interface IICS26RouterErrors { error IBCAppNotFound(string portId); - error IBCMembershipProofVerificationFailed(IICS26RouterMsgs.Packet packet, ILightClientMsgs.MsgMembership membershipMsg, bytes reason); + error IBCMembershipProofVerificationFailed( + IICS26RouterMsgs.Packet packet, ILightClientMsgs.MsgMembership membershipMsg, bytes reason + ); error IBCPacketHandlingFailed(IICS26RouterMsgs.Packet packet, bytes reason); } diff --git a/src/utils/ICS24Host.sol b/src/utils/ICS24Host.sol index 972da234..ca32460c 100644 --- a/src/utils/ICS24Host.sol +++ b/src/utils/ICS24Host.sol @@ -11,6 +11,9 @@ library ICS24Host { // Commitment generators that comply with // https://github.com/cosmos/ibc/tree/main/spec/core/ics-024-host-requirements#path-space + // TODO: Figure out what a reasonable threshold is for the timestamp, how long into the future and still be safe + uint256 public constant SECONDS_THRESHOLD = 7_952_338_800; // The year of our lord 2222 + enum PacketReceipt { NONE, SUCCESSFUL @@ -100,18 +103,25 @@ library ICS24Host { /// @notice Get the packet commitment bytes. function packetCommitmentBytes32(IICS26RouterMsgs.Packet memory packet) internal pure returns (bytes32) { + // Since we expect all packet commitments to be in nanoseconds, we need to convert the timestamp to nanoseconds + // If the timestamp is already in nanoseconds, we don't need to do anything + uint64 timestamp = packet.timeoutTimestamp; + if (isTimestampInSeconds(timestamp)) { + timestamp = SafeCast.toUint64(uint256(packet.timeoutTimestamp) * 1_000_000_000); + } + return sha256( - abi.encodePacked( - SafeCast.toUint64(uint256(packet.timeoutTimestamp) * 1_000_000_000), - uint64(0), - uint64(0), - sha256(packet.data), - packet.destPort, - packet.destChannel - ) + abi.encodePacked(timestamp, uint64(0), uint64(0), sha256(packet.data), packet.destPort, packet.destChannel) ); } + /// @notice Checks if a timestamp is in seconds or nanoseconds + /// @param timestamp The timestamp to check + /// @return isSeconds True if the timestamp is in seconds, false if in nanoseconds + function isTimestampInSeconds(uint64 timestamp) internal pure returns (bool isSeconds) { + return timestamp < SECONDS_THRESHOLD; + } + /// @notice Get the packet receipt commitment bytes. function packetAcknowledgementCommitmentBytes32(bytes memory ack) internal pure returns (bytes32) { return sha256(ack); diff --git a/test/ICS20TransferTest.t.sol b/test/ICS20TransferTest.t.sol index 9b6d4c78..1a3f0a32 100644 --- a/test/ICS20TransferTest.t.sol +++ b/test/ICS20TransferTest.t.sol @@ -341,7 +341,9 @@ contract ICS20TransferTest is Test { packet.sourcePort = newSourcePort; packet.sourceChannel = newSourceChannel; - bytes memory ack = ics20Transfer.onRecvPacket(IIBCAppCallbacks.OnRecvPacketCallback({ packet: packet, relayer: makeAddr("relayer") })); + bytes memory ack = ics20Transfer.onRecvPacket( + IIBCAppCallbacks.OnRecvPacketCallback({ packet: packet, relayer: makeAddr("relayer") }) + ); assertEq(string(ack), "{\"result\":\"AQ==\"}"); // the tokens should have been transferred back again @@ -352,12 +354,15 @@ contract ICS20TransferTest is Test { } function test_failure_onRecvPacket() public { - string memory ibcDenom = string(abi.encodePacked(packet.sourcePort, "/", packet.sourceChannel, "/", erc20AddressStr)); + string memory ibcDenom = + string(abi.encodePacked(packet.sourcePort, "/", packet.sourceChannel, "/", erc20AddressStr)); packet.data = ICS20Lib.marshalJSON(ibcDenom, defaultAmount, receiver, senderStr, "memo"); // test invalid version packet.version = "invalid"; - bytes memory ack = ics20Transfer.onRecvPacket(IIBCAppCallbacks.OnRecvPacketCallback({ packet: packet, relayer: makeAddr("relayer") })); + bytes memory ack = ics20Transfer.onRecvPacket( + IIBCAppCallbacks.OnRecvPacketCallback({ packet: packet, relayer: makeAddr("relayer") }) + ); assertEq(string(ack), "{\"error\":\"unexpected version: invalid\"}"); // Reset version packet.version = ICS20Lib.ICS20_VERSION; @@ -366,26 +371,44 @@ contract ICS20TransferTest is Test { data = bytes("invalid"); packet.data = data; vm.expectRevert(bytes("")); - ics20Transfer.onRecvPacket(IIBCAppCallbacks.OnRecvPacketCallback({ packet: packet, relayer: makeAddr("relayer") })); + ics20Transfer.onRecvPacket( + IIBCAppCallbacks.OnRecvPacketCallback({ packet: packet, relayer: makeAddr("relayer") }) + ); // test invalid amount data = ICS20Lib.marshalJSON(ibcDenom, 0, receiver, senderStr, "memo"); packet.data = data; - ack = ics20Transfer.onRecvPacket(IIBCAppCallbacks.OnRecvPacketCallback({ packet: packet, relayer: makeAddr("relayer") })); + ack = ics20Transfer.onRecvPacket( + IIBCAppCallbacks.OnRecvPacketCallback({ packet: packet, relayer: makeAddr("relayer") }) + ); assertEq(string(ack), "{\"error\":\"invalid amount: 0\"}"); - // test denom is not erc20 address - string memory invalidErc20Denom = string(abi.encodePacked(packet.sourcePort, "/", packet.sourceChannel, "/invalid")); + // test receiver chain is source, but denom is not erc20 address + string memory invalidErc20Denom = + string(abi.encodePacked(packet.sourcePort, "/", packet.sourceChannel, "/invalid")); data = ICS20Lib.marshalJSON(invalidErc20Denom, defaultAmount, receiver, senderStr, "memo"); packet.data = data; - ack = ics20Transfer.onRecvPacket(IIBCAppCallbacks.OnRecvPacketCallback({ packet: packet, relayer: makeAddr("relayer") })); + ack = ics20Transfer.onRecvPacket( + IIBCAppCallbacks.OnRecvPacketCallback({ packet: packet, relayer: makeAddr("relayer") }) + ); assertEq(string(ack), "{\"error\":\"invalid token contract: invalid\"}"); // test invalid receiver data = ICS20Lib.marshalJSON(ibcDenom, defaultAmount, receiver, "invalid", "memo"); packet.data = data; - ack = ics20Transfer.onRecvPacket(IIBCAppCallbacks.OnRecvPacketCallback({ packet: packet, relayer: makeAddr("relayer") })); + ack = ics20Transfer.onRecvPacket( + IIBCAppCallbacks.OnRecvPacketCallback({ packet: packet, relayer: makeAddr("relayer") }) + ); assertEq(string(ack), "{\"error\":\"invalid receiver: invalid\"}"); + + // just to document current limitations: sender chain is the source is not supported + string memory sourceDenom = "uatom"; + data = ICS20Lib.marshalJSON(sourceDenom, defaultAmount, receiver, senderStr, "memo"); + packet.data = data; + vm.expectRevert(bytes("not supported: sender denom is source")); + ics20Transfer.onRecvPacket( + IIBCAppCallbacks.OnRecvPacketCallback({ packet: packet, relayer: makeAddr("relayer") }) + ); } function _getPacketData() internal view returns (ICS20Lib.UnwrappedFungibleTokenPacketData memory) { diff --git a/test/ICS24Host.t.sol b/test/ICS24Host.t.sol new file mode 100644 index 000000000..a2a4cd52 --- /dev/null +++ b/test/ICS24Host.t.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.25 <0.9.0; + +// solhint-disable custom-errors,max-line-length + +import { Test } from "forge-std/Test.sol"; +import { ICS24Host } from "../src/utils/ICS24Host.sol"; +import { IICS26RouterMsgs } from "../src/msgs/IICS26RouterMsgs.sol"; +import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + +contract ICS24HostTest is Test { + function testFuzz_packetCommitmentBytes32(uint64 timestampInSeconds) public pure { + vm.assume(timestampInSeconds < ICS24Host.SECONDS_THRESHOLD); // ~100 years from now + vm.assume(timestampInSeconds > 946_681_200); // year 2000 + + IICS26RouterMsgs.Packet memory packetWithSeconds = IICS26RouterMsgs.Packet({ + sequence: 1, + timeoutTimestamp: timestampInSeconds, + sourcePort: "source-port", + sourceChannel: "source-channel", + destPort: "destination-port", + destChannel: "destination-channel", + version: "ics20-1", + data: bytes("data") + }); + bytes32 packetCommitmentWithSeconds = ICS24Host.packetCommitmentBytes32(packetWithSeconds); + + IICS26RouterMsgs.Packet memory packetWithNanoSeconds = IICS26RouterMsgs.Packet({ + sequence: 1, + timeoutTimestamp: SafeCast.toUint64(uint256(timestampInSeconds) * 1_000_000_000), + sourcePort: "source-port", + sourceChannel: "source-channel", + destPort: "destination-port", + destChannel: "destination-channel", + version: "ics20-1", + data: bytes("data") + }); + bytes32 packetCommitmentWithNanoseconds = ICS24Host.packetCommitmentBytes32(packetWithNanoSeconds); + + assertEq( + packetCommitmentWithSeconds, + packetCommitmentWithNanoseconds, + string( + abi.encodePacked( + "timestamp in seconds: ", + Strings.toString(packetWithSeconds.timeoutTimestamp), + ", timestamp in nanoseconds: ", + Strings.toString(packetWithNanoSeconds.timeoutTimestamp) + ) + ) + ); + } +} diff --git a/test/ICS26RouterTest.t.sol b/test/ICS26RouterTest.t.sol index 26ca6636..a93da21e 100644 --- a/test/ICS26RouterTest.t.sol +++ b/test/ICS26RouterTest.t.sol @@ -56,10 +56,9 @@ contract ICS26RouterTest is Test { ICS20Transfer ics20Transfer = new ICS20Transfer(address(ics26Router)); ics26Router.addIBCApp("transfer", address(ics20Transfer)); - uint64 nanoTimestamp = uint64((block.timestamp + 1000) * 1_000_000_000); IICS26RouterMsgs.Packet memory packet = IICS26RouterMsgs.Packet({ sequence: 1, - timeoutTimestamp: nanoTimestamp, + timeoutTimestamp: uint64(block.timestamp + 1000), sourcePort: "transfer", sourceChannel: counterpartyClientID, destPort: "transfer", @@ -72,7 +71,7 @@ contract ICS26RouterTest is Test { packet: packet, proofCommitment: "0x", // doesn't matter proofHeight: IICS02ClientMsgs.Height({ revisionNumber: 0, revisionHeight: 0 }) // doesn't matter - }); + }); bytes memory commitmentPath = ICS24Host.packetCommitmentPathCalldata( msgRecvPacket.packet.sourcePort, msgRecvPacket.packet.sourceChannel, msgRecvPacket.packet.sequence @@ -86,7 +85,10 @@ contract ICS26RouterTest is Test { }); vm.expectRevert( abi.encodeWithSelector( - IICS26RouterErrors.IBCMembershipProofVerificationFailed.selector, packet, expectedMsgMembership, abi.encodeWithSelector(DummyLightClient.MembershipShouldFail.selector, "membership should fail") + IICS26RouterErrors.IBCMembershipProofVerificationFailed.selector, + packet, + expectedMsgMembership, + abi.encodeWithSelector(DummyLightClient.MembershipShouldFail.selector, "membership should fail") ) ); ics26Router.recvPacket(msgRecvPacket); diff --git a/test/IntegrationTest.t.sol b/test/IntegrationTest.t.sol index de42d488..f7741fa5 100644 --- a/test/IntegrationTest.t.sol +++ b/test/IntegrationTest.t.sol @@ -12,6 +12,7 @@ import { IICS20TransferMsgs } from "../src/msgs/IICS20TransferMsgs.sol"; import { TestERC20 } from "./TestERC20.sol"; import { ICS02Client } from "../src/ICS02Client.sol"; import { IICS26Router } from "../src/ICS26Router.sol"; +import { IICS26RouterErrors } from "../src/errors/IICS26RouterErrors.sol"; import { ICS26Router } from "../src/ICS26Router.sol"; import { IICS26RouterMsgs } from "../src/msgs/IICS26RouterMsgs.sol"; import { DummyLightClient } from "./DummyLightClient.sol"; @@ -19,6 +20,7 @@ import { ILightClientMsgs } from "../src/msgs/ILightClientMsgs.sol"; import { ICS20Lib } from "../src/utils/ICS20Lib.sol"; import { ICS24Host } from "../src/utils/ICS24Host.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; contract IntegrationTest is Test { IICS02Client public ics02Client; @@ -197,7 +199,7 @@ contract IntegrationTest is Test { acknowledgement: ICS20Lib.SUCCESSFUL_ACKNOWLEDGEMENT_JSON, proofAcked: bytes("doesntmatter"), // dummy client will accept proofHeight: IICS02ClientMsgs.Height({ revisionNumber: 1, revisionHeight: 42 }) // dummy client will accept - }); + }); vm.expectEmit(); emit IICS20Transfer.ICS20Acknowledgement(_getPacketData(), ICS20Lib.SUCCESSFUL_ACKNOWLEDGEMENT_JSON, true); ics26Router.ackPacket(ackMsg); @@ -220,9 +222,11 @@ contract IntegrationTest is Test { string memory backReceiverStr = senderStr; string memory ibcDenom = string(abi.encodePacked("transfer/", counterpartyClient, "/", erc20AddressStr)); data = ICS20Lib.marshalJSON(ibcDenom, defaultAmount, backSender, backReceiverStr, "backmemo"); + + // For the packet back we pretend this is ibc-go and that the timeout is in nanoseconds packet = IICS26RouterMsgs.Packet({ sequence: 1, - timeoutTimestamp: uint64(block.timestamp + 1000), + timeoutTimestamp: SafeCast.toUint64(uint256(packet.timeoutTimestamp + 1000) * 1_000_000_000), sourcePort: "transfer", sourceChannel: counterpartyClient, destPort: "transfer", @@ -231,20 +235,24 @@ contract IntegrationTest is Test { data: data }); vm.expectEmit(); - emit IICS20Transfer.ICS20ReceiveTransfer(ICS20Lib.PacketDataJSON({ - denom: ibcDenom, - amount: defaultAmount, - sender: backSender, - receiver: backReceiverStr, - memo: "backmemo" - })); + emit IICS20Transfer.ICS20ReceiveTransfer( + ICS20Lib.PacketDataJSON({ + denom: ibcDenom, + amount: defaultAmount, + sender: backSender, + receiver: backReceiverStr, + memo: "backmemo" + }) + ); vm.expectEmit(); emit IICS26Router.WriteAcknowledgement(packet, ICS20Lib.SUCCESSFUL_ACKNOWLEDGEMENT_JSON); - ics26Router.recvPacket(IICS26RouterMsgs.MsgRecvPacket({ - packet: packet, - proofCommitment: bytes("doesntmatter"), // dummy client will accept - proofHeight: IICS02ClientMsgs.Height({ revisionNumber: 1, revisionHeight: 42 }) // dummy client will accept - })); + ics26Router.recvPacket( + IICS26RouterMsgs.MsgRecvPacket({ + packet: packet, + proofCommitment: bytes("doesntmatter"), // dummy client will accept + proofHeight: IICS02ClientMsgs.Height({ revisionNumber: 1, revisionHeight: 42 }) // will accept + }) + ); // Check balances are updated as expected uint256 backReceiverBalance = erc20.balanceOf(backReceiver); @@ -253,13 +261,84 @@ contract IntegrationTest is Test { assertEq(contractBalanceAfterRecv, 0); // Check that the ack is written - bytes32 ackPath = ICS24Host.packetAcknowledgementCommitmentKeyCalldata( - packet.destPort, packet.destChannel, packet.sequence - ); + bytes32 ackPath = + ICS24Host.packetAcknowledgementCommitmentKeyCalldata(packet.destPort, packet.destChannel, packet.sequence); bytes32 storedAck = ics26Router.getCommitment(ackPath); assertEq(storedAck, ICS24Host.packetAcknowledgementCommitmentBytes32(ICS20Lib.SUCCESSFUL_ACKNOWLEDGEMENT_JSON)); } + function test_failure_receiveICS20PacketHasTimedOut() public { + IICS26RouterMsgs.Packet memory packet = _sendICS20Transfer(); + + IICS26RouterMsgs.MsgAckPacket memory ackMsg = IICS26RouterMsgs.MsgAckPacket({ + packet: packet, + acknowledgement: ICS20Lib.SUCCESSFUL_ACKNOWLEDGEMENT_JSON, + proofAcked: bytes("doesntmatter"), // dummy client will accept + proofHeight: IICS02ClientMsgs.Height({ revisionNumber: 1, revisionHeight: 42 }) // dummy client will accept + }); + vm.expectEmit(); + emit IICS20Transfer.ICS20Acknowledgement(_getPacketData(), ICS20Lib.SUCCESSFUL_ACKNOWLEDGEMENT_JSON, true); + ics26Router.ackPacket(ackMsg); + + // commitment should be deleted + bytes32 path = ICS24Host.packetCommitmentKeyCalldata( + msgSendPacket.sourcePort, msgSendPacket.sourceChannel, packet.sequence + ); + bytes32 storedCommitment = ics26Router.getCommitment(path); + assertEq(storedCommitment, 0); + + uint256 senderBalanceAfterSend = erc20.balanceOf(sender); + uint256 contractBalanceAfterSend = erc20.balanceOf(address(ics20Transfer)); + assertEq(senderBalanceAfterSend, 0); + assertEq(contractBalanceAfterSend, defaultAmount); + + // Send back + string memory backSender = "cosmos1mhmwgrfrcrdex5gnr0vcqt90wknunsxej63feh"; + string memory backReceiverStr = senderStr; + string memory ibcDenom = string(abi.encodePacked("transfer/", counterpartyClient, "/", erc20AddressStr)); + data = ICS20Lib.marshalJSON(ibcDenom, defaultAmount, backSender, backReceiverStr, "backmemo"); + + uint64 timeoutTimestamp = uint64(block.timestamp - 1); + packet = IICS26RouterMsgs.Packet({ + sequence: 1, + timeoutTimestamp: timeoutTimestamp, + sourcePort: "transfer", + sourceChannel: counterpartyClient, + destPort: "transfer", + destChannel: clientIdentifier, + version: ICS20Lib.ICS20_VERSION, + data: data + }); + + vm.expectRevert( + abi.encodeWithSelector( + IICS26RouterErrors.IBCInvalidTimeoutTimestamp.selector, packet.timeoutTimestamp, block.timestamp + ) + ); + ics26Router.recvPacket( + IICS26RouterMsgs.MsgRecvPacket({ + packet: packet, + proofCommitment: bytes("doesntmatter"), // dummy client will accept + proofHeight: IICS02ClientMsgs.Height({ revisionNumber: 1, revisionHeight: 42 }) // will accept + }) + ); + + // should also fail with the timeout set as nanoseconds + packet.timeoutTimestamp = SafeCast.toUint64(uint256(packet.timeoutTimestamp) * 1_000_000_000); + vm.expectRevert( + abi.encodeWithSelector( + IICS26RouterErrors.IBCInvalidTimeoutTimestamp.selector, packet.timeoutTimestamp, block.timestamp + ) + ); + ics26Router.recvPacket( + IICS26RouterMsgs.MsgRecvPacket({ + packet: packet, + proofCommitment: bytes("doesntmatter"), // dummy client will accept + proofHeight: IICS02ClientMsgs.Height({ revisionNumber: 1, revisionHeight: 42 }) // will accept + }) + ); + } + function _sendICS20Transfer() internal returns (IICS26RouterMsgs.Packet memory) { erc20.mint(sender, defaultAmount); vm.startPrank(sender);