From d56b807f0d03b2c93741ac1c8ea5b6a56b744a38 Mon Sep 17 00:00:00 2001 From: Sai Kumar Date: Tue, 5 Mar 2024 13:11:08 +0530 Subject: [PATCH 1/8] add integration tests for IBC OnRecvPacket --- x/uibc/gmp/gmp_middleware_test.go | 34 ++++++ x/uibc/uics20/ibc_module_mocks_test.go | 41 +++++++ x/uibc/uics20/ibc_module_test.go | 159 +++++++++++++++++++++++++ 3 files changed, 234 insertions(+) create mode 100644 x/uibc/gmp/gmp_middleware_test.go create mode 100644 x/uibc/uics20/ibc_module_mocks_test.go create mode 100644 x/uibc/uics20/ibc_module_test.go diff --git a/x/uibc/gmp/gmp_middleware_test.go b/x/uibc/gmp/gmp_middleware_test.go new file mode 100644 index 0000000000..f9b808a02d --- /dev/null +++ b/x/uibc/gmp/gmp_middleware_test.go @@ -0,0 +1,34 @@ +package gmp + +import ( + "encoding/json" + "testing" + + "github.com/cometbft/cometbft/libs/log" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "gotest.tools/v3/assert" +) + +func TestGmpMemoHandler(t *testing.T) { + gmpHandler := NewHandler() + logger := log.NewNopLogger() + ctx := sdk.NewContext(nil, tmproto.Header{}, false, logger) + + // invalid memo + invalidMemo := "invalid memp" + err := gmpHandler.OnRecvPacket(ctx, sdk.Coin{}, invalidMemo, nil) + assert.ErrorContains(t, err, "invalid character") + + // valid memo + validMemo := Message{ + SourceChain: "source_chain", + SourceAddress: "source_addr", + Payload: nil, + Type: int64(1), + } + m, err := json.Marshal(validMemo) + assert.NilError(t, err) + err = gmpHandler.OnRecvPacket(ctx, sdk.Coin{}, string(m), nil) + assert.NilError(t, err) +} diff --git a/x/uibc/uics20/ibc_module_mocks_test.go b/x/uibc/uics20/ibc_module_mocks_test.go new file mode 100644 index 0000000000..afbe5b2149 --- /dev/null +++ b/x/uibc/uics20/ibc_module_mocks_test.go @@ -0,0 +1,41 @@ +package uics20 + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + porttypes "github.com/cosmos/ibc-go/v7/modules/core/05-port/types" + "github.com/cosmos/ibc-go/v7/modules/core/exported" + + "github.com/umee-network/umee/v6/x/leverage/types" +) + +type MOCKIBCModule struct { + porttypes.IBCModule +} + +func NewMockIBCModule() MOCKIBCModule { + return MOCKIBCModule{} +} + +func (m MOCKIBCModule) OnRecvPacket( + ctx sdk.Context, + packet channeltypes.Packet, + relayer sdk.AccAddress, +) exported.Acknowledgement { + return channeltypes.NewResultAcknowledgement([]byte("true")) +} + +type MockLeverageMsgServer struct { + types.MsgServer +} + +func NewMockLeverageMsgServer() MockLeverageMsgServer { + return MockLeverageMsgServer{} +} + +// Supply implements types.MsgServer. +func (m MockLeverageMsgServer) Supply(context.Context, *types.MsgSupply) (*types.MsgSupplyResponse, error) { + return &types.MsgSupplyResponse{}, nil +} diff --git a/x/uibc/uics20/ibc_module_test.go b/x/uibc/uics20/ibc_module_test.go new file mode 100644 index 0000000000..ffec1f25c5 --- /dev/null +++ b/x/uibc/uics20/ibc_module_test.go @@ -0,0 +1,159 @@ +package uics20 + +import ( + "encoding/json" + "testing" + + sdkmath "cosmossdk.io/math" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + ics20types "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" + "github.com/golang/mock/gomock" + "gotest.tools/v3/assert" + + "github.com/umee-network/umee/v6/tests/tsdk" + ltypes "github.com/umee-network/umee/v6/x/leverage/types" + ugovmocks "github.com/umee-network/umee/v6/x/ugov/mocks" + "github.com/umee-network/umee/v6/x/uibc" + "github.com/umee-network/umee/v6/x/uibc/mocks" + "github.com/umee-network/umee/v6/x/uibc/quota" +) + +var ( + tokenAmount = sdkmath.NewInt(100_000000) + // sender sending from their native token to umee + // here umee is receiver + atomCoin = sdk.NewCoin("uatom", tokenAmount) + atomIBC = sdk.NewCoin("ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9", tokenAmount) + senderAddr = "umee1mjk79fjjgpplak5wq838w0yd982gzkyf3qjpef" + recvAddr = "umee1y6xz2ggfc0pcsmyjlekh0j9pxh6hk87ymc9due" + fallbackAddr = "umee10h9stc5v6ntgeygf5xf945njqq5h32r5r2argu" + relAddr = sdk.MustAccAddressFromBech32(senderAddr) + ftData = ics20types.FungibleTokenPacketData{ + Denom: atomCoin.Denom, + Amount: atomCoin.Amount.String(), + Sender: senderAddr, + Receiver: recvAddr, + } + packet = channeltypes.Packet{ + Sequence: 10, + SourcePort: "transfer", + DestinationPort: "transfer", + SourceChannel: "channel-10", + DestinationChannel: "channel-1", + } +) + +func TestIBCOnRecvPacket(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + leverageMock := mocks.NewMockLeverage(ctrl) + oracleMock := mocks.NewMockOracle(ctrl) + mockLeverageMsgServer := NewMockLeverageMsgServer() + mockIBCModule := NewMockIBCModule() + + cdc := tsdk.NewCodec(uibc.RegisterInterfaces, ltypes.RegisterInterfaces) + storeKey := storetypes.NewMemoryStoreKey("quota") + ctx, _ := tsdk.NewCtxOneStore(t, storeKey) + eg := ugovmocks.NewSimpleEmergencyGroupBuilder() + kb := quota.NewKeeperBuilder(cdc, storeKey, leverageMock, oracleMock, eg) + ics20Module := NewICS20Module(mockIBCModule, cdc, kb, mockLeverageMsgServer) + + validMemoMsgs := func(noOfMsgs int) []*codectypes.Any { + msgs := make([]*codectypes.Any, 0) + msg, err := codectypes.NewAnyWithValue(ltypes.NewMsgSupply(relAddr, atomIBC)) + assert.NilError(t, err) + for i := 0; i < noOfMsgs; i++ { + msgs = append(msgs, msg) + } + return msgs + } + + tests := []struct { + name string + memo func(cdc codec.Codec) string + }{ + { + name: "fungible token packet data without memo", + memo: func(cdc codec.Codec) string { + return "" + }, + }, + { + name: "fungible token packet data with invalid memo message", + memo: func(cdc codec.Codec) string { + return "invalid_memo_message" + }, + }, + { + name: "valid memo without fallback_addr", + memo: func(cdc codec.Codec) string { + msgs := validMemoMsgs(1) + validMemo := uibc.ICS20Memo{ + Messages: msgs, + } + return string(cdc.MustMarshalJSON(&validMemo)) + }, + }, + { + name: "valid memo with valid fallback_addr", + memo: func(cdc codec.Codec) string { + msgs := validMemoMsgs(1) + validMemo := uibc.ICS20Memo{ + Messages: msgs, + FallbackAddr: fallbackAddr, + } + return string(cdc.MustMarshalJSON(&validMemo)) + }, + }, + { + name: "valid memo (more than one message) with valid fallback_addr", + memo: func(cdc codec.Codec) string { + msgs := validMemoMsgs(3) + validMemo := uibc.ICS20Memo{ + Messages: msgs, + FallbackAddr: fallbackAddr, + } + return string(cdc.MustMarshalJSON(&validMemo)) + }, + }, + } + + for _, tt := range tests { + ftData.Memo = tt.memo(cdc) + mar, err := json.Marshal(ftData) + assert.NilError(t, err) + packet.Data = mar + + t.Run(tt.name, func(t *testing.T) { + acc := ics20Module.OnRecvPacket(ctx, packet, relAddr) + assert.Equal(t, true, acc.Success()) + }) + } +} + +func TestDeserializeFTData(t *testing.T) { + cdc := tsdk.NewCodec(uibc.RegisterInterfaces, ltypes.RegisterInterfaces) + invalidPacketData := []byte("asdasd") + packet.Data = invalidPacketData + _, err := deserializeFTData(cdc, packet) + assert.ErrorContains(t, err, "invalid character") + + ftData := ics20types.FungibleTokenPacketData{ + Denom: atomCoin.Denom, + Amount: atomCoin.Amount.String(), + Sender: senderAddr, + Receiver: recvAddr, + Memo: "", + } + + mar, err := json.Marshal(ftData) + assert.NilError(t, err) + packet.Data = mar + recvFtData, err := deserializeFTData(cdc, packet) + assert.NilError(t, err) + assert.DeepEqual(t, recvFtData, ftData) +} From 016438ef3ff9ffd46f14313e351d1e890da91cac Mon Sep 17 00:00:00 2001 From: Sai Kumar Date: Tue, 5 Mar 2024 15:31:59 +0530 Subject: [PATCH 2/8] address the coderait suggestions --- x/uibc/gmp/gmp_middleware_test.go | 52 +++++++++++++++++------- x/uibc/uics20/ibc_module_mocks_test.go | 2 + x/uibc/uics20/ibc_module_test.go | 56 ++++++++++++++++++-------- 3 files changed, 80 insertions(+), 30 deletions(-) diff --git a/x/uibc/gmp/gmp_middleware_test.go b/x/uibc/gmp/gmp_middleware_test.go index f9b808a02d..49b354e9b0 100644 --- a/x/uibc/gmp/gmp_middleware_test.go +++ b/x/uibc/gmp/gmp_middleware_test.go @@ -15,20 +15,44 @@ func TestGmpMemoHandler(t *testing.T) { logger := log.NewNopLogger() ctx := sdk.NewContext(nil, tmproto.Header{}, false, logger) - // invalid memo - invalidMemo := "invalid memp" - err := gmpHandler.OnRecvPacket(ctx, sdk.Coin{}, invalidMemo, nil) - assert.ErrorContains(t, err, "invalid character") + tests := []struct { + name string + memo func() string + errMsg string + }{ + { + name: "invalid memo", + memo: func() string { + return "invalid memo" + }, + errMsg: "invalid character", + }, + { + name: "valid memo", + memo: func() string { + // valid memo + validMemo := Message{ + SourceChain: "source_chain", + SourceAddress: "source_addr", + Payload: nil, + Type: int64(1), + } + m, err := json.Marshal(validMemo) + assert.NilError(t, err) + return string(m) + }, + errMsg: "", + }, + } - // valid memo - validMemo := Message{ - SourceChain: "source_chain", - SourceAddress: "source_addr", - Payload: nil, - Type: int64(1), + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := gmpHandler.OnRecvPacket(ctx, sdk.Coin{}, tc.memo(), nil) + if len(tc.errMsg) != 0 { + assert.ErrorContains(t, err, tc.errMsg) + } else { + assert.NilError(t, err) + } + }) } - m, err := json.Marshal(validMemo) - assert.NilError(t, err) - err = gmpHandler.OnRecvPacket(ctx, sdk.Coin{}, string(m), nil) - assert.NilError(t, err) } diff --git a/x/uibc/uics20/ibc_module_mocks_test.go b/x/uibc/uics20/ibc_module_mocks_test.go index afbe5b2149..7ebad6b048 100644 --- a/x/uibc/uics20/ibc_module_mocks_test.go +++ b/x/uibc/uics20/ibc_module_mocks_test.go @@ -11,6 +11,7 @@ import ( "github.com/umee-network/umee/v6/x/leverage/types" ) +// MOCKIBCModule provides a mock implementation of the IBCModule interface for testing purposes. type MOCKIBCModule struct { porttypes.IBCModule } @@ -27,6 +28,7 @@ func (m MOCKIBCModule) OnRecvPacket( return channeltypes.NewResultAcknowledgement([]byte("true")) } +// MockLeverageMsgServer provides a mock implementation of the MsgServer interface for leverage module. type MockLeverageMsgServer struct { types.MsgServer } diff --git a/x/uibc/uics20/ibc_module_test.go b/x/uibc/uics20/ibc_module_test.go index ffec1f25c5..4be87e6b13 100644 --- a/x/uibc/uics20/ibc_module_test.go +++ b/x/uibc/uics20/ibc_module_test.go @@ -137,23 +137,47 @@ func TestIBCOnRecvPacket(t *testing.T) { func TestDeserializeFTData(t *testing.T) { cdc := tsdk.NewCodec(uibc.RegisterInterfaces, ltypes.RegisterInterfaces) - invalidPacketData := []byte("asdasd") - packet.Data = invalidPacketData - _, err := deserializeFTData(cdc, packet) - assert.ErrorContains(t, err, "invalid character") - ftData := ics20types.FungibleTokenPacketData{ - Denom: atomCoin.Denom, - Amount: atomCoin.Amount.String(), - Sender: senderAddr, - Receiver: recvAddr, - Memo: "", + tests := []struct { + name string + packetData func() []byte + errMsg string + }{ + { + name: "invalid packet data", + packetData: func() []byte { + return []byte("invalid packet data") + }, + errMsg: "invalid character", + }, + { + name: "valid packet data ", + packetData: func() []byte { + ftData := ics20types.FungibleTokenPacketData{ + Denom: atomCoin.Denom, + Amount: atomCoin.Amount.String(), + Sender: senderAddr, + Receiver: recvAddr, + Memo: "", + } + mar, err := json.Marshal(ftData) + assert.NilError(t, err) + return mar + }, + errMsg: "", + }, } - mar, err := json.Marshal(ftData) - assert.NilError(t, err) - packet.Data = mar - recvFtData, err := deserializeFTData(cdc, packet) - assert.NilError(t, err) - assert.DeepEqual(t, recvFtData, ftData) + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + packet.Data = tc.packetData() + recvFtData, err := deserializeFTData(cdc, packet) + if tc.errMsg != "" { + assert.ErrorContains(t, err, tc.errMsg) + } else { + assert.NilError(t, err) + assert.DeepEqual(t, recvFtData, ftData) + } + }) + } } From b9b89cef94610a7423547178db284b0be08fbbb5 Mon Sep 17 00:00:00 2001 From: Sai Kumar Date: Tue, 5 Mar 2024 16:27:36 +0530 Subject: [PATCH 3/8] fix the test case --- x/uibc/uics20/ibc_module_test.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/x/uibc/uics20/ibc_module_test.go b/x/uibc/uics20/ibc_module_test.go index 4be87e6b13..3dcea62aef 100644 --- a/x/uibc/uics20/ibc_module_test.go +++ b/x/uibc/uics20/ibc_module_test.go @@ -153,13 +153,6 @@ func TestDeserializeFTData(t *testing.T) { { name: "valid packet data ", packetData: func() []byte { - ftData := ics20types.FungibleTokenPacketData{ - Denom: atomCoin.Denom, - Amount: atomCoin.Amount.String(), - Sender: senderAddr, - Receiver: recvAddr, - Memo: "", - } mar, err := json.Marshal(ftData) assert.NilError(t, err) return mar From bbb352b40f74090488f98f5b931284068e797a52 Mon Sep 17 00:00:00 2001 From: Sai Kumar Date: Wed, 13 Mar 2024 18:47:42 +0530 Subject: [PATCH 4/8] added e2e tests for ibc memo tests --- tests/e2e/e2e_ibc_memo_test.go | 84 +++++++++++++++++++++++++ tests/e2e/e2e_ibc_test.go | 110 ++++++++++++++++++++------------- tests/e2e/setup/utils.go | 17 ++++- 3 files changed, 167 insertions(+), 44 deletions(-) create mode 100644 tests/e2e/e2e_ibc_memo_test.go diff --git a/tests/e2e/e2e_ibc_memo_test.go b/tests/e2e/e2e_ibc_memo_test.go new file mode 100644 index 0000000000..ec231cb43e --- /dev/null +++ b/tests/e2e/e2e_ibc_memo_test.go @@ -0,0 +1,84 @@ +package e2e + +import ( + "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx" + "gotest.tools/v3/assert" + + "github.com/umee-network/umee/v6/tests/accs" + setup "github.com/umee-network/umee/v6/tests/e2e/setup" + "github.com/umee-network/umee/v6/tests/tsdk" + ltypes "github.com/umee-network/umee/v6/x/leverage/types" + "github.com/umee-network/umee/v6/x/uibc" +) + +func (s *E2ETest) testIBCTokenTransferWithMemo(umeeAPIEndpoint string, atomQuota sdk.Coin) { + totalSupply, err := s.QueryTotalSupply(umeeAPIEndpoint) + s.T().Logf("total supply : %s", totalSupply.String()) + prevIBCAtomBalance := totalSupply.AmountOf(uatomIBCHash) + s.T().Logf("total balance of IBC ATOM : %s", prevIBCAtomBalance.String()) + + //<<<< Valid MEMO : gaia -> umee >> + atomFromGaia := mulCoin(atomQuota, "5.0") + atomFromGaia.Denom = "uatom" + + atomIBCDenom := atomFromGaia + atomIBCDenom.Denom = uatomIBCHash + cdc := tsdk.NewCodec(uibc.RegisterInterfaces, ltypes.RegisterInterfaces) + + // INVALID MEMO : with fallback_addr + // Collteralize msg is not supported + msgCollateralize := []sdk.Msg{ + ltypes.NewMsgCollateralize(accs.Alice, atomIBCDenom), + } + anyMsgOfCollateralize, err := tx.SetMsgs(msgCollateralize) + assert.NilError(s.T(), err) + fallbackAddr := "umee1mjk79fjjgpplak5wq838w0yd982gzkyf3qjpef" + invalidMemo := uibc.ICS20Memo{Messages: anyMsgOfCollateralize, FallbackAddr: fallbackAddr} + + invalidMemoBZ, err := cdc.MarshalJSON(&invalidMemo) + assert.NilError(s.T(), err) + s.SendIBC(setup.GaiaChainID, s.Chain.ID, accs.Alice.String(), atomFromGaia, false, "", string(invalidMemoBZ)) + updatedIBCAtomBalance := atomFromGaia.Amount.Add(prevIBCAtomBalance) + s.checkSupply(umeeAPIEndpoint, uatomIBCHash, updatedIBCAtomBalance) + s.checkLeverageAccountBalance(umeeAPIEndpoint, fallbackAddr, uatomIBCHash, math.ZeroInt()) + // fallback_addr has to get the sending amount + bAmount, err := s.QueryUmeeDenomBalance(umeeAPIEndpoint, fallbackAddr, uatomIBCHash) + assert.Equal(s.T(), true, atomIBCDenom.Equal(bAmount)) + // receiver doesn't receive the sending amount because due to invalid memo , recv address is override by fallback_addr + recvAmount, err := s.QueryUmeeDenomBalance(umeeAPIEndpoint, accs.Alice.String(), uatomIBCHash) + assert.Equal(s.T(), true, recvAmount.Amount.Equal(math.ZeroInt())) + + // INVALID MEMO : without fallback_addr + // receiver has to get the sending amount + invalidMemo = uibc.ICS20Memo{Messages: anyMsgOfCollateralize, FallbackAddr: ""} + invalidMemoBZ, err = cdc.MarshalJSON(&invalidMemo) + assert.NilError(s.T(), err) + s.SendIBC(setup.GaiaChainID, s.Chain.ID, accs.Alice.String(), atomFromGaia, false, "", string(invalidMemoBZ)) + updatedIBCAtomBalance = updatedIBCAtomBalance.Add(atomFromGaia.Amount) + s.checkSupply(umeeAPIEndpoint, uatomIBCHash, updatedIBCAtomBalance) + s.checkLeverageAccountBalance(umeeAPIEndpoint, fallbackAddr, uatomIBCHash, math.ZeroInt()) + // fallback_addr doesn't get the sending amount + bAmount, err = s.QueryUmeeDenomBalance(umeeAPIEndpoint, fallbackAddr, uatomIBCHash) + // same as previous amount (already fallback_addr have the amount) + assert.Equal(s.T(), true, atomIBCDenom.Equal(bAmount)) + // receiver has to receive the sending amount + recvAmount, err = s.QueryUmeeDenomBalance(umeeAPIEndpoint, accs.Alice.String(), uatomIBCHash) + assert.Equal(s.T(), true, atomIBCDenom.Equal(recvAmount)) + + // VALID MEMO : without fallback_addr + msgs := []sdk.Msg{ + ltypes.NewMsgSupplyCollateral(accs.Alice, atomIBCDenom), + } + anyMsg, err := tx.SetMsgs(msgs) + assert.NilError(s.T(), err) + memo := uibc.ICS20Memo{Messages: anyMsg} + + bz, err := cdc.MarshalJSON(&memo) + assert.NilError(s.T(), err) + s.SendIBC(setup.GaiaChainID, s.Chain.ID, accs.Alice.String(), atomFromGaia, false, "", string(bz)) + updatedIBCAtomBalance = updatedIBCAtomBalance.Add(atomFromGaia.Amount) + s.checkSupply(umeeAPIEndpoint, uatomIBCHash, updatedIBCAtomBalance) + s.checkLeverageAccountBalance(umeeAPIEndpoint, accs.Alice.String(), uatomIBCHash, atomFromGaia.Amount) +} diff --git a/tests/e2e/e2e_ibc_test.go b/tests/e2e/e2e_ibc_test.go index c790f1ca12..6246dc69a9 100644 --- a/tests/e2e/e2e_ibc_test.go +++ b/tests/e2e/e2e_ibc_test.go @@ -11,6 +11,7 @@ import ( appparams "github.com/umee-network/umee/v6/app/params" setup "github.com/umee-network/umee/v6/tests/e2e/setup" "github.com/umee-network/umee/v6/tests/grpc" + "github.com/umee-network/umee/v6/util/coin" "github.com/umee-network/umee/v6/x/uibc" ) @@ -68,8 +69,7 @@ func (s *E2ETest) checkSupply(endpoint, ibcDenom string, amount math.Int) { var err error s.Require().Eventually( func() bool { - var supply sdk.Coins - supply, err = s.QueryTotalSupply(endpoint) + supply, err := s.QueryTotalSupply(endpoint) if err != nil { return false } @@ -77,56 +77,73 @@ func (s *E2ETest) checkSupply(endpoint, ibcDenom string, amount math.Int) { return actualSupply.Equal(amount) }, 2*time.Minute, - 1*time.Second, + 2*time.Second, "check supply: %s (expected %s, actual %s) err: %v", ibcDenom, amount, actualSupply, err, ) } +func (s *E2ETest) checkLeverageAccountBalance(endpoint, addr, ibcDenom string, amount math.Int) { + collateral := math.ZeroInt() + var err error + s.Require().Eventually( + func() bool { + resp, err := s.QueryLeverageAccountBalances(endpoint, addr) + if err != nil { + return false + } + collateral = resp.Collateral.AmountOf(ibcDenom) + return resp.Collateral.AmountOf(coin.ToUTokenDenom(ibcDenom)).Equal(amount) && + resp.Supplied.AmountOf(ibcDenom).Equal(amount) + }, + 2*time.Minute, + 2*time.Second, + "check leverage supply and collateral: %s (expected %s, actual %s) err: %v", ibcDenom, amount, collateral, err, + ) +} + func (s *E2ETest) TestIBCTokenTransfer() { - // IBC inbound transfer of non x/leverage registered tokens must fail, because - // because we won't have price for it. + // require the recipient account receives the IBC tokens (IBC packets ACKd) + gaiaAPIEndpoint := s.GaiaREST() + umeeAPIEndpoint := s.UmeeREST() + // totalQuota := math.NewInt(120) + tokenQuota := math.NewInt(100) + + var atomPrice math.LegacyDec + // compute the amount of ATOM sent from umee to gaia which would meet atom's token quota + s.Require().Eventually(func() bool { + var err error + atomPrice, err = s.QueryHistAvgPrice(umeeAPIEndpoint, atomSymbol) + if err != nil { + return false + } + return atomPrice.GT(sdk.OneDec()) + }, + 2*time.Minute, + 1*time.Second, + "price of atom should be greater than 1", + ) + + atomQuota := sdk.NewCoin(uatomIBCHash, + sdk.NewDecFromInt(tokenQuota).Quo(atomPrice).Mul(powerReduction).RoundInt(), + ) + + // IBC Inflow is enabled s.Run("send_stake_to_umee", func() { // require the recipient account receives the IBC tokens (IBC packets ACKd) umeeAPIEndpoint := s.UmeeREST() recipient := s.AccountAddr(0).String() token := sdk.NewInt64Coin("stake", 3300000000) // 3300stake - s.SendIBC(setup.GaiaChainID, s.Chain.ID, recipient, token, false, "") - // Zero, since not a registered token - s.checkSupply(umeeAPIEndpoint, stakeIBCHash, sdk.ZeroInt()) + s.SendIBC(setup.GaiaChainID, s.Chain.ID, recipient, token, false, "", "") + s.checkSupply(umeeAPIEndpoint, stakeIBCHash, token.Amount) }) s.Run("ibc_transfer_quota", func() { - // require the recipient account receives the IBC tokens (IBC packets ACKd) - gaiaAPIEndpoint := s.GaiaREST() - umeeAPIEndpoint := s.UmeeREST() - // totalQuota := math.NewInt(120) - tokenQuota := math.NewInt(100) - - var atomPrice math.LegacyDec - // compute the amount of ATOM sent from umee to gaia which would meet atom's token quota - s.Require().Eventually(func() bool { - var err error - atomPrice, err = s.QueryHistAvgPrice(umeeAPIEndpoint, atomSymbol) - if err != nil { - return false - } - return atomPrice.GT(sdk.OneDec()) - }, - 2*time.Minute, - 1*time.Second, - "price of atom should be greater than 1", - ) - - atomQuota := sdk.NewCoin(uatomIBCHash, - sdk.NewDecFromInt(tokenQuota).Quo(atomPrice).Mul(powerReduction).RoundInt(), - ) - //<<<< INFLOW : gaia -> umee >> // send $500 ATOM from gaia to umee. (ibc_quota will not check token limit) atomFromGaia := mulCoin(atomQuota, "5.0") atomFromGaia.Denom = "uatom" - s.SendIBC(setup.GaiaChainID, s.Chain.ID, "", atomFromGaia, false, "") + s.SendIBC(setup.GaiaChainID, s.Chain.ID, "", atomFromGaia, false, "", "") s.checkSupply(umeeAPIEndpoint, uatomIBCHash, atomFromGaia.Amount) // <<< OUTLOW : umee -> gaia >> @@ -142,7 +159,7 @@ func (s *E2ETest) TestIBCTokenTransfer() { // << TOKEN QUOTA EXCCEED >> // send $110 UMEE from umee to gaia (token_quota is 100$) exceedUmee := mulCoin(umeeQuota, "1.1") - s.SendIBC(s.Chain.ID, setup.GaiaChainID, "", exceedUmee, true, "") + s.SendIBC(s.Chain.ID, setup.GaiaChainID, "", exceedUmee, true, "", "") // check the ibc (umee) quota after ibc txs - this one should have failed // supply don't change s.checkSupply(gaiaAPIEndpoint, umeeIBCHash, math.ZeroInt()) @@ -150,7 +167,7 @@ func (s *E2ETest) TestIBCTokenTransfer() { // send $110 ATOM from umee to gaia exceedAtom := mulCoin(atomQuota, "1.1") // supply will be not be decreased because sending amount is more than token quota so it will fail - s.SendIBC(s.Chain.ID, setup.GaiaChainID, "", exceedAtom, true, "uatom from umee to gaia") + s.SendIBC(s.Chain.ID, setup.GaiaChainID, "", exceedAtom, true, "uatom from umee to gaia", "") s.checkSupply(umeeAPIEndpoint, uatomIBCHash, atomFromGaia.Amount) // << BELOW TOKEN QUOTA >> @@ -158,21 +175,21 @@ func (s *E2ETest) TestIBCTokenTransfer() { // Note: receiver is null so hermes will default send to key_name (from config) of target chain (gaia) sendUmee := mulCoin(umeeQuota, "0.9") s.SendIBC(s.Chain.ID, setup.GaiaChainID, "", sendUmee, false, fmt.Sprintf( - "sending %s (less than token quota) ", sendUmee.String())) + "sending %s (less than token quota) ", sendUmee.String()), "") s.checkOutflows(umeeAPIEndpoint, appparams.BondDenom, true, sdk.NewDecFromInt(sendUmee.Amount), appparams.Name) s.checkSupply(gaiaAPIEndpoint, umeeIBCHash, sendUmee.Amount) // << BELOW TOKEN QUOTA 40$ but ATOM_QUOTA (40$)+ UMEE_QUOTA(90$) >= TOTAL QUOTA (120$) >> // send $40 ATOM from umee to gaia atom40 := mulCoin(atomQuota, "0.4") - s.SendIBC(s.Chain.ID, setup.GaiaChainID, "", atom40, true, "below token quota but not total quota") + s.SendIBC(s.Chain.ID, setup.GaiaChainID, "", atom40, true, "below token quota but not total quota", "") // supply will be not be decreased because sending more than total quota from umee to gaia s.checkSupply(umeeAPIEndpoint, uatomIBCHash, atomFromGaia.Amount) // ✅ << BELOW TOKEN QUTOA 5$ but ATOM_QUOTA (5$)+ UMEE_QUOTA(90$) <= TOTAL QUOTA (120$) // send $5 ATOM from umee to gaia sendAtom := mulCoin(atomQuota, "0.05") - s.SendIBC(s.Chain.ID, setup.GaiaChainID, "", sendAtom, false, "below both quotas") + s.SendIBC(s.Chain.ID, setup.GaiaChainID, "", sendAtom, false, "below both quotas", "") // remaing supply decreased uatom on umee s.checkSupply(umeeAPIEndpoint, uatomIBCHash, atomFromGaia.Amount.Sub(sendAtom.Amount)) s.checkOutflows(umeeAPIEndpoint, uatomIBCHash, true, sdk.NewDecFromInt(sendAtom.Amount), atomSymbol) @@ -183,13 +200,20 @@ func (s *E2ETest) TestIBCTokenTransfer() { coins, err := s.QueryTotalSupply(gaiaAPIEndpoint) // before sending back remainingTokens := coins.AmountOf(umeeIBCHash).Sub(returnUmee.Amount) s.Require().NoError(err) - s.SendIBC(setup.GaiaChainID, s.Chain.ID, "", returnUmee, false, "send back some umee") + s.SendIBC(setup.GaiaChainID, s.Chain.ID, "", returnUmee, false, "send back some umee", "") s.checkSupply(gaiaAPIEndpoint, umeeIBCHash, remainingTokens) // sending back remaining amount - s.SendIBC(setup.GaiaChainID, s.Chain.ID, "", sdk.NewCoin(umeeIBCHash, remainingTokens), false, "send back remaining umee") + s.SendIBC(setup.GaiaChainID, s.Chain.ID, "", sdk.NewCoin(umeeIBCHash, remainingTokens), false, "send back remaining umee", "") s.checkSupply(gaiaAPIEndpoint, umeeIBCHash, math.ZeroInt()) + /* + IBC Transfer with MEMO + */ + s.Run("ibc_transfer_with_memo", func() { + s.testIBCTokenTransferWithMemo(umeeAPIEndpoint, atomQuota) + }) + // reset the outflows s.T().Logf("waiting until quota reset, basically it will take around 300 seconds to do quota reset") s.Require().Eventually( @@ -233,7 +257,7 @@ func (s *E2ETest) TestIBCTokenTransfer() { s.Require().Equal(uibcParams.IbcStatus, uibc.IBCTransferStatus_IBC_TRANSFER_STATUS_QUOTA_DISABLED) // sending the umee tokens - they would have exceeded quota before - s.SendIBC(s.Chain.ID, setup.GaiaChainID, "", exceedUmee, false, "sending umee") + s.SendIBC(s.Chain.ID, setup.GaiaChainID, "", exceedUmee, false, "sending umee", "") s.checkSupply(gaiaAPIEndpoint, umeeIBCHash, exceedUmee.Amount) // Check the outflows s.Require().Eventually( @@ -244,7 +268,7 @@ func (s *E2ETest) TestIBCTokenTransfer() { } return a.Equal(sdk.ZeroDec()) }, - 30*time.Second, + 120*time.Second, 1*time.Second, ) }) diff --git a/tests/e2e/setup/utils.go b/tests/e2e/setup/utils.go index 87b6580aea..d86c59c5b9 100644 --- a/tests/e2e/setup/utils.go +++ b/tests/e2e/setup/utils.go @@ -55,7 +55,8 @@ func (s *E2ETestSuite) Delegate(testAccount, valIndex int, amount uint64) error return s.BroadcastTxWithRetry(msg, s.AccountClient(testAccount)) } -func (s *E2ETestSuite) SendIBC(srcChainID, dstChainID, recipient string, token sdk.Coin, failDueToQuota bool, desc string) { +func (s *E2ETestSuite) SendIBC(srcChainID, dstChainID, recipient string, token sdk.Coin, failDueToQuota bool, + desc, memo string) { if failDueToQuota { s.T().Logf("sending %s from %s to %s (exceed quota: %v) %s", token, srcChainID, dstChainID, failDueToQuota, desc) @@ -90,7 +91,11 @@ func (s *E2ETestSuite) SendIBC(srcChainID, dstChainID, recipient string, token s if len(recipient) != 0 { cmd = append(cmd, fmt.Sprintf("--receiver=%s", recipient)) } + if len(memo) != 0 { + cmd = append(cmd, fmt.Sprintf("--memo=%s", memo)) + } + s.T().Logf("exec cmd on hermes : %s", strings.Join(cmd, " ")) exec, err := s.DkrPool.Client.CreateExec(docker.CreateExecOptions{ Context: ctx, AttachStdout: true, @@ -198,6 +203,16 @@ func (s *E2ETestSuite) QueryUmeeAllBalances(endpoint, addr string) (sdk.Coins, e return balancesResp.Balances, nil } +func (s *E2ETestSuite) QueryLeverageAccountBalances(endpoint, addr string) (leveragetypes.QueryAccountBalancesResponse, + error) { + endpoint = fmt.Sprintf("%s//umee/leverage/v1/account_balances?address=%s", endpoint, addr) + var resp leveragetypes.QueryAccountBalancesResponse + if err := s.QueryREST(endpoint, &resp); err != nil { + return leveragetypes.QueryAccountBalancesResponse{}, err + } + return resp, nil +} + func (s *E2ETestSuite) QueryTotalSupply(endpoint string) (sdk.Coins, error) { endpoint = fmt.Sprintf("%s/cosmos/bank/v1beta1/supply", endpoint) var balancesResp banktypes.QueryTotalSupplyResponse From 07b46ff2100daccaa540775870d24f4f0096406b Mon Sep 17 00:00:00 2001 From: Sai Kumar Date: Sun, 17 Mar 2024 20:27:13 +0530 Subject: [PATCH 5/8] fix the memo tests --- tests/e2e/setup/setup.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/e2e/setup/setup.go b/tests/e2e/setup/setup.go index 48c3f1ed99..bcfdabf040 100644 --- a/tests/e2e/setup/setup.go +++ b/tests/e2e/setup/setup.go @@ -288,6 +288,8 @@ func (s *E2ETestSuite) initGenesis() { uibcGenState.Params.TotalQuota = sdk.NewDec(120) // quotas will reset every 300 seconds uibcGenState.Params.QuotaDuration = time.Second * 300 + // enable ics20 hooks (memo handling) + uibcGenState.Params.Ics20Hooks = true bz, err = s.cdc.MarshalJSON(&uibcGenState) s.Require().NoError(err) From 0cda333a9d698633e49010fb4e702d06139caae2 Mon Sep 17 00:00:00 2001 From: Sai Kumar Date: Tue, 19 Mar 2024 13:50:11 +0530 Subject: [PATCH 6/8] address the review comments --- tests/e2e/e2e_ibc_memo_test.go | 25 +++++++++------- x/uibc/uics20/ibc_module_test.go | 49 ++++++++++---------------------- 2 files changed, 30 insertions(+), 44 deletions(-) diff --git a/tests/e2e/e2e_ibc_memo_test.go b/tests/e2e/e2e_ibc_memo_test.go index ec231cb43e..5d312b5b2b 100644 --- a/tests/e2e/e2e_ibc_memo_test.go +++ b/tests/e2e/e2e_ibc_memo_test.go @@ -4,7 +4,7 @@ import ( "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/tx" - "gotest.tools/v3/assert" + "github.com/stretchr/testify/assert" "github.com/umee-network/umee/v6/tests/accs" setup "github.com/umee-network/umee/v6/tests/e2e/setup" @@ -14,6 +14,7 @@ import ( ) func (s *E2ETest) testIBCTokenTransferWithMemo(umeeAPIEndpoint string, atomQuota sdk.Coin) { + assert := assert.New(s.T()) totalSupply, err := s.QueryTotalSupply(umeeAPIEndpoint) s.T().Logf("total supply : %s", totalSupply.String()) prevIBCAtomBalance := totalSupply.AmountOf(uatomIBCHash) @@ -33,28 +34,32 @@ func (s *E2ETest) testIBCTokenTransferWithMemo(umeeAPIEndpoint string, atomQuota ltypes.NewMsgCollateralize(accs.Alice, atomIBCDenom), } anyMsgOfCollateralize, err := tx.SetMsgs(msgCollateralize) - assert.NilError(s.T(), err) + assert.Nil(err) fallbackAddr := "umee1mjk79fjjgpplak5wq838w0yd982gzkyf3qjpef" invalidMemo := uibc.ICS20Memo{Messages: anyMsgOfCollateralize, FallbackAddr: fallbackAddr} + // fallback_addr balance + iniBalance, err := s.QueryUmeeDenomBalance(umeeAPIEndpoint, fallbackAddr, uatomIBCHash) + assert.Equal(true, iniBalance.Amount.Equal(math.ZeroInt())) + invalidMemoBZ, err := cdc.MarshalJSON(&invalidMemo) - assert.NilError(s.T(), err) + assert.Nil(err) s.SendIBC(setup.GaiaChainID, s.Chain.ID, accs.Alice.String(), atomFromGaia, false, "", string(invalidMemoBZ)) updatedIBCAtomBalance := atomFromGaia.Amount.Add(prevIBCAtomBalance) s.checkSupply(umeeAPIEndpoint, uatomIBCHash, updatedIBCAtomBalance) s.checkLeverageAccountBalance(umeeAPIEndpoint, fallbackAddr, uatomIBCHash, math.ZeroInt()) // fallback_addr has to get the sending amount bAmount, err := s.QueryUmeeDenomBalance(umeeAPIEndpoint, fallbackAddr, uatomIBCHash) - assert.Equal(s.T(), true, atomIBCDenom.Equal(bAmount)) + assert.Equal(true, atomIBCDenom.Equal(bAmount)) // receiver doesn't receive the sending amount because due to invalid memo , recv address is override by fallback_addr recvAmount, err := s.QueryUmeeDenomBalance(umeeAPIEndpoint, accs.Alice.String(), uatomIBCHash) - assert.Equal(s.T(), true, recvAmount.Amount.Equal(math.ZeroInt())) + assert.Equal(true, recvAmount.Amount.Equal(math.ZeroInt())) // INVALID MEMO : without fallback_addr // receiver has to get the sending amount invalidMemo = uibc.ICS20Memo{Messages: anyMsgOfCollateralize, FallbackAddr: ""} invalidMemoBZ, err = cdc.MarshalJSON(&invalidMemo) - assert.NilError(s.T(), err) + assert.Nil(err) s.SendIBC(setup.GaiaChainID, s.Chain.ID, accs.Alice.String(), atomFromGaia, false, "", string(invalidMemoBZ)) updatedIBCAtomBalance = updatedIBCAtomBalance.Add(atomFromGaia.Amount) s.checkSupply(umeeAPIEndpoint, uatomIBCHash, updatedIBCAtomBalance) @@ -62,21 +67,21 @@ func (s *E2ETest) testIBCTokenTransferWithMemo(umeeAPIEndpoint string, atomQuota // fallback_addr doesn't get the sending amount bAmount, err = s.QueryUmeeDenomBalance(umeeAPIEndpoint, fallbackAddr, uatomIBCHash) // same as previous amount (already fallback_addr have the amount) - assert.Equal(s.T(), true, atomIBCDenom.Equal(bAmount)) + assert.Equal(true, atomIBCDenom.Equal(bAmount)) // receiver has to receive the sending amount recvAmount, err = s.QueryUmeeDenomBalance(umeeAPIEndpoint, accs.Alice.String(), uatomIBCHash) - assert.Equal(s.T(), true, atomIBCDenom.Equal(recvAmount)) + assert.Equal(true, atomIBCDenom.Equal(recvAmount)) // VALID MEMO : without fallback_addr msgs := []sdk.Msg{ ltypes.NewMsgSupplyCollateral(accs.Alice, atomIBCDenom), } anyMsg, err := tx.SetMsgs(msgs) - assert.NilError(s.T(), err) + assert.Nil(err) memo := uibc.ICS20Memo{Messages: anyMsg} bz, err := cdc.MarshalJSON(&memo) - assert.NilError(s.T(), err) + assert.Nil(err) s.SendIBC(setup.GaiaChainID, s.Chain.ID, accs.Alice.String(), atomFromGaia, false, "", string(bz)) updatedIBCAtomBalance = updatedIBCAtomBalance.Add(atomFromGaia.Amount) s.checkSupply(umeeAPIEndpoint, uatomIBCHash, updatedIBCAtomBalance) diff --git a/x/uibc/uics20/ibc_module_test.go b/x/uibc/uics20/ibc_module_test.go index 3dcea62aef..ea510d87db 100644 --- a/x/uibc/uics20/ibc_module_test.go +++ b/x/uibc/uics20/ibc_module_test.go @@ -5,7 +5,6 @@ import ( "testing" sdkmath "cosmossdk.io/math" - "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -62,68 +61,50 @@ func TestIBCOnRecvPacket(t *testing.T) { kb := quota.NewKeeperBuilder(cdc, storeKey, leverageMock, oracleMock, eg) ics20Module := NewICS20Module(mockIBCModule, cdc, kb, mockLeverageMsgServer) - validMemoMsgs := func(noOfMsgs int) []*codectypes.Any { + validMemoMsgs := func(noOfMsgs int, fallbackAddr string) string { msgs := make([]*codectypes.Any, 0) msg, err := codectypes.NewAnyWithValue(ltypes.NewMsgSupply(relAddr, atomIBC)) assert.NilError(t, err) for i := 0; i < noOfMsgs; i++ { msgs = append(msgs, msg) } - return msgs + validMemo := uibc.ICS20Memo{ + Messages: msgs, + } + if fallbackAddr != "" { + validMemo.FallbackAddr = fallbackAddr + } + return string(cdc.MustMarshalJSON(&validMemo)) } tests := []struct { name string - memo func(cdc codec.Codec) string + memo string }{ { name: "fungible token packet data without memo", - memo: func(cdc codec.Codec) string { - return "" - }, + memo: "", }, { name: "fungible token packet data with invalid memo message", - memo: func(cdc codec.Codec) string { - return "invalid_memo_message" - }, + memo: "invalid_memo_message", }, { name: "valid memo without fallback_addr", - memo: func(cdc codec.Codec) string { - msgs := validMemoMsgs(1) - validMemo := uibc.ICS20Memo{ - Messages: msgs, - } - return string(cdc.MustMarshalJSON(&validMemo)) - }, + memo: validMemoMsgs(1, ""), }, { name: "valid memo with valid fallback_addr", - memo: func(cdc codec.Codec) string { - msgs := validMemoMsgs(1) - validMemo := uibc.ICS20Memo{ - Messages: msgs, - FallbackAddr: fallbackAddr, - } - return string(cdc.MustMarshalJSON(&validMemo)) - }, + memo: validMemoMsgs(1, fallbackAddr), }, { name: "valid memo (more than one message) with valid fallback_addr", - memo: func(cdc codec.Codec) string { - msgs := validMemoMsgs(3) - validMemo := uibc.ICS20Memo{ - Messages: msgs, - FallbackAddr: fallbackAddr, - } - return string(cdc.MustMarshalJSON(&validMemo)) - }, + memo: validMemoMsgs(3, fallbackAddr), }, } for _, tt := range tests { - ftData.Memo = tt.memo(cdc) + ftData.Memo = tt.memo mar, err := json.Marshal(ftData) assert.NilError(t, err) packet.Data = mar From 8e22e9fe148c9b8da9a9dc77e5badbb89dfab361 Mon Sep 17 00:00:00 2001 From: Sai Kumar Date: Tue, 19 Mar 2024 14:15:00 +0530 Subject: [PATCH 7/8] address the review comments++ --- x/uibc/uics20/ibc_module_test.go | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/x/uibc/uics20/ibc_module_test.go b/x/uibc/uics20/ibc_module_test.go index ea510d87db..1318226a98 100644 --- a/x/uibc/uics20/ibc_module_test.go +++ b/x/uibc/uics20/ibc_module_test.go @@ -26,7 +26,6 @@ var ( // sender sending from their native token to umee // here umee is receiver atomCoin = sdk.NewCoin("uatom", tokenAmount) - atomIBC = sdk.NewCoin("ibc/C4CFF46FD6DE35CA4CF4CE031E643C8FDC9BA4B99AE598E9B0ED98FE3A2319F9", tokenAmount) senderAddr = "umee1mjk79fjjgpplak5wq838w0yd982gzkyf3qjpef" recvAddr = "umee1y6xz2ggfc0pcsmyjlekh0j9pxh6hk87ymc9due" fallbackAddr = "umee10h9stc5v6ntgeygf5xf945njqq5h32r5r2argu" @@ -37,6 +36,8 @@ var ( Sender: senderAddr, Receiver: recvAddr, } + + // ftData. packet = channeltypes.Packet{ Sequence: 10, SourcePort: "transfer", @@ -44,6 +45,9 @@ var ( SourceChannel: "channel-10", DestinationChannel: "channel-1", } + + atomIBCDenom = uibc.ExtractDenomFromPacketOnRecv(packet, ftData.Denom) + atomIBC = sdk.NewCoin(atomIBCDenom, tokenAmount) ) func TestIBCOnRecvPacket(t *testing.T) { @@ -118,27 +122,27 @@ func TestIBCOnRecvPacket(t *testing.T) { func TestDeserializeFTData(t *testing.T) { cdc := tsdk.NewCodec(uibc.RegisterInterfaces, ltypes.RegisterInterfaces) - + serialize := func() []byte { + d, err := json.Marshal(ftData) + assert.NilError(t, err) + return d + } tests := []struct { name string packetData func() []byte errMsg string }{ { - name: "invalid packet data", + name: "invalid json", packetData: func() []byte { return []byte("invalid packet data") }, errMsg: "invalid character", }, { - name: "valid packet data ", - packetData: func() []byte { - mar, err := json.Marshal(ftData) - assert.NilError(t, err) - return mar - }, - errMsg: "", + name: "valid packet data ", + packetData: serialize, + errMsg: "", }, } From e9637f08af1d4561f0a1b0b89fa9120ff41cd7ac Mon Sep 17 00:00:00 2001 From: Robert Zaremba Date: Tue, 19 Mar 2024 10:14:38 +0100 Subject: [PATCH 8/8] Update x/uibc/gmp/gmp_middleware_test.go --- x/uibc/gmp/gmp_middleware_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/x/uibc/gmp/gmp_middleware_test.go b/x/uibc/gmp/gmp_middleware_test.go index 49b354e9b0..e77ad66469 100644 --- a/x/uibc/gmp/gmp_middleware_test.go +++ b/x/uibc/gmp/gmp_middleware_test.go @@ -30,7 +30,6 @@ func TestGmpMemoHandler(t *testing.T) { { name: "valid memo", memo: func() string { - // valid memo validMemo := Message{ SourceChain: "source_chain", SourceAddress: "source_addr",