From 590fff9cad938be7cbddd8bf435d9de98a0bde76 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Tue, 6 Aug 2024 15:21:16 +0200 Subject: [PATCH 1/9] feat(ics20): onRecvPacket + e2e test --- abi/ICS20Transfer.json | 53 ++++- abi/ICS26Router.json | 155 ++++++++++++++ e2e/artifacts/genesis.json | 2 +- e2e/interchaintestv8/e2esuite/grpc_query.go | 22 ++ e2e/interchaintestv8/ibc_eureka_test.go | 197 +++++++++++++++--- .../types/ics20transfer/contract.go | 163 ++++++++++++++- .../types/ics26router/contract.go | 2 +- src/ICS20Transfer.sol | 46 +++- src/ICS26Router.sol | 20 +- src/errors/IICS20Errors.sol | 3 + src/errors/IICS26RouterErrors.sol | 7 + src/interfaces/IICS20Transfer.sol | 2 + src/utils/ICS20Lib.sol | 62 +++--- test/DummyLightClient.sol | 12 +- test/ICS02ClientTest.t.sol | 2 +- test/ICS20TransferTest.t.sol | 79 +++++++ test/ICS26RouterTest.t.sol | 52 +++++ test/IntegrationTest.t.sol | 75 ++++++- 18 files changed, 876 insertions(+), 78 deletions(-) diff --git a/abi/ICS20Transfer.json b/abi/ICS20Transfer.json index 9f6a9344..989cb2fc 100644 --- a/abi/ICS20Transfer.json +++ b/abi/ICS20Transfer.json @@ -87,7 +87,7 @@ "name": "onRecvPacket", "inputs": [ { - "name": "", + "name": "msg_", "type": "tuple", "internalType": "struct IIBCAppCallbacks.OnRecvPacketCallback", "components": [ @@ -430,6 +430,46 @@ ], "anonymous": false }, + { + "type": "event", + "name": "ICS20ReceiveTransfer", + "inputs": [ + { + "name": "packetData", + "type": "tuple", + "indexed": false, + "internalType": "struct ICS20Lib.PacketDataJSON", + "components": [ + { + "name": "denom", + "type": "string", + "internalType": "string" + }, + { + "name": "sender", + "type": "string", + "internalType": "string" + }, + { + "name": "receiver", + "type": "string", + "internalType": "string" + }, + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "memo", + "type": "string", + "internalType": "string" + } + ] + } + ], + "anonymous": false + }, { "type": "event", "name": "ICS20Timeout", @@ -599,6 +639,17 @@ } ] }, + { + "type": "error", + "name": "ICS20InvalidReceiver", + "inputs": [ + { + "name": "receiver", + "type": "string", + "internalType": "string" + } + ] + }, { "type": "error", "name": "ICS20InvalidSender", diff --git a/abi/ICS26Router.json b/abi/ICS26Router.json index 8eeb3eb7..3f169182 100644 --- a/abi/ICS26Router.json +++ b/abi/ICS26Router.json @@ -820,6 +820,103 @@ } ] }, + { + "type": "error", + "name": "IBCMembershipProofVerificationFailed", + "inputs": [ + { + "name": "packet", + "type": "tuple", + "internalType": "struct IICS26RouterMsgs.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": "struct ILightClientMsgs.MsgMembership", + "components": [ + { + "name": "proof", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "proofHeight", + "type": "tuple", + "internalType": "struct IICS02ClientMsgs.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", @@ -869,6 +966,64 @@ } ] }, + { + "type": "error", + "name": "IBCPacketHandlingFailed", + "inputs": [ + { + "name": "packet", + "type": "tuple", + "internalType": "struct IICS26RouterMsgs.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", diff --git a/e2e/artifacts/genesis.json b/e2e/artifacts/genesis.json index 2c96b3c9..bc7ddaf3 100644 --- a/e2e/artifacts/genesis.json +++ b/e2e/artifacts/genesis.json @@ -1,6 +1,6 @@ { "trustedClientState": "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000012754500000000000000000000000000000000000000000000000000000000001baf800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000673696d642d310000000000000000000000000000000000000000000000000000", - "trustedConsensusState": "0000000000000000000000000000000000000000000000000000000066b05d264416d86ca61c0fd693df00a5c755d74acdf8f9d5fd3f88dfa98df9dc9a08f906e2d69a8e2501adc2013515c2954fc617365a7d4e1a51bc49ca6bac597971949c", + "trustedConsensusState": "0000000000000000000000000000000000000000000000000000000066b21a6180d26d76e2ef512de8af9baf444a87782fee9b50f3f455d552ced6ffbaafada76076e95e1d308f10802c0140af31825da40f208e5012e88322a27563ebfb64c6", "updateClientVkey": "0x0068b9d316aced51c5923b2d50692f4a6a9bfefcd89392914b90e77545727fbe", "membershipVkey": "0x00a4245d249b5c35c9782cc899c8e370a35d5d928187dc9e7acbab7096764b72", "ucAndMembershipVkey": "0x00cea834e3408d45d29080a3146e4fb1fd0c06503d655bd787219caac86cf59c" diff --git a/e2e/interchaintestv8/e2esuite/grpc_query.go b/e2e/interchaintestv8/e2esuite/grpc_query.go index 494101cf..032948d2 100644 --- a/e2e/interchaintestv8/e2esuite/grpc_query.go +++ b/e2e/interchaintestv8/e2esuite/grpc_query.go @@ -3,6 +3,7 @@ package e2esuite import ( "context" "fmt" + abci "github.com/cometbft/cometbft/abci/types" "github.com/cosmos/gogoproto/proto" "google.golang.org/grpc" @@ -40,6 +41,27 @@ func populateQueryReqToPath(ctx context.Context, chain *cosmos.CosmosChain) erro return nil } +func ABCIQuery(ctx context.Context, chain *cosmos.CosmosChain, req *abci.RequestQuery) (*abci.ResponseQuery, error) { + // Create a connection to the gRPC server. + grpcConn, err := grpc.Dial( + chain.GetHostGRPCAddress(), + grpc.WithTransportCredentials(insecure.NewCredentials()), + ) + if err != nil { + return &abci.ResponseQuery{}, err + } + + defer grpcConn.Close() + + resp := &abci.ResponseQuery{} + err = grpcConn.Invoke(ctx, "cosmos.base.tendermint.v1beta1.Service/ABCIQuery", req, resp) + if err != nil { + return &abci.ResponseQuery{}, err + } + + return resp, nil +} + // Queries the chain with a query request and deserializes the response to T func GRPCQuery[T any](ctx context.Context, chain *cosmos.CosmosChain, req proto.Message, opts ...grpc.CallOption) (*T, error) { path, ok := queryReqToPath[proto.MessageName(req)] diff --git a/e2e/interchaintestv8/ibc_eureka_test.go b/e2e/interchaintestv8/ibc_eureka_test.go index 49476e98..1a265398 100644 --- a/e2e/interchaintestv8/ibc_eureka_test.go +++ b/e2e/interchaintestv8/ibc_eureka_test.go @@ -2,9 +2,13 @@ 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" @@ -272,7 +276,7 @@ func (s *IbcEurekaTestSuite) TestDeploy() { s.Require().NoError(err) s.Require().Equal(strings.ToLower(s.deployer.FormattedAddress()), strings.ToLower(owner.Hex())) - transferAddress, err := s.ics26Contract.GetIBCApp(nil, "transfer") + transferAddress, err := s.ics26Contract.GetIBCApp(nil, transfertypes.PortID) s.Require().NoError(err) s.Require().Equal(s.contractAddresses.Ics20Transfer, strings.ToLower(transferAddress.Hex())) })) @@ -292,14 +296,16 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { eth, simd := s.ChainA, s.ChainB + ics20Address := ethcommon.HexToAddress(s.contractAddresses.Ics20Transfer) transferAmount := big.NewInt(testvalues.TransferAmount) userAddress := crypto.PubkeyToAddress(s.key.PublicKey) receiver := s.UserB - var packet ics26router.IICS26RouterMsgsPacket + var sendPacket ics26router.IICS26RouterMsgsPacket + var returnPacket channeltypes.Packet var recvAck []byte + var ibcDenom string s.Require().True(s.Run("Approve the ICS20Transfer contract to spend the erc20 tokens", func() { - ics20Address := ethcommon.HexToAddress(s.contractAddresses.Ics20Transfer) tx, err := s.erc20Contract.Approve(s.GetTransactOpts(s.key), ics20Address, transferAmount) s.Require().NoError(err) receipt := s.GetTxReciept(ctx, eth, tx.Hash()) @@ -317,7 +323,7 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { Amount: transferAmount, Receiver: receiver.FormattedAddress(), SourceChannel: s.ethClientID, - DestPort: "transfer", + DestPort: transfertypes.PortID, TimeoutTimestamp: timeout, Memo: "testmemo", } @@ -337,14 +343,24 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { sendPacketEvent, err := e2esuite.GetEvmEvent(receipt, s.ics26Contract.ParseSendPacket) s.Require().NoError(err) - packet = sendPacketEvent.Packet - s.Require().Equal(uint32(1), packet.Sequence) - s.Require().Equal(timeout, packet.TimeoutTimestamp) - s.Require().Equal("transfer", packet.SourcePort) - s.Require().Equal(s.ethClientID, packet.SourceChannel) - s.Require().Equal("transfer", packet.DestPort) - s.Require().Equal(s.simdClientID, packet.DestChannel) - s.Require().Equal(transfertypes.Version, packet.Version) + sendPacket = sendPacketEvent.Packet + s.Require().Equal(uint32(1), sendPacket.Sequence) + s.Require().Equal(timeout, sendPacket.TimeoutTimestamp) + s.Require().Equal(transfertypes.PortID, sendPacket.SourcePort) + s.Require().Equal(s.ethClientID, sendPacket.SourceChannel) + s.Require().Equal(transfertypes.PortID, sendPacket.DestPort) + s.Require().Equal(s.simdClientID, sendPacket.DestChannel) + s.Require().Equal(transfertypes.Version, sendPacket.Version) + + s.True(s.Run("Verify balances", func() { + userBalance, err := s.erc20Contract.BalanceOf(nil, userAddress) + s.Require().NoError(err) + s.Require().Equal(big.NewInt(testvalues.StartingTokenAmount-testvalues.TransferAmount), userBalance) + ics20TransferBalance, err := s.erc20Contract.BalanceOf(nil, ics20Address) + 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 @@ -360,14 +376,14 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { txResp, err := s.BroadcastMessages(ctx, simd, s.UserB, 200_000, &channeltypes.MsgRecvPacket{ Packet: channeltypes.Packet{ - Sequence: uint64(packet.Sequence), - SourcePort: packet.SourcePort, - SourceChannel: packet.SourceChannel, - DestinationPort: packet.DestPort, - DestinationChannel: packet.DestChannel, - Data: packet.Data, + Sequence: uint64(sendPacket.Sequence), + SourcePort: sendPacket.SourcePort, + SourceChannel: sendPacket.SourceChannel, + DestinationPort: sendPacket.DestPort, + DestinationChannel: sendPacket.DestChannel, + Data: sendPacket.Data, TimeoutHeight: clienttypes.Height{}, - TimeoutTimestamp: packet.TimeoutTimestamp, + TimeoutTimestamp: sendPacket.TimeoutTimestamp, }, ProofCommitment: []byte("doesn't matter"), ProofHeight: clientState.LatestHeight, @@ -380,7 +396,7 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { s.Require().NotNil(recvAck) s.Require().True(s.Run("Verify balances", func() { - ibcDenom := transfertypes.ParseDenomTrace( + ibcDenom = transfertypes.ParseDenomTrace( fmt.Sprintf("%s/%s/%s", transfertypes.PortID, "00-mock-0", s.contractAddresses.Erc20), ).IBCDenom() @@ -396,7 +412,7 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { })) })) - s.True(s.Run("acknowledgePacket on Ethereum side", func() { + s.Require().True(s.Run("acknowledgePacket on Ethereum side", func() { clientState, err := s.sp1Ics07Contract.GetClientState(nil) s.Require().NoError(err) @@ -404,8 +420,7 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { latestHeight, err := simd.Height(ctx) s.Require().NoError(err) - // This will be a non-membership proof since no packets have been sent - packetAckPath := ibchost.PacketAcknowledgementPath(packet.DestPort, packet.DestChannel, uint64(packet.Sequence)) + packetAckPath := ibchost.PacketAcknowledgementPath(sendPacket.DestPort, sendPacket.DestChannel, uint64(sendPacket.Sequence)) proofHeight, ucAndMemProof, err := operator.UpdateClientAndMembershipProof( uint64(trustedHeight), uint64(latestHeight), packetAckPath, "--trust-level", testvalues.DefaultTrustLevel.String(), @@ -414,7 +429,7 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { s.Require().NoError(err) msg := ics26router.IICS26RouterMsgsMsgAckPacket{ - Packet: packet, + Packet: sendPacket, Acknowledgement: recvAck, ProofAcked: ucAndMemProof, ProofHeight: *proofHeight, @@ -426,4 +441,138 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { receipt := s.GetTxReciept(ctx, eth, tx.Hash()) s.Require().Equal(ethtypes.ReceiptStatusSuccessful, receipt.Status) })) + + s.Require().True(s.Run("Transfer back", func() { + timeout := uint64(time.Now().Add(30 * time.Minute).UnixNano()) + ibcCoin := sdk.NewCoin(ibcDenom, sdkmath.NewIntFromBigInt(transferAmount)) + + msgTransfer := transfertypes.MsgTransfer{ + SourcePort: transfertypes.PortID, + SourceChannel: s.simdClientID, + Token: ibcCoin, + Sender: s.UserB.FormattedAddress(), + Receiver: strings.ToLower(userAddress.Hex()), + TimeoutHeight: clienttypes.Height{}, + TimeoutTimestamp: timeout, + Memo: "backmemo", + DestPort: transfertypes.PortID, + DestChannel: s.ethClientID, + } + + txResp, err := s.BroadcastMessages(ctx, simd, s.UserB, 200_000, &msgTransfer) + s.Require().NoError(err) + returnPacket, err = ibctesting.ParsePacketFromEvents(txResp.Events) + s.Require().NoError(err) + + s.Require().Equal(uint64(1), returnPacket.Sequence) + s.Require().Equal(transfertypes.PortID, returnPacket.SourcePort) + s.Require().Equal(s.simdClientID, returnPacket.SourceChannel) + s.Require().Equal(transfertypes.PortID, returnPacket.DestinationPort) + s.Require().Equal(s.ethClientID, returnPacket.DestinationChannel) + s.Require().Equal(clienttypes.Height{}, returnPacket.TimeoutHeight) + s.Require().Equal(timeout, returnPacket.TimeoutTimestamp) + + var transferPacketData transfertypes.FungibleTokenPacketData + err = json.Unmarshal(returnPacket.Data, &transferPacketData) + s.Require().NoError(err) + s.Require().Equal(ibcDenom, transferPacketData.Denom) + s.Require().Equal(transferAmount.String(), transferPacketData.Amount) + s.Require().Equal(s.UserB.FormattedAddress(), transferPacketData.Sender) + s.Require().Equal(strings.ToLower(userAddress.Hex()), transferPacketData.Receiver) + s.Require().Equal("backmemo", transferPacketData.Memo) + + s.Require().True(s.Run("Verify balances", func() { + // Check the balance of UserB + resp, err := e2esuite.GRPCQuery[banktypes.QueryBalanceResponse](ctx, simd, &banktypes.QueryBalanceRequest{ + Address: s.UserB.FormattedAddress(), + Denom: ibcDenom, + }) + s.Require().NoError(err) + s.Require().NotNil(resp.Balance) + s.Require().Equal(int64(0), resp.Balance.Amount.Int64()) + s.Require().Equal(ibcDenom, resp.Balance.Denom) + })) + })) + + var returnWriteAckEvent *ics26router.ContractWriteAcknowledgement + s.Require().True(s.Run("Receive packet on Ethereum side", func() { + clientState, err := s.sp1Ics07Contract.GetClientState(nil) + s.Require().NoError(err) + + trustedHeight := clientState.LatestHeight.RevisionHeight + latestHeight, err := simd.Height(ctx) + s.Require().NoError(err) + + packetCommitmentPath := ibchost.PacketCommitmentPath(returnPacket.SourcePort, returnPacket.SourceChannel, returnPacket.Sequence) + storeProofResp, err := e2esuite.ABCIQuery(ctx, simd, &abci.RequestQuery{ + Path: "store/ibc/key", + Height: latestHeight - 1, // ? + Data: []byte(packetCommitmentPath), + Prove: true, + }) + _ = err + _ = storeProofResp + proofHeight, ucAndMemProof, err := operator.UpdateClientAndMembershipProof( + uint64(trustedHeight), uint64(latestHeight), packetCommitmentPath, + "--trust-level", testvalues.DefaultTrustLevel.String(), + "--trusting-period", strconv.Itoa(testvalues.DefaultTrustPeriod), + ) + s.Require().NoError(err) + + msg := ics26router.IICS26RouterMsgsMsgRecvPacket{ + Packet: ics26router.IICS26RouterMsgsPacket{ + Sequence: uint32(returnPacket.Sequence), + TimeoutTimestamp: returnPacket.TimeoutTimestamp, + SourcePort: returnPacket.SourcePort, + SourceChannel: returnPacket.SourceChannel, + DestPort: returnPacket.DestinationPort, + DestChannel: returnPacket.DestinationChannel, + Version: transfertypes.Version, + Data: returnPacket.Data, + }, + ProofCommitment: ucAndMemProof, + ProofHeight: *proofHeight, + } + + tx, err := s.ics26Contract.RecvPacket(s.GetTransactOpts(s.key), msg) + s.Require().NoError(err) + + receipt := s.GetTxReciept(ctx, eth, tx.Hash()) + s.Require().Equal(ethtypes.ReceiptStatusSuccessful, receipt.Status) + + returnWriteAckEvent, err = e2esuite.GetEvmEvent(receipt, s.ics26Contract.ParseWriteAcknowledgement) + s.Require().NoError(err) + + s.True(s.Run("Verify balances", func() { + userBalance, err := s.erc20Contract.BalanceOf(nil, userAddress) + s.Require().NoError(err) + s.Require().Equal(big.NewInt(testvalues.StartingTokenAmount), userBalance) + + ics20TransferBalance, err := s.erc20Contract.BalanceOf(nil, ics20Address) + s.Require().NoError(err) + s.Require().Equal(int64(0), ics20TransferBalance.Int64()) + })) + })) + + // TODO: When using a non-mock light client on the cosmos side, the client there needs to be updated at this point + + s.Require().True(s.Run("acknowledgePacket on Cosmos side", func() { + resp, err := e2esuite.GRPCQuery[clienttypes.QueryClientStateResponse](ctx, simd, &clienttypes.QueryClientStateRequest{ + ClientId: s.simdClientID, + }) + s.Require().NoError(err) + var clientState mock.ClientState + err = simd.Config().EncodingConfig.Codec.Unmarshal(resp.ClientState.Value, &clientState) + s.Require().NoError(err) + + txResp, err := s.BroadcastMessages(ctx, simd, s.UserB, 200_000, &channeltypes.MsgAcknowledgement{ + Packet: returnPacket, + Acknowledgement: returnWriteAckEvent.Acknowledgement, + ProofAcked: []byte("doesn't matter"), + ProofHeight: clienttypes.Height{}, + Signer: s.UserB.FormattedAddress(), + }) + s.Require().NoError(err) + s.Require().Equal(0, txResp.Code) + })) } diff --git a/e2e/interchaintestv8/types/ics20transfer/contract.go b/e2e/interchaintestv8/types/ics20transfer/contract.go index 65f5e8f9..89fbefc1 100644 --- a/e2e/interchaintestv8/types/ics20transfer/contract.go +++ b/e2e/interchaintestv8/types/ics20transfer/contract.go @@ -29,6 +29,15 @@ var ( _ = abi.ConvertType ) +// ICS20LibPacketDataJSON is an auto generated low-level Go binding around an user-defined struct. +type ICS20LibPacketDataJSON struct { + Denom string + Sender string + Receiver string + Amount *big.Int + Memo string +} + // ICS20LibUnwrappedFungibleTokenPacketData is an auto generated low-level Go binding around an user-defined struct. type ICS20LibUnwrappedFungibleTokenPacketData struct { Erc20ContractAddress common.Address @@ -88,7 +97,7 @@ type IICS26RouterMsgsPacket struct { // ContractMetaData contains all meta data concerning the Contract contract. var ContractMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"owner_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"onAcknowledgementPacket\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIIBCAppCallbacks.OnAcknowledgementPacketCallback\",\"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\":\"relayer\",\"type\":\"address\",\"internalType\":\"address\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"onRecvPacket\",\"inputs\":[{\"name\":\"\",\"type\":\"tuple\",\"internalType\":\"structIIBCAppCallbacks.OnRecvPacketCallback\",\"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\":\"relayer\",\"type\":\"address\",\"internalType\":\"address\"}]}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"onSendPacket\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIIBCAppCallbacks.OnSendPacketCallback\",\"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\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"onTimeoutPacket\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIIBCAppCallbacks.OnTimeoutPacketCallback\",\"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\":\"relayer\",\"type\":\"address\",\"internalType\":\"address\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"sendTransfer\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIICS20TransferMsgs.SendTransferMsg\",\"components\":[{\"name\":\"denom\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"receiver\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"memo\",\"type\":\"string\",\"internalType\":\"string\"}]}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"ICS20Acknowledgement\",\"inputs\":[{\"name\":\"packetData\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structICS20Lib.UnwrappedFungibleTokenPacketData\",\"components\":[{\"name\":\"erc20ContractAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"receiver\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"memo\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"acknowledgement\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"success\",\"type\":\"bool\",\"indexed\":false,\"internalType\":\"bool\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ICS20Timeout\",\"inputs\":[{\"name\":\"packetData\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structICS20Lib.UnwrappedFungibleTokenPacketData\",\"components\":[{\"name\":\"erc20ContractAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"receiver\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"memo\",\"type\":\"string\",\"internalType\":\"string\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ICS20Transfer\",\"inputs\":[{\"name\":\"packetData\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structICS20Lib.UnwrappedFungibleTokenPacketData\",\"components\":[{\"name\":\"erc20ContractAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"receiver\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"memo\",\"type\":\"string\",\"internalType\":\"string\"}]}],\"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\":\"error\",\"name\":\"AddressEmptyCode\",\"inputs\":[{\"name\":\"target\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"AddressInsufficientBalance\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"FailedInnerCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ICS20BytesSliceOutOfBounds\",\"inputs\":[{\"name\":\"length\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"start\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"end\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ICS20BytesSliceOverflow\",\"inputs\":[{\"name\":\"length\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ICS20InvalidAmount\",\"inputs\":[{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ICS20InvalidSender\",\"inputs\":[{\"name\":\"sender\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"type\":\"error\",\"name\":\"ICS20InvalidTokenContract\",\"inputs\":[{\"name\":\"tokenContract\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"type\":\"error\",\"name\":\"ICS20JSONClosingBraceNotFound\",\"inputs\":[{\"name\":\"position\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actual\",\"type\":\"bytes1\",\"internalType\":\"bytes1\"}]},{\"type\":\"error\",\"name\":\"ICS20JSONInvalidEscape\",\"inputs\":[{\"name\":\"position\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actual\",\"type\":\"bytes1\",\"internalType\":\"bytes1\"}]},{\"type\":\"error\",\"name\":\"ICS20JSONStringClosingDoubleQuoteNotFound\",\"inputs\":[{\"name\":\"position\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actual\",\"type\":\"bytes1\",\"internalType\":\"bytes1\"}]},{\"type\":\"error\",\"name\":\"ICS20JSONStringUnclosed\",\"inputs\":[{\"name\":\"bz\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"position\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ICS20JSONUnexpectedBytes\",\"inputs\":[{\"name\":\"position\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"expected\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"actual\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"ICS20MsgSenderIsNotPacketSender\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"packetSender\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ICS20UnexpectedERC20Balance\",\"inputs\":[{\"name\":\"expected\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actual\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ICS20UnexpectedVersion\",\"inputs\":[{\"name\":\"version\",\"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\":\"SafeERC20FailedOperation\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"StringsInsufficientHexLength\",\"inputs\":[{\"name\":\"value\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"length\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]}]", + ABI: "[{\"type\":\"constructor\",\"inputs\":[{\"name\":\"owner_\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"onAcknowledgementPacket\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIIBCAppCallbacks.OnAcknowledgementPacketCallback\",\"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\":\"relayer\",\"type\":\"address\",\"internalType\":\"address\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"onRecvPacket\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIIBCAppCallbacks.OnRecvPacketCallback\",\"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\":\"relayer\",\"type\":\"address\",\"internalType\":\"address\"}]}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"onSendPacket\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIIBCAppCallbacks.OnSendPacketCallback\",\"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\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"onTimeoutPacket\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIIBCAppCallbacks.OnTimeoutPacketCallback\",\"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\":\"relayer\",\"type\":\"address\",\"internalType\":\"address\"}]}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"owner\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"renounceOwnership\",\"inputs\":[],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"sendTransfer\",\"inputs\":[{\"name\":\"msg_\",\"type\":\"tuple\",\"internalType\":\"structIICS20TransferMsgs.SendTransferMsg\",\"components\":[{\"name\":\"denom\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"receiver\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sourceChannel\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"destPort\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"timeoutTimestamp\",\"type\":\"uint64\",\"internalType\":\"uint64\"},{\"name\":\"memo\",\"type\":\"string\",\"internalType\":\"string\"}]}],\"outputs\":[{\"name\":\"\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"stateMutability\":\"nonpayable\"},{\"type\":\"function\",\"name\":\"transferOwnership\",\"inputs\":[{\"name\":\"newOwner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"type\":\"event\",\"name\":\"ICS20Acknowledgement\",\"inputs\":[{\"name\":\"packetData\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structICS20Lib.UnwrappedFungibleTokenPacketData\",\"components\":[{\"name\":\"erc20ContractAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"receiver\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"memo\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"name\":\"acknowledgement\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"success\",\"type\":\"bool\",\"indexed\":false,\"internalType\":\"bool\"}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ICS20ReceiveTransfer\",\"inputs\":[{\"name\":\"packetData\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structICS20Lib.PacketDataJSON\",\"components\":[{\"name\":\"denom\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"sender\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"receiver\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"memo\",\"type\":\"string\",\"internalType\":\"string\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ICS20Timeout\",\"inputs\":[{\"name\":\"packetData\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structICS20Lib.UnwrappedFungibleTokenPacketData\",\"components\":[{\"name\":\"erc20ContractAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"receiver\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"memo\",\"type\":\"string\",\"internalType\":\"string\"}]}],\"anonymous\":false},{\"type\":\"event\",\"name\":\"ICS20Transfer\",\"inputs\":[{\"name\":\"packetData\",\"type\":\"tuple\",\"indexed\":false,\"internalType\":\"structICS20Lib.UnwrappedFungibleTokenPacketData\",\"components\":[{\"name\":\"erc20ContractAddress\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"receiver\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"memo\",\"type\":\"string\",\"internalType\":\"string\"}]}],\"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\":\"error\",\"name\":\"AddressEmptyCode\",\"inputs\":[{\"name\":\"target\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"AddressInsufficientBalance\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"FailedInnerCall\",\"inputs\":[]},{\"type\":\"error\",\"name\":\"ICS20BytesSliceOutOfBounds\",\"inputs\":[{\"name\":\"length\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"start\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"end\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ICS20BytesSliceOverflow\",\"inputs\":[{\"name\":\"length\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ICS20InvalidAmount\",\"inputs\":[{\"name\":\"amount\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ICS20InvalidReceiver\",\"inputs\":[{\"name\":\"receiver\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"type\":\"error\",\"name\":\"ICS20InvalidSender\",\"inputs\":[{\"name\":\"sender\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"type\":\"error\",\"name\":\"ICS20InvalidTokenContract\",\"inputs\":[{\"name\":\"tokenContract\",\"type\":\"string\",\"internalType\":\"string\"}]},{\"type\":\"error\",\"name\":\"ICS20JSONClosingBraceNotFound\",\"inputs\":[{\"name\":\"position\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actual\",\"type\":\"bytes1\",\"internalType\":\"bytes1\"}]},{\"type\":\"error\",\"name\":\"ICS20JSONInvalidEscape\",\"inputs\":[{\"name\":\"position\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actual\",\"type\":\"bytes1\",\"internalType\":\"bytes1\"}]},{\"type\":\"error\",\"name\":\"ICS20JSONStringClosingDoubleQuoteNotFound\",\"inputs\":[{\"name\":\"position\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actual\",\"type\":\"bytes1\",\"internalType\":\"bytes1\"}]},{\"type\":\"error\",\"name\":\"ICS20JSONStringUnclosed\",\"inputs\":[{\"name\":\"bz\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"position\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ICS20JSONUnexpectedBytes\",\"inputs\":[{\"name\":\"position\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"expected\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"actual\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"type\":\"error\",\"name\":\"ICS20MsgSenderIsNotPacketSender\",\"inputs\":[{\"name\":\"msgSender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"packetSender\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"type\":\"error\",\"name\":\"ICS20UnexpectedERC20Balance\",\"inputs\":[{\"name\":\"expected\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"actual\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"type\":\"error\",\"name\":\"ICS20UnexpectedVersion\",\"inputs\":[{\"name\":\"version\",\"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\":\"SafeERC20FailedOperation\",\"inputs\":[{\"name\":\"token\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"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. @@ -291,23 +300,23 @@ func (_Contract *ContractTransactorSession) OnAcknowledgementPacket(msg_ IIBCApp // OnRecvPacket is a paid mutator transaction binding the contract method 0x93ee0dc0. // -// Solidity: function onRecvPacket(((uint32,uint64,string,string,string,string,string,bytes),address) ) returns(bytes) -func (_Contract *ContractTransactor) OnRecvPacket(opts *bind.TransactOpts, arg0 IIBCAppCallbacksOnRecvPacketCallback) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "onRecvPacket", arg0) +// Solidity: function onRecvPacket(((uint32,uint64,string,string,string,string,string,bytes),address) msg_) returns(bytes) +func (_Contract *ContractTransactor) OnRecvPacket(opts *bind.TransactOpts, msg_ IIBCAppCallbacksOnRecvPacketCallback) (*types.Transaction, error) { + return _Contract.contract.Transact(opts, "onRecvPacket", msg_) } // OnRecvPacket is a paid mutator transaction binding the contract method 0x93ee0dc0. // -// Solidity: function onRecvPacket(((uint32,uint64,string,string,string,string,string,bytes),address) ) returns(bytes) -func (_Contract *ContractSession) OnRecvPacket(arg0 IIBCAppCallbacksOnRecvPacketCallback) (*types.Transaction, error) { - return _Contract.Contract.OnRecvPacket(&_Contract.TransactOpts, arg0) +// Solidity: function onRecvPacket(((uint32,uint64,string,string,string,string,string,bytes),address) msg_) returns(bytes) +func (_Contract *ContractSession) OnRecvPacket(msg_ IIBCAppCallbacksOnRecvPacketCallback) (*types.Transaction, error) { + return _Contract.Contract.OnRecvPacket(&_Contract.TransactOpts, msg_) } // OnRecvPacket is a paid mutator transaction binding the contract method 0x93ee0dc0. // -// Solidity: function onRecvPacket(((uint32,uint64,string,string,string,string,string,bytes),address) ) returns(bytes) -func (_Contract *ContractTransactorSession) OnRecvPacket(arg0 IIBCAppCallbacksOnRecvPacketCallback) (*types.Transaction, error) { - return _Contract.Contract.OnRecvPacket(&_Contract.TransactOpts, arg0) +// Solidity: function onRecvPacket(((uint32,uint64,string,string,string,string,string,bytes),address) msg_) returns(bytes) +func (_Contract *ContractTransactorSession) OnRecvPacket(msg_ IIBCAppCallbacksOnRecvPacketCallback) (*types.Transaction, error) { + return _Contract.Contract.OnRecvPacket(&_Contract.TransactOpts, msg_) } // OnSendPacket is a paid mutator transaction binding the contract method 0x340d036c. @@ -551,6 +560,140 @@ func (_Contract *ContractFilterer) ParseICS20Acknowledgement(log types.Log) (*Co return event, nil } +// ContractICS20ReceiveTransferIterator is returned from FilterICS20ReceiveTransfer and is used to iterate over the raw logs and unpacked data for ICS20ReceiveTransfer events raised by the Contract contract. +type ContractICS20ReceiveTransferIterator struct { + Event *ContractICS20ReceiveTransfer // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *ContractICS20ReceiveTransferIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(ContractICS20ReceiveTransfer) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(ContractICS20ReceiveTransfer) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *ContractICS20ReceiveTransferIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *ContractICS20ReceiveTransferIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// ContractICS20ReceiveTransfer represents a ICS20ReceiveTransfer event raised by the Contract contract. +type ContractICS20ReceiveTransfer struct { + PacketData ICS20LibPacketDataJSON + Raw types.Log // Blockchain specific contextual infos +} + +// FilterICS20ReceiveTransfer is a free log retrieval operation binding the contract event 0x9169ca6242a2d81c5bd346fe3e437825a5fcfb4b4845df61d0022c14bac4c393. +// +// Solidity: event ICS20ReceiveTransfer((string,string,string,uint256,string) packetData) +func (_Contract *ContractFilterer) FilterICS20ReceiveTransfer(opts *bind.FilterOpts) (*ContractICS20ReceiveTransferIterator, error) { + + logs, sub, err := _Contract.contract.FilterLogs(opts, "ICS20ReceiveTransfer") + if err != nil { + return nil, err + } + return &ContractICS20ReceiveTransferIterator{contract: _Contract.contract, event: "ICS20ReceiveTransfer", logs: logs, sub: sub}, nil +} + +// WatchICS20ReceiveTransfer is a free log subscription operation binding the contract event 0x9169ca6242a2d81c5bd346fe3e437825a5fcfb4b4845df61d0022c14bac4c393. +// +// Solidity: event ICS20ReceiveTransfer((string,string,string,uint256,string) packetData) +func (_Contract *ContractFilterer) WatchICS20ReceiveTransfer(opts *bind.WatchOpts, sink chan<- *ContractICS20ReceiveTransfer) (event.Subscription, error) { + + logs, sub, err := _Contract.contract.WatchLogs(opts, "ICS20ReceiveTransfer") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(ContractICS20ReceiveTransfer) + if err := _Contract.contract.UnpackLog(event, "ICS20ReceiveTransfer", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseICS20ReceiveTransfer is a log parse operation binding the contract event 0x9169ca6242a2d81c5bd346fe3e437825a5fcfb4b4845df61d0022c14bac4c393. +// +// Solidity: event ICS20ReceiveTransfer((string,string,string,uint256,string) packetData) +func (_Contract *ContractFilterer) ParseICS20ReceiveTransfer(log types.Log) (*ContractICS20ReceiveTransfer, error) { + event := new(ContractICS20ReceiveTransfer) + if err := _Contract.contract.UnpackLog(event, "ICS20ReceiveTransfer", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + // ContractICS20TimeoutIterator is returned from FilterICS20Timeout and is used to iterate over the raw logs and unpacked data for ICS20Timeout events raised by the Contract contract. type ContractICS20TimeoutIterator struct { Event *ContractICS20Timeout // Event containing the contract specifics and raw log diff --git a/e2e/interchaintestv8/types/ics26router/contract.go b/e2e/interchaintestv8/types/ics26router/contract.go index 5aea7408..39a4b298 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\":\"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\":\"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\":\"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/src/ICS20Transfer.sol b/src/ICS20Transfer.sol index d2a00a3a..b7900e3f 100644 --- a/src/ICS20Transfer.sol +++ b/src/ICS20Transfer.sol @@ -77,15 +77,55 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr emit ICS20Transfer(packetData); } - function onRecvPacket(OnRecvPacketCallback calldata) + function onRecvPacket(OnRecvPacketCallback calldata msg_) external override onlyOwner nonReentrant returns (bytes memory) { - // TODO: Implement - return ""; + // TODO Emit error event + if (keccak256(abi.encodePacked(msg_.packet.version)) != keccak256(abi.encodePacked(ICS20Lib.ICS20_VERSION))) { + return ICS20Lib.errorAck(abi.encodePacked("unexpected version: ", msg_.packet.version)); + } + + ICS20Lib.PacketDataJSON memory packetData = ICS20Lib.unmarshalJSON(msg_.packet.data); + if (packetData.amount == 0) { + return ICS20Lib.errorAck(abi.encodePacked("invalid amount: 0")); + } + + (address receiver, bool receiverConvertSuccess) = ICS20Lib.hexStringToAddress(packetData.receiver); + if (!receiverConvertSuccess) { + return ICS20Lib.errorAck(abi.encodePacked("invalid receiver: ", packetData.receiver)); + } + + // TODO: Handle non-contract denoms (destination chain is not source) + bytes memory denomPrefix = ICS20Lib.getDenomPrefix(msg_.packet.sourcePort, msg_.packet.sourceChannel); + bytes memory denom = bytes(packetData.denom); + if ( + denom.length >= denomPrefix.length + && 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)); + (address tokenContract, bool tokenContractConvertSuccess) = ICS20Lib.hexStringToAddress(unprefixedDenom); + if (!tokenContractConvertSuccess) { + return ICS20Lib.errorAck(abi.encodePacked("invalid token contract: ", unprefixedDenom)); + } + + IERC20(tokenContract).safeTransfer(receiver, packetData.amount); + } else { + // 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)); + } + + emit ICS20ReceiveTransfer(packetData); + + return ICS20Lib.SUCCESSFUL_ACKNOWLEDGEMENT_JSON; } function onAcknowledgementPacket(OnAcknowledgementPacketCallback calldata msg_) diff --git a/src/ICS26Router.sol b/src/ICS26Router.sol index 6e59ad4b..2f078b73 100644 --- a/src/ICS26Router.sol +++ b/src/ICS26Router.sol @@ -124,19 +124,25 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors, Ree value: abi.encodePacked(commitmentBz) }); - ics02Client.getClient(msg_.packet.destChannel).membership(membershipMsg); + try ics02Client.getClient(msg_.packet.destChannel).membership(membershipMsg) { + } catch (bytes memory reason) { + revert IBCMembershipProofVerificationFailed(msg_.packet, membershipMsg, reason); + } + uint64 nanoTimestamp = uint64(block.timestamp * 1_000_000_000); if (msg_.packet.timeoutTimestamp <= nanoTimestamp) { revert IBCInvalidTimeoutTimestamp(msg_.packet.timeoutTimestamp, nanoTimestamp); } - bytes memory ack = - app.onRecvPacket(IIBCAppCallbacks.OnRecvPacketCallback({ packet: msg_.packet, relayer: msg.sender })); - if (ack.length == 0) { - revert IBCAsyncAcknowledgementNotSupported(); - } + 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); + writeAcknowledgement(msg_.packet, ack); + } catch (bytes memory reason) { + revert IBCPacketHandlingFailed(msg_.packet, reason); + } IBCStore.setPacketReceipt(msg_.packet); diff --git a/src/errors/IICS20Errors.sol b/src/errors/IICS20Errors.sol index 24b4ba94..c63197ea 100644 --- a/src/errors/IICS20Errors.sol +++ b/src/errors/IICS20Errors.sol @@ -9,6 +9,9 @@ interface IICS20Errors { /// @param sender Address whose tokens are being transferred error ICS20InvalidSender(string sender); + /// @param receiver Address receiving the tokens + error ICS20InvalidReceiver(string receiver); + /// @param amount Amount of tokens being transferred error ICS20InvalidAmount(uint256 amount); diff --git a/src/errors/IICS26RouterErrors.sol b/src/errors/IICS26RouterErrors.sol index 69f1d7a6..50e12150 100644 --- a/src/errors/IICS26RouterErrors.sol +++ b/src/errors/IICS26RouterErrors.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8.25; +import {IICS26RouterMsgs} from "../msgs/IICS26RouterMsgs.sol"; +import {ILightClientMsgs} from "../msgs/ILightClientMsgs.sol"; + interface IICS26RouterErrors { /// @param portId port identifier error IBCPortAlreadyExists(string portId); @@ -20,4 +23,8 @@ interface IICS26RouterErrors { error IBCPacketCommitmentMismatch(bytes32 expected, bytes32 actual); error IBCAppNotFound(string portId); + + error IBCMembershipProofVerificationFailed(IICS26RouterMsgs.Packet packet, ILightClientMsgs.MsgMembership membershipMsg, bytes reason); + + error IBCPacketHandlingFailed(IICS26RouterMsgs.Packet packet, bytes reason); } diff --git a/src/interfaces/IICS20Transfer.sol b/src/interfaces/IICS20Transfer.sol index f8279794..e94bcc41 100644 --- a/src/interfaces/IICS20Transfer.sol +++ b/src/interfaces/IICS20Transfer.sol @@ -9,6 +9,8 @@ interface IICS20Transfer is IICS20TransferMsgs { /// @param packetData The transfer packet data event ICS20Transfer(ICS20Lib.UnwrappedFungibleTokenPacketData packetData); + event ICS20ReceiveTransfer(ICS20Lib.PacketDataJSON packetData); + // TODO: If we want error and/or success result in the event (resp.Result), parsing the acknowledgement is needed /// @notice Called after handling acknowledgement in onAcknowledgementPacket /// @param packetData The transfer packet data diff --git a/src/utils/ICS20Lib.sol b/src/utils/ICS20Lib.sol index 3b39fe33..fe2d1dc9 100644 --- a/src/utils/ICS20Lib.sol +++ b/src/utils/ICS20Lib.sol @@ -75,16 +75,16 @@ library ICS20Lib { returns (bytes memory) { return abi.encodePacked( - "{\"amount\":\"", - Strings.toString(amount), - "\",\"denom\":\"", + "{\"denom\":\"", escapedDenom, - "\",\"memo\":\"", - escapedMemo, - "\",\"receiver\":\"", - escapedReceiver, + "\",\"amount\":\"", + Strings.toString(amount), "\",\"sender\":\"", escapedSender, + "\",\"receiver\":\"", + escapedReceiver, + "\",\"memo\":\"", + escapedMemo, "\"}" ); } @@ -103,14 +103,14 @@ library ICS20Lib { returns (bytes memory) { return abi.encodePacked( - "{\"amount\":\"", - Strings.toString(amount), - "\",\"denom\":\"", + "{\"denom\":\"", escapedDenom, - "\",\"receiver\":\"", - escapedReceiver, + "\",\"amount\":\"", + Strings.toString(amount), "\",\"sender\":\"", escapedSender, + "\",\"receiver\":\"", + escapedReceiver, "\"}" ); } @@ -119,26 +119,25 @@ library ICS20Lib { * @dev unmarshalJSON unmarshals JSON bytes into PacketData. */ function unmarshalJSON(bytes calldata bz) internal pure returns (PacketDataJSON memory) { + // TODO: Consider if this should support other orders of fields (currently fixed order: denom, amount, etc) PacketDataJSON memory pd; uint256 pos = 0; unchecked { - if (bytes32(bz[pos:pos + 11]) != bytes32("{\"amount\":\"")) { + if (bytes32(bz[pos:pos + 10]) != bytes32("{\"denom\":\"")) { + revert IICS20Errors.ICS20JSONUnexpectedBytes(pos, bytes32("{\"denom\":\""), bytes32(bz[pos:pos + 10])); + } + (pd.denom, pos) = parseString(bz, pos + 10); + + if (bytes32(bz[pos:pos + 11]) != bytes32(",\"amount\":\"")) { revert IICS20Errors.ICS20JSONUnexpectedBytes(pos, bytes32("{\"amount\":\""), bytes32(bz[pos:pos + 11])); } (pd.amount, pos) = parseUint256String(bz, pos + 11); - if (bytes32(bz[pos:pos + 10]) != bytes32(",\"denom\":\"")) { - revert IICS20Errors.ICS20JSONUnexpectedBytes(pos, bytes32(",\"denom\":\""), bytes32(bz[pos:pos + 10])); - } - (pd.denom, pos) = parseString(bz, pos + 10); - if (uint256(uint8(bz[pos + 2])) == CHAR_M) { - if (bytes32(bz[pos:pos + 9]) != bytes32(",\"memo\":\"")) { - // solhint-disable-next-line max-line-length - revert IICS20Errors.ICS20JSONUnexpectedBytes(pos, bytes32(",\"memo\":\""), bytes32(bz[pos:pos + 9])); - } - (pd.memo, pos) = parseString(bz, pos + 9); + if (bytes32(bz[pos:pos + 11]) != bytes32(",\"sender\":\"")) { + revert IICS20Errors.ICS20JSONUnexpectedBytes(pos, bytes32(",\"sender\":\""), bytes32(bz[pos:pos + 11])); } + (pd.sender, pos) = parseString(bz, pos + 11); if (bytes32(bz[pos:pos + 13]) != bytes32(",\"receiver\":\"")) { revert IICS20Errors.ICS20JSONUnexpectedBytes( @@ -147,10 +146,13 @@ library ICS20Lib { } (pd.receiver, pos) = parseString(bz, pos + 13); - if (bytes32(bz[pos:pos + 11]) != bytes32(",\"sender\":\"")) { - revert IICS20Errors.ICS20JSONUnexpectedBytes(pos, bytes32(",\"sender\":\""), bytes32(bz[pos:pos + 11])); + if (uint256(uint8(bz[pos + 2])) == CHAR_M) { + if (bytes32(bz[pos:pos + 9]) != bytes32(",\"memo\":\"")) { + // solhint-disable-next-line max-line-length + revert IICS20Errors.ICS20JSONUnexpectedBytes(pos, bytes32(",\"memo\":\""), bytes32(bz[pos:pos + 9])); + } + (pd.memo, pos) = parseString(bz, pos + 9); } - (pd.sender, pos) = parseString(bz, pos + 11); if (pos != bz.length - 1 || uint256(uint8(bz[pos])) != CHAR_CLOSING_BRACE) { revert IICS20Errors.ICS20JSONClosingBraceNotFound(pos, bz[pos]); @@ -360,4 +362,12 @@ library ICS20Lib { memo: packetData.memo }); } + + function errorAck(bytes memory reason) internal pure returns (bytes memory) { + return abi.encodePacked("{\"error\":\"", reason, "\"}"); + } + + function getDenomPrefix(string calldata port, string calldata channel) internal pure returns (bytes memory) { + return abi.encodePacked(port, "/", channel, "/"); + } } diff --git a/test/DummyLightClient.sol b/test/DummyLightClient.sol index f323187a..9d8bee98 100644 --- a/test/DummyLightClient.sol +++ b/test/DummyLightClient.sol @@ -8,11 +8,15 @@ import { ILightClient } from "../src/interfaces/ILightClient.sol"; contract DummyLightClient is ILightClient { UpdateResult public updateResult; uint64 public membershipResult; + bool public membershipShouldFail; bytes public latestUpdateMsg; - constructor(UpdateResult updateResult_, uint64 membershipResult_) { + error MembershipShouldFail(string reason); + + constructor(UpdateResult updateResult_, uint64 membershipResult_, bool membershipShouldFail_) { updateResult = updateResult_; membershipResult = membershipResult_; + membershipShouldFail = membershipShouldFail_; } function updateClient(bytes calldata updateMsg) external returns (UpdateResult) { @@ -21,6 +25,9 @@ contract DummyLightClient is ILightClient { } function membership(MsgMembership calldata) external view returns (uint256) { + if (membershipShouldFail) { + revert MembershipShouldFail("membership should fail"); + } return membershipResult; } @@ -33,7 +40,8 @@ contract DummyLightClient is ILightClient { updateResult = updateResult_; } - function setMembershipResult(uint64 membershipResult_) external { + function setMembershipResult(uint64 membershipResult_, bool shouldFail) external { membershipResult = membershipResult_; + membershipShouldFail = shouldFail; } } diff --git a/test/ICS02ClientTest.t.sol b/test/ICS02ClientTest.t.sol index 2af2b119..691a575f 100644 --- a/test/ICS02ClientTest.t.sol +++ b/test/ICS02ClientTest.t.sol @@ -16,7 +16,7 @@ contract ICS02ClientTest is Test { DummyLightClient public lightClient; function setUp() public { - lightClient = new DummyLightClient(ILightClientMsgs.UpdateResult.Update, 0); + lightClient = new DummyLightClient(ILightClientMsgs.UpdateResult.Update, 0, false); ics02Client = new ICS02Client(address(this)); } diff --git a/test/ICS20TransferTest.t.sol b/test/ICS20TransferTest.t.sol index f43c4056..9b6d4c78 100644 --- a/test/ICS20TransferTest.t.sol +++ b/test/ICS20TransferTest.t.sol @@ -309,6 +309,85 @@ contract ICS20TransferTest is Test { ); } + function test_success_onRecvPacket() public { + erc20.mint(sender, defaultAmount); + + vm.prank(sender); + erc20.approve(address(ics20Transfer), defaultAmount); + + uint256 senderBalanceBefore = erc20.balanceOf(sender); + uint256 contractBalanceBefore = erc20.balanceOf(address(ics20Transfer)); + assertEq(senderBalanceBefore, defaultAmount); + assertEq(contractBalanceBefore, 0); + + vm.expectEmit(); + emit IICS20Transfer.ICS20Transfer(_getPacketData()); + ics20Transfer.onSendPacket(IIBCAppCallbacks.OnSendPacketCallback({ packet: packet, sender: sender })); + + uint256 senderBalanceAfterSend = erc20.balanceOf(sender); + uint256 contractBalanceAfterSend = erc20.balanceOf(address(ics20Transfer)); + assertEq(senderBalanceAfterSend, 0); + assertEq(contractBalanceAfterSend, defaultAmount); + + // Send back (onRecv) + string memory newSourcePort = packet.destPort; + string memory newSourceChannel = packet.destChannel; + string memory ibcDenom = string(abi.encodePacked(newSourcePort, "/", newSourceChannel, "/", erc20AddressStr)); + + bytes memory receiveData = ICS20Lib.marshalJSON(ibcDenom, defaultAmount, receiver, senderStr, "memo"); + packet.data = receiveData; + packet.destPort = packet.sourcePort; + packet.destChannel = packet.sourceChannel; + packet.sourcePort = newSourcePort; + packet.sourceChannel = newSourceChannel; + + 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 + uint256 senderBalanceAfterReceive = erc20.balanceOf(sender); + uint256 contractBalanceAfterReceive = erc20.balanceOf(address(ics20Transfer)); + assertEq(senderBalanceAfterReceive, defaultAmount); + assertEq(contractBalanceAfterReceive, 0); + } + + function test_failure_onRecvPacket() public { + 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") })); + assertEq(string(ack), "{\"error\":\"unexpected version: invalid\"}"); + // Reset version + packet.version = ICS20Lib.ICS20_VERSION; + + // test invalid data + data = bytes("invalid"); + packet.data = data; + vm.expectRevert(bytes("")); + 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") })); + assertEq(string(ack), "{\"error\":\"invalid amount: 0\"}"); + + // test 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") })); + 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") })); + assertEq(string(ack), "{\"error\":\"invalid receiver: invalid\"}"); + } + function _getPacketData() internal view returns (ICS20Lib.UnwrappedFungibleTokenPacketData memory) { return ICS20Lib.UnwrappedFungibleTokenPacketData({ sender: sender, diff --git a/test/ICS26RouterTest.t.sol b/test/ICS26RouterTest.t.sol index 1fc6a429..26ca6636 100644 --- a/test/ICS26RouterTest.t.sol +++ b/test/ICS26RouterTest.t.sol @@ -5,10 +5,16 @@ pragma solidity >=0.8.25 <0.9.0; import { Test } from "forge-std/Test.sol"; import { ICS02Client } from "../src/ICS02Client.sol"; +import { IICS02ClientMsgs } from "../src/msgs/IICS02ClientMsgs.sol"; import { ICS26Router } from "../src/ICS26Router.sol"; import { IICS26Router } from "../src/interfaces/IICS26Router.sol"; +import { IICS26RouterErrors } from "../src/errors/IICS26RouterErrors.sol"; +import { IICS26RouterMsgs } from "../src/msgs/IICS26RouterMsgs.sol"; import { ICS20Transfer } from "../src/ICS20Transfer.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; +import { DummyLightClient } from "./DummyLightClient.sol"; +import { ILightClientMsgs } from "../src/msgs/ILightClientMsgs.sol"; +import { ICS24Host } from "../src/utils/ICS24Host.sol"; contract ICS26RouterTest is Test { ICS02Client public ics02Client; @@ -39,4 +45,50 @@ contract ICS26RouterTest is Test { assertEq(address(ics20Transfer), address(ics26Router.getIBCApp("transfer"))); } + + function test_RecvPacketWithFailedMembershipVerification() public { + string memory counterpartyClientID = "42-dummy-01"; + DummyLightClient lightClient = new DummyLightClient(ILightClientMsgs.UpdateResult.Update, 0, true); + string memory clientIdentifier = ics02Client.addClient( + "07-tendermint", IICS02ClientMsgs.CounterpartyInfo(counterpartyClientID), address(lightClient) + ); + + 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, + sourcePort: "transfer", + sourceChannel: counterpartyClientID, + destPort: "transfer", + destChannel: clientIdentifier, + version: "ics20-1", + data: "0x" + }); + + IICS26RouterMsgs.MsgRecvPacket memory msgRecvPacket = IICS26RouterMsgs.MsgRecvPacket({ + 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 + ); + bytes32 commitmentBz = ICS24Host.packetCommitmentBytes32(msgRecvPacket.packet); + ILightClientMsgs.MsgMembership memory expectedMsgMembership = ILightClientMsgs.MsgMembership({ + proof: msgRecvPacket.proofCommitment, + proofHeight: msgRecvPacket.proofHeight, + path: commitmentPath, + value: abi.encodePacked(commitmentBz) + }); + vm.expectRevert( + abi.encodeWithSelector( + 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 70b43e40..a39833d7 100644 --- a/test/IntegrationTest.t.sol +++ b/test/IntegrationTest.t.sol @@ -41,7 +41,7 @@ contract IntegrationTest is Test { function setUp() public { ics02Client = new ICS02Client(address(this)); ics26Router = new ICS26Router(address(ics02Client), address(this)); - lightClient = new DummyLightClient(ILightClientMsgs.UpdateResult.Update, 0); + lightClient = new DummyLightClient(ILightClientMsgs.UpdateResult.Update, 0, false); ics20Transfer = new ICS20Transfer(address(ics26Router)); erc20 = new TestERC20(); erc20AddressStr = Strings.toHexString(address(erc20)); @@ -166,7 +166,7 @@ contract IntegrationTest is Test { IICS26RouterMsgs.Packet memory packet = _sendICS20Transfer(); // make light client return timestamp that is after our timeout - lightClient.setMembershipResult(msgSendPacket.timeoutTimestamp + uint64(1_000_000_000)); + lightClient.setMembershipResult(msgSendPacket.timeoutTimestamp + uint64(1_000_000_000), false); IICS26RouterMsgs.MsgTimeoutPacket memory timeoutMsg = IICS26RouterMsgs.MsgTimeoutPacket({ packet: packet, @@ -190,6 +190,77 @@ contract IntegrationTest is Test { assertEq(contractBalanceAfterTimeout, 0); } + function test_success_receiveICS20PacketWithKnownDenom() 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"; + address backReceiver = sender; + string memory backReceiverStr = senderStr; + string memory ibcDenom = string(abi.encodePacked("transfer/", counterpartyClient, "/", erc20AddressStr)); + data = ICS20Lib.marshalJSON(ibcDenom, defaultAmount, backSender, backReceiverStr, "backmemo"); + packet = IICS26RouterMsgs.Packet({ + sequence: 1, + timeoutTimestamp: uint64((block.timestamp + 1000) * 1_000_000_000), + sourcePort: "transfer", + sourceChannel: counterpartyClient, + destPort: "transfer", + destChannel: clientIdentifier, + version: ICS20Lib.ICS20_VERSION, + data: data + }); + vm.expectEmit(); + 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 + })); + + // Check balances are updated as expected + uint256 backReceiverBalance = erc20.balanceOf(backReceiver); + uint256 contractBalanceAfterRecv = erc20.balanceOf(address(ics20Transfer)); + assertEq(backReceiverBalance, defaultAmount); + assertEq(contractBalanceAfterRecv, 0); + + // Check that the ack is written + bytes32 ackPath = ICS24Host.packetAcknowledgementCommitmentKeyCalldata( + packet.destPort, packet.destChannel, packet.sequence + ); + bytes32 storedAck = ics26Router.getCommitment(ackPath); + assertEq(storedAck, ICS24Host.packetAcknowledgementCommitmentBytes32(ICS20Lib.SUCCESSFUL_ACKNOWLEDGEMENT_JSON)); + } + function _sendICS20Transfer() internal returns (IICS26RouterMsgs.Packet memory) { erc20.mint(sender, defaultAmount); vm.startPrank(sender); From fa091a58e226503007c9c5a81d0f21ca8380771c Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Tue, 6 Aug 2024 19:05:58 +0200 Subject: [PATCH 2/9] 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); From f179c274b7a3ec9938ce10dab4fb40941b12b99e Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 7 Aug 2024 11:24:08 +0800 Subject: [PATCH 3/9] imp: removed nanosecond logic --- e2e/interchaintestv8/go.mod | 2 +- e2e/interchaintestv8/ibc_eureka_test.go | 15 +++----------- src/ICS26Router.sol | 27 +++++++------------------ src/utils/ICS24Host.sol | 16 +-------------- 4 files changed, 12 insertions(+), 48 deletions(-) diff --git a/e2e/interchaintestv8/go.mod b/e2e/interchaintestv8/go.mod index 948c799a..082d442c 100644 --- a/e2e/interchaintestv8/go.mod +++ b/e2e/interchaintestv8/go.mod @@ -10,6 +10,7 @@ require ( cosmossdk.io/x/tx v0.13.3 cosmossdk.io/x/upgrade v0.1.2 github.com/CosmWasm/wasmd v0.50.0 + github.com/cometbft/cometbft v0.38.7 github.com/cosmos/cosmos-sdk v0.50.7 github.com/cosmos/gogoproto v1.4.12 github.com/cosmos/ibc-go/v8 v8.3.0 @@ -71,7 +72,6 @@ require ( github.com/cockroachdb/pebble v1.1.1 // indirect github.com/cockroachdb/redact v1.1.5 // indirect github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect - github.com/cometbft/cometbft v0.38.7 // indirect github.com/cometbft/cometbft-db v0.10.0 // indirect github.com/consensys/bavard v0.1.13 // indirect github.com/consensys/gnark-crypto v0.12.1 // indirect diff --git a/e2e/interchaintestv8/ibc_eureka_test.go b/e2e/interchaintestv8/ibc_eureka_test.go index dad2fe5a..054d3eaf 100644 --- a/e2e/interchaintestv8/ibc_eureka_test.go +++ b/e2e/interchaintestv8/ibc_eureka_test.go @@ -25,8 +25,6 @@ import ( 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" @@ -445,7 +443,8 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { var returnPacket channeltypes.Packet s.Require().True(s.Run("Transfer back", func() { - timeout := uint64(time.Now().Add(30 * time.Minute).UnixNano()) + // We need the timeout to be a whole number of seconds to be received by eth + timeout := uint64(time.Now().Add(30*time.Minute).Unix() * 1_000_000_000) ibcCoin := sdk.NewCoin(ibcDenom, sdkmath.NewIntFromBigInt(transferAmount)) msgTransfer := transfertypes.MsgTransfer{ @@ -506,14 +505,6 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { s.Require().NoError(err) packetCommitmentPath := ibchost.PacketCommitmentPath(returnPacket.SourcePort, returnPacket.SourceChannel, returnPacket.Sequence) - storeProofResp, err := e2esuite.ABCIQuery(ctx, simd, &abci.RequestQuery{ - Path: "store/ibc/key", - Height: latestHeight - 1, // ? - Data: []byte(packetCommitmentPath), - Prove: true, - }) - 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(), @@ -524,7 +515,7 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { msg := ics26router.IICS26RouterMsgsMsgRecvPacket{ Packet: ics26router.IICS26RouterMsgsPacket{ Sequence: uint32(returnPacket.Sequence), - TimeoutTimestamp: returnPacket.TimeoutTimestamp, + TimeoutTimestamp: returnPacket.TimeoutTimestamp / 1_000_000_000, SourcePort: returnPacket.SourcePort, SourceChannel: returnPacket.SourceChannel, DestPort: returnPacket.DestinationPort, diff --git a/src/ICS26Router.sol b/src/ICS26Router.sol index b84321db..765ccc07 100644 --- a/src/ICS26Router.sol +++ b/src/ICS26Router.sol @@ -123,31 +123,18 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors, Ree value: abi.encodePacked(commitmentBz) }); - // 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); - } - - uint64 timeoutTimestamp = msg_.packet.timeoutTimestamp; - if (!ICS24Host.isTimestampInSeconds(timeoutTimestamp)) { - timeoutTimestamp = uint64(timeoutTimestamp / 1_000_000_000); - } - if (timeoutTimestamp <= block.timestamp) { + ics02Client.getClient(msg_.packet.destChannel).membership(membershipMsg); + if (msg_.packet.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) { - if (ack.length == 0) { - revert IBCAsyncAcknowledgementNotSupported(); - } - - writeAcknowledgement(msg_.packet, ack); - } catch (bytes memory reason) { - revert IBCPacketHandlingFailed(msg_.packet, reason); + bytes memory ack = app.onRecvPacket(IIBCAppCallbacks.OnRecvPacketCallback({ packet: msg_.packet, relayer: msg.sender })); + if (ack.length == 0) { + revert IBCAsyncAcknowledgementNotSupported(); } + writeAcknowledgement(msg_.packet, ack); + IBCStore.setPacketReceipt(msg_.packet); emit RecvPacket(msg_.packet); diff --git a/src/utils/ICS24Host.sol b/src/utils/ICS24Host.sol index ca32460c..d13a8aef 100644 --- a/src/utils/ICS24Host.sol +++ b/src/utils/ICS24Host.sol @@ -103,25 +103,11 @@ 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(timestamp, uint64(0), uint64(0), sha256(packet.data), packet.destPort, packet.destChannel) + abi.encodePacked(SafeCast.toUint64(uint256(packet.timeoutTimestamp) * 1_000_000_000), 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); From 4962bc31834e788f7835d41083c149a2e6038dcc Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 7 Aug 2024 11:27:39 +0800 Subject: [PATCH 4/9] style: ran 'forge fmt' --- src/ICS26Router.sol | 3 ++- src/utils/ICS24Host.sol | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/ICS26Router.sol b/src/ICS26Router.sol index 765ccc07..dee7943a 100644 --- a/src/ICS26Router.sol +++ b/src/ICS26Router.sol @@ -128,7 +128,8 @@ contract ICS26Router is IICS26Router, IBCStore, Ownable, IICS26RouterErrors, Ree revert IBCInvalidTimeoutTimestamp(msg_.packet.timeoutTimestamp, block.timestamp); } - bytes memory ack = app.onRecvPacket(IIBCAppCallbacks.OnRecvPacketCallback({ packet: msg_.packet, relayer: msg.sender })); + bytes memory ack = + app.onRecvPacket(IIBCAppCallbacks.OnRecvPacketCallback({ packet: msg_.packet, relayer: msg.sender })); if (ack.length == 0) { revert IBCAsyncAcknowledgementNotSupported(); } diff --git a/src/utils/ICS24Host.sol b/src/utils/ICS24Host.sol index d13a8aef..cd7d6ff4 100644 --- a/src/utils/ICS24Host.sol +++ b/src/utils/ICS24Host.sol @@ -104,7 +104,14 @@ library ICS24Host { /// @notice Get the packet commitment bytes. function packetCommitmentBytes32(IICS26RouterMsgs.Packet memory packet) internal pure returns (bytes32) { 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( + SafeCast.toUint64(uint256(packet.timeoutTimestamp) * 1_000_000_000), + uint64(0), + uint64(0), + sha256(packet.data), + packet.destPort, + packet.destChannel + ) ); } From 493022f406dbc51258c047a07295451ab1507cf3 Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 7 Aug 2024 11:37:48 +0800 Subject: [PATCH 5/9] imp: added custom error --- src/ICS20Transfer.sol | 13 +++++-------- src/errors/IICS20Errors.sol | 3 +++ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/ICS20Transfer.sol b/src/ICS20Transfer.sol index dca04ac0..055bd10b 100644 --- a/src/ICS20Transfer.sol +++ b/src/ICS20Transfer.sol @@ -50,7 +50,7 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr return ibcRouter.sendPacket(msgSendPacket); } - function onSendPacket(OnSendPacketCallback calldata msg_) external override onlyOwner nonReentrant { + function onSendPacket(OnSendPacketCallback calldata msg_) external onlyOwner nonReentrant { if (keccak256(abi.encodePacked(msg_.packet.version)) != keccak256(abi.encodePacked(ICS20Lib.ICS20_VERSION))) { revert ICS20UnexpectedVersion(msg_.packet.version); } @@ -79,7 +79,6 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr function onRecvPacket(OnRecvPacketCallback calldata msg_) external - override onlyOwner nonReentrant returns (bytes memory) @@ -107,7 +106,7 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr && ICS20Lib.equal(ICS20Lib.slice(denom, 0, denomPrefix.length), denomPrefix) ) { // sender chain is not the source, unescrow tokens - // TODO: Implement escrow balance tracking + // TODO: Implement escrow balance tracking (#6) string memory unprefixedDenom = string(ICS20Lib.slice(denom, denomPrefix.length, denom.length - denomPrefix.length)); @@ -119,10 +118,9 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr IERC20(tokenContract).safeTransfer(receiver, packetData.amount); } else { // sender chain is the source, mint vouchers - // TODO: Implement escrow balance tracking + // TODO: Implement escrow balance tracking (#6) // TODO: Implement creating (new erc20 contracts), looking up and minting of vouchers - // solhint-disable-next-line - revert("not supported: sender denom is source"); + revert ICS20UnsupportedFeature("sender denom is source"); } emit ICS20ReceiveTransfer(packetData); @@ -132,7 +130,6 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr function onAcknowledgementPacket(OnAcknowledgementPacketCallback calldata msg_) external - override onlyOwner nonReentrant { @@ -149,7 +146,7 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr emit ICS20Acknowledgement(packetData, msg_.acknowledgement, isSuccessAck); } - function onTimeoutPacket(OnTimeoutPacketCallback calldata msg_) external override onlyOwner nonReentrant { + function onTimeoutPacket(OnTimeoutPacketCallback calldata msg_) external onlyOwner nonReentrant { ICS20Lib.UnwrappedFungibleTokenPacketData memory packetData = ICS20Lib.unwrapPacketData(msg_.packet.data); _refundTokens(packetData); diff --git a/src/errors/IICS20Errors.sol b/src/errors/IICS20Errors.sol index c63197ea..690fd380 100644 --- a/src/errors/IICS20Errors.sol +++ b/src/errors/IICS20Errors.sol @@ -25,6 +25,9 @@ interface IICS20Errors { /// @param actual Actual balance of the ERC20 token for ICS20Transfer error ICS20UnexpectedERC20Balance(uint256 expected, uint256 actual); + /// @param feature Unsupported feature + error ICS20UnsupportedFeature(string feature); + // ICS20Lib Errors: /// @param position position in packet data bytes From abb02942c8582efffe26314661e94876fec01e3d Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 7 Aug 2024 11:38:10 +0800 Subject: [PATCH 6/9] style: ran 'forge fmt' --- src/ICS20Transfer.sol | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/ICS20Transfer.sol b/src/ICS20Transfer.sol index 055bd10b..a7e19989 100644 --- a/src/ICS20Transfer.sol +++ b/src/ICS20Transfer.sol @@ -77,12 +77,7 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr emit ICS20Transfer(packetData); } - function onRecvPacket(OnRecvPacketCallback calldata msg_) - external - onlyOwner - nonReentrant - returns (bytes memory) - { + function onRecvPacket(OnRecvPacketCallback calldata msg_) external onlyOwner nonReentrant returns (bytes memory) { // TODO Emit error event if (keccak256(abi.encodePacked(msg_.packet.version)) != keccak256(abi.encodePacked(ICS20Lib.ICS20_VERSION))) { return ICS20Lib.errorAck(abi.encodePacked("unexpected version: ", msg_.packet.version)); @@ -128,11 +123,7 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr return ICS20Lib.SUCCESSFUL_ACKNOWLEDGEMENT_JSON; } - function onAcknowledgementPacket(OnAcknowledgementPacketCallback calldata msg_) - external - onlyOwner - nonReentrant - { + function onAcknowledgementPacket(OnAcknowledgementPacketCallback calldata msg_) external onlyOwner nonReentrant { ICS20Lib.UnwrappedFungibleTokenPacketData memory packetData = ICS20Lib.unwrapPacketData(msg_.packet.data); bool isSuccessAck = true; From 5d699fc2f02a31ef91b2fc2f7e18275b72f53bcf Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 7 Aug 2024 11:41:13 +0800 Subject: [PATCH 7/9] imp: removed needless custom error --- script/E2ETestDeploy.s.sol | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/script/E2ETestDeploy.s.sol b/script/E2ETestDeploy.s.sol index 9051e9ca..2e32791e 100644 --- a/script/E2ETestDeploy.s.sol +++ b/script/E2ETestDeploy.s.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.25 <0.9.0; +// solhint-disable gas-custom-errors,custom-errors + import { stdJson } from "forge-std/StdJson.sol"; import { Script } from "forge-std/Script.sol"; import { SP1ICS07Tendermint } from "@cosmos/sp1-ics07-tendermint/SP1ICS07Tendermint.sol"; @@ -25,8 +27,6 @@ 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) { @@ -61,9 +61,8 @@ contract E2ETestDeploy is Script { // Mint some tokens (address addr, bool ok) = ICS20Lib.hexStringToAddress(E2E_FAUCET); - if (!ok) { - revert E2EInvalidAddress(E2E_FAUCET); - } + require(ok, "failed to parse faucet address"); + erc20.mint(addr, 100_000_000_000); vm.stopBroadcast(); From 8b7d50ccc3f94fd495222a357b8e54f3be007ba7 Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 7 Aug 2024 12:51:48 +0800 Subject: [PATCH 8/9] fix(test): fixed all test cases --- test/ICS20TransferTest.t.sol | 2 +- test/ICS24Host.t.sol | 54 ------------------------------------ test/ICS26RouterTest.t.sol | 19 +------------ test/IntegrationTest.t.sol | 17 +----------- 4 files changed, 3 insertions(+), 89 deletions(-) delete mode 100644 test/ICS24Host.t.sol diff --git a/test/ICS20TransferTest.t.sol b/test/ICS20TransferTest.t.sol index 1a3f0a32..f6e06c3b 100644 --- a/test/ICS20TransferTest.t.sol +++ b/test/ICS20TransferTest.t.sol @@ -405,7 +405,7 @@ contract ICS20TransferTest is Test { string memory sourceDenom = "uatom"; data = ICS20Lib.marshalJSON(sourceDenom, defaultAmount, receiver, senderStr, "memo"); packet.data = data; - vm.expectRevert(bytes("not supported: sender denom is source")); + vm.expectRevert(abi.encodeWithSelector(IICS20Errors.ICS20UnsupportedFeature.selector, "sender denom is source")); ics20Transfer.onRecvPacket( IIBCAppCallbacks.OnRecvPacketCallback({ packet: packet, relayer: makeAddr("relayer") }) ); diff --git a/test/ICS24Host.t.sol b/test/ICS24Host.t.sol deleted file mode 100644 index a2a4cd52..000000000 --- a/test/ICS24Host.t.sol +++ /dev/null @@ -1,54 +0,0 @@ -// 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 a93da21e..7841af94 100644 --- a/test/ICS26RouterTest.t.sol +++ b/test/ICS26RouterTest.t.sol @@ -73,24 +73,7 @@ contract ICS26RouterTest is Test { proofHeight: IICS02ClientMsgs.Height({ revisionNumber: 0, revisionHeight: 0 }) // doesn't matter }); - bytes memory commitmentPath = ICS24Host.packetCommitmentPathCalldata( - msgRecvPacket.packet.sourcePort, msgRecvPacket.packet.sourceChannel, msgRecvPacket.packet.sequence - ); - bytes32 commitmentBz = ICS24Host.packetCommitmentBytes32(msgRecvPacket.packet); - ILightClientMsgs.MsgMembership memory expectedMsgMembership = ILightClientMsgs.MsgMembership({ - proof: msgRecvPacket.proofCommitment, - proofHeight: msgRecvPacket.proofHeight, - path: commitmentPath, - value: abi.encodePacked(commitmentBz) - }); - vm.expectRevert( - abi.encodeWithSelector( - IICS26RouterErrors.IBCMembershipProofVerificationFailed.selector, - packet, - expectedMsgMembership, - abi.encodeWithSelector(DummyLightClient.MembershipShouldFail.selector, "membership should fail") - ) - ); + vm.expectRevert(); ics26Router.recvPacket(msgRecvPacket); } } diff --git a/test/IntegrationTest.t.sol b/test/IntegrationTest.t.sol index f7741fa5..2a6aa448 100644 --- a/test/IntegrationTest.t.sol +++ b/test/IntegrationTest.t.sol @@ -226,7 +226,7 @@ contract IntegrationTest is Test { // For the packet back we pretend this is ibc-go and that the timeout is in nanoseconds packet = IICS26RouterMsgs.Packet({ sequence: 1, - timeoutTimestamp: SafeCast.toUint64(uint256(packet.timeoutTimestamp + 1000) * 1_000_000_000), + timeoutTimestamp: packet.timeoutTimestamp + 1000, sourcePort: "transfer", sourceChannel: counterpartyClient, destPort: "transfer", @@ -322,21 +322,6 @@ contract IntegrationTest is Test { 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) { From 2046fb7eb896e2cc22015672c9ca079b441ba534 Mon Sep 17 00:00:00 2001 From: srdtrk Date: Wed, 7 Aug 2024 13:01:59 +0800 Subject: [PATCH 9/9] style: ran linters --- test/ICS26RouterTest.t.sol | 2 -- test/IntegrationTest.t.sol | 1 - 2 files changed, 3 deletions(-) diff --git a/test/ICS26RouterTest.t.sol b/test/ICS26RouterTest.t.sol index 7841af94..28194591 100644 --- a/test/ICS26RouterTest.t.sol +++ b/test/ICS26RouterTest.t.sol @@ -8,13 +8,11 @@ import { ICS02Client } from "../src/ICS02Client.sol"; import { IICS02ClientMsgs } from "../src/msgs/IICS02ClientMsgs.sol"; import { ICS26Router } from "../src/ICS26Router.sol"; import { IICS26Router } from "../src/interfaces/IICS26Router.sol"; -import { IICS26RouterErrors } from "../src/errors/IICS26RouterErrors.sol"; import { IICS26RouterMsgs } from "../src/msgs/IICS26RouterMsgs.sol"; import { ICS20Transfer } from "../src/ICS20Transfer.sol"; import { Strings } from "@openzeppelin/contracts/utils/Strings.sol"; import { DummyLightClient } from "./DummyLightClient.sol"; import { ILightClientMsgs } from "../src/msgs/ILightClientMsgs.sol"; -import { ICS24Host } from "../src/utils/ICS24Host.sol"; contract ICS26RouterTest is Test { ICS02Client public ics02Client; diff --git a/test/IntegrationTest.t.sol b/test/IntegrationTest.t.sol index 2a6aa448..0c6af96b 100644 --- a/test/IntegrationTest.t.sol +++ b/test/IntegrationTest.t.sol @@ -20,7 +20,6 @@ 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;