diff --git a/app.go b/app.go index f3cd0e2e..63cb0e5b 100644 --- a/app.go +++ b/app.go @@ -343,7 +343,7 @@ func NewApp( app.lpKeeper = liquidityprovider.NewKeeper(app.accountKeeper, app.bankKeeper) app.issuerKeeper = issuer.NewKeeper(app.appCodec, keys[issuer.StoreKey], app.lpKeeper, app.inflationKeeper) app.authorityKeeper = authority.NewKeeper(app.appCodec, keys[authority.StoreKey], app.issuerKeeper, app.bankKeeper, app) - app.marketKeeper = market.NewKeeper(app.appCodec, keys[market.StoreKey], keys[market.StoreKeyIdx], app.accountKeeper, app.bankKeeper) + app.marketKeeper = market.NewKeeper(app.appCodec, keys[market.StoreKey], keys[market.StoreKeyIdx], app.accountKeeper, app.bankKeeper, app.GetSubspace(market.ModuleName)) app.buybackKeeper = buyback.NewKeeper(app.appCodec, keys[buyback.StoreKey], app.marketKeeper, app.accountKeeper, app.stakingKeeper, app.bankKeeper) app.bep3Keeper = bep3.NewKeeper(app.appCodec, keys[bep3.StoreKey], app.bankKeeper, app.accountKeeper, app.paramsKeeper.Subspace(bep3.ModuleName), GetMaccs()) @@ -623,6 +623,7 @@ func initParamsKeeper(appCodec codec.BinaryMarshaler, legacyAmino *codec.LegacyA paramsKeeper.Subspace(distrtypes.ModuleName) paramsKeeper.Subspace(emslashing.ModuleName) paramsKeeper.Subspace(govtypes.ModuleName).WithKeyTable(govtypes.ParamKeyTable()) + paramsKeeper.Subspace(market.ModuleName) paramsKeeper.Subspace(crisistypes.ModuleName) paramsKeeper.Subspace(ibctransfertypes.ModuleName) paramsKeeper.Subspace(ibchost.ModuleName) diff --git a/app_test.go b/app_test.go index 9c09df51..fa42f118 100644 --- a/app_test.go +++ b/app_test.go @@ -2,11 +2,24 @@ package emoney import ( "encoding/json" + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + clienttx "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authclient "github.com/cosmos/cosmos-sdk/x/auth/client" + authsign "github.com/cosmos/cosmos-sdk/x/auth/signing" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/e-money/em-ledger/x/authority" + "github.com/e-money/em-ledger/x/market/types" "github.com/tendermint/tendermint/libs/rand" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "os" "testing" + "time" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/libs/log" @@ -15,6 +28,196 @@ import ( abci "github.com/tendermint/tendermint/abci/types" ) +func setupMarketApp(t *testing.T, options ...func(*baseapp.BaseApp)) (sdk.Context, *EMoneyApp, EncodingConfig) { + t.Helper() + + encCfg := MakeEncodingConfig() + + db := dbm.NewMemDB() + + app := NewApp( + log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, + map[int64]bool{}, t.TempDir(), 0, encCfg, EmptyAppOptions{}, options..., + ) + require.NotNil(t, app) + + ctx := app.BaseApp.NewUncachedContext(true, tmproto.Header{ChainID: "test-market-chain"}) + ctx = ctx.WithBlockTime(time.Now()) + + app.marketKeeper.InitParamsStore(ctx) + + return ctx, app, encCfg +} + +func coins(s string) sdk.Coins { + coins, err := sdk.ParseCoinsNormalized(s) + if err != nil { + panic(err) + } + return coins +} + +func TestAppLimitOrder_0_Full_Err_Gas(t *testing.T) { + // Gas cost expended before entering the market module. + const gasStartingCost = 55914 + + ctx, app, enc := setupMarketApp(t, []func(*baseapp.BaseApp){}...) + require.NotNil(t, app) + + genesisState := ModuleBasics.DefaultGenesis(enc.Marshaler) + authorityState := authority.NewGenesisState( + rand.Bytes(sdk.AddrLen), nil, + ) + genesisState[authority.ModuleName] = enc.Marshaler.MustMarshalJSON(&authorityState) + + keystore1, acct1 := getNewAcctInfo(t) + keystore2, acct2 := getNewAcctInfo(t) + + bal1 := coins("100000chf,1000000ngm") + bal2 := coins("100000eur,1000000ngm") + supply := coins("100000chf,100000eur,2000000ngm") + + bankState := banktypes.NewGenesisState( + banktypes.DefaultParams(), + []banktypes.Balance{ + {acct1.GetAddress().String(), bal1}, + {acct2.GetAddress().String(), bal2}, + }, + supply, + []banktypes.Metadata{}, + ) + genesisState[banktypes.ModuleName] = enc.Marshaler.MustMarshalJSON(bankState) + + stateBytes, err := json.MarshalIndent(genesisState, "", " ") + require.NoError(t, err) + + app.bankKeeper.SetSupply(ctx, banktypes.NewSupply(supply)) + + // Initialize the chain + app.InitChain( + abci.RequestInitChain{ + Validators: []abci.ValidatorUpdate{}, + AppStateBytes: stateBytes, + }, + ) + + total := app.bankKeeper.GetSupply(ctx) + require.NotNil(t, total) + + app.accountKeeper.SetAccount( + ctx, app.accountKeeper.NewAccountWithAddress(ctx, acct1.GetAddress()), + ) + app.accountKeeper.SetAccount( + ctx, app.accountKeeper.NewAccountWithAddress(ctx, acct2.GetAddress()), + ) + + // + // Liquid 0 Gas Cost + // + msg := &types.MsgAddLimitOrder{ + TimeInForce: types.TimeInForce_GoodTillCancel, + Owner: acct1.GetAddress().String(), + Source: sdk.NewCoin("chf", sdk.NewInt(50000)), + Destination: sdk.NewCoin("eur", sdk.NewInt(50000)), + ClientOrderId: "testAddLimitOrder-chf-eur1", + } + + tx := getSignedTrx(ctx, t, app.accountKeeper, enc, msg, keystore1, acct1, 0, 0) + + gasInfo, _, err := app.Deliver(enc.TxConfig.TxEncoder(), tx) + require.NoError(t, err) + t.Log(gasInfo.GasUsed) + require.Equal(t, gasInfo.GasUsed, sdk.Gas(gasStartingCost)) + + // + // Destination denomination xxx does not exist and errs + // + msg2 := &types.MsgAddLimitOrder{ + TimeInForce: types.TimeInForce_GoodTillCancel, + Owner: acct1.GetAddress().String(), + Source: sdk.NewCoin("chf", sdk.NewInt(50000)), + Destination: sdk.NewCoin("xxx", sdk.NewInt(50000)), + ClientOrderId: "testAddLimitOrder-chf-eur2", + } + + tx = getSignedTrx(ctx, t, app.accountKeeper, enc, msg2, keystore1, acct1, 0, 1) + + gasInfo, _, err = app.Deliver(enc.TxConfig.TxEncoder(), tx) + require.Error(t, err) + t.Log(gasInfo.GasUsed) + require.Less(t, sdk.Gas(gasStartingCost), gasInfo.GasUsed) + + msg3 := &types.MsgAddLimitOrder{ + TimeInForce: types.TimeInForce_GoodTillCancel, + Owner: acct2.GetAddress().String(), + Source: sdk.NewCoin("eur", sdk.NewInt(50000)), + Destination: sdk.NewCoin("chf", sdk.NewInt(50000)), + ClientOrderId: "testAddLimitOrder-eur-chf", + } + + ctx = ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter()) + tx = getSignedTrx(ctx, t, app.accountKeeper, enc, msg3, keystore2, acct2, 0, 0) + + gasInfo, _, err = app.Deliver(enc.TxConfig.TxEncoder(), tx) + require.NoError(t, err) + t.Log(gasInfo.GasUsed) + require.Less(t, sdk.Gas(gasStartingCost), gasInfo.GasUsed) +} + +func getSignedTrx( + ctx sdk.Context, + t *testing.T, ak types.AccountKeeper, enc EncodingConfig, + msg *types.MsgAddLimitOrder, + keystore keyring.Keyring, acct keyring.Info, + accountNumber, sequence uint64, +) authsign.Tx { + acci := ak.GetAccount(ctx, acct.GetAddress()) + require.Equal(t, acci.GetAddress().String(), acct.GetAddress().String()) + + txBuilder := enc.TxConfig.NewTxBuilder() + txBuilder.SetMsgs(msg) + txBuilder.SetFeeAmount(coins("25000ngm")) + txBuilder.SetGasLimit(213456) + txBuilder.SetMemo("TestMarketOrder") + + txFactory := clienttx.Factory{}. + WithChainID(""). + WithTxConfig(enc.TxConfig). + WithSignMode(signing.SignMode_SIGN_MODE_DIRECT). + WithAccountNumber(accountNumber). + WithSequence(sequence). + WithKeybase(keystore) + + tx := txBuilder.GetTx() + signers := tx.GetSigners() + require.Equal(t, signers[0].String(), acct.GetAddress().String()) + + err := authclient.SignTx( + txFactory, client.Context{}, acct.GetName(), + txBuilder, true, true, + ) + require.NoError(t, err) + + _, err = enc.TxConfig.TxEncoder()(txBuilder.GetTx()) + require.NoError(t, err) + + return tx +} + +func getNewAcctInfo(t *testing.T) (keyring.Keyring, keyring.Info) { + keystore, err := keyring.New(t.Name()+"1", keyring.BackendMemory, "", nil) + require.NoError(t, err) + + uid := "theKeyName" + + info, _, err := keystore.NewMnemonic( + uid, keyring.English, sdk.FullFundraiserPath, hd.Secp256k1, + ) + require.NoError(t, err) + + return keystore, info +} + func TestSimAppExportAndBlockedAddrs(t *testing.T) { encCfg := MakeEncodingConfig() db := dbm.NewMemDB() diff --git a/docs/client/em/market/v1/query.swagger.json b/docs/client/em/market/v1/query.swagger.json index 4d1645f0..2932993c 100644 --- a/docs/client/em/market/v1/query.swagger.json +++ b/docs/client/em/market/v1/query.swagger.json @@ -147,6 +147,11 @@ "created": { "type": "string", "format": "date-time" + }, + "orig_order_created": { + "type": "string", + "format": "date-time", + "description": "applicable to CancelReplace orders: the creation timestamp of the original\norder." } } }, diff --git a/docs/proto/em/proto-docs.md b/docs/proto/em/proto-docs.md index e63415a4..0c4afc47 100644 --- a/docs/proto/em/proto-docs.md +++ b/docs/proto/em/proto-docs.md @@ -120,6 +120,7 @@ - [MsgCancelReplaceLimitOrderResponse](#em.market.v1.MsgCancelReplaceLimitOrderResponse) - [MsgCancelReplaceMarketOrder](#em.market.v1.MsgCancelReplaceMarketOrder) - [MsgCancelReplaceMarketOrderResponse](#em.market.v1.MsgCancelReplaceMarketOrderResponse) + - [TxParams](#em.market.v1.TxParams) - [Msg](#em.market.v1.Msg) @@ -1424,6 +1425,23 @@ + + + +### TxParams + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `trx_fee` | [uint64](#uint64) | | default fee for a market transaction. | +| `liquid_trx_fee` | [uint64](#uint64) | | Valid liquidity adding orders are free or adjusted to a minimum nominal/fee | +| `liquidity_rebate_minutes_span` | [int64](#int64) | | Minutes interval for eligible replacing transactions to receive a rebate. For a rebate to apply, the replacing transaction should occur these minutes after the signer's original trx. | + + + + + diff --git a/proto/em/market/v1/tx.proto b/proto/em/market/v1/tx.proto index 0af5c221..4c75dcdd 100644 --- a/proto/em/market/v1/tx.proto +++ b/proto/em/market/v1/tx.proto @@ -20,6 +20,21 @@ service Msg { returns (MsgCancelReplaceMarketOrderResponse); } +message TxParams { + // default fee for a market transaction. + uint64 trx_fee = 1 [(gogoproto.moretags) = "yaml:\"trx_fee\""]; + + // Valid liquidity adding orders are free or adjusted to a minimum nominal/fee + uint64 liquid_trx_fee = 2 [(gogoproto.moretags) = "yaml:\"liquid_trx_fee\""]; + + // Minutes interval for eligible replacing transactions to receive a rebate. + // For a rebate to apply, the replacing transaction should occur these minutes + // after the signer's original trx. + int64 liquidity_rebate_minutes_span = 3 [ + (gogoproto.moretags) = "yaml:\"liquidity_rebate_minutes_span\"" + ]; +} + message MsgAddLimitOrder { string owner = 1 [ (gogoproto.moretags) = "yaml:\"owner\"" ]; diff --git a/x/buyback/abci_test.go b/x/buyback/abci_test.go index d98bec65..ed286b98 100644 --- a/x/buyback/abci_test.go +++ b/x/buyback/abci_test.go @@ -13,6 +13,7 @@ import ( bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" embank "github.com/e-money/em-ledger/hooks/bank" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "strings" @@ -208,7 +209,13 @@ func generateMarketActivity(ctx sdk.Context, marketKeeper *market.Keeper, ak ban func order(account authtypes.AccountI, src, dst string) types.Order { s, _ := sdk.ParseCoinNormalized(src) d, _ := sdk.ParseCoinNormalized(dst) - o, err := types.NewOrder(time.Now(), types.TimeInForce_GoodTillCancel, s, d, account.GetAddress(), tmrand.Str(10)) + o, err := types.NewOrder( + time.Now(), + types.TimeInForce_GoodTillCancel, + s, d, + account.GetAddress(), + tmrand.Str(10), + ) if err != nil { panic(err) } @@ -230,7 +237,7 @@ func createTestComponents(t *testing.T) (sdk.Context, keeper.Keeper, *market.Kee encConfig := MakeTestEncodingConfig() var ( - keyMarket = sdk.NewKVStoreKey(types.ModuleName) + keyMarket = sdk.NewKVStoreKey(types.StoreKey) keyIndices = sdk.NewKVStoreKey(types.StoreKeyIdx) authCapKey = sdk.NewKVStoreKey("authCapKey") keyParams = sdk.NewKVStoreKey("params") @@ -238,7 +245,7 @@ func createTestComponents(t *testing.T) (sdk.Context, keeper.Keeper, *market.Kee buybackKey = sdk.NewKVStoreKey("buyback") bankKey = sdk.NewKVStoreKey(banktypes.ModuleName) - tkeyParams = sdk.NewTransientStoreKey("transient_params") + tkeyParams = sdk.NewTransientStoreKey(paramstypes.TStoreKey) blockedAddr = make(map[string]bool) maccPerms = map[string][]string{ @@ -248,12 +255,14 @@ func createTestComponents(t *testing.T) (sdk.Context, keeper.Keeper, *market.Kee db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(authCapKey, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(stakingKey, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyMarket, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyIndices, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(buybackKey, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(bankKey, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) err := ms.LoadLatestVersion() require.Nil(t, err) @@ -263,15 +272,19 @@ func createTestComponents(t *testing.T) (sdk.Context, keeper.Keeper, *market.Kee pk = paramskeeper.NewKeeper(encConfig.Marshaler, encConfig.Amino, keyParams, tkeyParams) ak = authkeeper.NewAccountKeeper( encConfig.Marshaler, authCapKey, pk.Subspace(authtypes.ModuleName), authtypes.ProtoBaseAccount, maccPerms, - ) - + ) bk = embank.Wrap(bankkeeper.NewBaseKeeper(encConfig.Marshaler, bankKey, ak, pk.Subspace(banktypes.ModuleName), blockedAddr)) + + marketKeeper = market.NewKeeper( + encConfig.Marshaler, keyMarket, keyIndices, ak, bk, + pk.Subspace(market.ModuleName), + ) ) initialSupply := coins(fmt.Sprintf("1000000eur,1000000usd,1000000chf,1000000jpy,1000000gbp,1000000%v,500000000pesos", stakingDenom)) bk.SetSupply(ctx, banktypes.NewSupply(initialSupply)) - marketKeeper := market.NewKeeper(encConfig.Marshaler, keyMarket, keyIndices, ak, bk) + marketKeeper.InitParamsStore(ctx) keeper := NewKeeper(encConfig.Marshaler, buybackKey, marketKeeper, ak, mockStakingKeeper{}, bk) keeper.SetUpdateInterval(ctx, time.Hour) diff --git a/x/market/keeper/grpc_query_test.go b/x/market/keeper/grpc_query_test.go index 7789aa6e..30d6eed1 100644 --- a/x/market/keeper/grpc_query_test.go +++ b/x/market/keeper/grpc_query_test.go @@ -25,7 +25,7 @@ func TestQueryByAccount(t *testing.T) { types.TimeInForce_GoodTillCancel, sdk.NewCoin("alx", sdk.OneInt()), sdk.NewCoin("blx", sdk.OneInt()), - myAddress, "myOrderID", + myAddress,"myOrderID", ) require.NoError(t, err) k.setOrder(ctx, &o) diff --git a/x/market/keeper/keeper.go b/x/market/keeper/keeper.go index 4ffac4df..0fb77fad 100644 --- a/x/market/keeper/keeper.go +++ b/x/market/keeper/keeper.go @@ -7,22 +7,15 @@ package keeper import ( "fmt" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "math" "sync" "time" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/e-money/em-ledger/x/market/types" - "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" -) - -const ( - // Gas prices must be predictable, and not depend on the number of passive orders matched. - gasPriceNewOrder = uint64(25000) - gasPriceCancelReplaceOrder = uint64(25000) - gasPriceCancelOrder = uint64(12500) + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/e-money/em-ledger/x/market/types" ) var _ marketKeeper = &Keeper{} @@ -35,17 +28,32 @@ type Keeper struct { ak types.AccountKeeper bk types.BankKeeper + paramStore paramtypes.Subspace + // accountOrders types.Orders appstateInit *sync.Once } -func NewKeeper(cdc codec.BinaryMarshaler, key sdk.StoreKey, keyIndices sdk.StoreKey, authKeeper types.AccountKeeper, bankKeeper types.BankKeeper) *Keeper { +func NewKeeper( + cdc codec.BinaryMarshaler, + key sdk.StoreKey, + keyIndices sdk.StoreKey, + authKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + paramStore paramtypes.Subspace, +) *Keeper { + // set params map + if !paramStore.HasKeyTable() { + paramStore = paramStore.WithKeyTable(types.ParamKeyTable()) + } + k := &Keeper{ cdc: cdc, key: key, keyIndices: keyIndices, ak: authKeeper, bk: bankKeeper, + paramStore: paramStore, appstateInit: new(sync.Once), } @@ -126,7 +134,7 @@ func (k *Keeper) GetSrcFromSlippage( } // If the order allows for slippage, adjust the source amount accordingly. - md := k.GetInstrument(ctx, srcDenom, dst.Denom) + md := k.GetInstrument(ctx.WithGasMeter(sdk.NewInfiniteGasMeter()), srcDenom, dst.Denom) if md == nil || md.LastPrice == nil { return sdk.Coin{}, sdkerrors.Wrapf( types.ErrNoMarketDataAvailable, "%v/%v", srcDenom, dst.Denom, @@ -140,22 +148,85 @@ func (k *Keeper) GetSrcFromSlippage( return slippageSource, nil } -func (k *Keeper) NewOrderSingle(ctx sdk.Context, aggressiveOrder types.Order) (*sdk.Result, error) { - // Use a fixed gas amount - ctx.GasMeter().ConsumeGas(gasPriceNewOrder, "NewOrderSingle") - ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) +// calcOrderGas computes the order gas by applying any liquidity rebate. The +// liquid fee i.e. 0 gas applies to liquid unfilled orders. For Orders filled +// partially the difference between the full fee minus (-) liquid fee is applied +// proportionally. +func (k *Keeper) calcOrderGas( + ctx sdk.Context, stdTrxFee sdk.Gas, dstFilled, dstAmount sdk.Int, +) sdk.Gas { + var liquidTrxFee sdk.Gas + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liquidTrxFee) + + if dstFilled.IsZero() { + // 0% fill -> 100% totalRebate + return liquidTrxFee + } + + dstRemaining := dstAmount.Sub(dstFilled) + if dstRemaining.IsZero() { + // 100% fill -> no rebate + return stdTrxFee + } + // totalRebate amount = Std fee - Liquid fee + totalRebate := sdk.NewIntFromUint64(stdTrxFee). + Sub(sdk.NewIntFromUint64(liquidTrxFee)) + + payPct := dstFilled.ToDec().Quo(dstAmount.ToDec()) + payGas := payPct.Mul(totalRebate.ToDec()).RoundInt().Uint64() + + return payGas +} + +// calcReplaceOrderGas computes gas for replacement orders. Because it needs +// the original order's creation time, this func acts is a precondition for +// calcOrderGas(). +func (k *Keeper) calcReplaceOrderGas( + ctx sdk.Context, dstFilled, dstAmount sdk.Int, origOrderTm time.Time, +) sdk.Gas { + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + var qualificationMin int64 + k.paramStore.Get(ctx, types.KeyLiquidityRebateMinutesSpan, &qualificationMin) + + elapsedMin := ctx.BlockTime().Sub(origOrderTm).Minutes() + if elapsedMin >= float64(qualificationMin) { + return k.calcOrderGas(ctx, stdTrxFee, dstFilled, dstAmount) + } + + return stdTrxFee +} + +// postNewOrderSingle is a deferred function for NewOrderSingle. It spends gas +// on any order and adds non fill-or-kill orders to the order-book. +func (k *Keeper) postNewOrderSingle( + ctx sdk.Context, orderGasMeter sdk.GasMeter, order *types.Order, + commitTrade func(), killOrder *bool, callerErr *error, +) { + k.OrderSpendGas(ctx, order, time.Time{}, orderGasMeter, callerErr) + + // Roll back any state changes made by the aggressive FillOrKill order. + if *killOrder { + // order has not been filled + return + } + + commitTrade() +} + +func (k *Keeper) NewOrderSingle(ctx sdk.Context, aggressiveOrder types.Order) (res *sdk.Result, err error) { // Set this to true to roll back any state changes made by the aggressive order. Used for FillOrKill orders. KillOrder := false ctx, commitTrade := ctx.CacheContext() - defer func() { - if KillOrder { - return - } + // the transactor's meter + orderGasMeter := ctx.GasMeter() + // impostor meter that would not panic + ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) - commitTrade() - }() + defer k.postNewOrderSingle(ctx, orderGasMeter, &aggressiveOrder, commitTrade, &KillOrder, &err) if err := aggressiveOrder.IsValid(); err != nil { return nil, err @@ -343,6 +414,9 @@ func (k *Keeper) NewOrderSingle(ctx sdk.Context, aggressiveOrder types.Order) (* func (k *Keeper) initializeFromStore(ctx sdk.Context) { k.appstateInit.Do(func() { + // TODO should we move the market params to genesis or const them? + k.InitParamsStore(ctx) + // TODO Reinstate this when the mem store arrives in v0.40 of the Cosmos SDK. //// Load the last known market state from app state. //store := ctx.KVStore(k.key) @@ -374,13 +448,78 @@ func (k Keeper) assetExists(ctx sdk.Context, asset sdk.Coin) bool { return total.AmountOf(asset.Denom).GT(sdk.ZeroInt()) } -func (k *Keeper) CancelReplaceLimitOrder(ctx sdk.Context, newOrder types.Order, origClientOrderId string) (*sdk.Result, error) { - // Use a fixed gas amount - ctx.GasMeter().ConsumeGas(gasPriceCancelReplaceOrder, "CancelReplaceOrder") +// OrderSpendGas is a deferred function from Order processing +// NewSingleOrder, NewCancelReplace functions to charge Gas. +func (k *Keeper) OrderSpendGas( + ctx sdk.Context, order *types.Order, origOrderCreated time.Time, + orderGasMeter sdk.GasMeter, callerErr *error, +) { + var stdTrxFee = k.GetTrxFee(ctx) + // Order failed + if *callerErr != nil || order == nil || order.IsValid() != nil { + orderGasMeter.ConsumeGas( + stdTrxFee, + fmt.Sprintf("cannot cover the erred/cancelled order %d gas", stdTrxFee), + ) + return + } + + // Non-liquidity adding order + if order.TimeInForce != types.TimeInForce_GoodTillCancel { + orderGasMeter.ConsumeGas( + stdTrxFee, "FOK, IOC orders cost the full gas", + ) + + return + } + + if order.IsFilled() { + orderGasMeter.ConsumeGas( + stdTrxFee, + fmt.Sprintf("cannot cover the standand order %d gas", stdTrxFee), + ) + return + } + // Rebate candidate + var orderGas sdk.Gas + + var gasMsg string + if origOrderCreated.IsZero() { + orderGas = k.calcOrderGas( + ctx, stdTrxFee, order.DestinationFilled, + order.Destination.Amount, + ) + gasMsg = fmt.Sprintf("aggressive order gas:%d", orderGas) + } else { + orderGas = k.calcReplaceOrderGas( + ctx, order.DestinationFilled, order.Destination.Amount, + origOrderCreated, + ) + gasMsg = fmt.Sprintf("replacing order gas:%d", orderGas) + } + + orderGasMeter.ConsumeGas( + orderGas, gasMsg, + ) +} + +func (k *Keeper) CancelReplaceLimitOrder( + ctx sdk.Context, newOrder types.Order, origClientOrderId string, +) (res *sdk.Result, err error) { + orderGasMeter := ctx.GasMeter() ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + // failed decoding of the original order results in panic origOrder := k.GetOrderByOwnerAndClientOrderId(ctx, newOrder.Owner, origClientOrderId) + // origOrder could be nil + origOrderCreated := time.Time{} + if origOrder != nil { + origOrderCreated = origOrder.Created + } + + defer k.OrderSpendGas(ctx, &newOrder, origOrderCreated, orderGasMeter, &err) + if origOrder == nil { return nil, sdkerrors.Wrap(types.ErrClientOrderIdNotFound, origClientOrderId) } @@ -410,6 +549,7 @@ func (k *Keeper) CancelReplaceLimitOrder(ctx sdk.Context, newOrder types.Order, newOrder.TimeInForce = origOrder.TimeInForce + // pass in the meter we will charge Gas. resAdd, err := k.NewOrderSingle(ctx, newOrder) if err != nil { return nil, err @@ -439,10 +579,15 @@ func (k *Keeper) GetOrderByOwnerAndClientOrderId(ctx sdk.Context, owner, clientO } func (k *Keeper) CancelOrder(ctx sdk.Context, owner sdk.AccAddress, clientOrderId string) (*sdk.Result, error) { - // Use a fixed gas amount - ctx.GasMeter().ConsumeGas(gasPriceCancelOrder, "CancelOrder") + cancelGasMeter := ctx.GasMeter() ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + // Use a fixed gas amount + cancelGasMeter.ConsumeGas(stdTrxFee, "CancelOrder") + // orders := k.accountOrders.GetAllOrders(owner) order := k.GetOrderByOwnerAndClientOrderId(ctx, owner.String(), clientOrderId) diff --git a/x/market/keeper/keeper_test.go b/x/market/keeper/keeper_test.go index a5eecbfe..3edac0ef 100644 --- a/x/market/keeper/keeper_test.go +++ b/x/market/keeper/keeper_test.go @@ -26,6 +26,7 @@ import ( bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" + paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" embank "github.com/e-money/em-ledger/hooks/bank" emtypes "github.com/e-money/em-ledger/types" "github.com/e-money/em-ledger/x/market/types" @@ -45,6 +46,154 @@ func init() { emtypes.ConfigureSDK() } + +func TestKeeper_calcOrderGas(t *testing.T) { + ctx, k, _, _ := createTestComponents(t) + + var ( + stdTrxFee sdk.Gas = k.GetTrxFee(ctx) + liquidTrxFee sdk.Gas = k.GetLiquidTrxFee(ctx) + ) + tests := []struct { + name string + ctx sdk.Context + stdTrxFee sdk.Gas + liquidTrxFee sdk.Gas + dstFilled sdk.Int + dstAmount sdk.Int + want sdk.Gas + }{ + { + name: "0% Filled Full Gas", + ctx: ctx, + stdTrxFee: stdTrxFee, + dstFilled: sdk.NewInt(0), + dstAmount: sdk.NewInt(0), + want: liquidTrxFee, + }, + { + name: "100% Filled Full Gas", + ctx: ctx, + stdTrxFee: stdTrxFee, + dstFilled: sdk.NewInt(1), + dstAmount: sdk.NewInt(1), + want: stdTrxFee, + }, + { + name: "10% Filled Full Gas", + ctx: ctx, + stdTrxFee: stdTrxFee, + dstFilled: sdk.NewInt(10), + dstAmount: sdk.NewInt(100), + want: stdTrxFee/10, + }, + { + name: "90% Filled Full Gas", + ctx: ctx, + stdTrxFee: stdTrxFee, + dstFilled: sdk.NewInt(90), + dstAmount: sdk.NewInt(100), + want: sdk.Gas(float64(stdTrxFee) * 0.9), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := k.calcOrderGas(tt.ctx, tt.stdTrxFee, tt.dstFilled, + tt.dstAmount); got != tt.want { + t.Errorf("calcOrderGas() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestKeeper_calcReplaceOrderGas(t *testing.T) { + ctx, k, _, _ := createTestComponents(t) + + ctx = ctx.WithBlockTime(time.Now()) + + var ( + stdTrxFee sdk.Gas = k.GetTrxFee(ctx) + liquidTrxFee sdk.Gas = k.GetLiquidTrxFee(ctx) + // set ctx to rebate allowance + 1 + liquidIntervalMin = time.Duration(k.GetLiquidityRebateMinutesSpan(ctx)+1) * + time.Minute + ) + + ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + + tests := []struct { + name string + ctx sdk.Context + stdTrxFee sdk.Gas + liquidTrxFee sdk.Gas + dstFilled sdk.Int + dstAmount sdk.Int + origOrderCreated time.Time + want sdk.Gas + }{ + { + name: "0% Filled Full Gas", + ctx: ctx.WithBlockTime(ctx.BlockTime().Add(liquidIntervalMin)), + stdTrxFee: stdTrxFee, + dstFilled: sdk.NewInt(0), + dstAmount: sdk.NewInt(0), + origOrderCreated: time.Now(), + want: liquidTrxFee, + }, + { + name: "0% Filled but not meeting qualified minutes, Full Gas", + ctx: ctx, + stdTrxFee: stdTrxFee, + dstFilled: sdk.NewInt(0), + dstAmount: sdk.NewInt(0), + origOrderCreated: time.Now(), + want: stdTrxFee, + }, + { + name: "100% Filled Full Gas", + ctx: ctx.WithBlockTime(ctx.BlockTime().Add(liquidIntervalMin)), + stdTrxFee: stdTrxFee, + dstFilled: sdk.NewInt(1), + dstAmount: sdk.NewInt(1), + origOrderCreated: time.Now(), + want: stdTrxFee, + }, + { + name: "10% Filled Full Gas", + ctx: ctx.WithBlockTime(ctx.BlockTime().Add(liquidIntervalMin)), + stdTrxFee: stdTrxFee, + dstFilled: sdk.NewInt(10), + dstAmount: sdk.NewInt(100), + origOrderCreated: time.Now(), + want: stdTrxFee / 10, + }, + { + name: "90% Filled Full Gas", + ctx: ctx.WithBlockTime(ctx.BlockTime().Add(liquidIntervalMin)), + stdTrxFee: stdTrxFee, + dstFilled: sdk.NewInt(90), + dstAmount: sdk.NewInt(100), + origOrderCreated: time.Now(), + want: sdk.Gas(float64(stdTrxFee) * 0.9), + }, + } + + for _, tt := range tests { + t.Run( + tt.name, func(t *testing.T) { + if got := k.calcReplaceOrderGas( + tt.ctx, tt.dstFilled, tt.dstAmount, tt.origOrderCreated, + ); got != tt.want { + t.Errorf( + "calcReplaceOrderGas() = %v, want %v", got, tt.want, + ) + } + }, + ) + } +} + func TestBasicTrade(t *testing.T) { ctx, k, ak, bk := createTestComponents(t) @@ -53,12 +202,18 @@ func TestBasicTrade(t *testing.T) { totalSupply := snapshotAccounts(ctx, bk) + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + var liquidTrxFee uint64 + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liquidTrxFee) + gasmeter := sdk.NewGasMeter(math.MaxUint64) src1, dst1 := "eur", "usd" order1 := order(ctx.BlockTime(), acc1, "100"+src1, "120"+dst1) _, err := k.NewOrderSingle(ctx.WithGasMeter(gasmeter), order1) require.NoError(t, err) - require.Equal(t, gasPriceNewOrder, gasmeter.GasConsumed()) + require.Equal(t, liquidTrxFee, gasmeter.GasConsumed(), "Liquid non-filled") require.Equal(t, ctx.BlockTime(), order1.Created) // Ensure that the instrument was registered @@ -83,7 +238,7 @@ func TestBasicTrade(t *testing.T) { require.Equal(t, *instruments[0].Timestamp, ctx.BlockTime()) // Ensure that gas usage is not higher due to the order being matched. - require.Equal(t, gasPriceNewOrder, gasmeter.GasConsumed()) + require.Equal(t, stdTrxFee, gasmeter.GasConsumed()) bal1 := bk.GetAllBalances(ctx, acc1.GetAddress()) bal2 := bk.GetAllBalances(ctx, acc2.GetAddress()) @@ -269,13 +424,24 @@ func TestCancelReplaceMarketOrderZeroSlippage(t *testing.T) { ) require.NoError(t, err) order, err := types.NewOrder( - ctx.BlockTime(), types.TimeInForce_GoodTillCancel, slippageSource, dest, acc1.GetAddress(), + ctx.BlockTime(), + types.TimeInForce_GoodTillCancel, + slippageSource, + dest, + acc1.GetAddress(), newClientID, ) require.NoError(t, err) + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + gasMeter := sdk.NewInfiniteGasMeter() + ctx = ctx.WithGasMeter(gasMeter) + _, err = k.CancelReplaceLimitOrder(ctx, order, clientID) require.NoError(t, err) + require.Equal(t, stdTrxFee, ctx.GasMeter().GasConsumed()) expOrder := &types.Order{ ID: 3, @@ -316,14 +482,32 @@ func TestCancelReplaceMarketOrder100Slippage(t *testing.T) { var o types.Order var err error + var liquidTrxFee uint64 + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liquidTrxFee) + + gasMeter := sdk.NewInfiniteGasMeter() + ctx = ctx.WithGasMeter(gasMeter) + // Establish market price by executing a 2:1 trade o = order(ctx.BlockTime(), acc2, "20eur", "10gbp") _, err = k.NewOrderSingle(ctx, o) require.NoError(t, err) + require.Equal(t, liquidTrxFee, ctx.GasMeter().GasConsumed(), "Liquid Trx are discounted") + + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + gasMeter = sdk.NewInfiniteGasMeter() + ctx = ctx.WithGasMeter(gasMeter) o = order(ctx.BlockTime(), acc1, "10gbp", "20eur") _, err = k.NewOrderSingle(ctx, o) require.NoError(t, err) + require.Equal( + t, stdTrxFee, ctx.GasMeter().GasConsumed(), + "Filled order costs std fee", + ) + acc1b := bk.GetAllBalances(ctx, acc1.GetAddress()) require.Equal(t, coins("20eur,90gbp").String(), acc1b.String()) @@ -341,8 +525,6 @@ func TestCancelReplaceMarketOrder100Slippage(t *testing.T) { clientID := limitOrder.ClientOrderID - acc1b = bk.GetAllBalances(ctx, acc1.GetAddress()) - // Gave 1 gbp and gained a eur acc1Bal := bk.GetAllBalances(ctx, acc1.GetAddress()) require.Equal(t, coins("20eur,90gbp").String(), acc1Bal.String()) @@ -382,8 +564,22 @@ func TestCancelReplaceMarketOrder100Slippage(t *testing.T) { ) require.NoError(t, err) - _, err = k.CancelReplaceLimitOrder(ctx, newOrder, clientID) + var qualificationMin int64 + k.paramStore.Get(ctx, types.KeyLiquidityRebateMinutesSpan, &qualificationMin) + + gasMeter = sdk.NewInfiniteGasMeter() + ctx = ctx.WithGasMeter(gasMeter) + + _, err = k.CancelReplaceLimitOrder(ctx.WithGasMeter(gasMeter).WithBlockTime( + ctx.BlockTime(). + Add(time.Duration(qualificationMin)*time.Minute), + ), newOrder, clientID) require.NoError(t, err) + require.Equal( + t, liquidTrxFee, ctx.GasMeter().GasConsumed(), + "Unfilled/liquid are discounted", + ) + expOrder := &types.Order{ ID: 3, TimeInForce: types.TimeInForce_GoodTillCancel, @@ -429,14 +625,14 @@ func TestGetSrcFromSlippage(t *testing.T) { srcDenom = "jpy" dest = sdk.NewCoin("eur", sdk.NewInt(100)) - slippedSource, err = k.GetSrcFromSlippage( + _, err = k.GetSrcFromSlippage( ctx, srcDenom, dest, sdk.ZeroDec(), ) require.Error(t, err, "No trades yet with jpy") srcDenom = "gbp" dest = sdk.NewCoin("dek", sdk.NewInt(100)) - slippedSource, err = k.GetSrcFromSlippage( + _, err = k.GetSrcFromSlippage( ctx, srcDenom, dest, sdk.ZeroDec(), ) require.Error(t, err, "No trades yet with dek") @@ -445,7 +641,7 @@ func TestGetSrcFromSlippage(t *testing.T) { srcDenom = "gbp" dest = sdk.NewCoin("eur", sdk.NewInt(100)) - slippedSource, err = k.GetSrcFromSlippage( + _, err = k.GetSrcFromSlippage( ctx, srcDenom, dest, slippage, ) require.Error(t, err) @@ -538,6 +734,9 @@ func TestFillOrKillMarketOrder1(t *testing.T) { string(res.Events[0].Attributes[len(res.Events[0].Attributes)-1].GetValue()), ) + stdTrxFee := k.GetTrxFee(ctx) + gasMeter := sdk.NewInfiniteGasMeter() + // Create a fill or kill order that cannot be satisfied by the current market srcDenom := "gbp" dest := sdk.NewCoin("eur", sdk.NewInt(200)) @@ -547,12 +746,14 @@ func TestFillOrKillMarketOrder1(t *testing.T) { require.NoError(t, err) limitOrder := order(ctx.BlockTime(), acc1, slippageSource.String(), dest.String()) limitOrder.TimeInForce = types.TimeInForce_FillOrKill - result, err := k.NewOrderSingle(ctx, limitOrder) + result, err := k.NewOrderSingle(ctx.WithGasMeter(gasMeter), limitOrder) require.NoError(t, err) require.Len(t, result.Events, 1) require.Equal(t, types.EventTypeMarket, result.Events[0].Type) require.Equal(t, "action", string(result.Events[0].Attributes[0].GetKey())) require.Equal(t, "expire", string(result.Events[0].Attributes[0].GetValue())) + require.Equal(t, gasMeter.GasConsumed(), stdTrxFee, + "Partially filled FillOrKill costs like a cancelled order") // Last order must fail completely due to not being fillable acc1Bal := bk.GetAllBalances(ctx, acc1.GetAddress()) @@ -573,10 +774,15 @@ func TestFillOrKillLimitOrder1(t *testing.T) { _, err := k.NewOrderSingle(ctx, o) require.NoError(t, err) + stdTrxFee := k.GetTrxFee(ctx) + gasMeter := sdk.NewInfiniteGasMeter() + order2 := order(ctx.BlockTime(), acc1, "200gbp", "200eur") order2.TimeInForce = types.TimeInForce_FillOrKill - _, err = k.NewOrderSingle(ctx, order2) + _, err = k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order2) require.NoError(t, err) + require.Equal(t, gasMeter.GasConsumed(), stdTrxFee, + "Partially filled FillOrKill costs like a cancelled order") // Order must fail completely due to not being fillable acc1Bal := bk.GetAllBalances(ctx, acc1.GetAddress()) @@ -592,32 +798,91 @@ func TestFillOrKillLimitOrder1(t *testing.T) { require.Equal(t, acc2Orders[0].Created, ctx.BlockTime()) } +func TestFillOrKillLimitOrder2Filled(t *testing.T) { + ctx, k, ak, bk := createTestComponents(t) + + acc1 := createAccount(ctx, ak, bk, randomAddress(), "500gbp") + acc2 := createAccount(ctx, ak, bk, randomAddress(), "500eur") + + // Create a tiny market for eur + o := order(ctx.BlockTime(), acc2, "100eur", "100gbp") + _, err := k.NewOrderSingle(ctx, o) + require.NoError(t, err) + + stdTrxFee := k.GetTrxFee(ctx) + gasMeter := sdk.NewInfiniteGasMeter() + + // A successful FOK Order + order2 := order(ctx.BlockTime(), acc1, "100gbp", "100eur") + order2.TimeInForce = types.TimeInForce_FillOrKill + _, err = k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order2) + require.NoError(t, err) + require.Equal(t, gasMeter.GasConsumed(), stdTrxFee, + "Filled order") + + acc1Bal := bk.GetAllBalances(ctx, acc1.GetAddress()) + require.Equal(t, coins("400gbp,100eur"), acc1Bal) + + acc2Bal := bk.GetAllBalances(ctx, acc2.GetAddress()) + require.Equal(t, coins("400eur,100gbp"), acc2Bal) + + // Test that the order book looks as expected + require.Empty(t, k.GetOrdersByOwner(ctx, acc1.GetAddress())) + require.Empty(t, k.GetOrdersByOwner(ctx, acc2.GetAddress())) +} + func TestImmediateOrCancel(t *testing.T) { ctx, k, ak, bk := createTestComponents(t) acc1 := createAccount(ctx, ak, bk, randomAddress(), "20gbp") acc2 := createAccount(ctx, ak, bk, randomAddress(), "20eur") - var o types.Order var err error - o = order(ctx.BlockTime(), acc2, "1eur", "1gbp") - _, err = k.NewOrderSingle(ctx, o) + o1 := order(ctx.BlockTime(), acc2, "1eur", "1gbp") + _, err = k.NewOrderSingle(ctx, o1) require.NoError(t, err) - o = order(ctx.BlockTime(), acc1, "2gbp", "2eur") - o.TimeInForce = types.TimeInForce_ImmediateOrCancel - cid := o.ClientOrderID - _, err = k.NewOrderSingle(ctx, o) + stdTrxFee := k.GetTrxFee(ctx) + gasMeter := sdk.NewInfiniteGasMeter() + + // IOK partially Filled + o2 := order(ctx.BlockTime(), acc1, "2gbp", "2eur") + o2.TimeInForce = types.TimeInForce_ImmediateOrCancel + cid2 := o2.ClientOrderID + _, err = k.NewOrderSingle(ctx.WithGasMeter(gasMeter), o2) require.NoError(t, err) - require.Equal(t, o.Created, ctx.BlockTime()) + require.Equal(t, o2.Created, ctx.BlockTime()) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) // Verify that order is not in book - order := k.GetOrderByOwnerAndClientOrderId(ctx, acc1.GetAddress().String(), cid) + order := k.GetOrderByOwnerAndClientOrderId(ctx, acc1.GetAddress().String(), cid2) require.Nil(t, order) bal1 := bk.GetAllBalances(ctx, acc1.GetAddress()) require.Equal(t, coins("19gbp,1eur"), bal1) + + // again o1 + _, err = k.NewOrderSingle(ctx, o1) + require.NoError(t, err) + + // IOK Filled + gasMeter = sdk.NewInfiniteGasMeter() + o2.Source = sdk.NewCoin("gbp", sdk.NewInt(1)) + o2.Destination = sdk.NewCoin("eur", sdk.NewInt(1)) + o2.TimeInForce = types.TimeInForce_ImmediateOrCancel + o2.ClientOrderID = cid() + _, err = k.NewOrderSingle(ctx.WithGasMeter(gasMeter), o2) + require.NoError(t, err) + require.Equal(t, o2.Created, ctx.BlockTime()) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) + + // Verify that order is not in book + order = k.GetOrderByOwnerAndClientOrderId(ctx, acc1.GetAddress().String(), o2.ClientOrderID) + require.Nil(t, order) + + bal2 := bk.GetAllBalances(ctx, acc1.GetAddress()) + require.Equal(t, coins("18gbp,2eur"), bal2) } func TestInsufficientGas(t *testing.T) { @@ -626,11 +891,685 @@ func TestInsufficientGas(t *testing.T) { acc1 := createAccount(ctx, ak, bk, randomAddress(), "888eur") order1 := order(ctx.BlockTime(), acc1, "888eur", "1121usd") - gasMeter := sdk.NewGasMeter(gasPriceNewOrder - 5000) + // Free: it adds liquidity + _, err := k.NewOrderSingle(ctx, order1) + require.NoError(t, err) + + acc2 := createAccount(ctx, ak, bk, randomAddress(), "1121usd") + order2 := order(ctx.BlockTime(), acc2, "1121usd", "888eur") + + var fullTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &fullTrxFee) + + // bummer missing 1 micro + gasMeter := sdk.NewGasMeter(fullTrxFee - 1) + + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + require.Panics(t, func() { + // Taking away liquidity not enough to cover the full Trx fee + _, _ = k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order2) + }) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) +} + +func TestLimitOrderLiquidAndFullGas(t *testing.T) { + ctx, k, ak, bk := createTestComponents(t) + + acc1 := createAccount(ctx, ak, bk, randomAddress(), "5000gbp") + acc2 := createAccount(ctx, ak, bk, randomAddress(), "5000eur") + + gasMeter := sdk.NewInfiniteGasMeter() + + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + var liquidTrxFee uint64 + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liquidTrxFee) + + order1 := order(ctx.BlockTime(), acc1, "1gbp", "1eur") + _, err := k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order1) + require.NoError(t, err) + require.Equal(t, liquidTrxFee, gasMeter.GasConsumed()) + + gasMeter = sdk.NewInfiniteGasMeter() + order2 := order(ctx.BlockTime(), acc2, "1eur", "1gbp") + _, err = k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order2) + require.NoError(t, err) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) +} + +func TestFilledLimitOrderGas(t *testing.T) { + ctx, k, ak, bk := createTestComponents(t) + + acc1 := createAccount(ctx, ak, bk, randomAddress(), "5000gbp") + acc2 := createAccount(ctx, ak, bk, randomAddress(), "5000eur") + + gasMeter := sdk.NewInfiniteGasMeter() + + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + var liquidTrxFee uint64 + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liquidTrxFee) + + order1 := order(ctx.BlockTime(), acc1, "1gbp", "1eur") + _, err := k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order1) + require.NoError(t, err) + require.Equal(t, liquidTrxFee, gasMeter.GasConsumed()) + + gasMeter = sdk.NewInfiniteGasMeter() + order2 := order(ctx.BlockTime(), acc2, "1eur", "1gbp") + _, err = k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order2) + require.NoError(t, err) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) + + // Already filled + order2.DestinationFilled = order2.Destination.Amount + gasMeter = sdk.NewInfiniteGasMeter() + _, err = k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order2) + require.Error(t, err) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) +} + +func TestSingleOrderPanicFullGas(t *testing.T) { + ctx, k, ak, bk := createTestComponents(t) + + acc1 := createAccount(ctx, ak, bk, randomAddress(), "5000gbp") + acc2 := createAccount(ctx, ak, bk, randomAddress(), "5000eur") + + gasMeter := sdk.NewInfiniteGasMeter() + + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + var liquidTrxFee uint64 + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liquidTrxFee) + + order1 := order(ctx.BlockTime(), acc1, "1gbp", "1eur") + _, err := k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order1) + require.NoError(t, err) + require.Equal(t, liquidTrxFee, gasMeter.GasConsumed()) + + gasMeter = sdk.NewInfiniteGasMeter() + order2 := order(ctx.BlockTime(), acc2, "1eur", "1gbp") + // Panic during banking interactions + k.bk = nil + require.Panics(t, func() { + k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order2) + }) + // Baseapp should charge the appropriate fee + require.Equal(t, sdk.Gas(0), gasMeter.GasConsumed()) +} + +func TestPartiallyLiquidLimitOrderGas(t *testing.T) { + ctx, k, ak, bk := createTestComponents(t) + + acc1 := createAccount(ctx, ak, bk, randomAddress(), "5000gbp") + acc2 := createAccount(ctx, ak, bk, randomAddress(), "5000eur") + + gasMeter := sdk.NewInfiniteGasMeter() + + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + var liquidTrxFee uint64 + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liquidTrxFee) + + order1 := order(ctx.BlockTime(), acc1, "1gbp", "1eur") + _, err := k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order1) + require.NoError(t, err) + require.Equal(t, liquidTrxFee, gasMeter.GasConsumed()) + + gasMeter = sdk.NewInfiniteGasMeter() + order2 := order(ctx.BlockTime(), acc2, "10eur", "10gbp") + _, err = k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order2) + require.NoError(t, err) + require.Equal( + t, sdk.Gas(float64(stdTrxFee-liquidTrxFee)*0.1), gasMeter.GasConsumed(), + "1/10 filled => 10% of total Gas", + ) + + gasMeter = sdk.NewInfiniteGasMeter() + order3 := order(ctx.BlockTime(), acc1, "36gbp", "36eur") + _, err = k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order3) + require.NoError(t, err) + require.Equal( + t, (stdTrxFee-liquidTrxFee)/4, gasMeter.GasConsumed(), + "9/36 filled => 25% of total Gas", + ) + + gasMeter = sdk.NewInfiniteGasMeter() + order4 := order(ctx.BlockTime(), acc2, "36eur", "36gbp") + _, err = k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order4) + require.NoError(t, err) + expGas := sdk.Gas(float64((stdTrxFee-liquidTrxFee)*3)/4) + require.Equal( + t, expGas, gasMeter.GasConsumed(), + "27/36 filled => 3/4 of total Gas", + ) +} + +func TestLiquidNewMarketOrderGas(t *testing.T) { + ctx, k, ak, bk := createTestComponents(t) + + acc1 := createAccount(ctx, ak, bk, randomAddress(), "5000gbp") + acc2 := createAccount(ctx, ak, bk, randomAddress(), "5000eur") + + gasMeter := sdk.NewInfiniteGasMeter() + + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + var liquidTrxFee uint64 + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liquidTrxFee) + + order1 := order(ctx.BlockTime(), acc1, "100gbp", "100eur") + _, err := k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order1) + require.NoError(t, err) + require.Equal(t, liquidTrxFee, gasMeter.GasConsumed()) + + order2 := order(ctx.BlockTime(), acc2, "100eur", "100gbp") + _, err = k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order2) + require.NoError(t, err) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) + + slippage := sdk.NewDecWithPrec(50, 2) + srcDenom := "gbp" + dest := sdk.NewCoin("eur", sdk.NewInt(200)) + slippageSource, err := k.GetSrcFromSlippage( + ctx, srcDenom, dest, slippage, + ) + require.NoError(t, err) + + gasMeter = sdk.NewInfiniteGasMeter() + ctx = ctx.WithGasMeter(gasMeter) + limitOrder := order(ctx.BlockTime(), acc1, slippageSource.String(), dest.String()) + _, err = k.NewOrderSingle(ctx, limitOrder) + require.NoError(t, err) + require.Equal(t, liquidTrxFee, ctx.GasMeter().GasConsumed()) +} + +func TestPartiallyLiquidMarketOrderGas(t *testing.T) { + ctx, k, ak, bk := createTestComponents(t) + + acc1 := createAccount(ctx, ak, bk, randomAddress(), "5000gbp") + acc2 := createAccount(ctx, ak, bk, randomAddress(), "500eur") + + gasMeter := sdk.NewGasMeter(math.MaxUint64) + + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + var liquidTrxFee uint64 + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liquidTrxFee) + + order1 := order(ctx.BlockTime(), acc1, "1gbp", "1eur") + _, err := k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order1) + require.NoError(t, err) + require.Equal(t, liquidTrxFee, gasMeter.GasConsumed()) + + order2 := order(ctx.BlockTime(), acc2, "10eur", "10gbp") + _, err = k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order2) + require.NoError(t, err) + require.Equal( + t, sdk.Gas(float64(stdTrxFee-liquidTrxFee)*0.1), gasMeter.GasConsumed(), + "1/10 filled => 10% of total Gas", + ) + + bal1Bef := bk.GetBalance(ctx, acc1.GetAddress(), "gbp") + + gasMeter = sdk.NewInfiniteGasMeter() + slippage := sdk.NewDecWithPrec(75, 2) + srcDenom := "gbp" + dest := sdk.NewCoin("eur", sdk.NewInt(36)) + slippageSource, err := k.GetSrcFromSlippage( + ctx, srcDenom, dest, slippage, + ) + require.NoError(t, err) + + ctx = ctx.WithGasMeter(gasMeter) + limitOrder := order(ctx.BlockTime(), acc1, slippageSource.String(), dest.String()) + _, err = k.NewOrderSingle(ctx, limitOrder) + require.NoError(t, err) + + bal1Aft := bk.GetBalance(ctx.WithGasMeter(sdk.NewInfiniteGasMeter()), acc1.GetAddress(), "gbp") + rebate := stdTrxFee-liquidTrxFee + filledPct := bal1Bef.Sub(bal1Aft).Amount.ToDec().Quo(sdk.NewDec(36)) + expGas := sdk.Gas( + filledPct.Mul(sdk.NewInt(int64(rebate)).ToDec()).RoundInt64(), + ) + require.Equal( + t, expGas, gasMeter.GasConsumed(), + "9/36 filled => 1/4 of total Gas", + ) +} + +func TestKeeperLimitOrderLiquidFullGas(t *testing.T) { + ctx, k, ak, bk := createTestComponents(t) + acc1 := createAccount(ctx, ak, bk, randomAddress(), "100000eur") + acc2 := createAccount(ctx, ak, bk, randomAddress(), "100000usd") + + order1cid := cid() + order1, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("100eur"), + coin("100usd"), acc1.GetAddress(), order1cid, + ) + _, err := k.NewOrderSingle(ctx, order1) + require.NoError(t, err) + + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + var liquidTrxFee uint64 + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liquidTrxFee) + + var qualificationMin int64 + k.paramStore.Get(ctx, types.KeyLiquidityRebateMinutesSpan, &qualificationMin) + + order2cid := cid() + order2, _ := types.NewOrder( + ctx.BlockTime(), + types.TimeInForce_GoodTillCancel, + coin("5000eur"), + coin("5000usd"), + acc1.GetAddress(), + order2cid, + ) + gasMeter := sdk.NewInfiniteGasMeter() + // Add + Interval Minutes + res, err := k.CancelReplaceLimitOrder( + ctx.WithGasMeter(gasMeter). + WithBlockTime(ctx.BlockTime(). + Add(time.Duration(qualificationMin)*time.Minute)), + order2, order1cid, + ) + require.True(t, err == nil, res.Log) + require.Equal( + t, liquidTrxFee, gasMeter.GasConsumed(), + "Liquid gas as unfilled replacing orders, past minute-interval", + ) + + { + orders := k.GetOrdersByOwner(ctx, acc1.GetAddress()) + require.Len(t, orders, 1) + require.Equal(t, order2cid, orders[0].ClientOrderID) + require.Equal(t, coin("5000eur").String(), orders[0].Source.String()) + require.Equal(t, coin("5000usd").String(), orders[0].Destination.String()) + // unfilled: remaining == source amount + require.Equal(t, sdk.NewInt(5000), orders[0].SourceRemaining) + } + + gasMeter = sdk.NewInfiniteGasMeter() + order3, err := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("5000chf"), + coin("5000usd"), acc1.GetAddress(), cid(), + ) + require.NoError(t, err) + require.Equal( + t, liquidTrxFee, gasMeter.GasConsumed(), + "Liquid trx changed instrument eur->chf", + ) + + // Wrong client order id for previous order submitted. + _, err = k.CancelReplaceLimitOrder(ctx.WithGasMeter(gasMeter), order3, order1cid) + require.True(t, types.ErrClientOrderIdNotFound.Is(err)) + require.Equal( + t, stdTrxFee, gasMeter.GasConsumed(), + "Erred replacing order is charged Std gas", + ) + + // Changing instrument of order + gasMeter = sdk.NewGasMeter(math.MaxUint64) + _, err = k.CancelReplaceLimitOrder(ctx.WithGasMeter(gasMeter), order3, order2cid) + require.True(t, types.ErrOrderInstrumentChanged.Is(err)) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed(), "erred trx") + + o := order(ctx.BlockTime(), acc2, "1000usd", "300eur") + _, err = k.NewOrderSingle(ctx, o) + require.NoError(t, err) + + bal1 := bk.GetAllBalances(ctx, acc1.GetAddress()) + bal2 := bk.GetAllBalances(ctx, acc2.GetAddress()) + + require.Equal(t, int64(300), bal2.AmountOf("eur").Int64()) + require.Equal(t, int64(300), bal1.AmountOf("usd").Int64()) + + filled := sdk.ZeroInt() + { + orders := k.GetOrdersByOwner(ctx, acc1.GetAddress()) + require.Len(t, orders, 1) + filled = orders[0].Source.Amount.Sub(orders[0].SourceRemaining) + } + + // CancelReplace and verify that previously filled amount is subtracted from the resulting order + order4cid := cid() + gasMeter = sdk.NewInfiniteGasMeter() + order4, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("3000eur"), + coin("3000usd"), acc1.GetAddress(), order4cid, + ) + ctx = ctx.WithGasMeter(gasMeter) + _, err = k.CancelReplaceLimitOrder( + ctx.WithBlockTime(ctx.BlockTime().Add( + time.Duration(qualificationMin)*time.Minute)), + order4, order2cid, + ) + require.NoError(t, err, err) + require.Equal( + t, gasMeter.GasConsumed(), sdk.Gas(float64(stdTrxFee-liquidTrxFee)*0.1), + "300/3000 filled => 10% of total Gas", + ) + require.Greaterf( + t, gasMeter.GasConsumed(), liquidTrxFee, + "Gas consumed should filled should be greater than LiquidTrxFee", + ) + + { + orders := k.GetOrdersByOwner(ctx, acc1.GetAddress()) + require.Len(t, orders, 1) + require.Equal(t, order4cid, orders[0].ClientOrderID) + require.Equal(t, coin("3000eur").String(), orders[0].Source.String()) + require.Equal(t, coin("3000usd").String(), orders[0].Destination.String()) + require.Equal( + t, sdk.NewInt(3000).Sub(filled).String(), + orders[0].SourceRemaining.String(), + ) + } +} +func TestLimitOrderErrGas(t *testing.T) { + ctx, k, ak, bk := createTestComponents(t) + acc1 := createAccount(ctx, ak, bk, randomAddress(), "100eur") + + order1cid := cid() + order1, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("100eur"), + coin("100usd"), acc1.GetAddress(), order1cid, + ) + _, err := k.NewOrderSingle(ctx, order1) + require.NoError(t, err) + + order2cid := cid() + order2, _ := types.NewOrder( + ctx.BlockTime(), + types.TimeInForce_GoodTillCancel, + /** 1 above balance **/ + coin("101eur"), + coin("101usd"), + acc1.GetAddress(), + order2cid, + ) + + stdTrxFee := k.GetTrxFee(ctx) + qualificationMin := k.GetLiquidityRebateMinutesSpan(ctx) + + gasMeter := sdk.NewInfiniteGasMeter() + // Add + Interval Minutes.. but will err + _, err = k.CancelReplaceLimitOrder( + ctx.WithGasMeter(gasMeter). + WithBlockTime(ctx.BlockTime(). + Add(time.Duration(qualificationMin)*time.Minute)), + order2, order1cid, + ) + require.Error(t, err, "Insufficient balance") + require.Equal( + t, stdTrxFee, gasMeter.GasConsumed(), + "Err -> Full fee", + ) +} + +func TestKeeperReplaceLimitFullLiquidGas(t *testing.T) { + ctx, k, ak, bk := createTestComponents(t) + acc1 := createAccount(ctx, ak, bk, randomAddress(), "20000eur") + acc2 := createAccount(ctx, ak, bk, randomAddress(), "45000usd") + + totalSupply := snapshotAccounts(ctx, bk) + + order1cid := cid() + order1, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("500eur"), + coin("1200usd"), acc1.GetAddress(), order1cid, + ) + _, err := k.NewOrderSingle(ctx, order1) + require.NoError(t, err) + + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + var liquidTrxFee uint64 + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liquidTrxFee) + + var qualificationMin int64 + k.paramStore.Get(ctx, types.KeyLiquidityRebateMinutesSpan, &qualificationMin) + + gasMeter := sdk.NewGasMeter(math.MaxUint64) + order2cid := cid() + order2, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("5000eur"), + coin("17000usd"), acc1.GetAddress(), order2cid, + ) + res, err := k.CancelReplaceLimitOrder(ctx.WithGasMeter(gasMeter), order2, order1cid) + require.True(t, err == nil, res.Log) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) + + { + orders := k.GetOrdersByOwner(ctx, acc1.GetAddress()) + require.Len(t, orders, 1) + require.Equal(t, order2cid, orders[0].ClientOrderID) + require.Equal(t, coin("5000eur"), orders[0].Source) + require.Equal(t, coin("17000usd"), orders[0].Destination) + require.Equal(t, sdk.NewInt(5000), orders[0].SourceRemaining) + } + + gasMeter = sdk.NewInfiniteGasMeter() + order3, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("500chf"), + coin("1700usd"), acc1.GetAddress(), cid(), + ) + // Wrong client order id for previous order submitted. + _, err = k.CancelReplaceLimitOrder(ctx.WithGasMeter(gasMeter), order3, order1cid) + require.True(t, types.ErrClientOrderIdNotFound.Is(err)) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) + + // Changing instrument of order + gasMeter = sdk.NewGasMeter(math.MaxUint64) + _, err = k.CancelReplaceLimitOrder(ctx.WithGasMeter(gasMeter), order3, order2cid) + require.True(t, types.ErrOrderInstrumentChanged.Is(err)) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) + + o := order(ctx.BlockTime(), acc2, "2600usd", "300eur") + _, err = k.NewOrderSingle(ctx, o) + require.NoError(t, err) + + bal1 := bk.GetAllBalances(ctx, acc1.GetAddress()) + bal2 := bk.GetAllBalances(ctx, acc2.GetAddress()) + + require.Equal(t, int64(300), bal2.AmountOf("eur").Int64()) + require.Equal(t, int64(1020), bal1.AmountOf("usd").Int64()) + + filled := sdk.ZeroInt() + { + orders := k.GetOrdersByOwner(ctx, acc1.GetAddress()) + require.Len(t, orders, 1) + filled = orders[0].Source.Amount.Sub(orders[0].SourceRemaining) + } + + // CancelReplace and verify that previously filled amount is subtracted from the resulting order + order4cid := cid() + order4, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("10000eur"), + coin("35050usd"), acc1.GetAddress(), order4cid, + ) + gasMeter = sdk.NewInfiniteGasMeter() + // add qualification minutes + res, err = k.CancelReplaceLimitOrder( + ctx.WithGasMeter(gasMeter). + WithBlockTime(ctx.BlockTime(). + Add(time.Duration(qualificationMin)*time.Minute)), + order4, order2cid, + ) + t.Log(gasMeter.String()) + require.True(t, err == nil, res.Log) + expGas := sdk.NewDec(int64(stdTrxFee - liquidTrxFee)). + Mul(sdk.NewDec(1020)). + Quo(sdk.NewDec(35050)). + RoundInt(). + Uint64() + require.Equal(t, expGas, gasMeter.GasConsumed(), + "Partial Fee: Full fee * destinationFilled(1020)/totalDestination(35050)") + + { + orders := k.GetOrdersByOwner(ctx, acc1.GetAddress()) + require.Len(t, orders, 1) + require.Equal(t, order4cid, orders[0].ClientOrderID) + require.Equal(t, coin("10000eur"), orders[0].Source) + require.Equal(t, coin("35050usd"), orders[0].Destination) + require.Equal(t, sdk.NewInt(10000).Sub(filled), orders[0].SourceRemaining) + } + + // CancelReplace with an order that asks for a larger source than the replaced order has remaining + order5 := order(ctx.BlockTime(), acc2, "42000usd", "8000eur") + _, err = k.NewOrderSingle(ctx, order5) + require.True(t, err == nil, res.Log) + + order6 := order(ctx.BlockTime(), acc1, "8000eur", "30000usd") + _, err = k.CancelReplaceLimitOrder(ctx, order6, order4cid) + require.True(t, types.ErrNoSourceRemaining.Is(err)) + + require.True(t, totalSupply.Sub(snapshotAccounts(ctx, bk)).IsZero()) +} +func TestReplaceMarketOrderErrGas(t *testing.T) { + ctx, k, ak, bk := createTestComponents(t) + acc1 := createAccount(ctx, ak, bk, randomAddress(), "500gbp") + acc2 := createAccount(ctx, ak, bk, randomAddress(), "500eur") + + var liquidTrxFee uint64 = k.GetLiquidTrxFee(ctx) + + var o types.Order + var err error + + // Establish market price by executing a 1:1 trade + o = order(ctx.BlockTime(), acc2, "1eur", "1gbp") + _, err = k.NewOrderSingle(ctx, o) + require.NoError(t, err) + + o = order(ctx.BlockTime(), acc1, "1gbp", "1eur") + _, err = k.NewOrderSingle(ctx, o) + require.NoError(t, err) + + // Make a market order that allows slippage + slippage := sdk.NewDecWithPrec(100, 2) + srcDenom := "gbp" + dest := sdk.NewCoin("eur", sdk.NewInt(100)) + slippageSource, err := k.GetSrcFromSlippage( + ctx, srcDenom, dest, slippage, + ) + require.NoError(t, err) + limitOrder := order(ctx.BlockTime(), acc1, slippageSource.String(), dest.String()) + _, err = k.NewOrderSingle(ctx, limitOrder) + require.NoError(t, err) + foundOrder := k.GetOrderByOwnerAndClientOrderId( + ctx, acc1.GetAddress().String(), limitOrder.ClientOrderID, + ) + require.NotNil(t, foundOrder, "Market order should exist") + // 100% slippage to double source + require.True(t, foundOrder.Source.IsEqual(sdk.NewCoin("gbp", sdk.NewInt(200)))) + + // Gave 1 gbp and gained a eur + acc1Bal := bk.GetAllBalances(ctx, acc1.GetAddress()) + require.Equal(t, coins("1eur,499gbp").String(), acc1Bal.String()) + slippage = sdk.NewDecWithPrec(0, 2) + srcDenom = "gbp" + dest = sdk.NewCoin("eur", sdk.NewInt(100)) + slippageSource, err = k.GetSrcFromSlippage( + ctx, srcDenom, dest, slippage, + ) + require.NoError(t, err) + limitOrder = order(ctx.BlockTime(), acc1, slippageSource.String(), dest.String()) + gasMeter := sdk.NewInfiniteGasMeter() + ctx = ctx.WithGasMeter(gasMeter) + // panic execution of single order + k.bk = nil require.Panics(t, func() { - k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order1) + _, _ = k.NewOrderSingle(ctx, limitOrder) }) + require.Equal( + t, liquidTrxFee, + ctx.GasMeter().GasConsumed(), "Panic gas consumption is unpredictable", + ) +} + +func TestCancelNewLimitFullGas(t *testing.T) { + ctx, k, ak, bk := createTestComponents(t) + + acc1 := createAccount(ctx, ak, bk, randomAddress(), "5000eur") + + gasMeter := sdk.NewGasMeter(math.MaxUint64) + + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + var liquidTrxFee uint64 + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liquidTrxFee) + + src1, dst1 := "eur", "usd" + order1 := order(ctx.BlockTime(), acc1, "100"+src1, "120"+dst1) + _, err := k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order1) + require.NoError(t, err) + require.Equal(t, liquidTrxFee, gasMeter.GasConsumed()) + + order := k.GetOrdersByOwner(ctx, acc1.GetAddress()) + require.Len(t, order, 1) + + cid := order[0].ClientOrderID + ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter()) + _, err = k.CancelOrder(ctx, acc1.GetAddress(), cid) + require.NoError(t, err) + require.Equal(t, stdTrxFee, ctx.GasMeter().GasConsumed()) +} + +func TestCancelNewMarketFullGas(t *testing.T) { + ctx, k, ak, bk := createTestComponents(t) + + acc1 := createAccount(ctx, ak, bk, randomAddress(), "5000gbp") + acc2 := createAccount(ctx, ak, bk, randomAddress(), "500eur") + + gasMeter := sdk.NewGasMeter(math.MaxUint64) + + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + var liquidTrxFee uint64 + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liquidTrxFee) + + order1 := order(ctx.BlockTime(), acc1, "100gbp", "120eur") + _, err := k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order1) + require.NoError(t, err) + require.Equal(t, liquidTrxFee, gasMeter.GasConsumed(), "Liquid trx") + + order2 := order(ctx.BlockTime(), acc2, "120eur", "100gbp") + _, err = k.NewOrderSingle(ctx.WithGasMeter(gasMeter), order2) + require.NoError(t, err) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed(), "Filled, non-liquid") + + gasMeter = sdk.NewGasMeter(math.MaxUint64) + slippage := sdk.NewDecWithPrec(50, 2) + srcDenom := "gbp" + dest := sdk.NewCoin("eur", sdk.NewInt(200)) + slippageSource, err := k.GetSrcFromSlippage( + ctx, srcDenom, dest, slippage, + ) + require.NoError(t, err) + limitOrder := order(ctx.BlockTime(), acc1, slippageSource.String(), dest.String()) + _, err = k.NewOrderSingle(ctx, limitOrder) + require.NoError(t, err) + require.Equal(t, liquidTrxFee, gasMeter.GasConsumed(), "Non-filled-Liquid") + + _, err = k.CancelOrder(ctx.WithGasMeter(gasMeter), acc1.GetAddress(), limitOrder.ClientOrderID) + require.NoError(t, err) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed(), "Cancel pays std fee") } func TestMultipleOrders(t *testing.T) { @@ -796,12 +1735,15 @@ func Test3(t *testing.T) { o := order(ctx.BlockTime(), acc1, "100eur", "120usd") k.NewOrderSingle(ctx, o) + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + gasMeter := sdk.NewGasMeter(math.MaxUint64) for i := 0; i < 4; i++ { o = order(ctx.BlockTime(), acc2, "30usd", "25eur") k.NewOrderSingle(ctx.WithGasMeter(gasMeter), o) } - require.Equal(t, 4*gasPriceNewOrder, gasMeter.GasConsumed()) + require.Equal(t, 4*stdTrxFee, gasMeter.GasConsumed()) bal1 := bk.GetAllBalances(ctx, acc1.GetAddress()) bal2 := bk.GetAllBalances(ctx, acc2.GetAddress()) @@ -819,11 +1761,17 @@ func TestDeleteOrder(t *testing.T) { cid := cid() - order1, _ := types.NewOrder(ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("100eur"), coin("120usd"), acc1.GetAddress(), cid) + order1, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("100eur"), + coin("120usd"), acc1.GetAddress(), cid, + ) _, err := k.NewOrderSingle(ctx, order1) require.NoError(t, err) - order2, _ := types.NewOrder(ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("100eur"), coin("77chf"), acc1.GetAddress(), cid) + order2, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("100eur"), + coin("77chf"), acc1.GetAddress(), cid, + ) _, err = k.NewOrderSingle(ctx, order2) require.Error(t, err) // Verify that client order ids cannot be duplicated. @@ -841,13 +1789,19 @@ func TestGetOrdersByOwnerAndCancel(t *testing.T) { acc2 := createAccount(ctx, ak, bk, randomAddress(), "120usd") for i := 0; i < 5; i++ { - order, _ := types.NewOrder(ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("5eur"), coin("12usd"), acc1.GetAddress(), cid()) + order, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("5eur"), + coin("12usd"), acc1.GetAddress(), cid(), + ) _, err := k.NewOrderSingle(ctx, order) require.NoError(t, err) } for i := 0; i < 5; i++ { - order, _ := types.NewOrder(ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("7usd"), coin("3chf"), acc2.GetAddress(), cid()) + order, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("7usd"), + coin("3chf"), acc2.GetAddress(), cid(), + ) res, err := k.NewOrderSingle(ctx, order) require.True(t, err == nil, res.Log) } @@ -856,7 +1810,10 @@ func TestGetOrdersByOwnerAndCancel(t *testing.T) { require.Len(t, allOrders1, 5) { - order, _ := types.NewOrder(ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("12usd"), coin("5eur"), acc2.GetAddress(), cid()) + order, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("12usd"), + coin("5eur"), acc2.GetAddress(), cid(), + ) res, err := k.NewOrderSingle(ctx, order) require.True(t, err == nil, res.Log) } @@ -864,15 +1821,19 @@ func TestGetOrdersByOwnerAndCancel(t *testing.T) { allOrders2 := k.GetOrdersByOwner(ctx, acc1.GetAddress()) require.Len(t, allOrders2, 4) + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + cid := allOrders2[2].ClientOrderID gasMeter := sdk.NewGasMeter(math.MaxUint64) _, err := k.CancelOrder(ctx.WithGasMeter(gasMeter), acc1.GetAddress(), cid) require.NoError(t, err) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) _, err = k.CancelOrder(ctx.WithGasMeter(gasMeter), acc1.GetAddress(), cid) require.Error(t, err) - require.Equal(t, 2*gasPriceCancelOrder, gasMeter.GasConsumed()) + require.Equal(t, 2*stdTrxFee, gasMeter.GasConsumed()) allOrders3 := k.GetOrdersByOwner(ctx, acc1.GetAddress()) require.Len(t, allOrders3, 3) @@ -890,8 +1851,15 @@ func TestCancelOrders1(t *testing.T) { ctx, k, ak, bk := createTestComponents(t) acc := createAccount(ctx, ak, bk, randomAddress(), "100eur") - _, err := k.CancelOrder(ctx, acc.GetAddress(), "abcde") + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + gasMeter := sdk.NewInfiniteGasMeter() + _, err := k.CancelOrder( + ctx.WithGasMeter(gasMeter), acc.GetAddress(), "abcde", + ) require.Error(t, err) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) } func TestKeeperCancelReplaceLimitOrder(t *testing.T) { @@ -902,16 +1870,22 @@ func TestKeeperCancelReplaceLimitOrder(t *testing.T) { totalSupply := snapshotAccounts(ctx, bk) order1cid := cid() - order1, _ := types.NewOrder(ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("500eur"), coin("1200usd"), acc1.GetAddress(), order1cid) + order1, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("500eur"), + coin("1200usd"), acc1.GetAddress(), order1cid, + ) _, err := k.NewOrderSingle(ctx, order1) require.NoError(t, err) + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + gasMeter := sdk.NewGasMeter(math.MaxUint64) order2cid := cid() order2, _ := types.NewOrder(ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("5000eur"), coin("17000usd"), acc1.GetAddress(), order2cid) res, err := k.CancelReplaceLimitOrder(ctx.WithGasMeter(gasMeter), order2, order1cid) require.True(t, err == nil, res.Log) - require.Equal(t, gasPriceCancelReplaceOrder, gasMeter.GasConsumed()) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) { orders := k.GetOrdersByOwner(ctx, acc1.GetAddress()) @@ -922,7 +1896,10 @@ func TestKeeperCancelReplaceLimitOrder(t *testing.T) { require.Equal(t, sdk.NewInt(5000), orders[0].SourceRemaining) } - order3, _ := types.NewOrder(ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("500chf"), coin("1700usd"), acc1.GetAddress(), cid()) + order3, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("500chf"), + coin("1700usd"), acc1.GetAddress(), cid(), + ) // Wrong client order id for previous order submitted. _, err = k.CancelReplaceLimitOrder(ctx, order3, order1cid) require.True(t, types.ErrClientOrderIdNotFound.Is(err)) @@ -931,7 +1908,7 @@ func TestKeeperCancelReplaceLimitOrder(t *testing.T) { gasMeter = sdk.NewGasMeter(math.MaxUint64) _, err = k.CancelReplaceLimitOrder(ctx.WithGasMeter(gasMeter), order3, order2cid) require.True(t, types.ErrOrderInstrumentChanged.Is(err)) - require.Equal(t, gasPriceCancelReplaceOrder, gasMeter.GasConsumed()) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) o := order(ctx.BlockTime(), acc2, "2600usd", "300eur") _, err = k.NewOrderSingle(ctx, o) @@ -950,11 +1927,34 @@ func TestKeeperCancelReplaceLimitOrder(t *testing.T) { filled = orders[0].Source.Amount.Sub(orders[0].SourceRemaining) } + var qualificationMin int64 + k.paramStore.Get(ctx, types.KeyLiquidityRebateMinutesSpan, &qualificationMin) + + var liquidTrxFee uint64 + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liquidTrxFee) + // CancelReplace and verify that previously filled amount is subtracted from the resulting order order4cid := cid() - order4, _ := types.NewOrder(ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("10000eur"), coin("35050usd"), acc1.GetAddress(), order4cid) - res, err = k.CancelReplaceLimitOrder(ctx, order4, order2cid) + gasMeter = sdk.NewInfiniteGasMeter() + ctx = ctx.WithGasMeter(gasMeter) + order4, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("10000eur"), + coin("35050usd"), acc1.GetAddress(), order4cid, + ) + res, err = k.CancelReplaceLimitOrder( + ctx.WithBlockTime(ctx.BlockTime().Add( + time.Duration(qualificationMin)*time.Minute)), + order4, order2cid, + ) require.True(t, err == nil, res.Log) + require.Lessf( + t, gasMeter.GasConsumed(), stdTrxFee, + "Gas consumed for partial filled should be less than stdTrxFee", + ) + require.Greaterf( + t, gasMeter.GasConsumed(), liquidTrxFee, + "Gas consumed should filled should be greater than LiquidTrxFee", + ) { orders := k.GetOrdersByOwner(ctx, acc1.GetAddress()) @@ -967,12 +1967,22 @@ func TestKeeperCancelReplaceLimitOrder(t *testing.T) { // CancelReplace with an order that asks for a larger source than the replaced order has remaining order5 := order(ctx.BlockTime(), acc2, "42000usd", "8000eur") - k.NewOrderSingle(ctx, order5) + res, err = k.NewOrderSingle(ctx, order5) require.True(t, err == nil, res.Log) order6 := order(ctx.BlockTime(), acc1, "8000eur", "30000usd") - _, err = k.CancelReplaceLimitOrder(ctx, order6, order4cid) + gasMeter = sdk.NewInfiniteGasMeter() + ctx = ctx.WithGasMeter(gasMeter) + _, err = k.CancelReplaceLimitOrder( + ctx.WithBlockTime(ctx.BlockTime().Add( + time.Duration(qualificationMin)*time.Minute)), + order6, order4cid, + ) require.True(t, types.ErrNoSourceRemaining.Is(err)) + require.Equal( + t, stdTrxFee, gasMeter.GasConsumed(), + "Erred orders should incur full fees", + ) require.True(t, totalSupply.Sub(snapshotAccounts(ctx, bk)).IsZero()) } @@ -985,16 +1995,25 @@ func TestKeeperCancelReplaceMarketOrder(t *testing.T) { totalSupply := snapshotAccounts(ctx, bk) order1cid := cid() - order1, _ := types.NewOrder(ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("500eur"), coin("1200usd"), acc1.GetAddress(), order1cid) + order1, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("500eur"), + coin("1200usd"), acc1.GetAddress(), order1cid, + ) _, err := k.NewOrderSingle(ctx, order1) require.NoError(t, err) + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + gasMeter := sdk.NewGasMeter(math.MaxUint64) order2cid := cid() - order2, _ := types.NewOrder(ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("5000eur"), coin("17000usd"), acc1.GetAddress(), order2cid) + order2, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("5000eur"), + coin("17000usd"), acc1.GetAddress(), order2cid, + ) res, err := k.CancelReplaceLimitOrder(ctx.WithGasMeter(gasMeter), order2, order1cid) require.True(t, err == nil, res.Log) - require.Equal(t, gasPriceCancelReplaceOrder, gasMeter.GasConsumed()) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) { orders := k.GetOrdersByOwner(ctx, acc1.GetAddress()) @@ -1005,7 +2024,10 @@ func TestKeeperCancelReplaceMarketOrder(t *testing.T) { require.Equal(t, sdk.NewInt(5000), orders[0].SourceRemaining) } - order3, _ := types.NewOrder(ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("500chf"), coin("1700usd"), acc1.GetAddress(), cid()) + order3, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("500chf"), + coin("1700usd"), acc1.GetAddress(), cid(), + ) // Wrong client order id for previous order submitted. _, err = k.CancelReplaceLimitOrder(ctx, order3, order1cid) require.True(t, types.ErrClientOrderIdNotFound.Is(err)) @@ -1014,7 +2036,7 @@ func TestKeeperCancelReplaceMarketOrder(t *testing.T) { gasMeter = sdk.NewGasMeter(math.MaxUint64) _, err = k.CancelReplaceLimitOrder(ctx.WithGasMeter(gasMeter), order3, order2cid) require.True(t, types.ErrOrderInstrumentChanged.Is(err)) - require.Equal(t, gasPriceCancelReplaceOrder, gasMeter.GasConsumed()) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) o := order(ctx.BlockTime(), acc2, "2600usd", "300eur") _, err = k.NewOrderSingle(ctx, o) @@ -1035,7 +2057,10 @@ func TestKeeperCancelReplaceMarketOrder(t *testing.T) { // CancelReplace and verify that previously filled amount is subtracted from the resulting order order4cid := cid() - order4, _ := types.NewOrder(ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("10000eur"), coin("35050usd"), acc1.GetAddress(), order4cid) + order4, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("10000eur"), + coin("35050usd"), acc1.GetAddress(), order4cid, + ) res, err = k.CancelReplaceLimitOrder(ctx, order4, order2cid) require.True(t, err == nil, res.Log) @@ -1065,14 +2090,20 @@ func TestOrdersChangeWithAccountBalance(t *testing.T) { acc := createAccount(ctx, ak, bk, randomAddress(), "15000eur") acc2 := createAccount(ctx, ak, bk, randomAddress(), "11000chf,100000eur") - order, _ := types.NewOrder(ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("10000eur"), coin("1000usd"), acc.GetAddress(), cid()) + order, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("10000eur"), + coin("1000usd"), acc.GetAddress(), cid(), + ) _, err := k.NewOrderSingle(ctx, order) require.NoError(t, err) { // Partially fill the order above acc2 := createAccount(ctx, ak, bk, randomAddress(), "900000usd") - order2, _ := types.NewOrder(ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("400usd"), coin("4000eur"), acc2.GetAddress(), cid()) + order2, _ := types.NewOrder( + ctx.BlockTime(), types.TimeInForce_GoodTillCancel, coin("400usd"), + coin("4000eur"), acc2.GetAddress(), cid(), + ) _, err = k.NewOrderSingle(ctx, order2) require.NoError(t, err) } @@ -1112,13 +2143,16 @@ func TestUnknownAsset(t *testing.T) { acc1 := createAccount(ctx, ak, bk, randomAddress(), "5000eur") + var stdTrxFee sdk.Gas + k1.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + gasMeter := sdk.NewGasMeter(math.MaxUint64) // Make an order with a destination that is not known by the supply module o := order(ctx.BlockTime(), acc1, "1000eur", "1200nok") _, err := k1.NewOrderSingle(ctx.WithGasMeter(gasMeter), o) require.True(t, types.ErrUnknownAsset.Is(err)) - require.Equal(t, gasPriceNewOrder, gasMeter.GasConsumed()) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed(), "Liquid non-filled") } func TestLoadFromStore(t *testing.T) { @@ -1137,6 +2171,7 @@ func TestLoadFromStore(t *testing.T) { require.NoError(t, err) _, k2, _, _ := createTestComponents(t) + k2.paramStore = k1.paramStore k2.key = k1.key // Create new keeper and let it inherit the store of the previous keeper @@ -1180,7 +2215,6 @@ func TestInvalidInstrument(t *testing.T) { _, err := k.NewOrderSingle(ctx, o) require.True(t, types.ErrInvalidInstrument.Is(err)) } - func TestSyntheticInstruments1(t *testing.T) { ctx, k, ak, bk := createTestComponents(t) acc1 := createAccount(ctx, ak, bk, randomAddress(), "5000eur") @@ -1201,11 +2235,18 @@ func TestSyntheticInstruments1(t *testing.T) { _, err = k.NewOrderSingle(ctx, o) require.NoError(t, err) + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + + var liquidTrxFee sdk.Gas + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liquidTrxFee) + gasMeter := sdk.NewGasMeter(math.MaxUint64) o = order(ctx.BlockTime(), acc2, "5000usd", "4485eur") _, err = k.NewOrderSingle(ctx.WithGasMeter(gasMeter), o) require.NoError(t, err) - require.Equal(t, gasMeter.GasConsumed(), gasPriceNewOrder) // Matches several orders, but should pay only the fixed fee + require.Greater(t, gasMeter.GasConsumed(), liquidTrxFee) // Matches several orders, but should pay only the fixed fee + require.Less(t, gasMeter.GasConsumed(), stdTrxFee) // Matches several orders, but should pay only the fixed fee // Ensure acc2 received at least some euro acc2Balance := bk.GetAllBalances(ctx, acc2.GetAddress()) @@ -1271,11 +2312,14 @@ func TestSyntheticInstruments2(t *testing.T) { require.NoError(t, err, res.Log) } + var stdTrxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &stdTrxFee) + gasMeter := sdk.NewGasMeter(math.MaxUint64) monsterOrder := order(ctx.BlockTime(), acc3, "3700000eur", "4000000usd") res, err := k.NewOrderSingle(ctx.WithGasMeter(gasMeter), monsterOrder) require.NoError(t, err, res.Log) - require.Equal(t, gasPriceNewOrder, gasMeter.GasConsumed()) + require.Equal(t, stdTrxFee, gasMeter.GasConsumed()) // require.Len(t, k.instruments, 0) @@ -1453,12 +2497,12 @@ func createTestComponentsWithEncoding(t *testing.T, encConfig simappparams.Encod t.Helper() var ( - keyMarket = sdk.NewKVStoreKey(types.ModuleName) + keyMarket = sdk.NewKVStoreKey(types.StoreKey) keyIndices = sdk.NewKVStoreKey(types.StoreKeyIdx) keyAuthCap = sdk.NewKVStoreKey("authCapKey") - keyParams = sdk.NewKVStoreKey("params") - keyBank = sdk.NewKVStoreKey(banktypes.ModuleName) - tkeyParams = sdk.NewTransientStoreKey("transient_params") + keyParams = sdk.NewKVStoreKey(paramstypes.StoreKey) + keyBank = sdk.NewKVStoreKey(banktypes.StoreKey) + tkeyParams = sdk.NewTransientStoreKey(paramstypes.TStoreKey) blockedAddr = make(map[string]bool) maccPerms = map[string][]string{} @@ -1471,6 +2515,7 @@ func createTestComponentsWithEncoding(t *testing.T, encConfig simappparams.Encod ms.MountStoreWithDB(keyAuthCap, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyBank, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) err := ms.LoadLatestVersion() require.Nil(t, err) @@ -1487,11 +2532,16 @@ func createTestComponentsWithEncoding(t *testing.T, encConfig simappparams.Encod ) wrappedBank = embank.Wrap(bk) + + marketKeeper = NewKeeper(encConfig.Marshaler, keyMarket, keyIndices, ak, + wrappedBank, + pk.Subspace(types.ModuleName)) ) + marketKeeper.InitParamsStore(ctx) + bk.SetSupply(ctx, banktypes.NewSupply(coins("1eur,1usd,1chf,1jpy,1gbp,1ngm"))) - marketKeeper := NewKeeper(encConfig.Marshaler, keyMarket, keyIndices, ak, wrappedBank) return ctx, marketKeeper, ak, wrappedBank } diff --git a/x/market/keeper/msg_server.go b/x/market/keeper/msg_server.go index e812dc6e..4e1849db 100644 --- a/x/market/keeper/msg_server.go +++ b/x/market/keeper/msg_server.go @@ -5,6 +5,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/e-money/em-ledger/x/market/types" + "time" ) var _ types.MsgServer = msgServer{} @@ -14,6 +15,7 @@ type marketKeeper interface { CancelOrder(ctx sdk.Context, owner sdk.AccAddress, clientOrderId string) (*sdk.Result, error) CancelReplaceLimitOrder(ctx sdk.Context, newOrder types.Order, origClientOrderId string) (*sdk.Result, error) GetSrcFromSlippage(ctx sdk.Context, srcDenom string, dst sdk.Coin, maxSlippage sdk.Dec) (sdk.Coin, error) + OrderSpendGas(ctx sdk.Context, order *types.Order, origOrderCreated time.Time, orderGasMeter sdk.GasMeter, callerErr *error) } type msgServer struct { k marketKeeper @@ -23,14 +25,25 @@ func NewMsgServerImpl(keeper marketKeeper) types.MsgServer { return &msgServer{k: keeper} } -func (m msgServer) AddLimitOrder(c context.Context, msg *types.MsgAddLimitOrder) (*types.MsgAddLimitOrderResponse, error) { +func (m msgServer) AddLimitOrder( + c context.Context, msg *types.MsgAddLimitOrder, +) (_ *types.MsgAddLimitOrderResponse, err error) { + var ( + order types.Order + ) + ctx := sdk.UnwrapSDKContext(c) + owner, err := sdk.AccAddressFromBech32(msg.Owner) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "owner") } - order, err := types.NewOrder(ctx.BlockTime(), msg.TimeInForce, msg.Source, msg.Destination, owner, msg.ClientOrderId) + order, err = types.NewOrder( + ctx.BlockTime(), msg.TimeInForce, msg.Source, msg.Destination, owner, + msg.ClientOrderId, + ) + if err != nil { return nil, err } diff --git a/x/market/keeper/msg_server_test.go b/x/market/keeper/msg_server_test.go index 8e73c11b..1ea505f7 100644 --- a/x/market/keeper/msg_server_test.go +++ b/x/market/keeper/msg_server_test.go @@ -11,6 +11,7 @@ import ( abcitypes "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/libs/rand" "testing" + "time" ) func TestAddLimitOrder(t *testing.T) { @@ -601,6 +602,12 @@ type marketKeeperMock struct { GetSrcFromSlippageFn func(ctx sdk.Context, srcDenom string, dst sdk.Coin, maxSlippage sdk.Dec) (sdk.Coin, error) } +func (m marketKeeperMock) OrderSpendGas( + ctx sdk.Context, order *types.Order, origOrderCreated time.Time, + orderGasMeter sdk.GasMeter, callerErr *error, +) { +} + func (m marketKeeperMock) NewMarketOrderWithSlippage(ctx sdk.Context, srcDenom string, dst sdk.Coin, maxSlippage sdk.Dec, owner sdk.AccAddress, timeInForce types.TimeInForce, clientOrderId string) (*sdk.Result, error) { if m.NewMarketOrderWithSlippageFn == nil { panic("not expected to be called") diff --git a/x/market/keeper/params.go b/x/market/keeper/params.go new file mode 100644 index 00000000..74d4b056 --- /dev/null +++ b/x/market/keeper/params.go @@ -0,0 +1,65 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/e-money/em-ledger/x/market/types" +) + +// InitParamsStore writes the params to the module store +func (k Keeper) InitParamsStore(ctx sdk.Context) uint64 { + defaultSet := types.DefaultTxParams() + var trxFee uint64 + if !k.paramStore.Has(ctx, types.KeyTrxFee) { + k.paramStore.Set(ctx, types.KeyTrxFee, defaultSet.TrxFee) + } + if !k.paramStore.Has(ctx, types.KeyLiquidTrxFee) { + k.paramStore.Set(ctx, types.KeyLiquidTrxFee, defaultSet.LiquidTrxFee) + } + if !k.paramStore.Has(ctx, types.KeyLiquidityRebateMinutesSpan) { + k.paramStore.Set( + ctx, types.KeyLiquidityRebateMinutesSpan, + defaultSet.LiquidityRebateMinutesSpan, + ) + } + + return trxFee +} + +// GetTrxFee retrieves the trx fee from the paramStore +func (k Keeper) GetTrxFee(ctx sdk.Context) uint64 { + var trxFee uint64 + k.paramStore.Get(ctx, types.KeyTrxFee, &trxFee) + + return trxFee +} + +// GetLiquidTrxFee retrieves the liquid trx fee from the paramStore +func (k Keeper) GetLiquidTrxFee(ctx sdk.Context) uint64 { + var liqTrxFee uint64 + k.paramStore.Get(ctx, types.KeyLiquidTrxFee, &liqTrxFee) + + return liqTrxFee +} + +// GetLiquidityRebateMinutesSpan retrieves the Minutes interval for eligible +// replacing transactions to receive a rebate. For a rebate to apply, the +// replacing transaction should occur these minutes after the signer's original +// trx. +func (k Keeper) GetLiquidityRebateMinutesSpan(ctx sdk.Context) int64 { + var liqTrxFee int64 + k.paramStore.Get(ctx, types.KeyLiquidityRebateMinutesSpan, &liqTrxFee) + + return liqTrxFee +} + +// GetParams returns the total market parameters set. +func (k Keeper) GetParams(ctx sdk.Context) types.TxParams { + return types.NewTxParams(k.GetTrxFee(ctx), k.GetLiquidTrxFee(ctx), + k.GetLiquidityRebateMinutesSpan(ctx)) +} + +// SetParams sets the total market parameters set. +func (k Keeper) SetParams(ctx sdk.Context, params types.TxParams) { + k.paramStore.SetParamSet(ctx, ¶ms) +} + diff --git a/x/market/keeper/params_test.go b/x/market/keeper/params_test.go new file mode 100644 index 00000000..841d78ed --- /dev/null +++ b/x/market/keeper/params_test.go @@ -0,0 +1,25 @@ +package keeper + +import ( + "github.com/e-money/em-ledger/x/market/types" + "github.com/stretchr/testify/require" + "testing" +) + +func TestParams(t *testing.T) { + ctx, k, _, _ := createTestComponents(t) + expParams := types.DefaultTxParams() + + params := k.GetParams(ctx) + require.Equal(t, expParams, params) + + expParams.TrxFee = 100_000 + expParams.LiquidTrxFee = 250 + expParams.LiquidityRebateMinutesSpan = 10 + + k.SetParams(ctx, expParams) + params = k.GetParams(ctx) + + require.Equal(t, expParams, params) +} + diff --git a/x/market/keeper/querier.go b/x/market/keeper/querier.go index 82e381d0..3187f56d 100644 --- a/x/market/keeper/querier.go +++ b/x/market/keeper/querier.go @@ -68,7 +68,7 @@ func queryByAccount(ctx sdk.Context, k *Keeper, path []string, req abci.RequestQ sort.Slice( orders, func(i, j int) bool { - return orders[i].ID < orders[i].ID + return orders[i].ID < orders[j].ID }) resp := types.QueryByAccountResponse{Orders: orders} diff --git a/x/market/types/tx.pb.go b/x/market/types/tx.pb.go index 96f33208..1342285a 100644 --- a/x/market/types/tx.pb.go +++ b/x/market/types/tx.pb.go @@ -31,6 +31,71 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package +type TxParams struct { + // default fee for a market transaction. + TrxFee uint64 `protobuf:"varint,1,opt,name=trx_fee,json=trxFee,proto3" json:"trx_fee,omitempty" yaml:"trx_fee"` + // Valid liquidity adding orders are free or adjusted to a minimum nominal/fee + LiquidTrxFee uint64 `protobuf:"varint,2,opt,name=liquid_trx_fee,json=liquidTrxFee,proto3" json:"liquid_trx_fee,omitempty" yaml:"liquid_trx_fee"` + // Minutes interval for eligible replacing transactions to receive a rebate. + // For a rebate to apply, the replacing transaction should occur these minutes + // after the signer's original trx. + LiquidityRebateMinutesSpan int64 `protobuf:"varint,3,opt,name=liquidity_rebate_minutes_span,json=liquidityRebateMinutesSpan,proto3" json:"liquidity_rebate_minutes_span,omitempty" yaml:"liquidity_rebate_minutes_span"` +} + +func (m *TxParams) Reset() { *m = TxParams{} } +func (m *TxParams) String() string { return proto.CompactTextString(m) } +func (*TxParams) ProtoMessage() {} +func (*TxParams) Descriptor() ([]byte, []int) { + return fileDescriptor_636272ab2288df51, []int{0} +} +func (m *TxParams) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *TxParams) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_TxParams.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *TxParams) XXX_Merge(src proto.Message) { + xxx_messageInfo_TxParams.Merge(m, src) +} +func (m *TxParams) XXX_Size() int { + return m.Size() +} +func (m *TxParams) XXX_DiscardUnknown() { + xxx_messageInfo_TxParams.DiscardUnknown(m) +} + +var xxx_messageInfo_TxParams proto.InternalMessageInfo + +func (m *TxParams) GetTrxFee() uint64 { + if m != nil { + return m.TrxFee + } + return 0 +} + +func (m *TxParams) GetLiquidTrxFee() uint64 { + if m != nil { + return m.LiquidTrxFee + } + return 0 +} + +func (m *TxParams) GetLiquidityRebateMinutesSpan() int64 { + if m != nil { + return m.LiquidityRebateMinutesSpan + } + return 0 +} + type MsgAddLimitOrder struct { Owner string `protobuf:"bytes,1,opt,name=owner,proto3" json:"owner,omitempty" yaml:"owner"` ClientOrderId string `protobuf:"bytes,2,opt,name=client_order_id,json=clientOrderId,proto3" json:"client_order_id,omitempty" yaml:"client_order_id"` @@ -43,7 +108,7 @@ func (m *MsgAddLimitOrder) Reset() { *m = MsgAddLimitOrder{} } func (m *MsgAddLimitOrder) String() string { return proto.CompactTextString(m) } func (*MsgAddLimitOrder) ProtoMessage() {} func (*MsgAddLimitOrder) Descriptor() ([]byte, []int) { - return fileDescriptor_636272ab2288df51, []int{0} + return fileDescriptor_636272ab2288df51, []int{1} } func (m *MsgAddLimitOrder) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -114,7 +179,7 @@ func (m *MsgAddLimitOrderResponse) Reset() { *m = MsgAddLimitOrderRespon func (m *MsgAddLimitOrderResponse) String() string { return proto.CompactTextString(m) } func (*MsgAddLimitOrderResponse) ProtoMessage() {} func (*MsgAddLimitOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_636272ab2288df51, []int{1} + return fileDescriptor_636272ab2288df51, []int{2} } func (m *MsgAddLimitOrderResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -156,7 +221,7 @@ func (m *MsgAddMarketOrder) Reset() { *m = MsgAddMarketOrder{} } func (m *MsgAddMarketOrder) String() string { return proto.CompactTextString(m) } func (*MsgAddMarketOrder) ProtoMessage() {} func (*MsgAddMarketOrder) Descriptor() ([]byte, []int) { - return fileDescriptor_636272ab2288df51, []int{2} + return fileDescriptor_636272ab2288df51, []int{3} } func (m *MsgAddMarketOrder) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -227,7 +292,7 @@ func (m *MsgAddMarketOrderResponse) Reset() { *m = MsgAddMarketOrderResp func (m *MsgAddMarketOrderResponse) String() string { return proto.CompactTextString(m) } func (*MsgAddMarketOrderResponse) ProtoMessage() {} func (*MsgAddMarketOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_636272ab2288df51, []int{3} + return fileDescriptor_636272ab2288df51, []int{4} } func (m *MsgAddMarketOrderResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -265,7 +330,7 @@ func (m *MsgCancelOrder) Reset() { *m = MsgCancelOrder{} } func (m *MsgCancelOrder) String() string { return proto.CompactTextString(m) } func (*MsgCancelOrder) ProtoMessage() {} func (*MsgCancelOrder) Descriptor() ([]byte, []int) { - return fileDescriptor_636272ab2288df51, []int{4} + return fileDescriptor_636272ab2288df51, []int{5} } func (m *MsgCancelOrder) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -315,7 +380,7 @@ func (m *MsgCancelOrderResponse) Reset() { *m = MsgCancelOrderResponse{} func (m *MsgCancelOrderResponse) String() string { return proto.CompactTextString(m) } func (*MsgCancelOrderResponse) ProtoMessage() {} func (*MsgCancelOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_636272ab2288df51, []int{5} + return fileDescriptor_636272ab2288df51, []int{6} } func (m *MsgCancelOrderResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -357,7 +422,7 @@ func (m *MsgCancelReplaceLimitOrder) Reset() { *m = MsgCancelReplaceLimi func (m *MsgCancelReplaceLimitOrder) String() string { return proto.CompactTextString(m) } func (*MsgCancelReplaceLimitOrder) ProtoMessage() {} func (*MsgCancelReplaceLimitOrder) Descriptor() ([]byte, []int) { - return fileDescriptor_636272ab2288df51, []int{6} + return fileDescriptor_636272ab2288df51, []int{7} } func (m *MsgCancelReplaceLimitOrder) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -435,7 +500,7 @@ func (m *MsgCancelReplaceLimitOrderResponse) Reset() { *m = MsgCancelRep func (m *MsgCancelReplaceLimitOrderResponse) String() string { return proto.CompactTextString(m) } func (*MsgCancelReplaceLimitOrderResponse) ProtoMessage() {} func (*MsgCancelReplaceLimitOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_636272ab2288df51, []int{7} + return fileDescriptor_636272ab2288df51, []int{8} } func (m *MsgCancelReplaceLimitOrderResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -478,7 +543,7 @@ func (m *MsgCancelReplaceMarketOrder) Reset() { *m = MsgCancelReplaceMar func (m *MsgCancelReplaceMarketOrder) String() string { return proto.CompactTextString(m) } func (*MsgCancelReplaceMarketOrder) ProtoMessage() {} func (*MsgCancelReplaceMarketOrder) Descriptor() ([]byte, []int) { - return fileDescriptor_636272ab2288df51, []int{8} + return fileDescriptor_636272ab2288df51, []int{9} } func (m *MsgCancelReplaceMarketOrder) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -556,7 +621,7 @@ func (m *MsgCancelReplaceMarketOrderResponse) Reset() { *m = MsgCancelRe func (m *MsgCancelReplaceMarketOrderResponse) String() string { return proto.CompactTextString(m) } func (*MsgCancelReplaceMarketOrderResponse) ProtoMessage() {} func (*MsgCancelReplaceMarketOrderResponse) Descriptor() ([]byte, []int) { - return fileDescriptor_636272ab2288df51, []int{9} + return fileDescriptor_636272ab2288df51, []int{10} } func (m *MsgCancelReplaceMarketOrderResponse) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -586,6 +651,7 @@ func (m *MsgCancelReplaceMarketOrderResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgCancelReplaceMarketOrderResponse proto.InternalMessageInfo func init() { + proto.RegisterType((*TxParams)(nil), "em.market.v1.TxParams") proto.RegisterType((*MsgAddLimitOrder)(nil), "em.market.v1.MsgAddLimitOrder") proto.RegisterType((*MsgAddLimitOrderResponse)(nil), "em.market.v1.MsgAddLimitOrderResponse") proto.RegisterType((*MsgAddMarketOrder)(nil), "em.market.v1.MsgAddMarketOrder") @@ -601,56 +667,64 @@ func init() { func init() { proto.RegisterFile("em/market/v1/tx.proto", fileDescriptor_636272ab2288df51) } var fileDescriptor_636272ab2288df51 = []byte{ - // 780 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x56, 0x4d, 0x6f, 0xd3, 0x40, - 0x10, 0x8d, 0x9b, 0x0f, 0xd4, 0x4d, 0x3f, 0x52, 0xd3, 0x0f, 0xc7, 0x45, 0x76, 0x65, 0x4a, 0x49, - 0x85, 0x6a, 0x93, 0x70, 0x41, 0xdc, 0x48, 0x01, 0x51, 0x89, 0x50, 0x61, 0x90, 0x8a, 0x7a, 0x89, - 0x1c, 0x7b, 0x6b, 0x56, 0xf5, 0x7a, 0x83, 0xed, 0xb4, 0xa9, 0xc4, 0x8d, 0x3f, 0xc0, 0x89, 0xdf, - 0xd4, 0x63, 0x8f, 0x88, 0x83, 0x85, 0xd2, 0x7f, 0x90, 0x23, 0x87, 0x0a, 0xd9, 0xeb, 0x04, 0xc7, - 0x69, 0xd2, 0x52, 0x95, 0x1e, 0x10, 0xa7, 0x36, 0x3b, 0xef, 0xbd, 0x19, 0xcd, 0x8c, 0xdf, 0x2e, - 0x58, 0x80, 0x58, 0xc1, 0x9a, 0xb3, 0x0f, 0x3d, 0xe5, 0xa0, 0xac, 0x78, 0x6d, 0xb9, 0xe9, 0x10, - 0x8f, 0xb0, 0x53, 0x10, 0xcb, 0xf4, 0x58, 0x3e, 0x28, 0xf3, 0xf3, 0x26, 0x31, 0x49, 0x18, 0x50, - 0x82, 0xff, 0x28, 0x86, 0x17, 0x74, 0xe2, 0x62, 0xe2, 0x2a, 0x0d, 0xcd, 0x85, 0xca, 0x41, 0xb9, - 0x01, 0x3d, 0xad, 0xac, 0xe8, 0x04, 0xd9, 0x51, 0xbc, 0x38, 0x20, 0x1d, 0xa9, 0xd1, 0x90, 0x68, - 0x12, 0x62, 0x5a, 0x50, 0x09, 0x7f, 0x35, 0x5a, 0x7b, 0x8a, 0x87, 0x30, 0x74, 0x3d, 0x0d, 0x37, - 0x29, 0x40, 0xfa, 0x39, 0x01, 0x0a, 0x35, 0xd7, 0x7c, 0x6a, 0x18, 0xaf, 0x10, 0x46, 0xde, 0xb6, - 0x63, 0x40, 0x87, 0x5d, 0x03, 0x59, 0x72, 0x68, 0x43, 0x87, 0x63, 0x56, 0x98, 0xd2, 0x64, 0xb5, - 0xd0, 0xf5, 0xc5, 0xa9, 0x23, 0x0d, 0x5b, 0x4f, 0xa4, 0xf0, 0x58, 0x52, 0x69, 0x98, 0xad, 0x82, - 0x59, 0xdd, 0x42, 0xd0, 0xf6, 0xea, 0x24, 0xe0, 0xd5, 0x91, 0xc1, 0x4d, 0x84, 0x0c, 0xbe, 0xeb, - 0x8b, 0x8b, 0x94, 0x91, 0x00, 0x48, 0xea, 0x34, 0x3d, 0x09, 0x33, 0x6d, 0x19, 0xec, 0x0e, 0x98, - 0x0e, 0x6a, 0xaa, 0x23, 0xbb, 0xbe, 0x47, 0x1c, 0x1d, 0x72, 0xe9, 0x15, 0xa6, 0x34, 0x53, 0x29, - 0xca, 0xf1, 0xc6, 0xc8, 0xef, 0x10, 0x86, 0x5b, 0xf6, 0x8b, 0x00, 0x50, 0xe5, 0xba, 0xbe, 0x38, - 0x4f, 0xc5, 0x07, 0x98, 0x92, 0x9a, 0xf7, 0x7e, 0xc3, 0xd8, 0x97, 0x20, 0xe7, 0x92, 0x56, 0xa0, - 0x98, 0x59, 0x61, 0x4a, 0xf9, 0x4a, 0x51, 0xa6, 0x6d, 0x94, 0x83, 0x36, 0xca, 0x51, 0x1b, 0xe5, - 0x4d, 0x82, 0xec, 0xea, 0xc2, 0xb1, 0x2f, 0xa6, 0xba, 0xbe, 0x38, 0x4d, 0x55, 0x29, 0x4d, 0x52, - 0x23, 0x3e, 0xbb, 0x03, 0xf2, 0x06, 0x74, 0x3d, 0x64, 0x6b, 0x1e, 0x22, 0x36, 0x97, 0xbd, 0x48, - 0x8e, 0x8f, 0xe4, 0x58, 0x2a, 0x17, 0xe3, 0x4a, 0x6a, 0x5c, 0x49, 0xe2, 0x01, 0x97, 0xec, 0xbd, - 0x0a, 0xdd, 0x26, 0xb1, 0x5d, 0x28, 0x75, 0xd2, 0x60, 0x8e, 0x06, 0x6b, 0x61, 0x17, 0xfe, 0xa1, - 0xc9, 0xac, 0x0f, 0x4c, 0x66, 0xb2, 0x3a, 0x77, 0xf3, 0xad, 0x67, 0x3f, 0x33, 0xa0, 0x80, 0xb5, - 0x36, 0xc2, 0x2d, 0x5c, 0x77, 0x2d, 0xd4, 0x6c, 0x6a, 0x26, 0xe4, 0x72, 0x61, 0x39, 0xef, 0x03, - 0x8d, 0xef, 0xbe, 0xb8, 0x66, 0x22, 0xef, 0x43, 0xab, 0x21, 0xeb, 0x04, 0x2b, 0xd1, 0x17, 0x48, - 0xff, 0x6c, 0xb8, 0xc6, 0xbe, 0xe2, 0x1d, 0x35, 0xa1, 0x2b, 0x3f, 0x83, 0x7a, 0xc7, 0x17, 0xf3, - 0x35, 0xad, 0xfd, 0x36, 0x12, 0xe9, 0xfa, 0xe2, 0x12, 0x4d, 0x9e, 0x94, 0x97, 0xd4, 0xd9, 0xe8, - 0xa8, 0x87, 0x95, 0x96, 0x41, 0x71, 0x68, 0xc6, 0xfd, 0x0d, 0xf8, 0x04, 0x66, 0x6a, 0xae, 0xb9, - 0xa9, 0xd9, 0x3a, 0xb4, 0x6e, 0x7c, 0xfa, 0x12, 0x07, 0x16, 0x07, 0xb3, 0xf7, 0xeb, 0xfa, 0x9a, - 0x01, 0x7c, 0x3f, 0xa4, 0xc2, 0xa6, 0xa5, 0xe9, 0xf0, 0x0a, 0xe6, 0xf1, 0x11, 0x70, 0xc4, 0x41, - 0x26, 0xb2, 0x35, 0xab, 0x7e, 0x7e, 0xb5, 0x8f, 0x3b, 0xbe, 0x38, 0xb7, 0xed, 0x20, 0x73, 0x33, - 0x5e, 0x59, 0xd7, 0x17, 0xc5, 0x48, 0x6f, 0x04, 0x5d, 0x52, 0x17, 0x7a, 0xa1, 0x01, 0x26, 0xab, - 0x81, 0xdb, 0x36, 0x3c, 0x1c, 0xca, 0x96, 0x0e, 0xb3, 0x55, 0x3a, 0xbe, 0x58, 0x78, 0x0d, 0x0f, - 0x93, 0xc9, 0x78, 0x9a, 0xec, 0x1c, 0xa2, 0xa4, 0x16, 0xec, 0x04, 0x7e, 0xf8, 0xa3, 0xc9, 0x5c, - 0xbb, 0x9d, 0x65, 0xaf, 0xd7, 0xce, 0x72, 0xd7, 0x66, 0x67, 0xab, 0x40, 0x1a, 0xbd, 0x17, 0xfd, - 0xf5, 0x39, 0xcb, 0x80, 0xe5, 0x24, 0xec, 0x2a, 0x16, 0xf7, 0x7f, 0x7f, 0xae, 0x68, 0xba, 0xd9, - 0x3f, 0x34, 0xdd, 0xdc, 0xdf, 0x35, 0xdd, 0x5b, 0x37, 0x6d, 0xba, 0xf7, 0xc0, 0xdd, 0x31, 0xfb, - 0xd7, 0xdb, 0xd3, 0xca, 0x59, 0x1a, 0xa4, 0x6b, 0xae, 0x19, 0x4c, 0x64, 0xf0, 0x75, 0x24, 0x0c, - 0xce, 0x22, 0x79, 0x83, 0xf3, 0x6b, 0xe3, 0xe3, 0xbd, 0x04, 0xec, 0x2e, 0x98, 0x49, 0xdc, 0xee, - 0xe2, 0x79, 0xcc, 0x18, 0x80, 0xbf, 0x7f, 0x01, 0xa0, 0xaf, 0xfd, 0x06, 0xe4, 0xe3, 0x17, 0xc7, - 0x9d, 0x21, 0x5e, 0x2c, 0xca, 0xaf, 0x8e, 0x8b, 0xf6, 0x25, 0x5b, 0x60, 0x69, 0x94, 0xe5, 0x97, - 0x46, 0x08, 0x0c, 0x21, 0xf9, 0x87, 0x97, 0x45, 0xf6, 0xd3, 0xb6, 0x01, 0x37, 0xd2, 0x2a, 0xd6, - 0xc7, 0xab, 0xc5, 0x3b, 0x57, 0xbe, 0x34, 0xb4, 0x97, 0xb9, 0xfa, 0xfc, 0xb8, 0x23, 0x30, 0x27, - 0x1d, 0x81, 0xf9, 0xd1, 0x11, 0x98, 0x2f, 0xa7, 0x42, 0xea, 0xe4, 0x54, 0x48, 0x7d, 0x3b, 0x15, - 0x52, 0xbb, 0x0f, 0x62, 0x4b, 0x0a, 0x37, 0x30, 0xb1, 0xe1, 0x91, 0x02, 0xf1, 0x86, 0x05, 0x0d, - 0x13, 0x3a, 0x4a, 0xbb, 0xf7, 0x18, 0x0f, 0xb7, 0xb5, 0x91, 0x0b, 0x1f, 0xda, 0x8f, 0x7e, 0x05, - 0x00, 0x00, 0xff, 0xff, 0x7e, 0x1f, 0x6d, 0x63, 0x01, 0x0c, 0x00, 0x00, + // 898 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x57, 0xcf, 0x6f, 0xdb, 0x54, + 0x1c, 0xaf, 0x97, 0x34, 0xa3, 0x2f, 0x6d, 0x96, 0x9a, 0x75, 0x73, 0x3c, 0x88, 0xab, 0x47, 0x29, + 0x99, 0xa6, 0xda, 0xa4, 0x5c, 0x10, 0x17, 0x84, 0x0b, 0x13, 0x93, 0x08, 0x03, 0xaf, 0xd2, 0xd0, + 0x2e, 0x96, 0x13, 0x7f, 0x6b, 0x9e, 0x6a, 0xfb, 0x65, 0x7e, 0x2f, 0x6d, 0x2a, 0x71, 0xe3, 0x1f, + 0xe0, 0xc4, 0xdf, 0xb4, 0xe3, 0x8e, 0x88, 0x83, 0x85, 0x52, 0xf1, 0x0f, 0xf8, 0xc8, 0x61, 0x42, + 0xf6, 0x73, 0x82, 0x93, 0x34, 0xd9, 0xa8, 0x4a, 0x0f, 0x68, 0xa7, 0xc4, 0xef, 0xf3, 0xe3, 0xfb, + 0xfc, 0xbe, 0xdf, 0x7c, 0x1c, 0xa3, 0x2d, 0x08, 0x8c, 0xc0, 0x89, 0x8e, 0x81, 0x1b, 0x27, 0x6d, + 0x83, 0x0f, 0xf5, 0x7e, 0x44, 0x39, 0x95, 0xd7, 0x21, 0xd0, 0xc5, 0xb2, 0x7e, 0xd2, 0x56, 0x6f, + 0x7b, 0xd4, 0xa3, 0x19, 0x60, 0xa4, 0xdf, 0x04, 0x47, 0x6d, 0xf6, 0x28, 0x0b, 0x28, 0x33, 0xba, + 0x0e, 0x03, 0xe3, 0xa4, 0xdd, 0x05, 0xee, 0xb4, 0x8d, 0x1e, 0x25, 0x61, 0x8e, 0x37, 0xa6, 0xac, + 0x73, 0x37, 0x01, 0x69, 0x1e, 0xa5, 0x9e, 0x0f, 0x46, 0x76, 0xd5, 0x1d, 0x1c, 0x19, 0x9c, 0x04, + 0xc0, 0xb8, 0x13, 0xf4, 0x05, 0x01, 0xff, 0x29, 0xa1, 0x77, 0x0e, 0x87, 0xdf, 0x39, 0x91, 0x13, + 0x30, 0xf9, 0x01, 0xba, 0xc9, 0xa3, 0xa1, 0x7d, 0x04, 0xa0, 0x48, 0xdb, 0x52, 0xab, 0x6c, 0xca, + 0x49, 0xac, 0xd5, 0xce, 0x9c, 0xc0, 0xff, 0x0c, 0xe7, 0x00, 0xb6, 0x2a, 0x3c, 0x1a, 0x3e, 0x04, + 0x90, 0x3f, 0x47, 0x35, 0x9f, 0x3c, 0x1f, 0x10, 0xd7, 0x1e, 0x6b, 0x6e, 0x64, 0x9a, 0x46, 0x12, + 0x6b, 0x5b, 0x42, 0x33, 0x8d, 0x63, 0x6b, 0x5d, 0x2c, 0x1c, 0x0a, 0x83, 0x63, 0xf4, 0xbe, 0xb8, + 0x26, 0xfc, 0xcc, 0x8e, 0xa0, 0xeb, 0x70, 0xb0, 0x03, 0x12, 0x0e, 0x38, 0x30, 0x9b, 0xf5, 0x9d, + 0x50, 0x29, 0x6d, 0x4b, 0xad, 0x92, 0xd9, 0x4a, 0x62, 0x6d, 0xa7, 0xe8, 0xb7, 0x80, 0x8e, 0x2d, + 0x75, 0x82, 0x5b, 0x19, 0xdc, 0x11, 0xe8, 0x93, 0x14, 0xfc, 0xeb, 0x06, 0xaa, 0x77, 0x98, 0xf7, + 0x85, 0xeb, 0x7e, 0x43, 0x02, 0xc2, 0x1f, 0x47, 0x2e, 0x44, 0xf2, 0x2e, 0x5a, 0xa5, 0xa7, 0x21, + 0x44, 0xd9, 0xdd, 0xae, 0x99, 0xf5, 0x24, 0xd6, 0xd6, 0x45, 0xa5, 0x6c, 0x19, 0x5b, 0x02, 0x96, + 0x4d, 0x74, 0xab, 0xe7, 0x13, 0x08, 0xb9, 0x4d, 0x53, 0x9d, 0x4d, 0xdc, 0xec, 0x5e, 0xd7, 0x4c, + 0x35, 0x89, 0xb5, 0x3b, 0x42, 0x31, 0x43, 0xc0, 0xd6, 0x86, 0x58, 0xc9, 0x2a, 0x3d, 0x72, 0xe5, + 0xa7, 0x68, 0x23, 0x3d, 0x7b, 0x9b, 0x84, 0xf6, 0x11, 0x8d, 0x7a, 0x90, 0xdd, 0x5d, 0x6d, 0xbf, + 0xa1, 0x17, 0x07, 0x40, 0x3f, 0x24, 0x01, 0x3c, 0x0a, 0x1f, 0xa6, 0x04, 0x53, 0x49, 0x62, 0xed, + 0x76, 0x7e, 0xf8, 0x45, 0x25, 0xb6, 0xaa, 0xfc, 0x1f, 0x9a, 0xfc, 0x35, 0xaa, 0x30, 0x3a, 0x48, + 0x1d, 0xcb, 0xdb, 0x52, 0xab, 0xba, 0xdf, 0xd0, 0xc5, 0xb8, 0xe8, 0xe9, 0xb8, 0xe8, 0xf9, 0xb8, + 0xe8, 0x07, 0x94, 0x84, 0xe6, 0xd6, 0x8b, 0x58, 0x5b, 0x49, 0x62, 0x6d, 0x43, 0xb8, 0x0a, 0x19, + 0xb6, 0x72, 0xbd, 0xfc, 0x14, 0x55, 0x5d, 0x60, 0x9c, 0x84, 0x0e, 0x27, 0x34, 0x54, 0x56, 0x5f, + 0x67, 0xa7, 0xe6, 0x76, 0xb2, 0xb0, 0x2b, 0x68, 0xb1, 0x55, 0x74, 0xc2, 0x2a, 0x52, 0x66, 0xcf, + 0xde, 0x02, 0xd6, 0xa7, 0x21, 0x03, 0x3c, 0x2a, 0xa1, 0x4d, 0x01, 0x76, 0xb2, 0x53, 0xf8, 0x1f, + 0x75, 0xe6, 0xfe, 0x54, 0x67, 0xd6, 0xcc, 0xcd, 0xeb, 0x3f, 0x7a, 0xf9, 0x67, 0x09, 0xd5, 0x03, + 0x67, 0x48, 0x82, 0x41, 0x60, 0x33, 0x9f, 0xf4, 0xfb, 0x8e, 0x07, 0x4a, 0x25, 0xdb, 0xce, 0x0f, + 0xa9, 0xc7, 0xef, 0xb1, 0xb6, 0xeb, 0x11, 0xfe, 0xe3, 0xa0, 0xab, 0xf7, 0x68, 0x60, 0xe4, 0x49, + 0x23, 0x3e, 0xf6, 0x98, 0x7b, 0x6c, 0xf0, 0xb3, 0x3e, 0x30, 0xfd, 0x4b, 0xe8, 0x8d, 0x62, 0xad, + 0xda, 0x71, 0x86, 0x4f, 0x72, 0x93, 0x24, 0xd6, 0xee, 0x8a, 0xe2, 0xb3, 0xf6, 0xd8, 0xba, 0x95, + 0x2f, 0x8d, 0xb9, 0xf8, 0x1e, 0x6a, 0xcc, 0xf5, 0x78, 0x32, 0x01, 0x3f, 0xa1, 0x5a, 0x87, 0x79, + 0x07, 0x4e, 0xd8, 0x03, 0xff, 0xda, 0xbb, 0x8f, 0x15, 0x74, 0x67, 0xba, 0xfa, 0x64, 0x5f, 0xbf, + 0x96, 0x91, 0x3a, 0x81, 0x2c, 0xe8, 0xfb, 0x4e, 0x0f, 0x2e, 0x11, 0x1e, 0xcf, 0x91, 0x42, 0x23, + 0xe2, 0x91, 0xd0, 0xf1, 0xed, 0x8b, 0x77, 0xfb, 0xe9, 0x28, 0xd6, 0x36, 0x1f, 0x47, 0xc4, 0x3b, + 0x28, 0xee, 0x2c, 0x89, 0x35, 0x2d, 0xf7, 0x5b, 0x20, 0xc7, 0xd6, 0xd6, 0x18, 0x9a, 0x52, 0xca, + 0x0e, 0x7a, 0x37, 0x84, 0xd3, 0xb9, 0x6a, 0xa5, 0xac, 0xda, 0xfe, 0x28, 0xd6, 0xea, 0xdf, 0xc2, + 0xe9, 0x6c, 0x31, 0x55, 0x14, 0xbb, 0x40, 0x88, 0xad, 0x7a, 0x38, 0xc3, 0x9f, 0xff, 0xd1, 0x94, + 0xaf, 0x3c, 0xce, 0x56, 0xaf, 0x36, 0xce, 0x2a, 0x57, 0x16, 0x67, 0x3b, 0x08, 0x2f, 0x9e, 0x8b, + 0xc9, 0xf8, 0xbc, 0x2a, 0xa3, 0x7b, 0xb3, 0xb4, 0xcb, 0x44, 0xdc, 0xdb, 0xf9, 0xb9, 0x64, 0xe8, + 0xae, 0xfe, 0xcb, 0xd0, 0xad, 0xfc, 0xb7, 0xa1, 0x7b, 0xf3, 0xba, 0x43, 0xf7, 0x43, 0xf4, 0xc1, + 0x92, 0xf9, 0x1b, 0xcf, 0xe9, 0xfe, 0xab, 0x12, 0x2a, 0x75, 0x98, 0x97, 0x76, 0x64, 0xfa, 0xdf, + 0x51, 0x73, 0xba, 0x17, 0xb3, 0x4f, 0x70, 0x75, 0x77, 0x39, 0x3e, 0x2e, 0x20, 0x3f, 0x43, 0xb5, + 0x99, 0xa7, 0xbb, 0x76, 0x91, 0xb2, 0x40, 0x50, 0x3f, 0x7a, 0x0d, 0x61, 0xe2, 0xfd, 0x3d, 0xaa, + 0x16, 0x1f, 0x1c, 0xef, 0xcd, 0xe9, 0x0a, 0xa8, 0xba, 0xb3, 0x0c, 0x9d, 0x58, 0x0e, 0xd0, 0xdd, + 0x45, 0x91, 0xdf, 0x5a, 0x60, 0x30, 0xc7, 0x54, 0x3f, 0x7e, 0x53, 0xe6, 0xa4, 0xec, 0x10, 0x29, + 0x0b, 0xa3, 0xe2, 0xfe, 0x72, 0xb7, 0xe2, 0xc9, 0xb5, 0xdf, 0x98, 0x3a, 0xae, 0x6c, 0x7e, 0xf5, + 0x62, 0xd4, 0x94, 0x5e, 0x8e, 0x9a, 0xd2, 0x1f, 0xa3, 0xa6, 0xf4, 0xcb, 0x79, 0x73, 0xe5, 0xe5, + 0x79, 0x73, 0xe5, 0xb7, 0xf3, 0xe6, 0xca, 0xb3, 0x07, 0x85, 0x21, 0x85, 0xbd, 0x80, 0x86, 0x70, + 0x66, 0x40, 0xb0, 0xe7, 0x83, 0xeb, 0x41, 0x64, 0x0c, 0xc7, 0x2f, 0x1d, 0xd9, 0xb4, 0x76, 0x2b, + 0xd9, 0x0b, 0xc5, 0x27, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x64, 0x77, 0xc4, 0x23, 0xe9, 0x0c, + 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -877,6 +951,44 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ Metadata: "em/market/v1/tx.proto", } +func (m *TxParams) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *TxParams) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *TxParams) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.LiquidityRebateMinutesSpan != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.LiquidityRebateMinutesSpan)) + i-- + dAtA[i] = 0x18 + } + if m.LiquidTrxFee != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.LiquidTrxFee)) + i-- + dAtA[i] = 0x10 + } + if m.TrxFee != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.TrxFee)) + i-- + dAtA[i] = 0x8 + } + return len(dAtA) - i, nil +} + func (m *MsgAddLimitOrder) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1316,6 +1428,24 @@ func encodeVarintTx(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return base } +func (m *TxParams) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.TrxFee != 0 { + n += 1 + sovTx(uint64(m.TrxFee)) + } + if m.LiquidTrxFee != 0 { + n += 1 + sovTx(uint64(m.LiquidTrxFee)) + } + if m.LiquidityRebateMinutesSpan != 0 { + n += 1 + sovTx(uint64(m.LiquidityRebateMinutesSpan)) + } + return n +} + func (m *MsgAddLimitOrder) Size() (n int) { if m == nil { return 0 @@ -1496,6 +1626,113 @@ func sovTx(x uint64) (n int) { func sozTx(x uint64) (n int) { return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (m *TxParams) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TxParams: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TxParams: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TrxFee", wireType) + } + m.TrxFee = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.TrxFee |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LiquidTrxFee", wireType) + } + m.LiquidTrxFee = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.LiquidTrxFee |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LiquidityRebateMinutesSpan", wireType) + } + m.LiquidityRebateMinutesSpan = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.LiquidityRebateMinutesSpan |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *MsgAddLimitOrder) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/market/types/tx_params.go b/x/market/types/tx_params.go new file mode 100644 index 00000000..fb2a2e22 --- /dev/null +++ b/x/market/types/tx_params.go @@ -0,0 +1,116 @@ +package types + +import ( + "errors" + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" +) + +const ( + defaultTrxFee = 25000 + defaultLiquidMinutesSpan = 5 +) + +var ( + // DefaultLiquidTrxFee zero by default + DefaultLiquidTrxFee = sdk.ZeroInt() + + // KeyTrxFee is store's key for TrxFee Param + KeyTrxFee = []byte("TrxFee") + // KeyLiquidTrxFee is store's key for the LiquidTrxFee + KeyLiquidTrxFee = []byte("LiquidTrxFee") + // KeyLiquidityRebateMinutesSpan is store's key for the + // LiquidityRebateMinutesSpan + KeyLiquidityRebateMinutesSpan = []byte("LiquidityRebateMinutesSpan") +) + +// ParamKeyTable for bank module. +func ParamKeyTable() paramtypes.KeyTable { + return paramtypes.NewKeyTable().RegisterParamSet(&TxParams{}) +} + +// ParamSetPairs implements params.ParamSet or map +func (p *TxParams) ParamSetPairs() paramtypes.ParamSetPairs { + return paramtypes.ParamSetPairs{ + paramtypes.NewParamSetPair(KeyTrxFee, &p.TrxFee, validateIsUInt), + paramtypes.NewParamSetPair(KeyLiquidTrxFee, &p.LiquidTrxFee, validateIsUInt), + paramtypes.NewParamSetPair( + KeyLiquidityRebateMinutesSpan, &p.LiquidityRebateMinutesSpan, + validateTimeSpan, + ), + } +} + +// NewTxParams creates a new parameter configuration for the market module +func NewTxParams(trxFee, liquidTrxFee uint64, liquidityRebateMinutes int64) TxParams { + return TxParams{ + TrxFee: trxFee, + LiquidTrxFee: liquidTrxFee, + LiquidityRebateMinutesSpan: liquidityRebateMinutes, + } +} + +// DefaultTxParams are the default Trx market parameters. +func DefaultTxParams() TxParams { + return TxParams{ + TrxFee: defaultTrxFee, + LiquidTrxFee: 0, + LiquidityRebateMinutesSpan: defaultLiquidMinutesSpan, + } +} + +// Validate all Tx Market Params parameters +func (p TxParams) Validate() error { + if err := validateIsNonZeroUInt(p.TrxFee); err != nil { + return err + } + + if err := validateIsUInt(p.LiquidTrxFee); err != nil { + return err + } + + if p.TrxFee < p.LiquidTrxFee { + return fmt.Errorf( + "standard fee:%d is less than liquid trx fee:%d", p.TrxFee, + p.LiquidTrxFee, + ) + } + + return validateTimeSpan(p.LiquidityRebateMinutesSpan) +} + +func validateTimeSpan(i interface{}) error { + m, ok := i.(int64) + + if !ok { + return fmt.Errorf("invalid minutes parameter type: %T", i) + } + + if m < 0 { + return fmt.Errorf("minutes parameter cannot be < 0: %d", m) + } + + return nil +} + +func validateIsUInt(u interface{}) error { + _, ok := u.(uint64) + if !ok { + return fmt.Errorf("invalid parameter type: %T", u) + } + + return nil +} + +func validateIsNonZeroUInt(u interface{}) error { + v, ok := u.(uint64) + if !ok { + return fmt.Errorf("invalid parameter type: %T", u) + } + if v == 0 { + return errors.New("cannot be 0") + } + + return nil +} diff --git a/x/market/types/tx_params_test.go b/x/market/types/tx_params_test.go new file mode 100644 index 00000000..960ead32 --- /dev/null +++ b/x/market/types/tx_params_test.go @@ -0,0 +1,31 @@ +package types + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func Test_ParamsEqual(t *testing.T) { + paramsA := NewTxParams(1, 0, 1) + err := paramsA.Validate() + require.NoError(t, err) + paramsB := NewTxParams(1, 0, 1) + err = paramsB.Validate() + require.NoError(t, err) + paramsC := NewTxParams(1, 1, 1) + err = paramsC.Validate() + require.NoError(t, err) + + require.True(t, paramsA.String() == paramsB.String()) + + require.False(t, paramsA.String() == paramsC.String()) +} + +func TestValidateParams(t *testing.T) { + require.NoError(t, DefaultTxParams().Validate()) + require.NoError(t, NewTxParams(1, 0, 5).Validate()) + require.NoError(t, NewTxParams(1, 1, 5).Validate()) + require.Error(t, NewTxParams(1, 2, 5).Validate()) + require.Error(t, NewTxParams(0, 0, 5).Validate()) + require.Error(t, NewTxParams(0, 0, -1).Validate()) +} \ No newline at end of file diff --git a/x/market/types/types.go b/x/market/types/types.go index f3c9caff..d853a3b7 100644 --- a/x/market/types/types.go +++ b/x/market/types/types.go @@ -31,7 +31,8 @@ func (o Order) MarshalJSON() ([]byte, error) { "denom": "%v", "amount": "%v" }, - "destination_filled": "%v" + "destination_filled": "%v", + "created": "%v" } `, o.ID, @@ -46,6 +47,7 @@ func (o Order) MarshalJSON() ([]byte, error) { o.Destination.Denom, o.Destination.Amount, o.DestinationFilled, + o.Created, ) return []byte(s), nil @@ -83,7 +85,7 @@ func (o Order) Price() sdk.Dec { } func (o Order) String() string { - return fmt.Sprintf("%d : %v -> %v @ %v\n(%v%v remaining) (%v%v filled) (%v%v filled)\n%v", o.ID, o.Source, o.Destination, o.Price(), o.SourceRemaining, o.Source.Denom, o.SourceFilled, o.Source.Denom, o.DestinationFilled, o.Destination.Denom, o.Owner) + return fmt.Sprintf("%d : %v -> %v @ %v\n(%v%v remaining) (%v%v filled) (%v%v filled)\n%v\nCreated:%v\n", o.ID, o.Source, o.Destination, o.Price(), o.SourceRemaining, o.Source.Denom, o.SourceFilled, o.Source.Denom, o.DestinationFilled, o.Destination.Denom, o.Owner, o.Created) } func (ep ExecutionPlan) DestinationCapacity() sdk.Dec { diff --git a/x/market/types/types_test.go b/x/market/types/types_test.go index 7a2874d9..d3a22033 100644 --- a/x/market/types/types_test.go +++ b/x/market/types/types_test.go @@ -15,7 +15,10 @@ import ( func TestSerialization(t *testing.T) { // Verify that non-public fields survive de-/serialization tm := time.Now() - order1, _ := NewOrder(tm, TimeInForce_GoodTillCancel, coin("100eur"), coin("120usd"), []byte("acc1"), "A") + order1, _ := NewOrder( + tm, TimeInForce_GoodTillCancel, coin("100eur"), coin("120usd"), + []byte("acc1"), "A", + ) order1.ID = 3123 order1.SourceRemaining = sdk.NewInt(50) order1.SourceFilled = sdk.NewInt(10) @@ -55,15 +58,24 @@ func TestSerialization(t *testing.T) { func TestInvalidOrder(t *testing.T) { // 0 amount source - _, err := NewOrder(time.Now(), TimeInForce_GoodTillCancel, coin("0eur"), coin("120usd"), []byte("acc"), "A") + _, err := NewOrder( + time.Now(), TimeInForce_GoodTillCancel, coin("0eur"), coin("120usd"), + []byte("acc"), "A", + ) require.Error(t, err) // 0 amount destination - _, err = NewOrder(time.Now(), TimeInForce_GoodTillCancel, coin("120eur"), coin("0usd"), []byte("acc"), "A") + _, err = NewOrder( + time.Now(), TimeInForce_GoodTillCancel, coin("120eur"), coin("0usd"), + []byte("acc"), "A", + ) require.Error(t, err) // Same denomination - _, err = NewOrder(time.Now(), TimeInForce_GoodTillCancel, coin("1000eur"), coin("850eur"), []byte("acc"), "A") + _, err = NewOrder( + time.Now(), TimeInForce_GoodTillCancel, coin("1000eur"), coin("850eur"), + []byte("acc"), "A", + ) require.Error(t, err) c := sdk.Coin{ @@ -72,7 +84,10 @@ func TestInvalidOrder(t *testing.T) { } // Negative source - _, err = NewOrder(time.Now(), TimeInForce_GoodTillCancel, c, coin("120usd"), []byte("acc"), "B") + _, err = NewOrder( + time.Now(), TimeInForce_GoodTillCancel, c, coin("120usd"), + []byte("acc"), "B", + ) require.Error(t, err) }