From 0a387ddeeee9baf03a0a3a6bf4f493f613bc653e Mon Sep 17 00:00:00 2001 From: Sai Kumar <17549398+gsk967@users.noreply.github.com> Date: Wed, 15 Nov 2023 12:20:15 +0530 Subject: [PATCH] feat: adding ibc quota v2 (#2296) * 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 --- app/upgrades.go | 9 + proto/umee/uibc/v1/genesis.proto | 12 +- proto/umee/uibc/v1/quota.proto | 18 ++ tests/e2e/e2e_ibc_test.go | 4 +- util/store/iter.go | 15 ++ util/store/store.go | 9 + x/uibc/README.md | 26 +-- x/uibc/genesis.go | 15 ++ x/uibc/genesis.pb.go | 165 ++++++++++++++--- x/uibc/params.go | 28 ++- x/uibc/quota.pb.go | 214 +++++++++++++++++++---- x/uibc/quota/ibc_module.go | 27 ++- x/uibc/quota/keeper/genesis.go | 6 + x/uibc/quota/keeper/intest/quota_test.go | 6 + x/uibc/quota/keeper/keys.go | 11 +- x/uibc/quota/keeper/migrations.go | 17 ++ x/uibc/quota/keeper/mocks_test.go | 4 +- x/uibc/quota/keeper/params_test.go | 6 +- x/uibc/quota/keeper/quota.go | 148 +++++++++++----- x/uibc/quota/keeper/quota_test.go | 115 +++++++++--- x/uibc/quota/keeper/unit_test.go | 6 +- 21 files changed, 687 insertions(+), 174 deletions(-) create mode 100644 x/uibc/quota/keeper/migrations.go diff --git a/app/upgrades.go b/app/upgrades.go index 46a4b45ac8..47e504e426 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -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. @@ -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 }, ) diff --git a/proto/umee/uibc/v1/genesis.proto b/proto/umee/uibc/v1/genesis.proto index 7b393bb6fe..84df483b26 100644 --- a/proto/umee/uibc/v1/genesis.proto +++ b/proto/umee/uibc/v1/genesis.proto @@ -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", @@ -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 + ]; } diff --git a/proto/umee/uibc/v1/quota.proto b/proto/umee/uibc/v1/quota.proto index 72fd7df2d5..ca91a5609e 100644 --- a/proto/umee/uibc/v1/quota.proto +++ b/proto/umee/uibc/v1/quota.proto @@ -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 diff --git a/tests/e2e/e2e_ibc_test.go b/tests/e2e/e2e_ibc_test.go index b307f31545..f41ca7bcac 100644 --- a/tests/e2e/e2e_ibc_test.go +++ b/tests/e2e/e2e_ibc_test.go @@ -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 diff --git a/util/store/iter.go b/util/store/iter.go index db789a001d..0260ac3bc3 100644 --- a/util/store/iter.go +++ b/util/store/iter.go @@ -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 +} diff --git a/util/store/store.go b/util/store/store.go index 1fd9f8af32..06e37f590c 100644 --- a/util/store/store.go +++ b/util/store/store.go @@ -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()) + } +} diff --git a/x/uibc/README.md b/x/uibc/README.md index 8162bdd814..36416555be 100644 --- a/x/uibc/README.md +++ b/x/uibc/README.md @@ -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. @@ -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. @@ -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 @@ -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 diff --git a/x/uibc/genesis.go b/x/uibc/genesis.go index 6c5467d81f..473377eecb 100644 --- a/x/uibc/genesis.go +++ b/x/uibc/genesis.go @@ -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), } } @@ -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 } diff --git a/x/uibc/genesis.pb.go b/x/uibc/genesis.pb.go index 8082cce5f1..5c2f1d014e 100644 --- a/x/uibc/genesis.pb.go +++ b/x/uibc/genesis.pb.go @@ -38,6 +38,10 @@ type GenesisState struct { TotalOutflowSum github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=total_outflow_sum,json=totalOutflowSum,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"total_outflow_sum"` // quota_expires defines quota expire time (as unix timestamp) for ibc-transfer denom. QuotaExpires time.Time `protobuf:"bytes,4,opt,name=quota_expires,json=quotaExpires,proto3,stdtime" json:"quota_duration,omitempty" yaml:"quota_expires"` + // inflows tracks IBC inflow transfers (in USD) for each denom during quota period. + Inflows github_com_cosmos_cosmos_sdk_types.DecCoins `protobuf:"bytes,5,rep,name=inflows,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.DecCoins" json:"inflows"` + // total_inflow_sum defines tracks total sum of IBC inflow transfers (in USD) during quota period. + TotalInflowSum github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,6,opt,name=total_inflow_sum,json=totalInflowSum,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"total_inflow_sum"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -80,35 +84,38 @@ func init() { func init() { proto.RegisterFile("umee/uibc/v1/genesis.proto", fileDescriptor_0196ecf2d08401fb) } var fileDescriptor_0196ecf2d08401fb = []byte{ - // 445 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x51, 0x31, 0x6f, 0xd3, 0x40, - 0x14, 0xb6, 0x49, 0x55, 0x81, 0x1b, 0x84, 0xb0, 0x32, 0x98, 0x08, 0xd9, 0x51, 0x06, 0x14, 0x09, - 0x72, 0xa7, 0xa4, 0x12, 0x03, 0x62, 0x0a, 0x41, 0x8c, 0xa0, 0x94, 0x89, 0x25, 0x3a, 0xbb, 0x57, - 0xf7, 0xd4, 0x9c, 0x9f, 0xf1, 0xbd, 0x4b, 0x9b, 0x81, 0xff, 0xd0, 0x99, 0x9f, 0xc0, 0xcc, 0x8f, - 0xc8, 0x58, 0x31, 0x21, 0x06, 0x17, 0x92, 0x8d, 0x91, 0x5f, 0x80, 0x7c, 0x77, 0x41, 0xed, 0xc6, - 0x64, 0x7f, 0xef, 0x7b, 0xef, 0xbb, 0xef, 0x7d, 0x2f, 0xe8, 0x6a, 0xc9, 0x39, 0xd5, 0x22, 0xcd, - 0xe8, 0x72, 0x44, 0x73, 0x5e, 0x70, 0x25, 0x14, 0x29, 0x2b, 0x40, 0x08, 0xdb, 0x0d, 0x47, 0x1a, - 0x8e, 0x2c, 0x47, 0xdd, 0x4e, 0x0e, 0x39, 0x18, 0x82, 0x36, 0x7f, 0xb6, 0xa7, 0xfb, 0x28, 0x03, - 0x25, 0x41, 0xcd, 0x2d, 0x61, 0x81, 0xa3, 0x62, 0x8b, 0x68, 0xca, 0x14, 0xa7, 0xcb, 0x51, 0xca, - 0x91, 0x8d, 0x68, 0x06, 0xa2, 0x70, 0x7c, 0x92, 0x03, 0xe4, 0x0b, 0x4e, 0x0d, 0x4a, 0xf5, 0x09, - 0x45, 0x21, 0xb9, 0x42, 0x26, 0x4b, 0xd7, 0x10, 0xdd, 0xf2, 0xf6, 0x51, 0x03, 0x32, 0xcb, 0xf4, - 0x3f, 0xb7, 0x82, 0xf6, 0x1b, 0xeb, 0xf5, 0x08, 0x19, 0xf2, 0x70, 0x1c, 0xec, 0x97, 0xac, 0x62, - 0x52, 0x45, 0x7e, 0xcf, 0x1f, 0x1c, 0x8c, 0x3b, 0xe4, 0xa6, 0x77, 0xf2, 0xce, 0x70, 0x93, 0xbd, - 0x75, 0x9d, 0x78, 0x33, 0xd7, 0x19, 0xca, 0xe0, 0x2e, 0x68, 0x3c, 0x59, 0xc0, 0xb9, 0x8a, 0xee, - 0xf4, 0x5a, 0x83, 0x83, 0xf1, 0x63, 0xe2, 0x16, 0x68, 0x2c, 0x13, 0x67, 0x99, 0x4c, 0x79, 0xf6, - 0x0a, 0x44, 0x31, 0x39, 0x6c, 0xa6, 0xbf, 0x5c, 0x27, 0x4f, 0x73, 0x81, 0xa7, 0x3a, 0x25, 0x19, - 0x48, 0xb7, 0xb0, 0xfb, 0x0c, 0xd5, 0xf1, 0x19, 0xc5, 0x55, 0xc9, 0xd5, 0x6e, 0x46, 0xcd, 0xfe, - 0x3d, 0x11, 0x9e, 0x06, 0x0f, 0x11, 0x90, 0x2d, 0xe6, 0xae, 0x32, 0x57, 0x5a, 0x46, 0xad, 0x9e, - 0x3f, 0xb8, 0x37, 0x79, 0xd9, 0x28, 0xff, 0xa8, 0x93, 0x27, 0xff, 0xa7, 0xfc, 0xed, 0xeb, 0x30, - 0x70, 0x46, 0xa7, 0x3c, 0x9b, 0x3d, 0x30, 0xb2, 0x6f, 0xad, 0xea, 0x91, 0x96, 0xe1, 0xa7, 0xe0, - 0xbe, 0x09, 0x6b, 0xce, 0x2f, 0x4a, 0x51, 0x71, 0x15, 0xed, 0x99, 0x4c, 0xba, 0xc4, 0x06, 0x4e, - 0x76, 0x81, 0x93, 0xf7, 0xbb, 0xc0, 0xad, 0x83, 0xdf, 0x75, 0x12, 0xd9, 0xc1, 0x63, 0x5d, 0x31, - 0x14, 0x50, 0x3c, 0x03, 0x29, 0x90, 0xcb, 0x12, 0x57, 0x7f, 0xea, 0xa4, 0xb3, 0x62, 0x72, 0xf1, - 0xa2, 0x7f, 0x4b, 0xba, 0x7f, 0x79, 0x9d, 0xf8, 0xb3, 0xb6, 0xa9, 0xbd, 0xb6, 0xa5, 0xc9, 0x74, - 0xfd, 0x2b, 0xf6, 0xd6, 0x9b, 0xd8, 0xbf, 0xda, 0xc4, 0xfe, 0xcf, 0x4d, 0xec, 0x5f, 0x6e, 0x63, - 0xef, 0x6a, 0x1b, 0x7b, 0xdf, 0xb7, 0xb1, 0xf7, 0xe1, 0xe6, 0x8e, 0xcd, 0x8d, 0x86, 0x05, 0xc7, - 0x73, 0xa8, 0xce, 0x0c, 0xa0, 0xcb, 0xe7, 0xf4, 0xc2, 0x5c, 0x3c, 0xdd, 0x37, 0x2e, 0x0f, 0xff, - 0x06, 0x00, 0x00, 0xff, 0xff, 0xcd, 0x24, 0x38, 0xc9, 0xa1, 0x02, 0x00, 0x00, + // 482 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x92, 0xbf, 0x8e, 0x13, 0x3d, + 0x14, 0xc5, 0x67, 0xbe, 0xcd, 0x17, 0xc0, 0x1b, 0xfe, 0x8d, 0x52, 0x0c, 0x11, 0x9a, 0x89, 0x52, + 0xa0, 0x48, 0x10, 0x5b, 0xc9, 0x4a, 0x14, 0x88, 0x2a, 0x04, 0x21, 0x2a, 0x50, 0x96, 0x8a, 0x26, + 0xf2, 0xcc, 0x3a, 0xb3, 0x56, 0xe2, 0xb9, 0xc3, 0xd8, 0xce, 0x6e, 0x0a, 0xde, 0x61, 0x9f, 0x83, + 0x9a, 0x87, 0x48, 0xb9, 0xa2, 0x42, 0x14, 0x59, 0x48, 0x3a, 0x1a, 0x24, 0x9e, 0x00, 0x8d, 0xed, + 0xa0, 0xdd, 0x8e, 0x02, 0xaa, 0x99, 0x7b, 0x8f, 0xef, 0xb9, 0xc7, 0x3f, 0x19, 0xb5, 0xb4, 0x60, + 0x8c, 0x68, 0x9e, 0xa4, 0x64, 0xd1, 0x27, 0x19, 0xcb, 0x99, 0xe4, 0x12, 0x17, 0x25, 0x28, 0x08, + 0x1a, 0x95, 0x86, 0x2b, 0x0d, 0x2f, 0xfa, 0xad, 0x66, 0x06, 0x19, 0x18, 0x81, 0x54, 0x7f, 0xf6, + 0x4c, 0xeb, 0x5e, 0x0a, 0x52, 0x80, 0x9c, 0x58, 0xc1, 0x16, 0x4e, 0x8a, 0x6c, 0x45, 0x12, 0x2a, + 0x19, 0x59, 0xf4, 0x13, 0xa6, 0x68, 0x9f, 0xa4, 0xc0, 0x73, 0xa7, 0xc7, 0x19, 0x40, 0x36, 0x67, + 0xc4, 0x54, 0x89, 0x9e, 0x12, 0xc5, 0x05, 0x93, 0x8a, 0x8a, 0xc2, 0x1d, 0x08, 0xaf, 0x64, 0x7b, + 0xa7, 0x41, 0x51, 0xab, 0x74, 0x7e, 0xd4, 0x50, 0xe3, 0x85, 0xcd, 0x7a, 0xa8, 0xa8, 0x62, 0xc1, + 0x00, 0xd5, 0x0b, 0x5a, 0x52, 0x21, 0x43, 0xbf, 0xed, 0x77, 0xf7, 0x07, 0x4d, 0x7c, 0x39, 0x3b, + 0x7e, 0x6d, 0xb4, 0x61, 0x6d, 0xb5, 0x8e, 0xbd, 0xb1, 0x3b, 0x19, 0x08, 0x74, 0x1d, 0xb4, 0x9a, + 0xce, 0xe1, 0x44, 0x86, 0xff, 0xb5, 0xf7, 0xba, 0xfb, 0x83, 0xfb, 0xd8, 0x5d, 0xa0, 0x8a, 0x8c, + 0x5d, 0x64, 0x3c, 0x62, 0xe9, 0x33, 0xe0, 0xf9, 0xf0, 0xa0, 0x9a, 0xfe, 0x70, 0x11, 0x3f, 0xcc, + 0xb8, 0x3a, 0xd6, 0x09, 0x4e, 0x41, 0xb8, 0x0b, 0xbb, 0x4f, 0x4f, 0x1e, 0xcd, 0x88, 0x5a, 0x16, + 0x4c, 0xee, 0x66, 0xe4, 0xf8, 0xf7, 0x8a, 0xe0, 0x18, 0xdd, 0x55, 0xa0, 0xe8, 0x7c, 0xe2, 0x3a, + 0x13, 0xa9, 0x45, 0xb8, 0xd7, 0xf6, 0xbb, 0x37, 0x86, 0x4f, 0x2b, 0xe7, 0x2f, 0xeb, 0xf8, 0xc1, + 0x9f, 0x39, 0x7f, 0xfa, 0xd8, 0x43, 0x2e, 0xe8, 0x88, 0xa5, 0xe3, 0xdb, 0xc6, 0xf6, 0x95, 0x75, + 0x3d, 0xd4, 0x22, 0x78, 0x8f, 0x6e, 0x1a, 0x58, 0x13, 0x76, 0x5a, 0xf0, 0x92, 0xc9, 0xb0, 0x66, + 0x98, 0xb4, 0xb0, 0x05, 0x8e, 0x77, 0xc0, 0xf1, 0x9b, 0x1d, 0x70, 0x9b, 0xe0, 0xfb, 0x3a, 0x0e, + 0xed, 0xe0, 0x91, 0x2e, 0xa9, 0xe2, 0x90, 0x3f, 0x02, 0xc1, 0x15, 0x13, 0x85, 0x5a, 0xfe, 0x5c, + 0xc7, 0xcd, 0x25, 0x15, 0xf3, 0x27, 0x9d, 0x2b, 0xd6, 0x9d, 0xb3, 0x8b, 0xd8, 0x1f, 0x37, 0x4c, + 0xef, 0xb9, 0x6d, 0x05, 0x33, 0x74, 0x8d, 0xe7, 0x16, 0xeb, 0xff, 0xff, 0x0a, 0xeb, 0x6e, 0x43, + 0x30, 0x45, 0x77, 0x2c, 0x55, 0xdb, 0x30, 0x50, 0xeb, 0x7f, 0x01, 0xea, 0x2d, 0xe3, 0xfa, 0x32, + 0x77, 0x4c, 0x87, 0xa3, 0xd5, 0xb7, 0xc8, 0x5b, 0x6d, 0x22, 0xff, 0x7c, 0x13, 0xf9, 0x5f, 0x37, + 0x91, 0x7f, 0xb6, 0x8d, 0xbc, 0xf3, 0x6d, 0xe4, 0x7d, 0xde, 0x46, 0xde, 0xdb, 0xcb, 0x3b, 0xaa, + 0x87, 0xd7, 0xcb, 0x99, 0x3a, 0x81, 0x72, 0x66, 0x0a, 0xb2, 0x78, 0x4c, 0x4e, 0xcd, 0x33, 0x4e, + 0xea, 0x06, 0xfd, 0xc1, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x12, 0x4f, 0x6e, 0xd5, 0x76, 0x03, + 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -131,6 +138,30 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size := m.TotalInflowSum.Size() + i -= size + if _, err := m.TotalInflowSum.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + if len(m.Inflows) > 0 { + for iNdEx := len(m.Inflows) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Inflows[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + } + } n1, err1 := github_com_cosmos_gogoproto_types.StdTimeMarshalTo(m.QuotaExpires, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdTime(m.QuotaExpires):]) if err1 != nil { return 0, err1 @@ -205,6 +236,14 @@ func (m *GenesisState) Size() (n int) { n += 1 + l + sovGenesis(uint64(l)) l = github_com_cosmos_gogoproto_types.SizeOfStdTime(m.QuotaExpires) n += 1 + l + sovGenesis(uint64(l)) + if len(m.Inflows) > 0 { + for _, e := range m.Inflows { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + l = m.TotalInflowSum.Size() + n += 1 + l + sovGenesis(uint64(l)) return n } @@ -377,6 +416,74 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Inflows", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Inflows = append(m.Inflows, types.DecCoin{}) + if err := m.Inflows[len(m.Inflows)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field TotalInflowSum", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.TotalInflowSum.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/uibc/params.go b/x/uibc/params.go index 6d5d797d27..4a6b756cce 100644 --- a/x/uibc/params.go +++ b/x/uibc/params.go @@ -10,10 +10,13 @@ import ( // DefaultParams returns default genesis params func DefaultParams() Params { return Params{ - IbcStatus: IBCTransferStatus_IBC_TRANSFER_STATUS_QUOTA_ENABLED, - TotalQuota: sdk.NewDec(1_000_000), - TokenQuota: sdk.NewDec(600_000), - QuotaDuration: time.Second * 60 * 60 * 24, // 24h + IbcStatus: IBCTransferStatus_IBC_TRANSFER_STATUS_QUOTA_ENABLED, + TotalQuota: sdk.NewDec(1_600_000), // $1.6M + TokenQuota: sdk.NewDec(1_200_000), // $1.2M + QuotaDuration: time.Second * 60 * 60 * 24, // 24h + InflowOutflowQuotaBase: sdk.NewDec(1_000_000), // 1M + InflowOutflowQuotaRate: sdk.MustNewDecFromStr("0.25"), + InflowOutflowQuotaTokenBase: sdk.NewDec(900_000), // $0.9M } } @@ -30,6 +33,15 @@ func (p Params) Validate() error { if err := validateQuota(p.TokenQuota, "quota per token"); err != nil { return err } + if err := validateQuota(p.InflowOutflowQuotaBase, "total inflow outflow quota base"); err != nil { + return err + } + if err := validateQuotaRate(p.InflowOutflowQuotaRate, "total inflow outflow quota rate"); err != nil { + return err + } + if err := validateQuota(p.InflowOutflowQuotaTokenBase, "total inflow outflow quota token base"); err != nil { + return err + } if p.TotalQuota.LT(p.TokenQuota) { return fmt.Errorf("token quota shouldn't be less than quota per denom") } @@ -64,6 +76,14 @@ func validateQuota(q sdk.Dec, typ string) error { return nil } +func validateQuotaRate(q sdk.Dec, typ string) error { + if q.LT(sdk.ZeroDec()) || q.GT(sdk.NewDec(2)) { + return fmt.Errorf("%s must be between 0 and 2: %s", typ, q) + } + + return validateQuota(q, typ) +} + // IBCTransferEnabled returns true if the ibc-transfer is enabled for both inflow and outflow." func (status IBCTransferStatus) IBCTransferEnabled() bool { return status != IBCTransferStatus_IBC_TRANSFER_STATUS_TRANSFERS_PAUSED diff --git a/x/uibc/quota.pb.go b/x/uibc/quota.pb.go index c79fc54588..10916eb2b9 100644 --- a/x/uibc/quota.pb.go +++ b/x/uibc/quota.pb.go @@ -83,6 +83,12 @@ type Params struct { TokenQuota github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=token_quota,json=tokenQuota,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"token_quota"` // quota_duration defines quota expires for each ibc-transfer denom in seconds QuotaDuration time.Duration `protobuf:"bytes,4,opt,name=quota_duration,json=quotaDuration,proto3,stdduration" json:"quota_duration,omitempty" yaml:"quota_duration"` + // inflow_outflow_quota_base defines the inflow outflow quota base of ibc-transfer in USD + InflowOutflowQuotaBase github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,5,opt,name=inflow_outflow_quota_base,json=inflowOutflowQuotaBase,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"inflow_outflow_quota_base"` + // inflow_outflow_quota_rate defines the rate of total inflows + InflowOutflowQuotaRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,6,opt,name=inflow_outflow_quota_rate,json=inflowOutflowQuotaRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"inflow_outflow_quota_rate"` + // inflow_outflow_quota_token_base defines the inflow outflow quota base for token + InflowOutflowQuotaTokenBase github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,7,opt,name=inflow_outflow_quota_token_base,json=inflowOutflowQuotaTokenBase,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"inflow_outflow_quota_token_base"` } func (m *Params) Reset() { *m = Params{} } @@ -140,39 +146,43 @@ func init() { func init() { proto.RegisterFile("umee/uibc/v1/quota.proto", fileDescriptor_651be1a0280abcb6) } var fileDescriptor_651be1a0280abcb6 = []byte{ - // 502 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x93, 0x3f, 0x6f, 0xd3, 0x4e, - 0x18, 0xc7, 0xed, 0xa4, 0xbf, 0x4a, 0xbd, 0xfe, 0xa8, 0x82, 0x05, 0x92, 0xdb, 0xc1, 0x0e, 0x81, - 0x46, 0xa1, 0x22, 0x67, 0xb5, 0x48, 0x0c, 0x08, 0x10, 0x76, 0xec, 0x4a, 0x96, 0x50, 0x9a, 0xfa, - 0xcf, 0x82, 0x84, 0x2c, 0xdb, 0xbd, 0x1a, 0x2b, 0xb1, 0x2f, 0xd8, 0xe7, 0x40, 0x26, 0x46, 0x18, - 0x19, 0x79, 0x21, 0xbc, 0x88, 0x8e, 0x15, 0x13, 0x62, 0x08, 0x28, 0xd9, 0x18, 0x79, 0x05, 0xc8, - 0x77, 0x8e, 0xda, 0x8a, 0x66, 0x63, 0xf2, 0x3d, 0xfe, 0x7e, 0xee, 0x73, 0x8f, 0x9e, 0xb3, 0x81, - 0x58, 0x24, 0x08, 0x29, 0x45, 0x1c, 0x84, 0xca, 0x64, 0x5f, 0x79, 0x53, 0x60, 0xe2, 0xc3, 0x71, - 0x86, 0x09, 0x16, 0xfe, 0x2f, 0x13, 0x58, 0x26, 0x70, 0xb2, 0xbf, 0x73, 0x2b, 0xc2, 0x11, 0xa6, - 0x81, 0x52, 0xae, 0x18, 0xb3, 0x23, 0x45, 0x18, 0x47, 0x23, 0xa4, 0xd0, 0x2a, 0x28, 0x4e, 0x95, - 0x93, 0x22, 0xf3, 0x49, 0x8c, 0xd3, 0x2a, 0xdf, 0x0e, 0x71, 0x9e, 0xe0, 0xdc, 0x63, 0x1b, 0x59, - 0xc1, 0xa2, 0xd6, 0x87, 0x3a, 0x58, 0x1f, 0xf8, 0x99, 0x9f, 0xe4, 0xc2, 0x33, 0x00, 0xe2, 0x20, - 0xf4, 0x72, 0xe2, 0x93, 0x22, 0x17, 0xf9, 0x26, 0xdf, 0xd9, 0x3a, 0x90, 0xe1, 0xe5, 0xe3, 0xa1, - 0xa9, 0xf5, 0x9c, 0xcc, 0x4f, 0xf3, 0x53, 0x94, 0xd9, 0x14, 0xb3, 0x36, 0xe2, 0x20, 0x64, 0x4b, - 0xe1, 0x15, 0xd8, 0x24, 0x98, 0xf8, 0x23, 0x8f, 0xb6, 0x2f, 0xd6, 0x9a, 0x7c, 0x67, 0x43, 0x7b, - 0x72, 0x36, 0x93, 0xb9, 0xef, 0x33, 0xb9, 0x1d, 0xc5, 0xe4, 0x75, 0x11, 0xc0, 0x10, 0x27, 0x55, - 0x03, 0xd5, 0xa3, 0x9b, 0x9f, 0x0c, 0x15, 0x32, 0x1d, 0xa3, 0x1c, 0xea, 0x28, 0xfc, 0xfa, 0xa5, - 0x0b, 0xaa, 0xfe, 0x74, 0x14, 0x5a, 0x80, 0x0a, 0x8f, 0x4b, 0x1f, 0xd3, 0x0f, 0x51, 0x5a, 0xe9, - 0xeb, 0xff, 0x46, 0x3f, 0x44, 0x29, 0xd3, 0xbf, 0x07, 0x5b, 0x54, 0xec, 0x2d, 0x67, 0x27, 0xae, - 0x35, 0xf9, 0xce, 0xe6, 0xc1, 0x36, 0x64, 0xc3, 0x85, 0xcb, 0xe1, 0x42, 0xbd, 0x02, 0xb4, 0xa7, - 0xe5, 0xe1, 0xbf, 0x66, 0xb2, 0x78, 0x75, 0xe3, 0x03, 0x9c, 0xc4, 0x04, 0x25, 0x63, 0x32, 0xfd, - 0x3d, 0x93, 0x6f, 0x4f, 0xfd, 0x64, 0xf4, 0xb8, 0x75, 0x95, 0x68, 0x7d, 0xfe, 0x21, 0xf3, 0xd6, - 0x0d, 0xfa, 0x72, 0x69, 0xdb, 0xfb, 0x58, 0x03, 0x37, 0xff, 0x9a, 0xaf, 0x70, 0x17, 0xc8, 0xa6, - 0xd6, 0xf3, 0x1c, 0x4b, 0xed, 0xdb, 0x87, 0x86, 0xe5, 0xd9, 0x8e, 0xea, 0xb8, 0xb6, 0xe7, 0xf6, - 0xed, 0x81, 0xd1, 0x33, 0x0f, 0x4d, 0x43, 0x6f, 0x70, 0x42, 0x1b, 0xb4, 0xae, 0x83, 0x8e, 0xdd, - 0x23, 0x47, 0xf5, 0x74, 0xd3, 0x56, 0xb5, 0x17, 0x86, 0xde, 0xe0, 0x85, 0x5d, 0x70, 0x67, 0x35, - 0x67, 0xf4, 0x19, 0x56, 0x13, 0xf6, 0x40, 0x7b, 0x35, 0x76, 0xe4, 0x3a, 0x17, 0xca, 0xba, 0x70, - 0x1f, 0xec, 0xae, 0x66, 0xcd, 0xfe, 0x05, 0xba, 0x26, 0x74, 0xc0, 0xbd, 0xeb, 0xd0, 0x65, 0x6d, - 0x7b, 0x03, 0xd5, 0xb5, 0x0d, 0xbd, 0xf1, 0x9f, 0xf6, 0xfc, 0x6c, 0x2e, 0xf1, 0xe7, 0x73, 0x89, - 0xff, 0x39, 0x97, 0xf8, 0x4f, 0x0b, 0x89, 0x3b, 0x5f, 0x48, 0xdc, 0xb7, 0x85, 0xc4, 0xbd, 0xbc, - 0x7c, 0xcf, 0xe5, 0x97, 0xd9, 0x4d, 0x11, 0x79, 0x8b, 0xb3, 0x21, 0x2d, 0x94, 0xc9, 0x23, 0xe5, - 0x1d, 0xfd, 0x89, 0x82, 0x75, 0x7a, 0x5b, 0x0f, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0xf5, 0x90, - 0x20, 0x70, 0x58, 0x03, 0x00, 0x00, + // 571 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x94, 0xcf, 0x8b, 0xd3, 0x40, + 0x14, 0xc7, 0x9b, 0xfd, 0x25, 0x3b, 0xab, 0xcb, 0x1a, 0x54, 0xd2, 0x15, 0x92, 0x5a, 0xdd, 0x52, + 0x17, 0x9b, 0xb0, 0x2b, 0x78, 0x10, 0x15, 0x9b, 0x26, 0x0b, 0x01, 0x69, 0xbb, 0x49, 0x7a, 0x11, + 0x24, 0x4c, 0xb2, 0xd3, 0x1a, 0xda, 0x64, 0x6a, 0x32, 0x69, 0xed, 0x49, 0xf0, 0xe4, 0xd1, 0xa3, + 0x7f, 0x88, 0x7f, 0xc4, 0x1e, 0x17, 0x4f, 0xe2, 0xa1, 0x4a, 0x7b, 0xf3, 0xe8, 0x1f, 0x20, 0x92, + 0x99, 0x94, 0xdd, 0x65, 0xdb, 0x5b, 0x3d, 0xcd, 0xbc, 0xbc, 0xef, 0x7c, 0xbe, 0xef, 0xbd, 0x24, + 0x03, 0x84, 0x24, 0x40, 0x48, 0x49, 0x7c, 0xd7, 0x53, 0x06, 0x07, 0xca, 0xbb, 0x04, 0x13, 0x28, + 0xf7, 0x23, 0x4c, 0x30, 0x7f, 0x3d, 0xcd, 0xc8, 0x69, 0x46, 0x1e, 0x1c, 0xec, 0xde, 0xea, 0xe0, + 0x0e, 0xa6, 0x09, 0x25, 0xdd, 0x31, 0xcd, 0xae, 0xd8, 0xc1, 0xb8, 0xd3, 0x43, 0x0a, 0x8d, 0xdc, + 0xa4, 0xad, 0x9c, 0x24, 0x11, 0x24, 0x3e, 0x0e, 0xb3, 0x7c, 0xde, 0xc3, 0x71, 0x80, 0x63, 0x87, + 0x1d, 0x64, 0x01, 0x4b, 0x15, 0xff, 0xae, 0x83, 0x8d, 0x26, 0x8c, 0x60, 0x10, 0xf3, 0x2f, 0x00, + 0xf0, 0x5d, 0xcf, 0x89, 0x09, 0x24, 0x49, 0x2c, 0x70, 0x05, 0xae, 0xbc, 0x7d, 0x28, 0xc9, 0x17, + 0xed, 0x65, 0x43, 0xad, 0xd9, 0x11, 0x0c, 0xe3, 0x36, 0x8a, 0x2c, 0x2a, 0x33, 0x37, 0x7d, 0xd7, + 0x63, 0x5b, 0xfe, 0x0d, 0xd8, 0x22, 0x98, 0xc0, 0x9e, 0x43, 0xcb, 0x17, 0x56, 0x0a, 0x5c, 0x79, + 0x53, 0x7d, 0x76, 0x3a, 0x96, 0x72, 0x3f, 0xc6, 0x52, 0xa9, 0xe3, 0x93, 0xb7, 0x89, 0x2b, 0x7b, + 0x38, 0xc8, 0x0a, 0xc8, 0x96, 0x4a, 0x7c, 0xd2, 0x55, 0xc8, 0xa8, 0x8f, 0x62, 0x59, 0x43, 0xde, + 0xb7, 0xaf, 0x15, 0x90, 0xd5, 0xa7, 0x21, 0xcf, 0x04, 0x14, 0x78, 0x9c, 0xf2, 0x18, 0xbe, 0x8b, + 0xc2, 0x0c, 0xbf, 0xba, 0x1c, 0x7c, 0x17, 0x85, 0x0c, 0xff, 0x01, 0x6c, 0x53, 0xb0, 0x33, 0x9b, + 0x9d, 0xb0, 0x56, 0xe0, 0xca, 0x5b, 0x87, 0x79, 0x99, 0x0d, 0x57, 0x9e, 0x0d, 0x57, 0xd6, 0x32, + 0x81, 0xfa, 0x3c, 0x35, 0xff, 0x3d, 0x96, 0x84, 0xcb, 0x07, 0x1f, 0xe1, 0xc0, 0x27, 0x28, 0xe8, + 0x93, 0xd1, 0x9f, 0xb1, 0x74, 0x7b, 0x04, 0x83, 0xde, 0xd3, 0xe2, 0x65, 0x45, 0xf1, 0xcb, 0x4f, + 0x89, 0x33, 0x6f, 0xd0, 0x87, 0x33, 0x1a, 0x3f, 0x04, 0x79, 0x3f, 0x6c, 0xf7, 0xf0, 0xd0, 0xc1, + 0x09, 0xa1, 0x2b, 0x3b, 0xe4, 0xc2, 0x18, 0x09, 0xeb, 0x4b, 0xe8, 0xf6, 0x0e, 0xc3, 0x37, 0x18, + 0x9d, 0x76, 0xad, 0xc2, 0x18, 0x2d, 0x34, 0x8e, 0x20, 0x41, 0xc2, 0xc6, 0xff, 0x31, 0x36, 0x21, + 0x41, 0xfc, 0x47, 0x0e, 0x48, 0x73, 0x9d, 0xd9, 0x7b, 0xa6, 0x8d, 0x5f, 0x5b, 0x82, 0xff, 0xdd, + 0xab, 0xfe, 0x76, 0xea, 0x90, 0x76, 0xbf, 0xff, 0x69, 0x05, 0xdc, 0xbc, 0xf2, 0x59, 0xf3, 0xf7, + 0x81, 0x64, 0xa8, 0x35, 0xc7, 0x36, 0xab, 0x75, 0xeb, 0x48, 0x37, 0x1d, 0xcb, 0xae, 0xda, 0x2d, + 0xcb, 0x69, 0xd5, 0xad, 0xa6, 0x5e, 0x33, 0x8e, 0x0c, 0x5d, 0xdb, 0xc9, 0xf1, 0x25, 0x50, 0x9c, + 0x27, 0x3a, 0x6e, 0x35, 0xec, 0xaa, 0xa3, 0x19, 0x56, 0x55, 0x7d, 0xa5, 0x6b, 0x3b, 0x1c, 0xbf, + 0x07, 0xee, 0x2d, 0xd6, 0xe9, 0x75, 0x26, 0x5b, 0xe1, 0xf7, 0x41, 0x69, 0xb1, 0xac, 0xd1, 0xb2, + 0xcf, 0x91, 0xab, 0xfc, 0x43, 0xb0, 0xb7, 0x58, 0x6b, 0xd4, 0xcf, 0xa5, 0x6b, 0x7c, 0x19, 0x3c, + 0x98, 0x27, 0x9d, 0xc5, 0x96, 0xd3, 0xac, 0xb6, 0x2c, 0x5d, 0xdb, 0x59, 0x57, 0x5f, 0x9e, 0x4e, + 0x44, 0xee, 0x6c, 0x22, 0x72, 0xbf, 0x26, 0x22, 0xf7, 0x79, 0x2a, 0xe6, 0xce, 0xa6, 0x62, 0xee, + 0xfb, 0x54, 0xcc, 0xbd, 0xbe, 0x38, 0xf7, 0xf4, 0x42, 0xa8, 0x84, 0x88, 0x0c, 0x71, 0xd4, 0xa5, + 0x81, 0x32, 0x78, 0xa2, 0xbc, 0xa7, 0x77, 0x97, 0xbb, 0x41, 0x7f, 0x92, 0xc7, 0xff, 0x02, 0x00, + 0x00, 0xff, 0xff, 0xf1, 0xe6, 0x9a, 0xe6, 0xcf, 0x04, 0x00, 0x00, } func (m *Params) Marshal() (dAtA []byte, err error) { @@ -195,6 +205,36 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size := m.InflowOutflowQuotaTokenBase.Size() + i -= size + if _, err := m.InflowOutflowQuotaTokenBase.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuota(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a + { + size := m.InflowOutflowQuotaRate.Size() + i -= size + if _, err := m.InflowOutflowQuotaRate.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuota(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x32 + { + size := m.InflowOutflowQuotaBase.Size() + i -= size + if _, err := m.InflowOutflowQuotaBase.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintQuota(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a n1, err1 := github_com_cosmos_gogoproto_types.StdDurationMarshalTo(m.QuotaDuration, dAtA[i-github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.QuotaDuration):]) if err1 != nil { return 0, err1 @@ -257,6 +297,12 @@ func (m *Params) Size() (n int) { n += 1 + l + sovQuota(uint64(l)) l = github_com_cosmos_gogoproto_types.SizeOfStdDuration(m.QuotaDuration) n += 1 + l + sovQuota(uint64(l)) + l = m.InflowOutflowQuotaBase.Size() + n += 1 + l + sovQuota(uint64(l)) + l = m.InflowOutflowQuotaRate.Size() + n += 1 + l + sovQuota(uint64(l)) + l = m.InflowOutflowQuotaTokenBase.Size() + n += 1 + l + sovQuota(uint64(l)) return n } @@ -415,6 +461,108 @@ func (m *Params) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InflowOutflowQuotaBase", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuota + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuota + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuota + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.InflowOutflowQuotaBase.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InflowOutflowQuotaRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuota + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuota + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuota + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.InflowOutflowQuotaRate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field InflowOutflowQuotaTokenBase", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuota + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuota + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuota + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.InflowOutflowQuotaTokenBase.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipQuota(dAtA[iNdEx:]) diff --git a/x/uibc/quota/ibc_module.go b/x/uibc/quota/ibc_module.go index 5edea4eaec..bf87008e6a 100644 --- a/x/uibc/quota/ibc_module.go +++ b/x/uibc/quota/ibc_module.go @@ -47,20 +47,19 @@ func (im ICS20Module) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet, return channeltypes.NewErrorAcknowledgement(transfertypes.ErrReceiveDisabled) } - // TODO: re-enable inflow checks - // if params.IbcStatus.InflowQuotaEnabled() { - // var data transfertypes.FungibleTokenPacketData - // if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { - // ackErr := sdkerrors.ErrInvalidType.Wrap("cannot unmarshal ICS-20 transfer packet data") - // return channeltypes.NewErrorAcknowledgement(ackErr) - // } - - // isSourceChain := transfertypes.SenderChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) - // ackErr := im.kb.Keeper(&ctx).CheckIBCInflow(ctx, packet, data.Denom, isSourceChain) - // if ackErr != nil { - // return ackErr - // } - // } + if params.IbcStatus.OutflowQuotaEnabled() { + var data transfertypes.FungibleTokenPacketData + if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil { + ackErr := sdkerrors.ErrInvalidType.Wrap("cannot unmarshal ICS-20 transfer packet data") + return channeltypes.NewErrorAcknowledgement(ackErr) + } + + isSourceChain := transfertypes.SenderChainIsSource(packet.GetSourcePort(), packet.GetSourceChannel(), data.Denom) + ackErr := im.kb.Keeper(&ctx).RecordIBCInflow(ctx, packet, data.Denom, data.Amount, isSourceChain) + if ackErr != nil { + return ackErr + } + } return im.IBCModule.OnRecvPacket(ctx, packet, relayer) } diff --git a/x/uibc/quota/keeper/genesis.go b/x/uibc/quota/keeper/genesis.go index a710d6ac43..2fd86cb0d4 100644 --- a/x/uibc/quota/keeper/genesis.go +++ b/x/uibc/quota/keeper/genesis.go @@ -14,7 +14,9 @@ func (kb Builder) InitGenesis(ctx sdk.Context, genState uibc.GenesisState) { util.Panic(err) k.SetTokenOutflows(genState.Outflows) + k.SetTokenInflows(genState.Inflows) k.SetTotalOutflowSum(genState.TotalOutflowSum) + k.SetTotalInflow(genState.TotalInflowSum) err = k.SetExpire(genState.QuotaExpires) util.Panic(err) @@ -25,6 +27,8 @@ func (kb Builder) ExportGenesis(ctx sdk.Context) *uibc.GenesisState { k := kb.Keeper(&ctx) outflows, err := k.GetAllOutflows() util.Panic(err) + inflows, err := k.GetAllInflows() + util.Panic(err) quotaExpires, err := k.GetExpire() util.Panic(err) @@ -33,5 +37,7 @@ func (kb Builder) ExportGenesis(ctx sdk.Context) *uibc.GenesisState { Outflows: outflows, TotalOutflowSum: k.GetTotalOutflow(), QuotaExpires: *quotaExpires, + Inflows: inflows, + TotalInflowSum: k.GetTotalInflow(), } } diff --git a/x/uibc/quota/keeper/intest/quota_test.go b/x/uibc/quota/keeper/intest/quota_test.go index ec8beca1eb..63789caab2 100644 --- a/x/uibc/quota/keeper/intest/quota_test.go +++ b/x/uibc/quota/keeper/intest/quota_test.go @@ -25,12 +25,18 @@ func TestResetQuota(t *testing.T) { q := k.GetTokenOutflows(umeeQuota.Denom) assert.DeepEqual(t, q, umeeQuota) + k.SetTokenInflow(umeeQuota) + i := k.GetTokenInflow(umeeQuota.Denom) + assert.DeepEqual(t, i, umeeQuota) + // reset the quota k.ResetAllQuotas() // check the quota after reset q = k.GetTokenOutflows(umeeQuota.Denom) assert.DeepEqual(t, q.Amount, sdk.NewDec(0)) + i = k.GetTokenInflow(umeeQuota.Denom) + assert.DeepEqual(t, i.Amount, sdk.NewDec(0)) } func TestKeeper_CheckAndUpdateQuota(t *testing.T) { diff --git a/x/uibc/quota/keeper/keys.go b/x/uibc/quota/keeper/keys.go index 8eb9321fc6..5867c8cb9d 100644 --- a/x/uibc/quota/keeper/keys.go +++ b/x/uibc/quota/keeper/keys.go @@ -9,9 +9,16 @@ var ( keyTotalOutflows = []byte{0x02} keyParams = []byte{0x03} keyQuotaExpires = []byte{0x04} + keyPrefixDenomInflows = []byte{0x05} + keyTotalInflows = []byte{0x06} ) -func KeyTotalOutflows(ibcDenom string) []byte { - // KeyPrefixDenomQuota | denom +func keyTotalOutflow(ibcDenom string) []byte { + // keyPrefixDenomOutflows | denom return util.ConcatBytes(0, keyPrefixDenomOutflows, []byte(ibcDenom)) } + +func keyTokenInflow(ibcDenom string) []byte { + // keyPrefixDenomInflows | denom + return util.ConcatBytes(0, keyPrefixDenomInflows, []byte(ibcDenom)) +} diff --git a/x/uibc/quota/keeper/migrations.go b/x/uibc/quota/keeper/migrations.go new file mode 100644 index 0000000000..d1bf8d9b8f --- /dev/null +++ b/x/uibc/quota/keeper/migrations.go @@ -0,0 +1,17 @@ +package keeper + +import sdk "github.com/cosmos/cosmos-sdk/types" + +// getOldTotalOutflow returns the total outflow of ibc-transfer amount. +// Note: only use for v6.2 migration from v6.1.0 +func (k Keeper) getOldTotalOutflow() sdk.Dec { + bz := k.store.Get(keyTotalOutflows) + return sdk.MustNewDecFromStr(string(bz)) +} + +// MigrateTotalOutflowSum migrate the old total outflow type to new one +// Note: only use for v6.2 migration from v6.1.0 +func (k Keeper) MigrateTotalOutflowSum() { + oldTotalOutflow := k.getOldTotalOutflow() + k.SetTotalOutflowSum(oldTotalOutflow) +} diff --git a/x/uibc/quota/keeper/mocks_test.go b/x/uibc/quota/keeper/mocks_test.go index 47deef2cbe..bf16448878 100644 --- a/x/uibc/quota/keeper/mocks_test.go +++ b/x/uibc/quota/keeper/mocks_test.go @@ -48,7 +48,9 @@ type Oracle struct { func (o Oracle) Price(_ sdk.Context, denom string) (sdk.Dec, error) { p, ok := o.prices[denom] if !ok { - return p, ltypes.ErrNotRegisteredToken.Wrap(denom) + // When token exists in leverage registry but price is not found we are returning `0` + // https: //github.com/umee-network/umee/blob/v6.1.0/x/oracle/keeper/historic_avg.go#L126 + return sdk.ZeroDec(), nil } return p, nil } diff --git a/x/uibc/quota/keeper/params_test.go b/x/uibc/quota/keeper/params_test.go index 8e7e7a302f..5c0a38be15 100644 --- a/x/uibc/quota/keeper/params_test.go +++ b/x/uibc/quota/keeper/params_test.go @@ -22,6 +22,10 @@ func TestUnitParams(t *testing.T) { params.IbcStatus = uibc.IBCTransferStatus_IBC_TRANSFER_STATUS_QUOTA_DISABLED params.TokenQuota = sdk.MustNewDecFromStr("12.23") params.TotalQuota = sdk.MustNewDecFromStr("3.4321") + params.InflowOutflowQuotaBase = sdk.MustNewDecFromStr("3.4321") + params.InflowOutflowQuotaRate = sdk.MustNewDecFromStr("0.2") + params.InflowOutflowQuotaTokenBase = sdk.MustNewDecFromStr("0.2") + err := k.SetParams(params) require.NoError(err) // check the updated params @@ -48,7 +52,7 @@ func TestValidateEmergencyQuotaParamsUpdate(t *testing.T) { {"valid total quota update", mkParams(99, 10, 50), ""}, {"valid update", mkParams(0, 0, 50), ""}, - {"invalid update", mkParams(201, 11, 50), "can't increase"}, + {"invalid update", mkParams(201, 11, 50), "can't increase"}, {"invalid total quota update", mkParams(101, 10, 50), "can't increase"}, {"invalid token quota update", mkParams(10, 12, 50), "can't increase"}, {"invalid quota duration update1", mkParams(100, 10, 51), "can't change QuotaDuration"}, diff --git a/x/uibc/quota/keeper/quota.go b/x/uibc/quota/keeper/quota.go index ce46fd4fc7..b02ff66531 100644 --- a/x/uibc/quota/keeper/quota.go +++ b/x/uibc/quota/keeper/quota.go @@ -21,26 +21,14 @@ var ten = sdk.MustNewDecFromStr("10") // GetAllOutflows returns sum of outflows of all tokens in USD value. func (k Keeper) GetAllOutflows() (sdk.DecCoins, error) { - var outflows sdk.DecCoins - // creating PrefixStore upfront will remove the prefix from the key when running the iterator. - store := k.PrefixStore(keyPrefixDenomOutflows) - iter := sdk.KVStorePrefixIterator(store, nil) - defer iter.Close() - - for ; iter.Valid(); iter.Next() { - o := sdk.DecCoin{Denom: string(iter.Key())} - if err := o.Amount.Unmarshal(iter.Value()); err != nil { - return nil, err - } - outflows = append(outflows, o) - } - - return outflows, nil + iter := sdk.KVStorePrefixIterator(k.store, keyPrefixDenomOutflows) + return store.LoadAllDecCoins(iter, len(keyPrefixDenomOutflows)) } // GetTokenOutflows returns sum of denom outflows in USD value in the DecCoin structure. func (k Keeper) GetTokenOutflows(denom string) sdk.DecCoin { - amount, _ := store.GetDec(k.store, KeyTotalOutflows(denom), "total_outflow") + // When token outflow is not stored in store it will return 0 + amount, _ := store.GetDec(k.store, keyTotalOutflow(denom), "total_outflow") return sdk.NewDecCoinFromDec(denom, amount) } @@ -52,23 +40,66 @@ func (k Keeper) SetTokenOutflows(outflows sdk.DecCoins) { } } +// SetTotalOutflowSum save the total outflow of ibc-transfer amount. +func (k Keeper) SetTotalOutflowSum(amount sdk.Dec) { + err := store.SetDec(k.store, keyTotalOutflows, amount, "total_outflow_sum") + util.Panic(err) +} + +// GetTotalOutflow returns the total outflow of ibc-transfer amount. +func (k Keeper) GetTotalOutflow() sdk.Dec { + // When total outflow is not stored in store it will return 0 + amount, _ := store.GetDec(k.store, keyTotalOutflows, "total_outflow") + return amount +} + // SetTokenOutflow save the outflows of denom into store. func (k Keeper) SetTokenOutflow(outflow sdk.DecCoin) { - key := KeyTotalOutflows(outflow.Denom) + key := keyTotalOutflow(outflow.Denom) err := store.SetDec(k.store, key, outflow.Amount, "total_outflow") util.Panic(err) } -// GetTotalOutflow returns the total outflow of ibc-transfer amount. -func (k Keeper) GetTotalOutflow() sdk.Dec { - // TODO: use store.Get/SetDec - bz := k.store.Get(keyTotalOutflows) - return sdk.MustNewDecFromStr(string(bz)) +// GetAllInflows returns inflows of all registered tokens in USD value. +func (k Keeper) GetAllInflows() (sdk.DecCoins, error) { + iter := sdk.KVStorePrefixIterator(k.store, keyPrefixDenomInflows) + return store.LoadAllDecCoins(iter, len(keyPrefixDenomInflows)) } -// SetTotalOutflowSum save the total outflow of ibc-transfer amount. -func (k Keeper) SetTotalOutflowSum(amount sdk.Dec) { - k.store.Set(keyTotalOutflows, []byte(amount.String())) +// SetTokenInflows saves provided updated IBC inflows as a pair: USD value, denom name in the +// DecCoin structure. +func (k Keeper) SetTokenInflows(inflows sdk.DecCoins) { + for _, q := range inflows { + k.SetTokenInflow(q) + } +} + +// SetTokenInflow save the inflow of denom into store. +func (k Keeper) SetTokenInflow(inflow sdk.DecCoin) { + key := keyTokenInflow(inflow.Denom) + err := store.SetDec(k.store, key, inflow.Amount, "token_inflow") + util.Panic(err) +} + +// GetTokenInflow returns the inflow of denom from store. +func (k Keeper) GetTokenInflow(denom string) sdk.DecCoin { + key := keyTokenInflow(denom) + // When token inflow is not stored in store it will return 0 + amount, _ := store.GetDec(k.store, key, "token_inflow") + return sdk.NewDecCoinFromDec(denom, amount) +} + +// GetTotalInflow returns the total inflow of ibc-transfer amount. +func (k Keeper) GetTotalInflow() sdk.Dec { + // When total inflow is not stored in store it will return 0 + amount, _ := store.GetDec(k.store, keyTotalInflows, "total_inflows") + return amount +} + +// SetTotalInflow save the total inflow of ibc-transfer amount. +func (k Keeper) SetTotalInflow(amount sdk.Dec) { + err := store.SetDec(k.store, keyTotalInflows, amount, "total_inflows") + util.Panic(err) } // SetExpire save the quota expire time of ibc denom into. @@ -89,17 +120,15 @@ func (k Keeper) ResetAllQuotas() error { return err } zero := sdk.NewDec(0) - zeroBz, err := zero.Marshal() - if err != nil { - return err - } + // outflows k.SetTotalOutflowSum(zero) - store := k.PrefixStore(keyPrefixDenomOutflows) - iter := sdk.KVStorePrefixIterator(store, nil) - defer iter.Close() - for ; iter.Valid(); iter.Next() { - store.Set(iter.Key(), zeroBz) - } + ps := k.PrefixStore(keyPrefixDenomOutflows) + store.DeleteByPrefixStore(ps) + + // inflows + k.SetTotalInflow(zero) + ps = k.PrefixStore(keyPrefixDenomInflows) + store.DeleteByPrefixStore(ps) return nil } @@ -118,17 +147,29 @@ func (k Keeper) CheckAndUpdateQuota(denom string, newOutflow sdkmath.Int) error o := k.GetTokenOutflows(denom) o.Amount = o.Amount.Add(exchangePrice) - if !params.TokenQuota.IsZero() && o.Amount.GT(params.TokenQuota) { - return uibc.ErrQuotaExceeded + inToken := k.GetTokenInflow(denom) + if !params.TokenQuota.IsZero() { + if o.Amount.GT(params.TokenQuota) || + o.Amount.GT(params.InflowOutflowQuotaTokenBase.Add((params.InflowOutflowQuotaRate.Mul(inToken.Amount)))) { + return uibc.ErrQuotaExceeded + } } + // Allow outflow either of two conditions + // 1. Total Outflow Sum <= Total Outflow Quota + // or + // 2. Total Outflow Sum <= params.InflowOutflowQuotaBase + (params.InflowOutflowQuotaRate * sum of all inflows) totalOutflowSum := k.GetTotalOutflow().Add(exchangePrice) - if !params.TotalQuota.IsZero() && totalOutflowSum.GT(params.TotalQuota) { - return uibc.ErrQuotaExceeded + ttlInSum := k.GetTotalInflow() + if !params.TotalQuota.IsZero() { + if totalOutflowSum.GT(params.TotalQuota) || + totalOutflowSum.GT(params.InflowOutflowQuotaBase.Add(ttlInSum.Mul(params.InflowOutflowQuotaRate))) { + return uibc.ErrQuotaExceeded + } } - k.SetTokenOutflow(o) k.SetTotalOutflowSum(totalOutflowSum) + return nil } @@ -188,9 +229,9 @@ func (k Keeper) UndoUpdateQuota(denom string, amount sdkmath.Int) error { return nil } -// CheckIBCInflow validates if inflow token is registered in x/leverage -func (k Keeper) CheckIBCInflow(ctx sdk.Context, - packet channeltypes.Packet, dataDenom string, isSourceChain bool, +// RecordIBCInflow will save the inflow amount if token is registered otherwise it will skip +func (k Keeper) RecordIBCInflow(ctx sdk.Context, + packet channeltypes.Packet, dataDenom, dataAmount string, isSourceChain bool, ) exported.Acknowledgement { // if chain is recevier and sender chain is source then we need create ibc_denom (ibc/hash(channel,denom)) to // check ibc_denom is exists in leverage token registry @@ -201,14 +242,27 @@ func (k Keeper) CheckIBCInflow(ctx sdk.Context, prefixedDenom := sourcePrefix + dataDenom // construct the denomination trace from the full raw denomination and get the ibc_denom ibcDenom := transfertypes.ParseDenomTrace(prefixedDenom).IBCDenom() - _, err := k.leverage.GetTokenSettings(ctx, ibcDenom) + ts, err := k.leverage.GetTokenSettings(ctx, ibcDenom) if err != nil { + // skip if token is not a registered token on leverage if ltypes.ErrNotRegisteredToken.Is(err) { - return channeltypes.NewErrorAcknowledgement(err) + return nil } - // other leverage keeper error -> log the error and allow the inflow transfer. - ctx.Logger().Error("IBC inflows: can't load token registry", "err", err) } + + // get the exchange price (eg: UMEE) in USD from oracle using SYMBOL Denom eg: `UMEE` + exchangeRate, err := k.oracle.Price(*k.ctx, strings.ToUpper(ts.SymbolDenom)) + if err != nil { + return channeltypes.NewErrorAcknowledgement(err) + } + // calculate total exchange rate + powerReduction := ten.Power(uint64(ts.Exponent)) + inflowInUSD := sdk.MustNewDecFromStr(dataAmount).Quo(powerReduction).Mul(exchangeRate) + + tokenInflow := sdk.NewDecCoinFromDec(ibcDenom, inflowInUSD) + k.SetTokenInflow(tokenInflow) + totalInflowSum := k.GetTotalInflow() + k.SetTotalInflow(totalInflowSum.Add(inflowInUSD)) } return nil diff --git a/x/uibc/quota/keeper/quota_test.go b/x/uibc/quota/keeper/quota_test.go index c1a6c87d5d..27a608810d 100644 --- a/x/uibc/quota/keeper/quota_test.go +++ b/x/uibc/quota/keeper/quota_test.go @@ -3,34 +3,36 @@ package keeper import ( "testing" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" + "gotest.tools/v3/assert" ibcutil "github.com/umee-network/umee/v6/util/ibc" + "github.com/umee-network/umee/v6/x/uibc" ) func TestUnitGetQuotas(t *testing.T) { k := initKeeperSimpleMock(t) quotas, err := k.GetAllOutflows() - require.NoError(t, err) - require.Equal(t, len(quotas), 0) + assert.NilError(t, err) + assert.Equal(t, len(quotas), 0) setQuotas := sdk.DecCoins{sdk.NewInt64DecCoin("test_uumee", 10000)} k.SetTokenOutflows(setQuotas) quotas, err = k.GetAllOutflows() - require.NoError(t, err) - require.Equal(t, setQuotas, quotas) + assert.NilError(t, err) + assert.DeepEqual(t, setQuotas, quotas) // get the quota of denom quota := k.GetTokenOutflows(setQuotas[0].Denom) - require.Equal(t, quota.Denom, setQuotas[0].Denom) + assert.Equal(t, quota.Denom, setQuotas[0].Denom) } func TestUnitGetLocalDenom(t *testing.T) { out := ibcutil.GetLocalDenom("umee") - require.Equal(t, "umee", out) + assert.Equal(t, "umee", out) } func TestUnitCheckAndUpdateQuota(t *testing.T) { @@ -43,20 +45,23 @@ func TestUnitCheckAndUpdateQuota(t *testing.T) { // k.setQuotaParams(10, 100) k.SetTokenOutflow(sdk.NewInt64DecCoin(umee, 6)) + k.SetTokenInflow(sdk.NewInt64DecCoin(umee, 6)) k.SetTotalOutflowSum(sdk.NewDec(50)) + k.SetTotalInflow(sdk.NewDec(50)) + k.SetTokenInflow(sdk.NewDecCoin(umee, math.NewInt(50))) err := k.CheckAndUpdateQuota(umee, sdk.NewInt(1)) - require.NoError(t, err) + assert.NilError(t, err) k.checkOutflows(umee, 8, 52) // transferring 2 umee => 4USD, will exceed the quota (8+4 > 10) err = k.CheckAndUpdateQuota(umee, sdk.NewInt(2)) - require.ErrorContains(t, err, "quota") + assert.ErrorContains(t, err, "quota") k.checkOutflows(umee, 8, 52) // transferring 1 umee => 2USD, will will be still OK (8+2 <= 10) err = k.CheckAndUpdateQuota(umee, sdk.NewInt(1)) - require.NoError(t, err) + assert.NilError(t, err) k.checkOutflows(umee, 10, 54) // 2. Setting TokenQuota param to 0 should unlimit the token quota check @@ -65,42 +70,110 @@ func TestUnitCheckAndUpdateQuota(t *testing.T) { // transferring 20 umee => 40USD, will skip the token quota check, but will update outflows err = k.CheckAndUpdateQuota(umee, sdk.NewInt(20)) - require.NoError(t, err) + assert.NilError(t, err) k.checkOutflows(umee, 50, 94) - // transferring additional 5 umee => 10USD, will fail total quota check + // transferring additional 5 umee => 10USD, will fail total quota check but it will pass inflow quota check + // sum of outflows <= $1M + params.InflowOutflowQuotaRate * sum of all inflows = (10_000_000)+ (50*0) = 10_000_000 + // 104 <= 10_000_000 err = k.CheckAndUpdateQuota(umee, sdk.NewInt(5)) - require.ErrorContains(t, err, "quota") + assert.ErrorContains(t, err, "quota") + k.checkOutflows(umee, 50, 94) + + // it will fail total quota check and inflow quota check also + err = k.CheckAndUpdateQuota(umee, sdk.NewInt(5000000000)) + assert.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(umee, sdk.NewInt(5)) - require.NoError(t, err) + assert.NilError(t, err) k.checkOutflows(umee, 60, 104) // 4. Setting TokenQuota to 65 // k.setQuotaParams(65, 0) err = k.CheckAndUpdateQuota(umee, sdk.NewInt(1)) - require.NoError(t, err) + assert.NilError(t, err) k.checkOutflows(umee, 62, 106) err = k.CheckAndUpdateQuota(umee, sdk.NewInt(2)) // exceeds token quota - require.ErrorContains(t, err, "quota") + assert.ErrorContains(t, err, "quota") + + // Checking ibc outflow quota with ibc inflows + dp := uibc.DefaultParams() + dp.TotalQuota = sdk.NewDec(200) + dp.TokenQuota = sdk.NewDec(500) + dp.InflowOutflowQuotaTokenBase = sdk.NewDec(100) + err = k.SetParams(dp) + assert.NilError(t, err) + + k.SetTokenOutflow(sdk.NewDecCoin(umee, math.NewInt(80))) + k.SetTokenInflow(sdk.NewDecCoin(umee, math.NewInt(80))) + // 80*2 (160) > InflowOutflowQuotaTokenBase(100) + 25% of Token Inflow (80) = 160 > 100+20 + err = k.CheckAndUpdateQuota(umee, sdk.NewInt(80)) // exceeds token quota + assert.ErrorContains(t, err, "quota") + err = k.CheckAndUpdateQuota(umee, sdk.NewInt(5)) + assert.NilError(t, err) + + // Unlimited token quota but limit the total outflow + dp.TokenQuota = sdk.NewDec(0) + dp.InflowOutflowQuotaBase = sdk.NewDec(100) + dp.TotalQuota = sdk.NewDec(100) + err = k.SetParams(dp) + k.SetTotalOutflowSum(sdk.NewDec(80)) + // 80+(20*2) > Total Outflow Quota Limit (100) + err = k.CheckAndUpdateQuota(umee, sdk.NewInt(20)) // exceeds token quota + assert.ErrorContains(t, err, "quota") + err = k.CheckAndUpdateQuota(umee, sdk.NewInt(10)) + assert.NilError(t, err) + + k.ResetAllQuotas() + + err = k.SetParams(dp) + assert.NilError(t, err) + k.SetTotalInflow(sdk.NewDec(100)) + // 80+(80*2) > InflowOutflowQuotaBase(100) + 25% of Total Inflow Sum (100) = 240 > 100+25 + err = k.CheckAndUpdateQuota(umee, sdk.NewInt(80)) // exceeds token quota + assert.ErrorContains(t, err, "quota") + // 80+(5*2) > InflowOutflowQuotaBase(100) + 25% of Total Inflow Sum (100) = 90 < 100+25 + err = k.CheckAndUpdateQuota(umee, sdk.NewInt(5)) + assert.NilError(t, err) } func TestUnitGetExchangePrice(t *testing.T) { k := initKeeperSimpleMock(t) p, err := k.getExchangePrice(umee, sdk.NewInt(12)) - require.NoError(t, err) - require.Equal(t, sdk.NewDec(24), p) + assert.NilError(t, err) + assert.DeepEqual(t, sdk.NewDec(24), p) + // ATOM is leverage registered token but price is not avaiable p, err = k.getExchangePrice(atom, sdk.NewInt(3)) - require.NoError(t, err) - require.Equal(t, sdk.NewDec(30), p) + assert.NilError(t, err) + assert.DeepEqual(t, sdk.ZeroDec(), p) _, err = k.getExchangePrice("notexisting", sdk.NewInt(10)) - require.ErrorContains(t, err, "not found") + assert.ErrorContains(t, err, "not found") +} + +func TestSetAndGetIBCInflows(t *testing.T) { + k := initKeeperSimpleMock(t) + inflowSum := sdk.MustNewDecFromStr("123123") + k.SetTotalInflow(inflowSum) + + rv := k.GetTotalInflow() + assert.DeepEqual(t, inflowSum, rv) + + // inflow of token + inflowOfToken := sdk.NewDecCoin("abcd", math.NewInt(1000000)) + k.SetTokenInflow(inflowOfToken) + + val := k.GetTokenInflow(inflowOfToken.Denom) + assert.DeepEqual(t, val, inflowOfToken) + + inflows, err := k.GetAllInflows() + assert.NilError(t, err) + assert.DeepEqual(t, inflows[0], inflowOfToken) } diff --git a/x/uibc/quota/keeper/unit_test.go b/x/uibc/quota/keeper/unit_test.go index ede6259918..d31d6e398e 100644 --- a/x/uibc/quota/keeper/unit_test.go +++ b/x/uibc/quota/keeper/unit_test.go @@ -35,7 +35,6 @@ func initKeeper(t *testing.T, l uibc.Leverage, o uibc.Oracle) TestKeeper { func initKeeperSimpleMock(t *testing.T) TestKeeper { lmock := NewLeverageKeeperMock(umee, atom) omock := NewOracleMock(umee, sdk.NewDec(2)) - omock.prices[atom] = sdk.NewDec(10) return initKeeper(t, lmock, omock) } @@ -54,6 +53,9 @@ func (k TestKeeper) checkOutflows(denom string, perToken, total int64) { } func (k TestKeeper) setQuotaParams(perToken, total int64) { - err := k.SetParams(uibc.Params{TokenQuota: sdk.NewDec(perToken), TotalQuota: sdk.NewDec(total)}) + dp := uibc.DefaultParams() + dp.TokenQuota = sdk.NewDec(perToken) + dp.TotalQuota = sdk.NewDec(total) + err := k.SetParams(dp) require.NoError(k.t, err) }