Skip to content

Commit

Permalink
test: metoken e2e (#2232)
Browse files Browse the repository at this point in the history
* added util func for meToken rest

* metoken and leverage setup with initial tests

* swap e2e test

* fix txs

* finished e2e tests

* PR refactor

* Revert "PR refactor"

This reverts commit 142d0aa.

* PR changes

* pr comments

---------

Co-authored-by: Adam Moser <[email protected]>
  • Loading branch information
kosegor and toteki authored Sep 11, 2023
1 parent d78960b commit 9430be7
Show file tree
Hide file tree
Showing 5 changed files with 349 additions and 17 deletions.
223 changes: 223 additions & 0 deletions tests/e2e/e2e_metoken_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package e2e

import (
"strings"
"time"

sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/umee-network/umee/v6/app"
"github.com/umee-network/umee/v6/tests/grpc"
ltypes "github.com/umee-network/umee/v6/x/leverage/types"
"github.com/umee-network/umee/v6/x/metoken"
"github.com/umee-network/umee/v6/x/metoken/mocks"
)

func (s *E2ETest) TestMetokenSwapAndRedeem() {
var prices []metoken.IndexPrices
var index metoken.Index
valAddr, err := s.Chain.Validators[0].KeyInfo.GetAddress()
s.Require().NoError(err)
expectedBalance := mocks.EmptyUSDIndexBalances(mocks.MeUSDDenom)

if app.Experimental {
s.T().Skip("Skipping tests for experimental module x/metoken")
}

s.Run(
"create_stable_index", func() {
tokens := []ltypes.Token{
mocks.ValidToken(mocks.USDTBaseDenom, mocks.USDTSymbolDenom, 6),
mocks.ValidToken(mocks.USDCBaseDenom, mocks.USDCSymbolDenom, 6),
mocks.ValidToken(mocks.ISTBaseDenom, mocks.ISTSymbolDenom, 6),
}

err = grpc.LeverageRegistryUpdate(s.Umee, tokens, nil)
s.Require().NoError(err)

meUSD := mocks.StableIndex(mocks.MeUSDDenom)
err = grpc.MetokenRegistryUpdate(s.Umee, []metoken.Index{meUSD}, nil)
s.Require().NoError(err)

prices = s.checkMetokenBalance(meUSD.Denom, expectedBalance)
},
)

s.Run(
"swap_100USDT_success", func() {
index = s.getMetokenIndex(mocks.MeUSDDenom)

hundredUSDT := sdk.NewCoin(mocks.USDTBaseDenom, sdkmath.NewInt(100_000000))
fee := index.Fee.MinFee.MulInt(hundredUSDT.Amount).TruncateInt()

assetSettings, i := index.AcceptedAsset(mocks.USDTBaseDenom)
s.Require().True(i >= 0)

amountToSwap := hundredUSDT.Amount.Sub(fee)
amountToReserves := assetSettings.ReservePortion.MulInt(amountToSwap).TruncateInt()
amountToLeverage := amountToSwap.Sub(amountToReserves)

usdtPrice, err := prices[0].PriceByBaseDenom(mocks.USDTBaseDenom)
s.Require().NoError(err)
returned := usdtPrice.SwapRate.MulInt(amountToSwap).TruncateInt()

s.executeSwap(valAddr.String(), hundredUSDT, mocks.MeUSDDenom)

expectedBalance.MetokenSupply.Amount = expectedBalance.MetokenSupply.Amount.Add(returned)
usdtBalance, i := expectedBalance.AssetBalance(mocks.USDTBaseDenom)
s.Require().True(i >= 0)
usdtBalance.Fees = usdtBalance.Fees.Add(fee)
usdtBalance.Reserved = usdtBalance.Reserved.Add(amountToReserves)
usdtBalance.Leveraged = usdtBalance.Leveraged.Add(amountToLeverage)
expectedBalance.SetAssetBalance(usdtBalance)

prices = s.checkMetokenBalance(mocks.MeUSDDenom, expectedBalance)
},
)

s.Run(
"redeem_200meUSD_failure", func() {
twoHundredsMeUSD := sdk.NewCoin(mocks.MeUSDDenom, sdkmath.NewInt(200_000000))

s.executeRedeemWithFailure(
valAddr.String(),
twoHundredsMeUSD,
mocks.USDTBaseDenom,
"not enough",
)

prices = s.checkMetokenBalance(mocks.MeUSDDenom, expectedBalance)
},
)

s.Run(
"redeem_50meUSD_success", func() {
fiftyMeUSD := sdk.NewCoin(mocks.MeUSDDenom, sdkmath.NewInt(50_000000))

s.executeRedeemSuccess(valAddr.String(), fiftyMeUSD, mocks.USDTBaseDenom)

usdtPrice, err := prices[0].PriceByBaseDenom(mocks.USDTBaseDenom)
s.Require().NoError(err)
usdtToRedeem := usdtPrice.RedeemRate.MulInt(fiftyMeUSD.Amount).TruncateInt()
fee := index.Fee.MinFee.MulInt(usdtToRedeem).TruncateInt()

assetSettings, i := index.AcceptedAsset(mocks.USDTBaseDenom)
s.Require().True(i >= 0)
amountFromReserves := assetSettings.ReservePortion.MulInt(usdtToRedeem).TruncateInt()
amountFromLeverage := usdtToRedeem.Sub(amountFromReserves)

expectedBalance.MetokenSupply.Amount = expectedBalance.MetokenSupply.Amount.Sub(fiftyMeUSD.Amount)
usdtBalance, i := expectedBalance.AssetBalance(mocks.USDTBaseDenom)
s.Require().True(i >= 0)
usdtBalance.Fees = usdtBalance.Fees.Add(fee)
usdtBalance.Reserved = usdtBalance.Reserved.Sub(amountFromReserves)
usdtBalance.Leveraged = usdtBalance.Leveraged.Sub(amountFromLeverage)
expectedBalance.SetAssetBalance(usdtBalance)

_ = s.checkMetokenBalance(mocks.MeUSDDenom, expectedBalance)
},
)
}

func (s *E2ETest) checkMetokenBalance(denom string, expectedBalance metoken.IndexBalances) []metoken.IndexPrices {
var prices []metoken.IndexPrices
s.Require().Eventually(
func() bool {
resp, err := s.QueryMetokenBalances(denom)
if err != nil {
return false
}

var exist bool
for _, balance := range resp.IndexBalances {
if balance.MetokenSupply.Denom == expectedBalance.MetokenSupply.Denom {
exist = true
s.Require().Equal(expectedBalance, balance)
break
}
}

s.Require().True(exist)
prices = resp.Prices
return true
},
30*time.Second,
500*time.Millisecond,
)

return prices
}

func (s *E2ETest) getMetokenIndex(denom string) metoken.Index {
index := metoken.Index{}
s.Require().Eventually(
func() bool {
resp, err := s.QueryMetokenIndexes(denom)
if err != nil {
return false
}

var exist bool
for _, indx := range resp.Registry {
if indx.Denom == denom {
exist = true
index = indx
break
}
}

s.Require().True(exist)
return true
},
30*time.Second,
500*time.Millisecond,
)

return index
}

func (s *E2ETest) executeSwap(umeeAddr string, asset sdk.Coin, meTokenDenom string) {
s.Require().Eventually(
func() bool {
err := s.TxMetokenSwap(umeeAddr, asset, meTokenDenom)
if err != nil {
return false
}

return true
},
30*time.Second,
500*time.Millisecond,
)
}

func (s *E2ETest) executeRedeemSuccess(umeeAddr string, meToken sdk.Coin, assetDenom string) {
s.Require().Eventually(
func() bool {
err := s.TxMetokenRedeem(umeeAddr, meToken, assetDenom)
if err != nil {
return false
}

return true
},
30*time.Second,
500*time.Millisecond,
)
}

func (s *E2ETest) executeRedeemWithFailure(umeeAddr string, meToken sdk.Coin, assetDenom, errMsg string) {
s.Require().Eventually(
func() bool {
err := s.TxMetokenRedeem(umeeAddr, meToken, assetDenom)
if err != nil && strings.Contains(err.Error(), errMsg) {
return true
}

return false
},
30*time.Second,
500*time.Millisecond,
)
}
3 changes: 2 additions & 1 deletion tests/e2e/setup/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/go-bip39"
appparams "github.com/umee-network/umee/v6/app/params"
"github.com/umee-network/umee/v6/x/metoken/mocks"
)

const (
PhotonDenom = "photon"
InitBalanceStr = "510000000000" + appparams.BondDenom + ",100000000000" + PhotonDenom
InitBalanceStr = "510000000000" + appparams.BondDenom + ",100000000000" + PhotonDenom + ",100000000000" + mocks.USDTBaseDenom
GaiaChainID = "test-gaia-chain"

EthChainID uint = 15
Expand Down
43 changes: 43 additions & 0 deletions tests/e2e/setup/metoken.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package setup

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/umee-network/umee/v6/x/metoken"
)

func (s *E2ETestSuite) QueryMetokenBalances(denom string) (metoken.QueryIndexBalancesResponse, error) {
endpoint := fmt.Sprintf("%s/umee/metoken/v1/index_balances?metoken_denom=%s", s.UmeeREST(), denom)
var resp metoken.QueryIndexBalancesResponse

return resp, s.QueryREST(endpoint, &resp)
}

func (s *E2ETestSuite) QueryMetokenIndexes(denom string) (metoken.QueryIndexesResponse, error) {
endpoint := fmt.Sprintf("%s/umee/metoken/v1/indexes?metoken_denom=%s", s.UmeeREST(), denom)
var resp metoken.QueryIndexesResponse

return resp, s.QueryREST(endpoint, &resp)
}

func (s *E2ETestSuite) TxMetokenSwap(umeeAddr string, asset sdk.Coin, meTokenDenom string) error {
req := &metoken.MsgSwap{
User: umeeAddr,
Asset: asset,
MetokenDenom: meTokenDenom,
}

return s.broadcastTxWithRetry(req)
}

func (s *E2ETestSuite) TxMetokenRedeem(umeeAddr string, meToken sdk.Coin, assetDenom string) error {
req := &metoken.MsgRedeem{
User: umeeAddr,
Metoken: meToken,
AssetDenom: assetDenom,
}

return s.broadcastTxWithRetry(req)
}
52 changes: 37 additions & 15 deletions tests/e2e/setup/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,21 +135,7 @@ func (s *E2ETestSuite) QueryREST(endpoint string, valPtr interface{}) error {
return fmt.Errorf("tx query returned non-200 status: %d (%s)", resp.StatusCode, endpoint)
}

if valProto, ok := valPtr.(proto.Message); ok {
bz, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body: %w, endpoint: %s", err, endpoint)
}
if err = s.cdc.UnmarshalJSON(bz, valProto); err != nil {
return fmt.Errorf("failed to protoJSON.decode response body: %w, endpoint: %s", err, endpoint)
}
} else {
if err := json.NewDecoder(resp.Body).Decode(valPtr); err != nil {
return fmt.Errorf("failed to json.decode response body: %w, endpoint: %s", err, endpoint)
}
}

return nil
return decodeRespBody(s.cdc, endpoint, resp.Body, valPtr)
}

func (s *E2ETestSuite) QueryUmeeTx(endpoint, txHash string) error {
Expand Down Expand Up @@ -245,6 +231,24 @@ func (s *E2ETestSuite) QueryUmeeBalance(
return umeeBalance, umeeAddr
}

func (s *E2ETestSuite) broadcastTxWithRetry(msg sdk.Msg) error {
var err error
for retry := 0; retry < 3; retry++ {
// retry if txs fails, because sometimes account sequence mismatch occurs due to txs pending
_, err = s.Umee.Client.Tx.BroadcastTx(msg)
if err == nil {
return nil
}

if err != nil && !strings.Contains(err.Error(), "incorrect account sequence") {
return err
}
time.Sleep(time.Millisecond * 300)
}

return err
}

func decodeTx(cdc codec.Codec, txBytes []byte) (*sdktx.Tx, error) {
var raw sdktx.TxRaw

Expand Down Expand Up @@ -281,3 +285,21 @@ func decodeTx(cdc codec.Codec, txBytes []byte) (*sdktx.Tx, error) {
Signatures: raw.Signatures,
}, nil
}

func decodeRespBody(cdc codec.Codec, endpoint string, body io.ReadCloser, valPtr interface{}) error {
if valProto, ok := valPtr.(proto.Message); ok {
bz, err := io.ReadAll(body)
if err != nil {
return fmt.Errorf("failed to read response body: %w, endpoint: %s", err, endpoint)
}
if err = cdc.UnmarshalJSON(bz, valProto); err != nil {
return fmt.Errorf("failed to protoJSON.decode response body: %w, endpoint: %s", err, endpoint)
}
} else {
if err := json.NewDecoder(body).Decode(valPtr); err != nil {
return fmt.Errorf("failed to json.decode response body: %w, endpoint: %s", err, endpoint)
}
}

return nil
}
Loading

0 comments on commit 9430be7

Please sign in to comment.