Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests: add E2E tests for IBC-OnRecvPacket Memo Handler #2446

Merged
merged 13 commits into from
Mar 19, 2024
84 changes: 84 additions & 0 deletions tests/e2e/e2e_ibc_memo_test.go
Original file line number Diff line number Diff line change
@@ -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)
gsk967 marked this conversation as resolved.
Show resolved Hide resolved
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)
gsk967 marked this conversation as resolved.
Show resolved Hide resolved
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)
}
110 changes: 67 additions & 43 deletions tests/e2e/e2e_ibc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -68,65 +69,81 @@ 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
}
actualSupply = supply.AmountOf(ibcDenom)
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 >>
Expand All @@ -142,37 +159,37 @@ 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())

// 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 >>
// send $90 UMEE from umee to gaia (ibc_quota will check)
// 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)
Expand All @@ -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(
Expand Down Expand Up @@ -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(
Expand All @@ -244,7 +268,7 @@ func (s *E2ETest) TestIBCTokenTransfer() {
}
return a.Equal(sdk.ZeroDec())
},
30*time.Second,
120*time.Second,
1*time.Second,
)
})
Expand Down
17 changes: 16 additions & 1 deletion tests/e2e/setup/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
58 changes: 58 additions & 0 deletions x/uibc/gmp/gmp_middleware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
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)

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
robert-zaremba marked this conversation as resolved.
Show resolved Hide resolved
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: "",
},
}

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)
}
})
}
}
Loading
Loading