From 298254c54a2b1f701c6b5ba23560917ba1b801a0 Mon Sep 17 00:00:00 2001 From: Gjermund Garaba Date: Wed, 7 Aug 2024 09:14:48 +0200 Subject: [PATCH] feat(ics20): implement on receive packet (#20) Co-authored-by: srdtrk --- abi/ICS20Transfer.json | 53 ++++- abi/ICS26Router.json | 171 ++++++++++++++++ e2e/artifacts/genesis.json | 2 +- e2e/interchaintestv8/e2esuite/grpc_query.go | 23 +++ e2e/interchaintestv8/go.mod | 2 +- e2e/interchaintestv8/ibc_eureka_test.go | 188 +++++++++++++++--- .../types/ics20transfer/contract.go | 163 ++++++++++++++- .../types/ics26router/contract.go | 2 +- script/E2ETestDeploy.s.sol | 5 +- script/MockE2ETestDeploy.s.sol | 2 + src/ICS20Transfer.sol | 64 ++++-- src/errors/IICS20Errors.sol | 6 + src/errors/IICS26RouterErrors.sol | 9 + src/interfaces/IICS20Transfer.sol | 2 + src/utils/ICS20Lib.sol | 62 +++--- src/utils/ICS24Host.sol | 3 + test/DummyLightClient.sol | 12 +- test/ICS02ClientTest.t.sol | 2 +- test/ICS20TransferTest.t.sol | 102 ++++++++++ test/ICS26RouterTest.t.sol | 35 ++++ test/IntegrationTest.t.sol | 138 ++++++++++++- 21 files changed, 960 insertions(+), 86 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..b0c81943 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", @@ -918,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 494101cf..ed7630c2 100644 --- a/e2e/interchaintestv8/e2esuite/grpc_query.go +++ b/e2e/interchaintestv8/e2esuite/grpc_query.go @@ -12,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" ) @@ -40,6 +42,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/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 4d0f7981..18dd550c 100644 --- a/e2e/interchaintestv8/ibc_eureka_test.go +++ b/e2e/interchaintestv8/ibc_eureka_test.go @@ -4,6 +4,7 @@ import ( "context" "crypto/ecdsa" "encoding/hex" + "encoding/json" "fmt" "math/big" "os" @@ -19,6 +20,9 @@ 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" transfertypes "github.com/cosmos/ibc-go/v8/modules/apps/transfer/types" @@ -304,7 +308,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())) })) @@ -324,12 +328,12 @@ 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 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()) @@ -340,7 +344,7 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { s.Require().Equal(transferAmount, allowance) })) - var packet ics26router.IICS26RouterMsgsPacket + var sendPacket ics26router.IICS26RouterMsgsPacket s.Require().True(s.Run("sendTransfer on Ethereum side", func() { timeout := uint64(time.Now().Add(30 * time.Minute).Unix()) msgSendTransfer := ics20transfer.IICS20TransferMsgsSendTransferMsg{ @@ -348,7 +352,7 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { Amount: transferAmount, Receiver: receiver.FormattedAddress(), SourceChannel: s.ethClientID, - DestPort: "transfer", + DestPort: transfertypes.PortID, TimeoutTimestamp: timeout, Memo: "testmemo", } @@ -368,19 +372,29 @@ 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 var recvAck []byte + var ibcDenom string s.Require().True(s.Run("recvPacket on Cosmos side", func() { resp, err := e2esuite.GRPCQuery[clienttypes.QueryClientStateResponse](ctx, simd, &clienttypes.QueryClientStateRequest{ ClientId: s.simdClientID, @@ -392,14 +406,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 * 1_000_000_000, + TimeoutTimestamp: sendPacket.TimeoutTimestamp * 1_000_000_000, }, ProofCommitment: []byte("doesn't matter"), ProofHeight: clientState.LatestHeight, @@ -412,7 +426,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() @@ -428,7 +442,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) @@ -437,7 +451,7 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { s.Require().NoError(err) // This will be a membership proof since the acknowledgement is written - 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(), @@ -446,7 +460,7 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { s.Require().NoError(err) msg := ics26router.IICS26RouterMsgsMsgAckPacket{ - Packet: packet, + Packet: sendPacket, Acknowledgement: recvAck, ProofAcked: ucAndMemProof, ProofHeight: *proofHeight, @@ -458,6 +472,134 @@ func (s *IbcEurekaTestSuite) TestICS20Transfer() { receipt := s.GetTxReciept(ctx, eth, tx.Hash()) s.Require().Equal(ethtypes.ReceiptStatusSuccessful, receipt.Status) })) + + var returnPacket channeltypes.Packet + s.Require().True(s.Run("Transfer back", func() { + // 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{ + 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) + 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 / 1_000_000_000, + 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"), // Because mock light client + ProofHeight: clienttypes.Height{}, + Signer: s.UserB.FormattedAddress(), + }) + s.Require().NoError(err) + s.Require().Equal(uint32(0), txResp.Code) + })) } func (s *IbcEurekaTestSuite) TestICS20Timeout() { 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..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\":\"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\":\"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 f2bdba16..7f04de57 100644 --- a/script/E2ETestDeploy.s.sol +++ b/script/E2ETestDeploy.s.sol @@ -5,6 +5,8 @@ pragma solidity >=0.8.25 <0.9.0; This script is used for end-to-end testing with SP1_PROVER=network. */ +// 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"; @@ -63,7 +65,8 @@ contract E2ETestDeploy is Script { // Mint some tokens (address addr, bool ok) = ICS20Lib.hexStringToAddress(E2E_FAUCET); - require(ok, "invalid address"); + require(ok, "failed to parse faucet address"); + erc20.mint(addr, 100_000_000_000); vm.stopBroadcast(); diff --git a/script/MockE2ETestDeploy.s.sol b/script/MockE2ETestDeploy.s.sol index bf5c186d..18bf5fb8 100644 --- a/script/MockE2ETestDeploy.s.sol +++ b/script/MockE2ETestDeploy.s.sol @@ -5,6 +5,8 @@ pragma solidity >=0.8.25 <0.9.0; This script is used for local testing with SP1_PROVER=mock. */ +// 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"; diff --git a/src/ICS20Transfer.sol b/src/ICS20Transfer.sol index d2a00a3a..a7e19989 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); } @@ -77,23 +77,53 @@ contract ICS20Transfer is IIBCApp, IICS20Transfer, IICS20Errors, Ownable, Reentr emit ICS20Transfer(packetData); } - function onRecvPacket(OnRecvPacketCallback calldata) - external - override - onlyOwner - nonReentrant - returns (bytes memory) - { - // TODO: Implement - return ""; + 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)); + } + + 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 (#6) + + 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 (#6) + // TODO: Implement creating (new erc20 contracts), looking up and minting of vouchers + revert ICS20UnsupportedFeature("sender denom is source"); + } + + emit ICS20ReceiveTransfer(packetData); + + return ICS20Lib.SUCCESSFUL_ACKNOWLEDGEMENT_JSON; } - function onAcknowledgementPacket(OnAcknowledgementPacketCallback calldata msg_) - external - override - onlyOwner - nonReentrant - { + function onAcknowledgementPacket(OnAcknowledgementPacketCallback calldata msg_) external onlyOwner nonReentrant { ICS20Lib.UnwrappedFungibleTokenPacketData memory packetData = ICS20Lib.unwrapPacketData(msg_.packet.data); bool isSuccessAck = true; @@ -107,7 +137,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 24b4ba94..690fd380 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); @@ -22,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 diff --git a/src/errors/IICS26RouterErrors.sol b/src/errors/IICS26RouterErrors.sol index 69f1d7a6..817691cd 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,10 @@ 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/src/utils/ICS24Host.sol b/src/utils/ICS24Host.sol index 972da234..cd7d6ff4 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 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..f6e06c3b 100644 --- a/test/ICS20TransferTest.t.sol +++ b/test/ICS20TransferTest.t.sol @@ -309,6 +309,108 @@ 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 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") }) + ); + 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\"}"); + + // 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(abi.encodeWithSelector(IICS20Errors.ICS20UnsupportedFeature.selector, "sender denom is source")); + ics20Transfer.onRecvPacket( + IIBCAppCallbacks.OnRecvPacketCallback({ packet: packet, relayer: makeAddr("relayer") }) + ); + } + 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..28194591 100644 --- a/test/ICS26RouterTest.t.sol +++ b/test/ICS26RouterTest.t.sol @@ -5,10 +5,14 @@ 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 { 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"; contract ICS26RouterTest is Test { ICS02Client public ics02Client; @@ -39,4 +43,35 @@ 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)); + + IICS26RouterMsgs.Packet memory packet = IICS26RouterMsgs.Packet({ + sequence: 1, + timeoutTimestamp: uint64(block.timestamp + 1000), + 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 + }); + + vm.expectRevert(); + ics26Router.recvPacket(msgRecvPacket); + } } diff --git a/test/IntegrationTest.t.sol b/test/IntegrationTest.t.sol index ba004a5e..0c6af96b 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"; @@ -41,7 +42,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)); @@ -165,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 + 1); + lightClient.setMembershipResult(msgSendPacket.timeoutTimestamp + 1, false); IICS26RouterMsgs.MsgTimeoutPacket memory timeoutMsg = IICS26RouterMsgs.MsgTimeoutPacket({ packet: packet, @@ -189,6 +190,139 @@ 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"); + + // For the packet back we pretend this is ibc-go and that the timeout is in nanoseconds + packet = IICS26RouterMsgs.Packet({ + sequence: 1, + timeoutTimestamp: packet.timeoutTimestamp + 1000, + 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 }) // 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 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 + }) + ); + } + function _sendICS20Transfer() internal returns (IICS26RouterMsgs.Packet memory) { erc20.mint(sender, defaultAmount); vm.startPrank(sender);