diff --git a/tests/e2e/e2e_leverage_test.go b/tests/e2e/e2e_leverage_test.go new file mode 100644 index 0000000000..3765be3829 --- /dev/null +++ b/tests/e2e/e2e_leverage_test.go @@ -0,0 +1,141 @@ +package e2e + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + + appparams "github.com/umee-network/umee/v6/app/params" + "github.com/umee-network/umee/v6/tests/grpc" + "github.com/umee-network/umee/v6/x/leverage/fixtures" + leveragetypes "github.com/umee-network/umee/v6/x/leverage/types" +) + +func (s *E2ETest) leverageSupply(addr sdk.AccAddress, denom string, amount uint64) { + asset := sdk.NewCoin(denom, sdk.NewIntFromUint64(amount)) + s.mustSucceedTx(leveragetypes.NewMsgSupply(addr, asset)) +} + +func (s *E2ETest) leverageWithdraw(addr sdk.AccAddress, denom string, amount uint64) { + asset := sdk.NewCoin(denom, sdk.NewIntFromUint64(amount)) + s.mustSucceedTx(leveragetypes.NewMsgWithdraw(addr, asset)) +} + +func (s *E2ETest) leverageMaxWithdraw(addr sdk.AccAddress, denom string) { + s.mustSucceedTx(leveragetypes.NewMsgMaxWithdraw(addr, denom)) +} + +func (s *E2ETest) leverageCollateralize(addr sdk.AccAddress, denom string, amount uint64) { + asset := sdk.NewCoin(denom, sdk.NewIntFromUint64(amount)) + s.mustSucceedTx(leveragetypes.NewMsgCollateralize(addr, asset)) +} + +func (s *E2ETest) leverageDecollateralize(addr sdk.AccAddress, denom string, amount uint64) { + asset := sdk.NewCoin(denom, sdk.NewIntFromUint64(amount)) + s.mustSucceedTx(leveragetypes.NewMsgDecollateralize(addr, asset)) +} + +func (s *E2ETest) leverageSupplyCollateral(addr sdk.AccAddress, denom string, amount uint64) { + asset := sdk.NewCoin(denom, sdk.NewIntFromUint64(amount)) + s.mustSucceedTx(leveragetypes.NewMsgSupplyCollateral(addr, asset)) +} + +func (s *E2ETest) leverageBorrow(addr sdk.AccAddress, denom string, amount uint64) { + asset := sdk.NewCoin(denom, sdk.NewIntFromUint64(amount)) + s.mustSucceedTx(leveragetypes.NewMsgBorrow(addr, asset)) +} + +func (s *E2ETest) leverageMaxBorrow(addr sdk.AccAddress, denom string) { + s.mustSucceedTx(leveragetypes.NewMsgMaxBorrow(addr, denom)) +} + +func (s *E2ETest) leverageRepay(addr sdk.AccAddress, denom string, amount uint64) { + asset := sdk.NewCoin(denom, sdk.NewIntFromUint64(amount)) + s.mustSucceedTx(leveragetypes.NewMsgRepay(addr, asset)) +} + +func (s *E2ETest) leverageLiquidate(addr, target sdk.AccAddress, repayDenom string, repayAmount uint64, reward string) { + repay := sdk.NewCoin(repayDenom, sdk.NewIntFromUint64(repayAmount)) + s.mustSucceedTx(leveragetypes.NewMsgLiquidate(addr, target, repay, reward)) +} + +func (s *E2ETest) leverageLeveragedLiquidate(addr, target sdk.AccAddress, repay, reward string) { + s.mustSucceedTx(leveragetypes.NewMsgLeveragedLiquidate(addr, target, repay, reward)) +} + +func (s *E2ETest) TestLeverageBasics() { + umeeNoMedians := fixtures.Token(appparams.BondDenom, "UMEE", 6) + umeeNoMedians.HistoricMedians = 0 + updateTokens := []leveragetypes.Token{ + umeeNoMedians, + } + + s.Run( + "leverage update registry", func() { + s.Require().NoError( + grpc.LeverageRegistryUpdate(s.Umee, []leveragetypes.Token{}, updateTokens), + ) + }, + ) + + valAddr, err := s.Chain.Validators[0].KeyInfo.GetAddress() + s.Require().NoError(err) + + s.Run( + "initial leverage supply", func() { + s.leverageSupply(valAddr, appparams.BondDenom, 100_000_000) + }, + ) + s.Run( + "initial leverage withdraw", func() { + s.leverageWithdraw(valAddr, "u/"+appparams.BondDenom, 10_000_000) + }, + ) + s.Run( + "initial leverage collateralize", func() { + s.leverageCollateralize(valAddr, "u/"+appparams.BondDenom, 80_000_000) + }, + ) + s.Run( + "initial leverage borrow", func() { + s.leverageBorrow(valAddr, appparams.BondDenom, 12_000_000) + }, + ) + s.Run( + "initial leverage repay", func() { + s.leverageRepay(valAddr, appparams.BondDenom, 2_000_000) + }, + ) + s.Run( + "too high leverage borrow", func() { + asset := sdk.NewCoin( + appparams.BondDenom, + sdk.NewIntFromUint64(30_000_000), + ) + s.mustFailTx(leveragetypes.NewMsgBorrow(valAddr, asset), "undercollateralized") + }, + ) + s.Run( + "leverage add special pairs", func() { + pairs := []leveragetypes.SpecialAssetPair{ + { + // a set allowing UMEE to borrow more of itself + Borrow: appparams.BondDenom, + Collateral: appparams.BondDenom, + CollateralWeight: sdk.MustNewDecFromStr("0.75"), + LiquidationThreshold: sdk.MustNewDecFromStr("0.8"), + }, + } + s.Require().NoError( + grpc.LeverageSpecialPairsUpdate(s.Umee, []leveragetypes.SpecialAssetSet{}, pairs), + ) + }, + ) + s.Run( + "special pair leverage borrow", func() { + asset := sdk.NewCoin( + appparams.BondDenom, + sdk.NewIntFromUint64(30_000_000), + ) + s.mustSucceedTx(leveragetypes.NewMsgBorrow(valAddr, asset)) + }, + ) +} diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index 027be4cf31..e32af4795f 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -5,6 +5,7 @@ import ( "time" "github.com/stretchr/testify/suite" + sdk "github.com/cosmos/cosmos-sdk/types" setup "github.com/umee-network/umee/v6/tests/e2e/setup" "github.com/umee-network/umee/v6/tests/grpc" @@ -18,6 +19,36 @@ func TestE2ETestSuite(t *testing.T) { suite.Run(t, new(E2ETest)) } +// mustEventuallySucceedTx executes an sdk.Msg, retrying several times if receiving any error, +// and requires that the transaction eventually succeeded with nil error. Since this function +// retries for 5 seconds and ignores errors, it is suitable for scenario setup transaction or +// those which might require a few blocks elapsing before they succeed. +func (s *E2ETest) mustEventuallySucceedTx(msg sdk.Msg) { + s.Require().Eventually( + func() bool { + return s.BroadcastTxWithRetry(msg) == nil + }, + 5*time.Second, + 500*time.Millisecond, + ) +} + +// mustSucceedTx executes an sdk.Msg (retrying several times if receiving incorrect account sequence) and +// requires that the error returned is nil. +func (s *E2ETest) mustSucceedTx(msg sdk.Msg) { + s.Require().NoError(s.BroadcastTxWithRetry(msg)) +} + +// mustFailTx executes an sdk.Msg (retrying several times if receiving incorrect account sequence) and +// requires that the error returned contains a given substring. If the substring is empty, simply requires +// non-nil error. +func (s *E2ETest) mustFailTx(msg sdk.Msg, errSubstring string) { + s.Require().ErrorContains( + s.BroadcastTxWithRetry(msg), + errSubstring, + ) +} + // TestMedians queries for the oracle params, collects historical // prices based on those params, checks that the stored medians and // medians deviations are correct, updates the oracle params with diff --git a/tests/e2e/setup/keys.go b/tests/e2e/setup/keys.go index 5dfcd5b4c0..62a080fe48 100644 --- a/tests/e2e/setup/keys.go +++ b/tests/e2e/setup/keys.go @@ -6,18 +6,20 @@ import ( "github.com/cosmos/cosmos-sdk/crypto/keyring" 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 ( + ATOM = "ATOM" + ATOMBaseDenom = "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2" + ATOMExponent = 6 + PhotonDenom = "photon" InitBalanceStr = "510000000000" + appparams.BondDenom + ",100000000000" + PhotonDenom + ",100000000000" + mocks.USDTBaseDenom GaiaChainID = "test-gaia-chain" - EthChainID uint = 15 - EthMinerPK = "0xb1bab011e03a9862664706fc3bbaa1b16651528e5f0e7fbfcbfdd8be302a13e7" - PriceFeederContainerRepo = "ghcr.io/umee-network/price-feeder-umee" PriceFeederServerPort = "7171/tcp" PriceFeederMaxStartupTime = 20 // seconds @@ -32,12 +34,6 @@ var ( stakeAmountCoin2 = sdk.NewCoin(appparams.BondDenom, stakeAmount2) ) -var ( - ATOM = "ATOM" - ATOMBaseDenom = "ibc/27394FB092D2ECCD56123C74F36E4C1F926001CEADA9CA97EA622B25F41E5EB2" - ATOMExponent = 6 -) - func createMnemonic() (string, error) { entropySeed, err := bip39.NewEntropy(256) if err != nil { diff --git a/tests/e2e/setup/metoken.go b/tests/e2e/setup/metoken.go index 4f5baed802..97027f0779 100644 --- a/tests/e2e/setup/metoken.go +++ b/tests/e2e/setup/metoken.go @@ -29,7 +29,7 @@ func (s *E2ETestSuite) TxMetokenSwap(umeeAddr string, asset sdk.Coin, meTokenDen MetokenDenom: meTokenDenom, } - return s.broadcastTxWithRetry(req) + return s.BroadcastTxWithRetry(req) } func (s *E2ETestSuite) TxMetokenRedeem(umeeAddr string, meToken sdk.Coin, assetDenom string) error { @@ -39,5 +39,5 @@ func (s *E2ETestSuite) TxMetokenRedeem(umeeAddr string, meToken sdk.Coin, assetD AssetDenom: assetDenom, } - return s.broadcastTxWithRetry(req) + return s.BroadcastTxWithRetry(req) } diff --git a/tests/e2e/setup/setup.go b/tests/e2e/setup/setup.go index 0a3525dbf3..b8246ac49a 100644 --- a/tests/e2e/setup/setup.go +++ b/tests/e2e/setup/setup.go @@ -135,6 +135,11 @@ func (s *E2ETestSuite) initNodes() { ) } + // TODO: + // create non-validator accounts which can be used for testing. + // since they don't vote for price feeder, these accounts are + // much less vulnerable to "incorrect account sequence" problems. + // copy the genesis file to the remaining validators for _, val := range s.Chain.Validators[1:] { _, err := copyFile( diff --git a/tests/e2e/setup/utils.go b/tests/e2e/setup/utils.go index b7a31568c7..e643ebf65e 100644 --- a/tests/e2e/setup/utils.go +++ b/tests/e2e/setup/utils.go @@ -231,7 +231,7 @@ func (s *E2ETestSuite) QueryUmeeBalance( return umeeBalance, umeeAddr } -func (s *E2ETestSuite) broadcastTxWithRetry(msg sdk.Msg) error { +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 diff --git a/tests/grpc/gov.go b/tests/grpc/gov.go index 6e3f0fe702..9fb353ba7b 100644 --- a/tests/grpc/gov.go +++ b/tests/grpc/gov.go @@ -98,6 +98,32 @@ func LeverageRegistryUpdate(umeeClient client.Client, addTokens, updateTokens [] return MakeVoteAndCheckProposal(umeeClient, *resp) } +// LeverageSpecialPairsUpdate submits a gov transaction to update leverage special assets, +// votes, and waits for proposal to pass. +func LeverageSpecialPairsUpdate( + umeeClient client.Client, + sets []ltypes.SpecialAssetSet, + pairs []ltypes.SpecialAssetPair, +) error { + msg := ltypes.MsgGovUpdateSpecialAssets{ + Authority: checkers.GovModuleAddr, + Description: "Special Assets Proposal", + Sets: sets, + Pairs: pairs, + } + + resp, err := umeeClient.Tx.TxSubmitProposalWithMsg([]sdk.Msg{&msg}) + if err != nil { + return err + } + + if len(resp.Logs) == 0 { + return fmt.Errorf("no logs in response") + } + + return MakeVoteAndCheckProposal(umeeClient, *resp) +} + // MetokenRegistryUpdate submits a gov transaction to update metoken registry, votes, and waits for proposal to pass. func MetokenRegistryUpdate(umeeClient client.Client, addIndexes, updateIndexes []metoken.Index) error { msg := metoken.MsgGovUpdateRegistry{ diff --git a/x/leverage/keeper/borrows.go b/x/leverage/keeper/borrows.go index 623e5898cb..bd37e01084 100644 --- a/x/leverage/keeper/borrows.go +++ b/x/leverage/keeper/borrows.go @@ -25,7 +25,7 @@ func (k Keeper) assertBorrowerHealth(ctx sdk.Context, borrowerAddr sdk.AccAddres borrowedValue := position.BorrowedValue() borrowLimit := position.Limit() if borrowedValue.GT(borrowLimit.Mul(maxUsage)) { - return types.ErrUndercollaterized.Wrapf( + return types.ErrUndercollateralized.Wrapf( "borrowed: %s, limit: %s, max usage %s", borrowedValue, borrowLimit, maxUsage, ) } diff --git a/x/leverage/keeper/grpc_query_test.go b/x/leverage/keeper/grpc_query_test.go index 7d80dade80..93098e6421 100644 --- a/x/leverage/keeper/grpc_query_test.go +++ b/x/leverage/keeper/grpc_query_test.go @@ -25,7 +25,7 @@ func (s *IntegrationTestSuite) TestQuerier_RegisteredTokens() { "valid: get the all registered tokens", "", types.QueryRegisteredTokens{}, - 6, + 7, }, { "valid: get the registered token info by base_denom", diff --git a/x/leverage/keeper/msg_server_test.go b/x/leverage/keeper/msg_server_test.go index 21aed69c4a..1c545dce24 100644 --- a/x/leverage/keeper/msg_server_test.go +++ b/x/leverage/keeper/msg_server_test.go @@ -17,7 +17,7 @@ func (s *IntegrationTestSuite) TestAddTokensToRegistry() { govAccAddr := checkers.GovModuleAddr registeredUmee := fixtures.Token("uumee", "UMEE", 6) ntA := fixtures.Token("unta", "ABCD", 6) - // new token with existed symbol denom + // new token with existing symbol denom ntB := fixtures.Token("untb", "ABCD", 6) testCases := []struct { name string @@ -55,7 +55,7 @@ func (s *IntegrationTestSuite) TestAddTokensToRegistry() { }, }, "", - 7, + 8, }, { "regisering new token with existed symbol denom", types.MsgGovUpdateRegistry{ @@ -66,7 +66,7 @@ func (s *IntegrationTestSuite) TestAddTokensToRegistry() { }, }, "", - 8, + 9, }, } @@ -173,7 +173,7 @@ func (s *IntegrationTestSuite) TestUpdateRegistry() { s.Require().NoError(err) // no tokens should have been deleted tokens := s.app.LeverageKeeper.GetAllRegisteredTokens(s.ctx) - s.Require().Len(tokens, 6) + s.Require().Len(tokens, 7) token, err := s.app.LeverageKeeper.GetTokenSettings(s.ctx, "uumee") s.Require().NoError(err) @@ -432,7 +432,7 @@ func (s *IntegrationTestSuite) TestMsgWithdraw() { nil, nil, sdk.Coin{}, - types.ErrUndercollaterized, + types.ErrUndercollateralized, }, { "acceptable withdrawal (dump borrower)", dumpborrower, @@ -448,7 +448,7 @@ func (s *IntegrationTestSuite) TestMsgWithdraw() { nil, nil, sdk.Coin{}, - types.ErrUndercollaterized, + types.ErrUndercollateralized, }, { "acceptable withdrawal (pump borrower)", pumpborrower, @@ -464,7 +464,7 @@ func (s *IntegrationTestSuite) TestMsgWithdraw() { nil, nil, sdk.Coin{}, - types.ErrUndercollaterized, + types.ErrUndercollateralized, }, { "borrow limit (undercollateralized due to borrow factor but not collateral weight)", stableUmeeBorrower, @@ -472,7 +472,7 @@ func (s *IntegrationTestSuite) TestMsgWithdraw() { nil, nil, sdk.Coin{}, - types.ErrUndercollaterized, + types.ErrUndercollateralized, }, } @@ -580,6 +580,17 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { s.borrow(stableUmeeBorrower, coin.New(umeeDenom, 30_000000)) // UMEE and STABLE have the same price but different collateral weights + // supply some DAI ($1 at e18) + daiSupplier := s.newAccount(coin.New(daiDenom, 1_000000_000000_000000)) + s.supply(daiSupplier, coin.New(daiDenom, 1_000000_000000_000000)) + + // create a $0.4 DAI borrower using $1.0 PAIRED collateral + specialBorrower := s.newAccount(coin.New(pairedDenom, 1_000000)) + s.supply(specialBorrower, coin.New(pairedDenom, 1_000000)) + s.collateralize(specialBorrower, coin.New("u/"+pairedDenom, 1_000000)) + s.borrow(specialBorrower, coin.New(daiDenom, 400000_000000_000000)) + // PAIRED and DAI have the same price, 0.25 collateral weight, but 0.5 special pair weight + zeroUmee := coin.Zero(umeeDenom) zeroUUmee := coin.New("u/"+umeeDenom, 0) tcs := []struct { @@ -663,6 +674,15 @@ func (s *IntegrationTestSuite) TestMsgMaxWithdraw() { coin.New(stableDenom, 40_000000), nil, }, + { + "max withdraw with special pair", + specialBorrower, + pairedDenom, + coin.New("u/"+pairedDenom, 200000), + coin.New("u/"+pairedDenom, 200000), + coin.New(pairedDenom, 200000), + nil, + }, } for _, tc := range tcs { @@ -1074,7 +1094,7 @@ func (s *IntegrationTestSuite) TestMsgDecollateralize() { "above borrow limit", borrower, coin.New("u/"+atomDenom, 100_000000), - types.ErrUndercollaterized, + types.ErrUndercollateralized, }, { @@ -1087,7 +1107,7 @@ func (s *IntegrationTestSuite) TestMsgDecollateralize() { "above borrow limit (undercollateralized under historic prices but ok with current prices)", dumpborrower, coin.New("u/"+pumpDenom, 20_000000), - types.ErrUndercollaterized, + types.ErrUndercollateralized, }, { "acceptable decollateralize (pump borrower)", @@ -1099,7 +1119,7 @@ func (s *IntegrationTestSuite) TestMsgDecollateralize() { "above borrow limit (undercollateralized under current prices but ok with historic prices)", pumpborrower, coin.New("u/"+dumpDenom, 20_000000), - types.ErrUndercollaterized, + types.ErrUndercollateralized, }, } @@ -1320,6 +1340,17 @@ func (s *IntegrationTestSuite) TestMsgBorrow() { s.borrow(stableUmeeBorrower, coin.New(umeeDenom, 3_000000)) // UMEE and STABLE have the same price but different collateral weights + // supply some DAI ($1 at e18) + daiSupplier := s.newAccount(coin.New(daiDenom, 1_000000_000000_000000)) + s.supply(daiSupplier, coin.New(daiDenom, 1_000000_000000_000000)) + + // create a $0.4 DAI borrower using $1.0 PAIRED collateral + specialBorrower := s.newAccount(coin.New(pairedDenom, 1_000000)) + s.supply(specialBorrower, coin.New(pairedDenom, 1_000000)) + s.collateralize(specialBorrower, coin.New("u/"+pairedDenom, 1_000000)) + s.borrow(specialBorrower, coin.New(daiDenom, 200000_000000_000000)) + // PAIRED and DAI have the same price, 0.25 collateral weight, but 0.5 special pair weight + tcs := []testCase{ { "uToken", @@ -1350,7 +1381,7 @@ func (s *IntegrationTestSuite) TestMsgBorrow() { "stable umee borrower (borrow factor limit)", stableUmeeBorrower, coin.New(umeeDenom, 1_000000), - types.ErrUndercollaterized, + types.ErrUndercollateralized, }, { "max supply utilization", borrower, @@ -1365,12 +1396,12 @@ func (s *IntegrationTestSuite) TestMsgBorrow() { "borrow limit", borrower, coin.New(atomDenom, 100_000000), - types.ErrUndercollaterized, + types.ErrUndercollateralized, }, { "zero collateral", supplier, coin.New(atomDenom, 1_000000), - types.ErrUndercollaterized, + types.ErrUndercollateralized, }, { "dump borrower (acceptable)", dumpborrower, @@ -1380,7 +1411,7 @@ func (s *IntegrationTestSuite) TestMsgBorrow() { "dump borrower (borrow limit)", dumpborrower, coin.New(dumpDenom, 10_000000), - types.ErrUndercollaterized, + types.ErrUndercollateralized, }, { "pump borrower (acceptable)", pumpborrower, @@ -1390,7 +1421,22 @@ func (s *IntegrationTestSuite) TestMsgBorrow() { "pump borrower (borrow limit)", pumpborrower, coin.New(pumpDenom, 2_000000), - types.ErrUndercollaterized, + types.ErrUndercollateralized, + }, { + "special borrower (up to regular limit)", + specialBorrower, + coin.New(daiDenom, 50000_000000_000000), + nil, + }, { + "special borrower (up to special limit)", + specialBorrower, + coin.New(daiDenom, 250000_000000_000000), + nil, + }, { + "special borrower (past special limit)", + specialBorrower, + coin.New(daiDenom, 50000_000000_000000), + types.ErrUndercollateralized, }, } diff --git a/x/leverage/keeper/oracle_test.go b/x/leverage/keeper/oracle_test.go index 914033cfd8..d2e67013df 100644 --- a/x/leverage/keeper/oracle_test.go +++ b/x/leverage/keeper/oracle_test.go @@ -68,6 +68,7 @@ func (m *mockOracleKeeper) Reset() { "DUMP": sdk.MustNewDecFromStr("0.50"), // A token which has recently halved in price "PUMP": sdk.MustNewDecFromStr("2.00"), // A token which has recently doubled in price "STABLE": sdk.MustNewDecFromStr("4.21"), // Same price as umee + "PAIRED": sdk.MustNewDecFromStr("1.00"), } m.historicExchangeRates = map[string]sdk.Dec{ "UMEE": sdk.MustNewDecFromStr("4.21"), @@ -76,6 +77,7 @@ func (m *mockOracleKeeper) Reset() { "DUMP": sdk.MustNewDecFromStr("1.00"), "PUMP": sdk.MustNewDecFromStr("1.00"), "STABLE": sdk.MustNewDecFromStr("4.21"), + "PAIRED": sdk.MustNewDecFromStr("1.00"), } } diff --git a/x/leverage/keeper/outage_test.go b/x/leverage/keeper/outage_test.go index 0f6f394c38..9a12b73ef4 100644 --- a/x/leverage/keeper/outage_test.go +++ b/x/leverage/keeper/outage_test.go @@ -178,7 +178,7 @@ func (s *IntegrationTestSuite) TestCollateralPriceOutage() { Asset: coin.New("u/"+umeeDenom, 1), } _, err = srv.Withdraw(ctx, msg4) - require.ErrorIs(err, types.ErrUndercollaterized, "withdraw collateral umee") + require.ErrorIs(err, types.ErrUndercollateralized, "withdraw collateral umee") // UMEE can still be collateralized s.supply(umeeSupplier, coin.New(umeeDenom, 50_000000)) @@ -203,7 +203,7 @@ func (s *IntegrationTestSuite) TestCollateralPriceOutage() { Asset: coin.New("u/"+umeeDenom, 1), } _, err = srv.Decollateralize(ctx, msg7) - require.ErrorIs(err, types.ErrUndercollaterized, "decollateralize collateral umee") + require.ErrorIs(err, types.ErrUndercollateralized, "decollateralize collateral umee") // UMEE cannot be borrowed since UMEE value is unknown msg8 := &types.MsgBorrow{ diff --git a/x/leverage/keeper/position.go b/x/leverage/keeper/position.go index 62fbdaefd7..178cfd866f 100644 --- a/x/leverage/keeper/position.go +++ b/x/leverage/keeper/position.go @@ -21,7 +21,7 @@ var minimumBorrowFactor = sdk.MustNewDecFromStr("0.5") func (k Keeper) GetAccountPosition(ctx sdk.Context, addr sdk.AccAddress, isForLiquidation bool, ) (types.AccountPosition, error) { tokenSettings := k.GetAllRegisteredTokens(ctx) - specialPairs := []types.SpecialAssetPair{} + specialPairs := k.GetAllSpecialAssetPairs(ctx) collateral := k.GetBorrowerCollateral(ctx, addr) collateralValue := sdk.NewDecCoins() borrowed := k.GetBorrowerBorrows(ctx, addr) diff --git a/x/leverage/keeper/suite_test.go b/x/leverage/keeper/suite_test.go index 6d24796f7d..41f2c19e99 100644 --- a/x/leverage/keeper/suite_test.go +++ b/x/leverage/keeper/suite_test.go @@ -28,6 +28,7 @@ const ( pumpDenom = "upump" dumpDenom = "udump" stableDenom = "stable" + pairedDenom = "upaired" ) type IntegrationTestSuite struct { @@ -78,16 +79,30 @@ func (s *IntegrationTestSuite) SetupTest() { leverage.InitGenesis(ctx, app.LeverageKeeper, *types.DefaultGenesis()) require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, newToken(appparams.BondDenom, "UMEE", 6))) require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, newToken(atomDenom, "ATOM", 6))) - require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, newToken(daiDenom, "DAI", 18))) + daiToken := newToken(daiDenom, "DAI", 18) // high exponent token will need bigger maxSupply for testing + daiToken.MaxSupply = daiToken.MaxSupply.Mul(sdk.NewInt(1_000_000_000_000)) + require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, daiToken)) // additional tokens for historacle testing require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, newToken(dumpDenom, "DUMP", 6))) require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, newToken(pumpDenom, "PUMP", 6))) + // additional token for special pairs + require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, newToken(pairedDenom, "PAIRED", 6))) // additional tokens for borrow factor testing stable := newToken(stableDenom, "STABLE", 6) stable.CollateralWeight = sdk.MustNewDecFromStr("0.8") stable.LiquidationThreshold = sdk.MustNewDecFromStr("0.9") require.NoError(app.LeverageKeeper.SetTokenSettings(ctx, stable)) + // override DefaultGenesis params with some special asset pairs + app.LeverageKeeper.SetSpecialAssetPair(ctx, + types.SpecialAssetPair{ + Collateral: pairedDenom, + Borrow: daiDenom, + CollateralWeight: sdk.MustNewDecFromStr("0.5"), + LiquidationThreshold: sdk.MustNewDecFromStr("0.75"), + }, + ) + // override DefaultGenesis params with fixtures.Params app.LeverageKeeper.SetParams(ctx, fixtures.Params()) diff --git a/x/leverage/types/errors.go b/x/leverage/types/errors.go index 231c317670..01d2ee0595 100644 --- a/x/leverage/types/errors.go +++ b/x/leverage/types/errors.go @@ -37,7 +37,7 @@ var ( // 4XX = Price Sensitive ErrBadValue = errors.Register(ModuleName, 400, "bad USD value") ErrInvalidOraclePrice = errors.Register(ModuleName, 401, "invalid oracle price") - ErrUndercollaterized = errors.Register(ModuleName, 402, "borrow positions are undercollaterized") + ErrUndercollateralized = errors.Register(ModuleName, 402, "borrow positions are undercollateralized") ErrLiquidationIneligible = errors.Register(ModuleName, 403, "borrower not eligible for liquidation") ErrNoHistoricMedians = errors.Register(ModuleName, 405, "insufficient historic medians available")