Skip to content

Commit

Permalink
feat: quota zero check (#1973)
Browse files Browse the repository at this point in the history
* feat: add params quota zero check for infinite quota

* add unit tests

* changelog

* renames
  • Loading branch information
robert-zaremba authored Apr 5, 2023
1 parent 98e7e93 commit 6cc69bb
Show file tree
Hide file tree
Showing 10 changed files with 249 additions and 65 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@ Ref: https://keepachangelog.com/en/1.0.0/
- [1963](https://github.com/umee-network/umee/pull/1963) ICA Host integration.
- [1953](https://github.com/umee-network/umee/pull/1953) IBC: accept only inflow of leverage registered tokens
- [1967](https://github.com/umee-network/umee/pull/1967) Gravity Bridge phase out phase-2: disable Umee -> Ethereum transfers.
- [1967](https://github.com/umee-network/umee/pull/1967) Gravity Bridge phase out phase-2: disable Umee -> Ethereum transfers.

### Improvements

- [1959](https://github.com/umee-network/umee/pull/1959) Update IBC to v6.1
- [1913](https://github.com/umee-network/umee/pull/1913), [1974](https://github.com/umee-network/umee/pull/1974) uibc: quota status check.
- [1973](https://github.com/umee-network/umee/pull/1973) UIBC: handle zero Quota Params.

### Fixes

Expand Down
1 change: 0 additions & 1 deletion x/uibc/ics20/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ func (k Keeper) Transfer(goCtx context.Context, msg *ibctransfertypes.MsgTransfe
}

return resp, err

}

// OnRecvPacket delegates the OnRecvPacket call to the embedded ICS-20 transfer
Expand Down
2 changes: 1 addition & 1 deletion x/uibc/quota/keeper/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func (k Keeper) InitGenesis(ctx sdk.Context, genState uibc.GenesisState) {
err := k.SetParams(ctx, genState.Params)
util.Panic(err)

k.SetOutflows(ctx, genState.Outflows)
k.SetTokenOutflows(ctx, genState.Outflows)
k.SetTotalOutflowSum(ctx, genState.TotalOutflowSum)

err = k.SetExpire(ctx, genState.QuotaExpires)
Expand Down
2 changes: 1 addition & 1 deletion x/uibc/quota/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (q Querier) Outflows(goCtx context.Context, req *uibc.QueryOutflows) (
if len(req.Denom) == 0 {
o = q.GetTotalOutflow(ctx)
} else {
d, err := q.GetOutflows(ctx, req.Denom)
d, err := q.GetTokenOutflows(ctx, req.Denom)
if err != nil {
return nil, err
}
Expand Down
10 changes: 0 additions & 10 deletions x/uibc/quota/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (

"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/simapp"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
"github.com/cosmos/cosmos-sdk/x/staking"
Expand All @@ -18,7 +17,6 @@ import (

umeeapp "github.com/umee-network/umee/v4/app"
appparams "github.com/umee-network/umee/v4/app/params"
"github.com/umee-network/umee/v4/tests/tsdk"
"github.com/umee-network/umee/v4/x/uibc"
"github.com/umee-network/umee/v4/x/uibc/quota/keeper"
)
Expand Down Expand Up @@ -88,11 +86,3 @@ func initKeeperTestSuite(t *testing.T) *KeeperTestSuite {

return s
}

// creates keeper without external dependencies (app, leverage etc...)
func initSimpleKeeper(t *testing.T) (sdk.Context, keeper.Keeper) {
storeKey := storetypes.NewMemoryStoreKey("quota")
k := keeper.NewKeeper(nil, storeKey, nil, nil, nil)
ctx, _ := tsdk.NewCtxOneStore(t, storeKey)
return ctx, k
}
56 changes: 56 additions & 0 deletions x/uibc/quota/keeper/mocks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package keeper

import (
"errors"

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

ltypes "github.com/umee-network/umee/v4/x/leverage/types"
)

type LeverageKeeper struct {
tokenSettings map[string]ltypes.Token
}

func (k LeverageKeeper) GetTokenSettings(ctx sdk.Context, baseDenom string) (ltypes.Token, error) {
ts, ok := k.tokenSettings[baseDenom]
if !ok {
return ts, errors.New("token settings not found")
}
return ts, nil
}
func (k LeverageKeeper) ExchangeUToken(ctx sdk.Context, uToken sdk.Coin) (sdk.Coin, error) {
panic("not implemented")
}
func (k LeverageKeeper) DeriveExchangeRate(ctx sdk.Context, denom string) sdk.Dec {
panic("not implemented")
}

func NewLeverageKeeperMock(denoms ...string) LeverageKeeper {
tokenSettings := map[string]ltypes.Token{}
for _, d := range denoms {
tokenSettings[d] = ltypes.Token{
BaseDenom: d,
SymbolDenom: d,
}
}
return LeverageKeeper{tokenSettings: tokenSettings}
}

type Oracle struct {
prices map[string]sdk.Dec
}

func (o Oracle) Price(ctx sdk.Context, denom string) (sdk.Dec, error) {
p, ok := o.prices[denom]
if !ok {
return p, ltypes.ErrNotRegisteredToken.Wrap(denom)
}
return p, nil
}

func NewOracleMock(denom string, price sdk.Dec) Oracle {
prices := map[string]sdk.Dec{}
prices[denom] = price
return Oracle{prices: prices}
}
43 changes: 21 additions & 22 deletions x/uibc/quota/keeper/quota.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (

var ten = sdk.MustNewDecFromStr("10")

// GetAllOutflows returns outflows of all tokens.
// GetAllOutflows returns sum of outflows of all tokens in USD value.
func (k Keeper) GetAllOutflows(ctx sdk.Context) (sdk.DecCoins, error) {
var outflows sdk.DecCoins
store := k.PrefixStore(&ctx, uibc.KeyPrefixDenomOutflows)
Expand All @@ -33,29 +33,30 @@ func (k Keeper) GetAllOutflows(ctx sdk.Context) (sdk.DecCoins, error) {
return outflows, nil
}

// GetOutflows retunes the rate limits of ibc denom.
func (k Keeper) GetOutflows(ctx sdk.Context, ibcDenom string) (sdk.DecCoin, error) {
// GetTokenOutflows returns sum of denom outflows in USD value in the DecCoin structure.
func (k Keeper) GetTokenOutflows(ctx sdk.Context, denom string) (sdk.DecCoin, error) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(uibc.KeyTotalOutflows(ibcDenom))
bz := store.Get(uibc.KeyTotalOutflows(denom))
if bz == nil {
return coin.ZeroDec(ibcDenom), nil
return coin.ZeroDec(denom), nil
}

var d sdk.Dec
err := d.Unmarshal(bz)

return sdk.NewDecCoinFromDec(ibcDenom, d), err
return sdk.NewDecCoinFromDec(denom, d), err
}

// SetOutflows save the updated IBC outflows of by provided tokens.
func (k Keeper) SetOutflows(ctx sdk.Context, outflows sdk.DecCoins) {
// SetTokenOutflows saves provided updated IBC outflows as a pair: USD value, denom name in the
// DecCoin structure.
func (k Keeper) SetTokenOutflows(ctx sdk.Context, outflows sdk.DecCoins) {
for _, q := range outflows {
k.SetDenomOutflow(ctx, q)
k.SetTokenOutflow(ctx, q)
}
}

// SetDenomOutflow save the outflows of denom into store.
func (k Keeper) SetDenomOutflow(ctx sdk.Context, outflow sdk.DecCoin) {
// SetTokenOutflow save the outflows of denom into store.
func (k Keeper) SetTokenOutflow(ctx sdk.Context, outflow sdk.DecCoin) {
store := ctx.KVStore(k.storeKey)
key := uibc.KeyTotalOutflows(outflow.Denom)
bz, err := outflow.Amount.Marshal()
Expand Down Expand Up @@ -131,12 +132,6 @@ func (k Keeper) ResetAllQuotas(ctx sdk.Context) error {
// updates the current quota metrics.
func (k Keeper) CheckAndUpdateQuota(ctx sdk.Context, denom string, newOutflow sdkmath.Int) error {
params := k.GetParams(ctx)

o, err := k.GetOutflows(ctx, denom)
if err != nil {
return err
}

exchangePrice, err := k.getExchangePrice(ctx, denom, newOutflow)
if err != nil {
if ltypes.ErrNotRegisteredToken.Is(err) {
Expand All @@ -146,17 +141,21 @@ func (k Keeper) CheckAndUpdateQuota(ctx sdk.Context, denom string, newOutflow sd
}
}

o, err := k.GetTokenOutflows(ctx, denom)
if err != nil {
return err
}
o.Amount = o.Amount.Add(exchangePrice)
if o.Amount.GT(params.TokenQuota) {
if !params.TokenQuota.IsZero() && o.Amount.GT(params.TokenQuota) {
return uibc.ErrQuotaExceeded
}

totalOutflowSum := k.GetTotalOutflow(ctx).Add(exchangePrice)
if totalOutflowSum.GT(params.TotalQuota) {
if !params.TotalQuota.IsZero() && totalOutflowSum.GT(params.TotalQuota) {
return uibc.ErrQuotaExceeded
}

k.SetDenomOutflow(ctx, o)
k.SetTokenOutflow(ctx, o)
k.SetTotalOutflowSum(ctx, totalOutflowSum)
return nil
}
Expand Down Expand Up @@ -193,7 +192,7 @@ func (k Keeper) getExchangePrice(ctx sdk.Context, denom string, amount sdkmath.I

// UndoUpdateQuota subtracts `amount` from quota metric of the ibc denom.
func (k Keeper) UndoUpdateQuota(ctx sdk.Context, denom string, amount sdkmath.Int) error {
o, err := k.GetOutflows(ctx, denom)
o, err := k.GetTokenOutflows(ctx, denom)
if err != nil {
return err
}
Expand All @@ -215,7 +214,7 @@ func (k Keeper) UndoUpdateQuota(ctx sdk.Context, denom string, amount sdkmath.In
return nil
}

k.SetDenomOutflow(ctx, o)
k.SetTokenOutflow(ctx, o)

totalOutflowSum := k.GetTotalOutflow(ctx)
k.SetTotalOutflowSum(ctx, totalOutflowSum.Sub(exchangePrice))
Expand Down
33 changes: 3 additions & 30 deletions x/uibc/quota/keeper/quota_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,50 +5,23 @@ import (

sdk "github.com/cosmos/cosmos-sdk/types"
"gotest.tools/v3/assert"

ibcutil "github.com/umee-network/umee/v4/util/ibc"
)

func TestGetQuotas(t *testing.T) {
ctx, k := initSimpleKeeper(t)

quotas, err := k.GetAllOutflows(ctx)
assert.NilError(t, err)
assert.Equal(t, len(quotas), 0)

setQuotas := sdk.DecCoins{sdk.NewInt64DecCoin("test_uumee", 10000)}

k.SetOutflows(ctx, setQuotas)
quotas, err = k.GetAllOutflows(ctx)
assert.NilError(t, err)
assert.DeepEqual(t, setQuotas, quotas)

// get the quota of denom
quota, err := k.GetOutflows(ctx, setQuotas[0].Denom)
assert.NilError(t, err)
assert.Equal(t, quota.Denom, setQuotas[0].Denom)
}

func TestGetLocalDenom(t *testing.T) {
out := ibcutil.GetLocalDenom("umee")
assert.Equal(t, "umee", out)
}

func TestResetQuota(t *testing.T) {
s := initKeeperTestSuite(t)
ctx, k := s.ctx, s.app.UIbcQuotaKeeper

umeeQuota := sdk.NewInt64DecCoin("uumee", 1000)
k.SetDenomOutflow(ctx, umeeQuota)
q, err := k.GetOutflows(ctx, umeeQuota.Denom)
k.SetTokenOutflow(ctx, umeeQuota)
q, err := k.GetTokenOutflows(ctx, umeeQuota.Denom)
assert.NilError(t, err)
assert.DeepEqual(t, q, umeeQuota)

// reset the quota
k.ResetAllQuotas(ctx)

// check the quota after reset
q, err = k.GetOutflows(ctx, umeeQuota.Denom)
q, err = k.GetTokenOutflows(ctx, umeeQuota.Denom)
assert.NilError(t, err)
assert.DeepEqual(t, q.Amount, sdk.NewDec(0))
}
108 changes: 108 additions & 0 deletions x/uibc/quota/keeper/quota_unit_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package keeper

import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"

ibcutil "github.com/umee-network/umee/v4/util/ibc"
)

func TestGetQuotas(t *testing.T) {
k := initUmeeKeeper(t)
ctx := *k.ctx

quotas, err := k.GetAllOutflows(ctx)
require.NoError(t, err)
require.Equal(t, len(quotas), 0)

setQuotas := sdk.DecCoins{sdk.NewInt64DecCoin("test_uumee", 10000)}

k.SetTokenOutflows(ctx, setQuotas)
quotas, err = k.GetAllOutflows(ctx)
require.NoError(t, err)
require.Equal(t, setQuotas, quotas)

// get the quota of denom
quota, err := k.GetTokenOutflows(ctx, setQuotas[0].Denom)
require.NoError(t, err)
require.Equal(t, quota.Denom, setQuotas[0].Denom)
}

func TestGetLocalDenom(t *testing.T) {
out := ibcutil.GetLocalDenom("umee")
require.Equal(t, "umee", out)
}

func TestCheckAndUpdateQuota(t *testing.T) {
k := initUmeeKeeper(t)

// initUmeeKeeper sets umee price: 2usd

// 1. We set the quota param to 10 and existing outflow sum to 6 USD.
// Transfer of 2 USD in Umee should work, but additional transfer of 4 USD should fail.
//
k.setQuotaParams(10, 100)
k.SetTokenOutflow(*k.ctx, sdk.NewInt64DecCoin(umee, 6))
k.SetTotalOutflowSum(*k.ctx, sdk.NewDec(50))

err := k.CheckAndUpdateQuota(*k.ctx, umee, sdk.NewInt(1))
require.NoError(t, err)
k.checkOutflows(umee, 8, 52)

// transferring 2 umee => 4USD, will exceed the quota (8+4 > 10)
err = k.CheckAndUpdateQuota(*k.ctx, umee, sdk.NewInt(2))
require.ErrorContains(t, err, "quota")
k.checkOutflows(umee, 8, 52)

// transferring 1 umee => 2USD, will will be still OK (8+2 <= 10)
err = k.CheckAndUpdateQuota(*k.ctx, umee, sdk.NewInt(1))
require.NoError(t, err)
k.checkOutflows(umee, 10, 54)

// 2. Setting TokenQuota param to 0 should unlimit the token quota check
//
k.setQuotaParams(0, 100)

// transferring 20 umee => 40USD, will skip the token quota check, but will update outflows
err = k.CheckAndUpdateQuota(*k.ctx, umee, sdk.NewInt(20))
require.NoError(t, err)
k.checkOutflows(umee, 50, 94)

// transferring additional 5 umee => 10USD, will fail total quota check
err = k.CheckAndUpdateQuota(*k.ctx, umee, sdk.NewInt(5))
require.ErrorContains(t, err, "quota")
k.checkOutflows(umee, 50, 94)

// 3. Setting TotalQuota param to 0 should unlimit the total quota check
//
k.setQuotaParams(0, 0)
err = k.CheckAndUpdateQuota(*k.ctx, umee, sdk.NewInt(5))
require.NoError(t, err)
k.checkOutflows(umee, 60, 104)

// 4. Setting TokenQuota to 65
//
k.setQuotaParams(65, 0)
err = k.CheckAndUpdateQuota(*k.ctx, umee, sdk.NewInt(1))
require.NoError(t, err)
k.checkOutflows(umee, 62, 106)

err = k.CheckAndUpdateQuota(*k.ctx, umee, sdk.NewInt(2)) // exceeds token quota
require.ErrorContains(t, err, "quota")
}

func TestGetExchangePrice(t *testing.T) {
k := initUmeeKeeper(t)
p, err := k.getExchangePrice(*k.ctx, umee, sdk.NewInt(12))
require.NoError(t, err)
require.Equal(t, sdk.NewDec(24), p)

p, err = k.getExchangePrice(*k.ctx, atom, sdk.NewInt(3))
require.NoError(t, err)
require.Equal(t, sdk.NewDec(30), p)

_, err = k.getExchangePrice(*k.ctx, "notexisting", sdk.NewInt(10))
require.ErrorContains(t, err, "not found")
}
Loading

0 comments on commit 6cc69bb

Please sign in to comment.