Skip to content

Commit

Permalink
update mock keepers for unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
toteki committed Jun 21, 2023
1 parent 118dac7 commit ebc9784
Show file tree
Hide file tree
Showing 2 changed files with 311 additions and 0 deletions.
244 changes: 244 additions & 0 deletions x/leverage/keeper/mock_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,244 @@
package keeper

import (
"errors"

sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"

"github.com/umee-network/umee/v5/x/leverage/types"
oracletypes "github.com/umee-network/umee/v5/x/oracle/types"
)

// mockBankKeeper mocks the bank keeper
type mockBankKeeper struct {
spendableCoins map[string]sdk.Coins
}

func newMockBankKeeper() mockBankKeeper {
m := mockBankKeeper{
spendableCoins: map[string]sdk.Coins{},
}
return m
}

// MintCoins adds coins to a module account
func (m *mockBankKeeper) MintCoins(
_ sdk.Context, toModule string, coins sdk.Coins,
) error {
moduleAddr := authtypes.NewModuleAddress(toModule)
moduleBalance, ok := m.spendableCoins[moduleAddr.String()]
if !ok {
moduleBalance = sdk.NewCoins()
}
m.spendableCoins[moduleAddr.String()] = moduleBalance.Add(coins...)
return nil
}

// BurnCoins removes coins from a module account
func (m *mockBankKeeper) BurnCoins(
_ sdk.Context, fromModule string, coins sdk.Coins,
) error {
moduleAddr := authtypes.NewModuleAddress(fromModule)
moduleBalance, ok := m.spendableCoins[moduleAddr.String()]
if !ok {
moduleBalance = sdk.NewCoins()
}
if coins.IsAnyGT(moduleBalance) {
return errors.New("mock bank: insufficient module balance to burn")
}
m.spendableCoins[moduleAddr.String()] = moduleBalance.Sub(coins...)
return nil
}

// SendCoins sends coins from one account's spendable coins to another.
// Error on insufficient balance.
func (m *mockBankKeeper) SendCoins(
_ sdk.Context, fromAddr, toAddr sdk.AccAddress, coins sdk.Coins,
) error {
toBalance, ok := m.spendableCoins[toAddr.String()]
if !ok {
toBalance = sdk.NewCoins()
}
fromBalance, ok := m.spendableCoins[fromAddr.String()]
if !ok {
fromBalance = sdk.NewCoins()
}
if coins.IsAnyGT(fromBalance) {
return errors.New("mock bank: insufficient from balance")
}
m.spendableCoins[fromAddr.String()] = fromBalance.Sub(coins...)
m.spendableCoins[toAddr.String()] = toBalance.Add(coins...)
return nil
}

// SendCoinsFromModuleToAccount sends coins from a module balance to an account's spendable coins.
// Error on insufficient module balance.
func (m *mockBankKeeper) SendCoinsFromModuleToAccount(
_ sdk.Context, fromModule string, toAddr sdk.AccAddress, coins sdk.Coins,
) error {
moduleAddr := authtypes.NewModuleAddress(fromModule)
spendable, ok := m.spendableCoins[toAddr.String()]
if !ok {
spendable = sdk.NewCoins()
}
moduleBalance, ok := m.spendableCoins[moduleAddr.String()]
if !ok {
moduleBalance = sdk.NewCoins()
}
if coins.IsAnyGT(moduleBalance) {
return errors.New("mock bank: insufficient module balance")
}
m.spendableCoins[moduleAddr.String()] = moduleBalance.Sub(coins...)
m.spendableCoins[toAddr.String()] = spendable.Add(coins...)
return nil
}

// SendCoinsFromAccountToModule sends coins from an account's spendable balance to a module balance.
// Error on insufficient spendable coins.
func (m *mockBankKeeper) SendCoinsFromAccountToModule(
_ sdk.Context, fromAddr sdk.AccAddress, toModule string, coins sdk.Coins,
) error {
moduleAddr := authtypes.NewModuleAddress(toModule)
spendable, ok := m.spendableCoins[fromAddr.String()]
if !ok {
spendable = sdk.NewCoins()
}
moduleBalance, ok := m.spendableCoins[moduleAddr.String()]
if !ok {
moduleBalance = sdk.NewCoins()
}
if coins.IsAnyGT(spendable) {
return errors.New("mock bank: insufficient account balance")
}
m.spendableCoins[fromAddr.String()] = spendable.Sub(coins...)
m.spendableCoins[moduleAddr.String()] = moduleBalance.Add(coins...)
return nil
}

// SendCoinsFromModuleToModule sends coins from one module balance to another.
// Error on insufficient module balance.
func (m *mockBankKeeper) SendCoinsFromModuleToModule(_ sdk.Context, fromModule, toModule string, coins sdk.Coins) error {
fromAddr := authtypes.NewModuleAddress(fromModule)
fromBalance, ok := m.spendableCoins[fromAddr.String()]
if !ok {
fromBalance = sdk.NewCoins()
}
toAddr := authtypes.NewModuleAddress(toModule)
toBalance, ok := m.spendableCoins[toAddr.String()]
if !ok {
toBalance = sdk.NewCoins()
}
if coins.IsAnyGT(fromBalance) {
return errors.New("mock bank: insufficient module balance")
}
m.spendableCoins[fromAddr.String()] = fromBalance.Sub(coins...)
m.spendableCoins[toAddr.String()] = toBalance.Add(coins...)
return nil
}

// SpendableCoins returns an account's spendable coins, without validating the address
func (m *mockBankKeeper) SpendableCoins(_ sdk.Context, addr sdk.AccAddress) sdk.Coins {
spendable, ok := m.spendableCoins[addr.String()]
if !ok {
return sdk.NewCoins()
}
return spendable
}

// GetAllBalances returns an account's spendable coins
func (m *mockBankKeeper) GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins {
return m.SpendableCoins(ctx, addr)
}

// GetBalance returns an element from an account's spendable coins
func (m *mockBankKeeper) GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin {
return sdk.NewCoin(denom, m.SpendableCoins(ctx, addr).AmountOf(denom))
}

// FundAccount mints new coins and sends them to an address.
func (m *mockBankKeeper) FundAccount(addr sdk.AccAddress, coins sdk.Coins) {
coins = sdk.NewCoins(coins...) // prevents panic: Wrong argument: coins must be sorted
spendable, ok := m.spendableCoins[addr.String()]
if !ok {
spendable = sdk.NewCoins()
}
m.spendableCoins[addr.String()] = spendable.Add(coins...)
}

// FundModule mints new coins and adds them to a module balance.
func (m *mockBankKeeper) FundModule(module string, coins sdk.Coins) {
coins = sdk.NewCoins(coins...) // prevents panic: Wrong argument: coins must be sorted
moduleAddr := authtypes.NewModuleAddress(module)
balance, ok := m.spendableCoins[moduleAddr.String()]
if !ok {
balance = sdk.NewCoins()
}
m.spendableCoins[moduleAddr.String()] = balance.Add(coins...)
}

type mockOracleKeeper struct {
baseExchangeRates map[string]sdk.Dec
symbolExchangeRates map[string]sdk.Dec
historicExchangeRates map[string]sdk.Dec
}

func newMockOracleKeeper() *mockOracleKeeper {
m := &mockOracleKeeper{
baseExchangeRates: make(map[string]sdk.Dec),
symbolExchangeRates: make(map[string]sdk.Dec),
historicExchangeRates: make(map[string]sdk.Dec),
}
m.Reset()

return m
}

func (m *mockOracleKeeper) MedianOfHistoricMedians(ctx sdk.Context, denom string, numStamps uint64,
) (sdk.Dec, uint32, error) {
p, ok := m.historicExchangeRates[denom]
if !ok {
// This error matches oracle behavior on zero historic medians
return sdk.ZeroDec(), 0, types.ErrNoHistoricMedians.Wrapf(
"requested %d, got %d",
numStamps,
0,
)
}

return p, uint32(numStamps), nil
}

func (m *mockOracleKeeper) GetExchangeRate(_ sdk.Context, denom string) (sdk.Dec, error) {
p, ok := m.symbolExchangeRates[denom]
if !ok {
// This error matches oracle behavior on missing asset price
return sdk.ZeroDec(), oracletypes.ErrUnknownDenom.Wrap(denom)
}

return p, nil
}

// Clear clears a denom from the mock oracle, simulating an outage.
func (m *mockOracleKeeper) Clear(denom string) {
delete(m.symbolExchangeRates, denom)
delete(m.historicExchangeRates, denom)
}

// Reset restores the mock oracle's prices to its default values.
func (m *mockOracleKeeper) Reset() {
m.symbolExchangeRates = map[string]sdk.Dec{
"UMEE": sdk.MustNewDecFromStr("4.21"),
"ATOM": sdk.MustNewDecFromStr("39.38"),
"DAI": sdk.MustNewDecFromStr("1.00"),
"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
}
m.historicExchangeRates = map[string]sdk.Dec{
"UMEE": sdk.MustNewDecFromStr("4.21"),
"ATOM": sdk.MustNewDecFromStr("39.38"),
"DAI": sdk.MustNewDecFromStr("1.00"),
"DUMP": sdk.MustNewDecFromStr("1.00"),
"PUMP": sdk.MustNewDecFromStr("1.00"),
}
}
67 changes: 67 additions & 0 deletions x/leverage/keeper/unit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package keeper

import (
"fmt"
"testing"

sdkmath "cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/codec"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"

"github.com/umee-network/umee/v5/tests/tsdk"
"github.com/umee-network/umee/v5/x/leverage/types"
)

// creates keeper with mock leverage keeper
func newTestKeeper(t *testing.T) testKeeper {
// codec and store
cdc := codec.NewProtoCodec(nil)
storeKey := storetypes.NewMemoryStoreKey(types.StoreKey)
ctx, _ := tsdk.NewCtxOneStore(t, storeKey)
// keepers
ok := newMockOracleKeeper()
bk := newMockBankKeeper()
// does not initialize a proper paramtypes.Subspace since test keeper overrides
k := NewKeeper(cdc, storeKey, paramtypes.Subspace{}, &bk, ok, true)
msrv := NewMsgServerImpl(k)
// modify genesis
gen := types.DefaultGenesis()
gen.LastInterestTime = 1 // initializes last interest time
k.InitGenesis(ctx, *gen)
return testKeeper{k, bk, *ok, t, ctx, sdk.ZeroInt(), msrv, gen.Params}
}

type testKeeper struct {
Keeper
bk mockBankKeeper
ok mockOracleKeeper
t *testing.T
ctx sdk.Context
setupAccountCounter sdkmath.Int
msrv types.MsgServer
// stores params directly to avoid using paramtypes.Subspace
params types.Params
}

// SetParams overrides leverage keeper's SetParams
func (k testKeeper) SetParams(_ sdk.Context, params types.Params) {
k.params = params
}

// GetParams overrides leverage keeper's GetParams
func (k testKeeper) GetParams(ctx sdk.Context) (params types.Params) {
return k.params
}

// newAccount creates a new account for testing, and funds it with any input tokens.
func (k *testKeeper) newAccount(funds ...sdk.Coin) sdk.AccAddress {
// create a unique address
k.setupAccountCounter = k.setupAccountCounter.Add(sdk.OneInt())
addrStr := fmt.Sprintf("%-20s", "addr"+k.setupAccountCounter.String()+"_______________")
addr := sdk.AccAddress([]byte(addrStr))
// we skip accountKeeper SetAccount, because we are using mock bank keeper
k.bk.FundAccount(addr, funds)
return addr
}

0 comments on commit ebc9784

Please sign in to comment.