Skip to content

Commit

Permalink
feat: adding ibc quota v2 (#2296)
Browse files Browse the repository at this point in the history
* WIP: adding quota v2

* adding inflow quota check

* trying to fix ibc tests

* update the protos

* enable goconst in golang-lint

* address the review comments

* add new params values to upgrade handler

* address the second review comments

* removed denom metadata tracker from docs

* add check for token outflows with InflowOutflowQuotaTokenBase

* fix the ibc e2e tests

* move the new migration of  uibc params to keeper

* make keys functions to private

* add tests for uibc quota inflows

* refactored GetAllInflows and GetAllOutflows funs

* Update util/store/iter.go

* fix the build issue

* refactor LoadAllDecCoins func

---------

Co-authored-by: Robert Zaremba <robert@zaremba.ch>
  • Loading branch information
gsk967 and robert-zaremba authored Nov 15, 2023
1 parent b7f9124 commit 0a387dd
Show file tree
Hide file tree
Showing 21 changed files with 687 additions and 174 deletions.
9 changes: 9 additions & 0 deletions app/upgrades.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (

"github.com/umee-network/umee/v6/util"
leveragetypes "github.com/umee-network/umee/v6/x/leverage/types"
"github.com/umee-network/umee/v6/x/uibc"
)

// RegisterUpgradeHandlersregisters upgrade handlers.
Expand Down Expand Up @@ -117,6 +118,14 @@ func (app *UmeeApp) registerUpgrade6_2(upgradeInfo upgradetypes.Plan) {
govParams := app.GovKeeper.GetParams(ctx)
govParams.MinInitialDepositRatio = sdk.NewDecWithPrec(1, 1).String()
err = app.GovKeeper.SetParams(ctx, govParams)
if err != nil {
return fromVM, err
}

// uibc migrations
uIBCKeeper := app.UIbcQuotaKeeperB.Keeper(&ctx)
uIBCKeeper.MigrateTotalOutflowSum()
err = uIBCKeeper.SetParams(uibc.DefaultParams())
return fromVM, err
},
)
Expand Down
12 changes: 11 additions & 1 deletion proto/umee/uibc/v1/genesis.proto
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ message GenesisState {
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins",
(gogoproto.nullable) = false
];

// total_outflow_sum defines the total outflow sum of ibc-transfer in USD.
string total_outflow_sum = 3 [
(cosmos_proto.scalar) = "cosmos.Dec",
Expand All @@ -31,4 +30,15 @@ message GenesisState {
(gogoproto.jsontag) = "quota_duration,omitempty",
(gogoproto.moretags) = "yaml:\"quota_expires\""
];
// inflows tracks IBC inflow transfers (in USD) for each denom during quota period.
repeated cosmos.base.v1beta1.DecCoin inflows = 5 [
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.DecCoins",
(gogoproto.nullable) = false
];
// total_inflow_sum defines tracks total sum of IBC inflow transfers (in USD) during quota period.
string total_inflow_sum = 6 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}
18 changes: 18 additions & 0 deletions proto/umee/uibc/v1/quota.proto
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,24 @@ message Params {
(gogoproto.jsontag) = "quota_duration,omitempty",
(gogoproto.moretags) = "yaml:\"quota_duration\""
];
// inflow_outflow_quota_base defines the inflow outflow quota base of ibc-transfer in USD
string inflow_outflow_quota_base = 5 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// inflow_outflow_quota_rate defines the rate of total inflows
string inflow_outflow_quota_rate = 6 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// inflow_outflow_quota_token_base defines the inflow outflow quota base for token
string inflow_outflow_quota_token_base = 7 [
(cosmos_proto.scalar) = "cosmos.Dec",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}

// IBCTransferStatus status of ibc-transfer quota check for inflow and outflow
Expand Down
4 changes: 2 additions & 2 deletions tests/e2e/e2e_ibc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ func (s *E2ETest) TestIBCTokenTransfer() {
// 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 $15 ATOM from umee to gaia
// ✅ << 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")
// remaing supply decreased uatom on umee
Expand Down
15 changes: 15 additions & 0 deletions util/store/iter.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,18 @@ func SumCoins(s storetypes.KVStore, f StrExtractor) sdk.Coins {
// StrExtractor is a function type which will take a bytes string value and extracts
// string out of it.
type StrExtractor func([]byte) string

// LoadAllDecCoins iterates over all records in the prefix store and unmarshals value into the dec coin list.
func LoadAllDecCoins(iter db.Iterator, prefixLen int) (sdk.DecCoins, error) {
var coins sdk.DecCoins
for ; iter.Valid(); iter.Next() {
key, val := iter.Key(), iter.Value()
o := sdk.DecCoin{Denom: string(key[prefixLen:])}
if err := o.Amount.Unmarshal(val); err != nil {
return nil, err
}
coins = append(coins, o)
}

return coins, nil
}
9 changes: 9 additions & 0 deletions util/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,3 +209,12 @@ func GetInteger[T Integer](store sdk.KVStore, key []byte) (T, bool) {
}
panic("not possible: all types must be covered above")
}

// DeleteByPrefixStore will delete all keys stored in prefix store
func DeleteByPrefixStore(store sdk.KVStore) {
iter := sdk.KVStorePrefixIterator(store, nil)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
store.Delete(iter.Key())
}
}
26 changes: 9 additions & 17 deletions x/uibc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,12 @@

The `x/uibc` is a Cosmos Module providing:

- IBC Denom Metadata Tracker for [ICS-20](https://github.com/cosmos/ibc/tree/main/spec/app/ics-020-fungible-token-transfer) transferred tokens to backfill denom metadata into the x/bank standard Cosmos SDK module.
- IBC Quota is an ICS-4 middleware for the ICS-20 token transfer app to apply quota mechanism.

## Content

- [IBC Denom Metadata Tracker](#ibc-denom-metadata-tracker)
- [IBC Quota](#ibc-quota)

## IBC Denom Metadata Tracker

`x/bank.types.Metadata` is a structure which provides essential information about denom, such as display denom name, description, symbol, list of units (unit name and decimal exponent), and the default unit (`Base`).

ICS-20 is a x/bank token transfer protocol over IBC.
The core implementation doesn't create bank `Metadata` when a new token is transferred for the very first time. It's worth to note that token received through IBC is identified by the port ID, channel ID and the source denom ID.
The purpose of the `x/uibc/ics20` module is to wrap the core IBC module and create a denom `Metadata` whenever it is missing. Look at the [`TrackDenomMetadata`](ics20/keeper/keeper.go) function for more details.

### Considerations

The IBC ICS-20 doesn't carry any metadata information, so we only fill up the base denom. Importantly, we don't know about the `Exponent`, and we set `Exponent := 0`. In many cases this is wrong, and should be overwritten by chain governance.

## IBC Quota

Hack or lending abuse is impossible to stop once the funds leave the chain. One mitigation is to limit the IBC inflows and outflows and be able to stop a chain and recover the funds with a migration.
Expand All @@ -43,9 +29,9 @@ All outflows are measured in token average USD value using our x/oracle `AvgKeep

We define 2 Quotas for ICS-20 transfers. Each quota only tracks tokens x/leverage Token Registry.

- `Params.TokenQuota`: upper limit of a sum of all outflows per token. Initially it's set to 0.6m USD per token. It limits the outflows value for each token.
- `Params.TokenQuota`: upper limit of a sum of all outflows per token. It's set to 1.2M USD per token. It limits the outflows value for each token.
NOTE: we measure per token as defined in the x/leverage, not the IBC Denom Path (there can be multiple paths). Since creating a channel is permission less, we want to use same quota token.
- `Params.TotalQuota`: upper limit of a sum of all token outflows combined. Initially it's set to 1m USD. Example of IBC outflows reaching the total quota: 300k USD worth of ATOM, 200k USD worth of STATOM, 250k USD worth of UMEE and 250k USD worth JUNO.
- `Params.TotalQuota`: upper limit of a sum of all token outflows combined. For example if it's set to 1.6M USD then IBC outflows reaching the total quota will be 600k USD worth of ATOM, 500k USD worth of STATOM, 250k USD worth of UMEE and 250k USD worth JUNO.

If a quota parameter is set to zero then we consider it as unlimited.

Expand All @@ -57,7 +43,11 @@ Transfer of tokens, which are not registered in the x/leverage Token Registry ar

#### Inflows

We only allow inflows of tokens registered in x/leverage Token Registry. Other inflow transfers will be rejected.
All inflows are measured in token average USD value using our x/oracle `AvgKeeper`. The `AvgKeeper` aggregates TVWAP prices over 16h window.
We are only tracking inflows for tokens which are registered in x/leverage Token Registry.

- `Genesis.TotalInflowSum` : Sum of all IBC Tokens Inflows which are registered in x/leverage Token Registry.
- `Genesis.Inflows`: IBC Inflow of each registered token.

#### ICS-20 Quota control

Expand All @@ -75,6 +65,8 @@ In the state we store:
- Running sum of total outflow values, serialized as `sdk.Dec`.
- Running sum of per token outflow values, serialized as `sdk.Dec`.
- Next quota expire time (after which the quota reset happens).
- Running sum of total inflow values, serialized as `sdk.Dec`.
- Running sum of per token inflow values, serialized as `sdk.Dec`.

### Messages

Expand Down
15 changes: 15 additions & 0 deletions x/uibc/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ func NewGenesisState(params Params, outflows sdk.DecCoins, outflowSum sdk.Dec) *
func DefaultGenesisState() *GenesisState {
return &GenesisState{
Params: DefaultParams(),
Inflows: nil,
Outflows: nil,
TotalOutflowSum: sdk.NewDec(0),
TotalInflowSum: sdk.NewDec(0),
}
}

Expand All @@ -38,9 +40,22 @@ func (gs GenesisState) Validate() error {
}
}

for _, o := range gs.Inflows {
if o.Amount.IsNil() {
return sdkerrors.ErrInvalidRequest.Wrap("ibc denom inflow must be defined")
}
if err := o.Validate(); err != nil {
return err
}
}

if gs.TotalOutflowSum.IsNegative() {
return fmt.Errorf("total outflow sum cannot be negative : %s ", gs.TotalOutflowSum.String())
}

if gs.TotalInflowSum.IsNegative() {
return fmt.Errorf("total inflow sum cannot be negative : %s ", gs.TotalInflowSum.String())
}

return nil
}
Loading

0 comments on commit 0a387dd

Please sign in to comment.