mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-10 10:05:18 +00:00
Swap module scaffolding and params (#922)
* swap module scaffolding * global swap fee * can't think of a reason for begin blocker; removing for abci.go for now; * test pair types; refactor pair name logic; simplify pairs validation and fix stack overflow error * check comparison * use test package * init swap module genesis * add basic marshall tests * remove reward apy from pairs * fix integration helpers * use max swap fee constant; fix validation of swap fee; add tests to cover param validation and param set setup * use noerror over nil * start genesis tests * test param set validation mirrors param validation * add genesis tests * remove print statement * add subtests for genesis test cases; add extra querier test for unknown route; add keeper params testing * add spec * update swagger * find replace hard -> swap in comments * remove unused method * rename pairs to allowed pools; pool is more commonly used, and allowedPool makes it more clear what swap parameter is for. In addition, we won't conflict with Pool data structure for storing a created pool in the store. * remove generated link * missed spec rename * validate token order for allowed pools * fix swagger * json should be snakecase; change allowedPools to allowed_pools Co-authored-by: Nick DeLuca <nickdeluca08@gmail.com>
This commit is contained in:
parent
cae7503f7b
commit
9d9b169e6a
14
app/app.go
14
app/app.go
@ -43,6 +43,7 @@ import (
|
||||
"github.com/kava-labs/kava/x/issuance"
|
||||
"github.com/kava-labs/kava/x/kavadist"
|
||||
"github.com/kava-labs/kava/x/pricefeed"
|
||||
"github.com/kava-labs/kava/x/swap"
|
||||
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
||||
)
|
||||
|
||||
@ -85,6 +86,7 @@ var (
|
||||
incentive.AppModuleBasic{},
|
||||
issuance.AppModuleBasic{},
|
||||
hard.AppModuleBasic{},
|
||||
swap.AppModuleBasic{},
|
||||
)
|
||||
|
||||
// module account permissions
|
||||
@ -158,6 +160,7 @@ type App struct {
|
||||
incentiveKeeper incentive.Keeper
|
||||
issuanceKeeper issuance.Keeper
|
||||
hardKeeper hard.Keeper
|
||||
swapKeeper swap.Keeper
|
||||
|
||||
// the module manager
|
||||
mm *module.Manager
|
||||
@ -181,7 +184,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
|
||||
gov.StoreKey, params.StoreKey, upgrade.StoreKey, evidence.StoreKey,
|
||||
validatorvesting.StoreKey, auction.StoreKey, cdp.StoreKey, pricefeed.StoreKey,
|
||||
bep3.StoreKey, kavadist.StoreKey, incentive.StoreKey, issuance.StoreKey, committee.StoreKey,
|
||||
hard.StoreKey,
|
||||
hard.StoreKey, swap.StoreKey,
|
||||
)
|
||||
tkeys := sdk.NewTransientStoreKeys(params.TStoreKey)
|
||||
|
||||
@ -212,6 +215,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
|
||||
incentiveSubspace := app.paramsKeeper.Subspace(incentive.DefaultParamspace)
|
||||
issuanceSubspace := app.paramsKeeper.Subspace(issuance.DefaultParamspace)
|
||||
hardSubspace := app.paramsKeeper.Subspace(hard.DefaultParamspace)
|
||||
swapSubspace := app.paramsKeeper.Subspace(swap.DefaultParamspace)
|
||||
|
||||
// add keepers
|
||||
app.accountKeeper = auth.NewAccountKeeper(
|
||||
@ -393,6 +397,11 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
|
||||
app.accountKeeper,
|
||||
app.supplyKeeper,
|
||||
)
|
||||
app.swapKeeper = swap.NewKeeper(
|
||||
app.cdc,
|
||||
keys[swap.StoreKey],
|
||||
swapSubspace,
|
||||
)
|
||||
|
||||
// register the staking hooks
|
||||
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
|
||||
@ -428,6 +437,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
|
||||
committee.NewAppModule(app.committeeKeeper, app.accountKeeper),
|
||||
issuance.NewAppModule(app.issuanceKeeper, app.accountKeeper, app.supplyKeeper),
|
||||
hard.NewAppModule(app.hardKeeper, app.supplyKeeper, app.pricefeedKeeper),
|
||||
swap.NewAppModule(app.swapKeeper),
|
||||
)
|
||||
|
||||
// During begin block slashing happens after distr.BeginBlocker so that
|
||||
@ -448,7 +458,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, appOpts AppOptio
|
||||
validatorvesting.ModuleName, distr.ModuleName,
|
||||
staking.ModuleName, bank.ModuleName, slashing.ModuleName,
|
||||
gov.ModuleName, mint.ModuleName, evidence.ModuleName,
|
||||
pricefeed.ModuleName, cdp.ModuleName, hard.ModuleName, auction.ModuleName,
|
||||
pricefeed.ModuleName, cdp.ModuleName, hard.ModuleName, auction.ModuleName, swap.ModuleName,
|
||||
bep3.ModuleName, kavadist.ModuleName, incentive.ModuleName, committee.ModuleName, issuance.ModuleName,
|
||||
supply.ModuleName, // calculates the total supply from account - should run after modules that modify accounts in genesis
|
||||
crisis.ModuleName, // runs the invariants at genesis - should run after other modules
|
||||
|
@ -38,6 +38,7 @@ import (
|
||||
"github.com/kava-labs/kava/x/issuance"
|
||||
"github.com/kava-labs/kava/x/kavadist"
|
||||
"github.com/kava-labs/kava/x/pricefeed"
|
||||
"github.com/kava-labs/kava/x/swap"
|
||||
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
|
||||
)
|
||||
|
||||
@ -87,6 +88,7 @@ func (tApp TestApp) GetIncentiveKeeper() incentive.Keeper { return tApp.incentiv
|
||||
func (tApp TestApp) GetHardKeeper() hard.Keeper { return tApp.hardKeeper }
|
||||
func (tApp TestApp) GetCommitteeKeeper() committee.Keeper { return tApp.committeeKeeper }
|
||||
func (tApp TestApp) GetIssuanceKeeper() issuance.Keeper { return tApp.issuanceKeeper }
|
||||
func (tApp TestApp) GetSwapKeeper() swap.Keeper { return tApp.swapKeeper }
|
||||
|
||||
// InitializeFromGenesisStates calls InitChain on the app using the default genesis state, overwitten with any passed in genesis states
|
||||
func (tApp TestApp) InitializeFromGenesisStates(genesisStates ...GenesisState) TestApp {
|
||||
|
@ -2409,7 +2409,29 @@ paths:
|
||||
$ref: "#/definitions/Coin"
|
||||
500:
|
||||
description: Server internal error
|
||||
|
||||
/swap/parameters:
|
||||
get:
|
||||
summary: Get the current parameters of the swap module
|
||||
tags:
|
||||
- Swap
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
200:
|
||||
description: Swap module parameters
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
height:
|
||||
type: string
|
||||
example: "100"
|
||||
result:
|
||||
type: array
|
||||
x-nullable: true
|
||||
items:
|
||||
$ref: "#/definitions/SwapParams"
|
||||
500:
|
||||
description: Server internal error
|
||||
/bank/accounts/{address}/transfers:
|
||||
post:
|
||||
summary: Send coins from one account to another
|
||||
@ -4837,6 +4859,25 @@ definitions:
|
||||
active:
|
||||
type: boolean
|
||||
example: true
|
||||
SwapParams:
|
||||
type: object
|
||||
properties:
|
||||
allowed_pools:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/AllowedPool"
|
||||
swap_fee:
|
||||
type: string
|
||||
example: "0.03"
|
||||
AllowedPool:
|
||||
type: object
|
||||
properties:
|
||||
token_a:
|
||||
type: string
|
||||
example: "ukava"
|
||||
token_b:
|
||||
type: string
|
||||
example: "usdx"
|
||||
HardParams:
|
||||
type: object
|
||||
properties:
|
||||
|
28
x/swap/alias.go
Normal file
28
x/swap/alias.go
Normal file
@ -0,0 +1,28 @@
|
||||
package swap
|
||||
|
||||
import (
|
||||
"github.com/kava-labs/kava/x/swap/keeper"
|
||||
"github.com/kava-labs/kava/x/swap/types"
|
||||
)
|
||||
|
||||
const (
|
||||
ModuleName = types.ModuleName
|
||||
QuerierRoute = types.QuerierRoute
|
||||
RouterKey = types.RouterKey
|
||||
StoreKey = types.StoreKey
|
||||
DefaultParamspace = types.DefaultParamspace
|
||||
)
|
||||
|
||||
type (
|
||||
GenesisState = types.GenesisState
|
||||
Keeper = keeper.Keeper
|
||||
)
|
||||
|
||||
var (
|
||||
NewKeeper = keeper.NewKeeper
|
||||
NewQuerier = keeper.NewQuerier
|
||||
ModuleCdc = types.ModuleCdc
|
||||
ParamKeyTable = types.ParamKeyTable
|
||||
RegisterCodec = types.RegisterCodec
|
||||
DefaultGenesisState = types.DefaultGenesisState
|
||||
)
|
58
x/swap/client/cli/query.go
Normal file
58
x/swap/client/cli/query.go
Normal file
@ -0,0 +1,58 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
|
||||
"github.com/kava-labs/kava/x/swap/types"
|
||||
)
|
||||
|
||||
// GetQueryCmd returns the cli query commands for the module
|
||||
func GetQueryCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
swapQueryCmd := &cobra.Command{
|
||||
Use: types.ModuleName,
|
||||
Short: "Querying commands for the swap module",
|
||||
DisableFlagParsing: true,
|
||||
SuggestionsMinimumDistance: 2,
|
||||
RunE: client.ValidateCmd,
|
||||
}
|
||||
|
||||
swapQueryCmd.AddCommand(flags.GetCommands(
|
||||
queryParamsCmd(queryRoute, cdc),
|
||||
)...)
|
||||
|
||||
return swapQueryCmd
|
||||
}
|
||||
|
||||
func queryParamsCmd(queryRoute string, cdc *codec.Codec) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "params",
|
||||
Short: "get the swap module parameters",
|
||||
Long: "Get the current global swap module parameters.",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
// Query
|
||||
route := fmt.Sprintf("custom/%s/%s", queryRoute, types.QueryGetParams)
|
||||
res, height, err := cliCtx.QueryWithData(route, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
|
||||
// Decode and print results
|
||||
var params types.Params
|
||||
if err := cdc.UnmarshalJSON(res, ¶ms); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal params: %w", err)
|
||||
}
|
||||
return cliCtx.PrintOutput(params)
|
||||
},
|
||||
}
|
||||
}
|
28
x/swap/client/cli/tx.go
Normal file
28
x/swap/client/cli/tx.go
Normal file
@ -0,0 +1,28 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
|
||||
"github.com/kava-labs/kava/x/swap/types"
|
||||
)
|
||||
|
||||
// GetTxCmd returns the transaction commands for this module
|
||||
func GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
swapTxCmd := &cobra.Command{
|
||||
Use: types.ModuleName,
|
||||
Short: fmt.Sprintf("%s transactions subcommands", types.ModuleName),
|
||||
DisableFlagParsing: true,
|
||||
SuggestionsMinimumDistance: 2,
|
||||
RunE: client.ValidateCmd,
|
||||
}
|
||||
|
||||
swapTxCmd.AddCommand(flags.PostCommands()...)
|
||||
|
||||
return swapTxCmd
|
||||
}
|
37
x/swap/client/rest/query.go
Normal file
37
x/swap/client/rest/query.go
Normal file
@ -0,0 +1,37 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
|
||||
"github.com/kava-labs/kava/x/swap/types"
|
||||
)
|
||||
|
||||
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
r.HandleFunc(fmt.Sprintf("/%s/parameters", types.ModuleName), queryParamsHandlerFn(cliCtx)).Methods("GET")
|
||||
}
|
||||
|
||||
func queryParamsHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
cliCtx, ok := rest.ParseQueryHeightOrReturnBadRequest(w, cliCtx, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryGetParams)
|
||||
|
||||
res, height, err := cliCtx.QueryWithData(route, nil)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
cliCtx = cliCtx.WithHeight(height)
|
||||
rest.PostProcessResponse(w, cliCtx, res)
|
||||
}
|
||||
}
|
17
x/swap/client/rest/rest.go
Normal file
17
x/swap/client/rest/rest.go
Normal file
@ -0,0 +1,17 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
)
|
||||
|
||||
// REST variable names
|
||||
// nolint
|
||||
const ()
|
||||
|
||||
// RegisterRoutes registers swap-related REST handlers to a router
|
||||
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
registerQueryRoutes(cliCtx, r)
|
||||
registerTxRoutes(cliCtx, r)
|
||||
}
|
9
x/swap/client/rest/tx.go
Normal file
9
x/swap/client/rest/tx.go
Normal file
@ -0,0 +1,9 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
)
|
||||
|
||||
func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) {}
|
24
x/swap/genesis.go
Normal file
24
x/swap/genesis.go
Normal file
@ -0,0 +1,24 @@
|
||||
package swap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/swap/types"
|
||||
)
|
||||
|
||||
// InitGenesis initializes story state from genesis file
|
||||
func InitGenesis(ctx sdk.Context, k Keeper, gs types.GenesisState) {
|
||||
if err := gs.Validate(); err != nil {
|
||||
panic(fmt.Sprintf("failed to validate %s genesis state: %s", ModuleName, err))
|
||||
}
|
||||
|
||||
k.SetParams(ctx, gs.Params)
|
||||
}
|
||||
|
||||
// ExportGenesis exports the genesis state
|
||||
func ExportGenesis(ctx sdk.Context, k Keeper) types.GenesisState {
|
||||
params := k.GetParams(ctx)
|
||||
return types.NewGenesisState(params)
|
||||
}
|
17
x/swap/handler.go
Normal file
17
x/swap/handler.go
Normal file
@ -0,0 +1,17 @@
|
||||
package swap
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
)
|
||||
|
||||
// NewHandler creates an sdk.Handler for swap messages
|
||||
func NewHandler(k Keeper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) {
|
||||
ctx = ctx.WithEventManager(sdk.NewEventManager())
|
||||
switch msg := msg.(type) {
|
||||
default:
|
||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg)
|
||||
}
|
||||
}
|
||||
}
|
32
x/swap/keeper/integration_test.go
Normal file
32
x/swap/keeper/integration_test.go
Normal file
@ -0,0 +1,32 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/swap/types"
|
||||
)
|
||||
|
||||
func i(in int64) sdk.Int { return sdk.NewInt(in) }
|
||||
func c(denom string, amount int64) sdk.Coin { return sdk.NewInt64Coin(denom, amount) }
|
||||
func cs(coins ...sdk.Coin) sdk.Coins { return sdk.NewCoins(coins...) }
|
||||
|
||||
func NewAuthGenStateFromAccs(accounts ...authexported.GenesisAccount) app.GenesisState {
|
||||
authGenesis := auth.NewGenesisState(auth.DefaultParams(), accounts)
|
||||
return app.GenesisState{auth.ModuleName: auth.ModuleCdc.MustMarshalJSON(authGenesis)}
|
||||
}
|
||||
|
||||
func NewSwapGenStateMulti() app.GenesisState {
|
||||
swapGenesis := types.GenesisState{
|
||||
Params: types.Params{
|
||||
AllowedPools: types.AllowedPools{
|
||||
types.NewAllowedPool("ukava", "usdx"),
|
||||
},
|
||||
SwapFee: sdk.MustNewDecFromStr("0.03"),
|
||||
},
|
||||
}
|
||||
|
||||
return app.GenesisState{types.ModuleName: types.ModuleCdc.MustMarshalJSON(swapGenesis)}
|
||||
}
|
29
x/swap/keeper/keeper.go
Normal file
29
x/swap/keeper/keeper.go
Normal file
@ -0,0 +1,29 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/params/subspace"
|
||||
|
||||
"github.com/kava-labs/kava/x/swap/types"
|
||||
)
|
||||
|
||||
// Keeper keeper for the swap module
|
||||
type Keeper struct {
|
||||
key sdk.StoreKey
|
||||
cdc *codec.Codec
|
||||
paramSubspace subspace.Subspace
|
||||
}
|
||||
|
||||
// NewKeeper creates a new keeper
|
||||
func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramstore subspace.Subspace) Keeper {
|
||||
if !paramstore.HasKeyTable() {
|
||||
paramstore = paramstore.WithKeyTable(types.ParamKeyTable())
|
||||
}
|
||||
|
||||
return Keeper{
|
||||
key: key,
|
||||
cdc: cdc,
|
||||
paramSubspace: paramstore,
|
||||
}
|
||||
}
|
19
x/swap/keeper/params.go
Normal file
19
x/swap/keeper/params.go
Normal file
@ -0,0 +1,19 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/swap/types"
|
||||
)
|
||||
|
||||
// GetParams returns the params from the store
|
||||
func (k Keeper) GetParams(ctx sdk.Context) types.Params {
|
||||
var p types.Params
|
||||
k.paramSubspace.GetParamSet(ctx, &p)
|
||||
return p
|
||||
}
|
||||
|
||||
// SetParams sets params on the store
|
||||
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) {
|
||||
k.paramSubspace.SetParamSet(ctx, ¶ms)
|
||||
}
|
39
x/swap/keeper/params_test.go
Normal file
39
x/swap/keeper/params_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/swap/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
)
|
||||
|
||||
func TestParams_SetterAndGetter(t *testing.T) {
|
||||
tApp := app.NewTestApp()
|
||||
keeper := tApp.GetSwapKeeper()
|
||||
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||
params := types.Params{
|
||||
AllowedPools: types.AllowedPools{
|
||||
types.NewAllowedPool("ukava", "usdx"),
|
||||
},
|
||||
SwapFee: sdk.MustNewDecFromStr("0.03"),
|
||||
}
|
||||
keeper.SetParams(ctx, params)
|
||||
assert.Equal(t, keeper.GetParams(ctx), params)
|
||||
|
||||
oldParams := params
|
||||
params = types.Params{
|
||||
AllowedPools: types.AllowedPools{
|
||||
types.NewAllowedPool("hard", "ukava"),
|
||||
},
|
||||
SwapFee: sdk.MustNewDecFromStr("0.01"),
|
||||
}
|
||||
keeper.SetParams(ctx, params)
|
||||
assert.NotEqual(t, keeper.GetParams(ctx), oldParams)
|
||||
assert.Equal(t, keeper.GetParams(ctx), params)
|
||||
}
|
36
x/swap/keeper/querier.go
Normal file
36
x/swap/keeper/querier.go
Normal file
@ -0,0 +1,36 @@
|
||||
package keeper
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
"github.com/kava-labs/kava/x/swap/types"
|
||||
)
|
||||
|
||||
// NewQuerier is the module level router for state queries
|
||||
func NewQuerier(k Keeper) sdk.Querier {
|
||||
return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err error) {
|
||||
switch path[0] {
|
||||
case types.QueryGetParams:
|
||||
return queryGetParams(ctx, req, k)
|
||||
default:
|
||||
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint", types.ModuleName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// query params in the swap store
|
||||
func queryGetParams(ctx sdk.Context, req abci.RequestQuery, keeper Keeper) ([]byte, error) {
|
||||
// Get params
|
||||
params := keeper.GetParams(ctx)
|
||||
|
||||
// Encode results
|
||||
bz, err := codec.MarshalJSONIndent(types.ModuleCdc, params)
|
||||
if err != nil {
|
||||
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error())
|
||||
}
|
||||
return bz, nil
|
||||
}
|
65
x/swap/keeper/querier_test.go
Normal file
65
x/swap/keeper/querier_test.go
Normal file
@ -0,0 +1,65 @@
|
||||
package keeper_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
tmtime "github.com/tendermint/tendermint/types/time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/kava-labs/kava/app"
|
||||
"github.com/kava-labs/kava/x/swap/keeper"
|
||||
"github.com/kava-labs/kava/x/swap/types"
|
||||
)
|
||||
|
||||
type QuerierTestSuite struct {
|
||||
suite.Suite
|
||||
keeper keeper.Keeper
|
||||
app app.TestApp
|
||||
ctx sdk.Context
|
||||
querier sdk.Querier
|
||||
}
|
||||
|
||||
func (suite *QuerierTestSuite) SetupTest() {
|
||||
tApp := app.NewTestApp()
|
||||
ctx := tApp.NewContext(true, abci.Header{Height: 1, Time: tmtime.Now()})
|
||||
|
||||
tApp.InitializeFromGenesisStates(
|
||||
NewSwapGenStateMulti(),
|
||||
)
|
||||
|
||||
suite.ctx = ctx
|
||||
suite.app = tApp
|
||||
suite.keeper = tApp.GetSwapKeeper()
|
||||
suite.querier = keeper.NewQuerier(suite.keeper)
|
||||
}
|
||||
|
||||
func (suite *QuerierTestSuite) TestUnkownRequest() {
|
||||
ctx := suite.ctx.WithIsCheckTx(false)
|
||||
bz, err := suite.querier(ctx, []string{"invalid-path"}, abci.RequestQuery{})
|
||||
suite.Nil(bz)
|
||||
suite.EqualError(err, "unknown request: unknown swap query endpoint")
|
||||
}
|
||||
|
||||
func (suite *QuerierTestSuite) TestQueryParams() {
|
||||
ctx := suite.ctx.WithIsCheckTx(false)
|
||||
bz, err := suite.querier(ctx, []string{types.QueryGetParams}, abci.RequestQuery{})
|
||||
suite.Nil(err)
|
||||
suite.NotNil(bz)
|
||||
|
||||
var p types.Params
|
||||
suite.Nil(types.ModuleCdc.UnmarshalJSON(bz, &p))
|
||||
|
||||
swapGenesisState := NewSwapGenStateMulti()
|
||||
gs := types.GenesisState{}
|
||||
types.ModuleCdc.UnmarshalJSON(swapGenesisState["swap"], &gs)
|
||||
|
||||
suite.Equal(gs.Params, p)
|
||||
}
|
||||
|
||||
func TestQuerierTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(QuerierTestSuite))
|
||||
}
|
166
x/swap/module.go
Normal file
166
x/swap/module.go
Normal file
@ -0,0 +1,166 @@
|
||||
package swap
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/module"
|
||||
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
|
||||
// "github.com/kava-labs/kava/x/swap/simulation"
|
||||
"github.com/kava-labs/kava/x/swap/client/cli"
|
||||
"github.com/kava-labs/kava/x/swap/client/rest"
|
||||
"github.com/kava-labs/kava/x/swap/keeper"
|
||||
"github.com/kava-labs/kava/x/swap/types"
|
||||
)
|
||||
|
||||
var (
|
||||
_ module.AppModule = AppModule{}
|
||||
_ module.AppModuleBasic = AppModuleBasic{}
|
||||
// _ module.AppModuleSimulation = AppModule{}
|
||||
)
|
||||
|
||||
// AppModuleBasic app module basics object
|
||||
type AppModuleBasic struct{}
|
||||
|
||||
// Name get module name
|
||||
func (AppModuleBasic) Name() string {
|
||||
return ModuleName
|
||||
}
|
||||
|
||||
// RegisterCodec register module codec
|
||||
func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) {
|
||||
RegisterCodec(cdc)
|
||||
}
|
||||
|
||||
// DefaultGenesis default genesis state
|
||||
func (AppModuleBasic) DefaultGenesis() json.RawMessage {
|
||||
return ModuleCdc.MustMarshalJSON(DefaultGenesisState())
|
||||
}
|
||||
|
||||
// ValidateGenesis module validate genesis
|
||||
func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error {
|
||||
var gs GenesisState
|
||||
err := ModuleCdc.UnmarshalJSON(bz, &gs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gs.Validate()
|
||||
}
|
||||
|
||||
// RegisterRESTRoutes registers REST routes for the swap module.
|
||||
func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) {
|
||||
rest.RegisterRoutes(ctx, rtr)
|
||||
}
|
||||
|
||||
// GetTxCmd returns the root tx command for the swap module.
|
||||
func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command {
|
||||
return cli.GetTxCmd(cdc)
|
||||
}
|
||||
|
||||
// GetQueryCmd returns no root query command for the swap module.
|
||||
func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command {
|
||||
return cli.GetQueryCmd(types.StoreKey, cdc)
|
||||
}
|
||||
|
||||
//____________________________________________________________________________
|
||||
|
||||
// AppModule app module type
|
||||
type AppModule struct {
|
||||
AppModuleBasic
|
||||
|
||||
keeper Keeper
|
||||
}
|
||||
|
||||
// NewAppModule creates a new AppModule object
|
||||
func NewAppModule(keeper Keeper) AppModule {
|
||||
return AppModule{
|
||||
AppModuleBasic: AppModuleBasic{},
|
||||
keeper: keeper,
|
||||
}
|
||||
}
|
||||
|
||||
// Name module name
|
||||
func (AppModule) Name() string {
|
||||
return ModuleName
|
||||
}
|
||||
|
||||
// RegisterInvariants register module invariants
|
||||
func (AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {}
|
||||
|
||||
// Route module message route name
|
||||
func (AppModule) Route() string {
|
||||
return ModuleName
|
||||
}
|
||||
|
||||
// NewHandler module handler
|
||||
func (am AppModule) NewHandler() sdk.Handler {
|
||||
return NewHandler(am.keeper)
|
||||
}
|
||||
|
||||
// QuerierRoute module querier route name
|
||||
func (AppModule) QuerierRoute() string {
|
||||
return QuerierRoute
|
||||
}
|
||||
|
||||
// NewQuerierHandler returns no sdk.Querier.
|
||||
func (am AppModule) NewQuerierHandler() sdk.Querier {
|
||||
return keeper.NewQuerier(am.keeper)
|
||||
}
|
||||
|
||||
// InitGenesis module init-genesis
|
||||
func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate {
|
||||
var genesisState GenesisState
|
||||
ModuleCdc.MustUnmarshalJSON(data, &genesisState)
|
||||
InitGenesis(ctx, am.keeper, genesisState)
|
||||
|
||||
return []abci.ValidatorUpdate{}
|
||||
}
|
||||
|
||||
// ExportGenesis module export genesis
|
||||
func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage {
|
||||
gs := ExportGenesis(ctx, am.keeper)
|
||||
return ModuleCdc.MustMarshalJSON(gs)
|
||||
}
|
||||
|
||||
// BeginBlock module begin-block
|
||||
func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {
|
||||
}
|
||||
|
||||
// EndBlock module end-block
|
||||
func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate {
|
||||
return []abci.ValidatorUpdate{}
|
||||
}
|
||||
|
||||
//____________________________________________________________________________
|
||||
|
||||
// // GenerateGenesisState creates a randomized GenState of the swap module
|
||||
// func (AppModuleBasic) GenerateGenesisState(simState *module.SimulationState) {
|
||||
// simulation.RandomizedGenState(simState)
|
||||
// }
|
||||
|
||||
// // ProposalContents doesn't return any content functions for governance proposals.
|
||||
// func (AppModuleBasic) ProposalContents(_ module.SimulationState) []sim.WeightedProposalContent {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // RandomizedParams returns nil because swap has no params.
|
||||
// func (AppModuleBasic) RandomizedParams(r *rand.Rand) []sim.ParamChange {
|
||||
// return simulation.ParamChanges(r)
|
||||
// }
|
||||
|
||||
// // RegisterStoreDecoder registers a decoder for swap module's types
|
||||
// func (AppModuleBasic) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) {
|
||||
// sdr[StoreKey] = simulation.DecodeStore
|
||||
// }
|
||||
|
||||
// // WeightedOperations returns the all the swap module operations with their respective weights.
|
||||
// func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation {
|
||||
// return nil
|
||||
// }
|
13
x/swap/spec/01_concepts.md
Normal file
13
x/swap/spec/01_concepts.md
Normal file
@ -0,0 +1,13 @@
|
||||
<!--
|
||||
order: 1
|
||||
-->
|
||||
|
||||
# Concepts
|
||||
|
||||
## Automated Market Maker
|
||||
|
||||
The swap module provides for functionality and governance of an Automated Market Maker protocol. The main state transitions in the swap module include deposits/withdrawals to liquidity pools by liquidity providers and token swaps executed against liquidity pools by users. Each liquidity pool consists of a unique pair of two tokens. A global swap fee set by governance is paid by users to execute trades, with the proceeds going to the relevant pool's liquidity providers.
|
||||
|
||||
## SWP Token distribution
|
||||
|
||||
[See Incentive Module](../../incentive/spec/01_concepts.md)
|
35
x/swap/spec/02_state.md
Normal file
35
x/swap/spec/02_state.md
Normal file
@ -0,0 +1,35 @@
|
||||
<!--
|
||||
order: 2
|
||||
-->
|
||||
|
||||
# State
|
||||
|
||||
## Parameters and Genesis State
|
||||
|
||||
`Parameters` define the governance parameters and default behavior of the swap module.
|
||||
|
||||
```go
|
||||
// Params are governance parameters for the swap module
|
||||
type Params struct {
|
||||
AllowedPools AllowedPools `json:"allowed_pools" yaml:"allowed_pools"`
|
||||
SwapFee sdk.Dec `json:"swap_fee" yaml:"swap_fee"`
|
||||
}
|
||||
|
||||
// AllowedPool defines a tradable pool
|
||||
type AllowedPool struct {
|
||||
TokenA string `json:"token_a" yaml:"token_a"`
|
||||
TokenB string `json:"token_b" yaml:"token_b"`
|
||||
}
|
||||
|
||||
// AllowedPools is a slice of AllowedPool
|
||||
type AllowedPools []AllowedPool
|
||||
```
|
||||
|
||||
`GenesisState` defines the state that must be persisted when the blockchain stops/restarts in order for the normal function of the swap module to resume.
|
||||
|
||||
```go
|
||||
// GenesisState is the state that must be provided at genesis.
|
||||
type GenesisState struct {
|
||||
Params Params `json:"params" yaml:"params"`
|
||||
}
|
||||
```
|
5
x/swap/spec/03_messages.md
Normal file
5
x/swap/spec/03_messages.md
Normal file
@ -0,0 +1,5 @@
|
||||
<!--
|
||||
order: 3
|
||||
-->
|
||||
|
||||
# Messages
|
9
x/swap/spec/04_events.md
Normal file
9
x/swap/spec/04_events.md
Normal file
@ -0,0 +1,9 @@
|
||||
<!--
|
||||
order: 4
|
||||
-->
|
||||
|
||||
# Events
|
||||
|
||||
The swap module emits the following events:
|
||||
|
||||
## Handlers
|
19
x/swap/spec/05_params.md
Normal file
19
x/swap/spec/05_params.md
Normal file
@ -0,0 +1,19 @@
|
||||
<!--
|
||||
order: 5
|
||||
-->
|
||||
|
||||
# Parameters
|
||||
|
||||
Example parameters for the swap module:
|
||||
|
||||
| Key | Type | Example | Description |
|
||||
| ------------ | ------------------- | ------------- | --------------------------------------- |
|
||||
| AllowedPools | array (AllowedPool) | [{see below}] | Array of tradable pools supported |
|
||||
| SwapFee | sdk.Dec | 0.03 | Global trading fee in percentage format |
|
||||
|
||||
Example parameters for `AllowedPool`:
|
||||
|
||||
| Key | Type | Example | Description |
|
||||
| ------ | ------ | ------- | ------------------- |
|
||||
| TokenA | string | "ukava" | First coin's denom |
|
||||
| TokenB | string | "usdx" | Second coin's denom |
|
20
x/swap/spec/README.md
Normal file
20
x/swap/spec/README.md
Normal file
@ -0,0 +1,20 @@
|
||||
<!--
|
||||
order: 0
|
||||
title: "Swap Overview"
|
||||
parent:
|
||||
title: "swap"
|
||||
-->
|
||||
|
||||
# `swap`
|
||||
|
||||
<!-- TOC -->
|
||||
|
||||
1. **[Concepts](01_concepts.md)**
|
||||
2. **[State](02_state.md)**
|
||||
3. **[Messages](03_messages.md)**
|
||||
4. **[Events](04_events.md)**
|
||||
5. **[Params](05_params.md)**
|
||||
|
||||
## Abstract
|
||||
|
||||
`x/swap` is a Cosmos SDK module that implements an Automated Market Maker (AMM) that enables users to swap coins by trading against liquidity pools.
|
18
x/swap/types/codec.go
Normal file
18
x/swap/types/codec.go
Normal file
@ -0,0 +1,18 @@
|
||||
package types
|
||||
|
||||
import "github.com/cosmos/cosmos-sdk/codec"
|
||||
|
||||
// ModuleCdc generic sealed codec to be used throughout module
|
||||
var ModuleCdc *codec.Codec
|
||||
|
||||
func init() {
|
||||
cdc := codec.New()
|
||||
RegisterCodec(cdc)
|
||||
codec.RegisterCrypto(cdc)
|
||||
ModuleCdc = cdc.Seal()
|
||||
}
|
||||
|
||||
// RegisterCodec registers the necessary types for swap module
|
||||
func RegisterCodec(cdc *codec.Codec) {
|
||||
cdc.RegisterConcrete(AllowedPool{}, "swap/AllowedPool", nil)
|
||||
}
|
11
x/swap/types/errors.go
Normal file
11
x/swap/types/errors.go
Normal file
@ -0,0 +1,11 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
)
|
||||
|
||||
// DONTCOVER
|
||||
|
||||
var (
|
||||
ErrCustom = sdkerrors.Register(ModuleName, 2, "")
|
||||
)
|
6
x/swap/types/events.go
Normal file
6
x/swap/types/events.go
Normal file
@ -0,0 +1,6 @@
|
||||
package types
|
||||
|
||||
// Event types for swap module
|
||||
const (
|
||||
// EventTypeCustom = ""
|
||||
)
|
42
x/swap/types/genesis.go
Normal file
42
x/swap/types/genesis.go
Normal file
@ -0,0 +1,42 @@
|
||||
package types
|
||||
|
||||
import "bytes"
|
||||
|
||||
// GenesisState is the state that must be provided at genesis.
|
||||
type GenesisState struct {
|
||||
Params Params `json:"params" yaml:"params"`
|
||||
}
|
||||
|
||||
// NewGenesisState creates a new genesis state.
|
||||
func NewGenesisState(params Params) GenesisState {
|
||||
return GenesisState{
|
||||
Params: params,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates the module's genesis state
|
||||
func (gs GenesisState) Validate() error {
|
||||
if err := gs.Params.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultGenesisState returns a default genesis state
|
||||
func DefaultGenesisState() GenesisState {
|
||||
return NewGenesisState(
|
||||
DefaultParams(),
|
||||
)
|
||||
}
|
||||
|
||||
// Equal checks whether two gov GenesisState structs are equivalent
|
||||
func (gs GenesisState) Equal(gs2 GenesisState) bool {
|
||||
b1 := ModuleCdc.MustMarshalBinaryBare(gs)
|
||||
b2 := ModuleCdc.MustMarshalBinaryBare(gs2)
|
||||
return bytes.Equal(b1, b2)
|
||||
}
|
||||
|
||||
// IsEmpty returns true if a GenesisState is empty
|
||||
func (gs GenesisState) IsEmpty() bool {
|
||||
return gs.Equal(GenesisState{})
|
||||
}
|
168
x/swap/types/genesis_test.go
Normal file
168
x/swap/types/genesis_test.go
Normal file
@ -0,0 +1,168 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kava-labs/kava/x/swap/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGenesis_Default(t *testing.T) {
|
||||
defaultGenesis := types.DefaultGenesisState()
|
||||
|
||||
require.NoError(t, defaultGenesis.Validate())
|
||||
|
||||
defaultParams := types.DefaultParams()
|
||||
assert.Equal(t, defaultParams, defaultGenesis.Params)
|
||||
}
|
||||
|
||||
func TestGenesis_Empty(t *testing.T) {
|
||||
emptyGenesis := types.GenesisState{}
|
||||
assert.True(t, emptyGenesis.IsEmpty())
|
||||
|
||||
emptyGenesis = types.GenesisState{
|
||||
Params: types.Params{},
|
||||
}
|
||||
assert.True(t, emptyGenesis.IsEmpty())
|
||||
}
|
||||
|
||||
func TestGenesis_NotEmpty(t *testing.T) {
|
||||
nonEmptyGenesis := types.GenesisState{
|
||||
Params: types.Params{
|
||||
AllowedPools: types.NewAllowedPools(types.NewAllowedPool("ukava", "hard")),
|
||||
SwapFee: sdk.ZeroDec(),
|
||||
},
|
||||
}
|
||||
assert.False(t, nonEmptyGenesis.IsEmpty())
|
||||
}
|
||||
|
||||
func TestGenesis_Validate_SwapFee(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
swapFee sdk.Dec
|
||||
expectErr bool
|
||||
}
|
||||
// More comprehensive swap fee tests are in prams_test.go
|
||||
testCases := []args{
|
||||
{
|
||||
"normal",
|
||||
sdk.MustNewDecFromStr("0.25"),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"negative",
|
||||
sdk.MustNewDecFromStr("-0.5"),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"greater than 1.0",
|
||||
sdk.MustNewDecFromStr("1.001"),
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
genesisState := types.GenesisState{
|
||||
Params: types.Params{
|
||||
AllowedPools: types.DefaultAllowedPools,
|
||||
SwapFee: tc.swapFee,
|
||||
},
|
||||
}
|
||||
|
||||
err := genesisState.Validate()
|
||||
if tc.expectErr {
|
||||
assert.NotNil(t, err)
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenesis_Validate_AllowedPools(t *testing.T) {
|
||||
type args struct {
|
||||
name string
|
||||
pairs types.AllowedPools
|
||||
expectErr bool
|
||||
}
|
||||
// More comprehensive pair validation tests are in pair_test.go, params_test.go
|
||||
testCases := []args{
|
||||
{
|
||||
"normal",
|
||||
types.DefaultAllowedPools,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid",
|
||||
types.AllowedPools{
|
||||
{
|
||||
TokenA: "same",
|
||||
TokenB: "same",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
genesisState := types.GenesisState{
|
||||
Params: types.Params{
|
||||
AllowedPools: tc.pairs,
|
||||
SwapFee: types.DefaultSwapFee,
|
||||
},
|
||||
}
|
||||
|
||||
err := genesisState.Validate()
|
||||
if tc.expectErr {
|
||||
assert.NotNil(t, err)
|
||||
} else {
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenesis_Equal(t *testing.T) {
|
||||
params := types.Params{
|
||||
types.NewAllowedPools(types.NewAllowedPool("ukava", "usdx")),
|
||||
sdk.MustNewDecFromStr("0.85"),
|
||||
}
|
||||
|
||||
genesisA := types.GenesisState{params}
|
||||
genesisB := types.GenesisState{params}
|
||||
|
||||
assert.True(t, genesisA.Equal(genesisB))
|
||||
}
|
||||
|
||||
func TestGenesis_NotEqual(t *testing.T) {
|
||||
baseParams := types.Params{
|
||||
types.NewAllowedPools(types.NewAllowedPool("ukava", "usdx")),
|
||||
sdk.MustNewDecFromStr("0.85"),
|
||||
}
|
||||
|
||||
// Base params
|
||||
genesisAParams := baseParams
|
||||
genesisA := types.GenesisState{genesisAParams}
|
||||
|
||||
// Different swap fee
|
||||
genesisBParams := baseParams
|
||||
genesisBParams.SwapFee = sdk.MustNewDecFromStr("0.84")
|
||||
genesisB := types.GenesisState{genesisBParams}
|
||||
|
||||
// Different pairs
|
||||
genesisCParams := baseParams
|
||||
genesisCParams.AllowedPools = types.NewAllowedPools(types.NewAllowedPool("ukava", "hard"))
|
||||
genesisC := types.GenesisState{genesisCParams}
|
||||
|
||||
// A and B have different swap fees
|
||||
assert.False(t, genesisA.Equal(genesisB))
|
||||
// A and C have different pair token B denoms
|
||||
assert.False(t, genesisA.Equal(genesisC))
|
||||
// A and B and different swap fees and pair token B denoms
|
||||
assert.False(t, genesisA.Equal(genesisB))
|
||||
}
|
22
x/swap/types/keys.go
Normal file
22
x/swap/types/keys.go
Normal file
@ -0,0 +1,22 @@
|
||||
package types
|
||||
|
||||
const (
|
||||
// ModuleName name that will be used throughout the module
|
||||
ModuleName = "swap"
|
||||
|
||||
// StoreKey Top level store key where all module items will be stored
|
||||
StoreKey = ModuleName
|
||||
|
||||
// RouterKey Top level router key
|
||||
RouterKey = ModuleName
|
||||
|
||||
// QuerierRoute Top level query string
|
||||
QuerierRoute = ModuleName
|
||||
|
||||
// DefaultParamspace default name for parameter store
|
||||
DefaultParamspace = ModuleName
|
||||
)
|
||||
|
||||
var (
|
||||
// KeyPrefix = []byte{0x01}
|
||||
)
|
1
x/swap/types/msg.go
Normal file
1
x/swap/types/msg.go
Normal file
@ -0,0 +1 @@
|
||||
package types
|
91
x/swap/types/params.go
Normal file
91
x/swap/types/params.go
Normal file
@ -0,0 +1,91 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/params"
|
||||
)
|
||||
|
||||
// Parameter keys and default values
|
||||
var (
|
||||
KeyAllowedPools = []byte("AllowedPools")
|
||||
KeySwapFee = []byte("SwapFee")
|
||||
DefaultAllowedPools = AllowedPools{}
|
||||
DefaultSwapFee = sdk.ZeroDec()
|
||||
MaxSwapFee = sdk.OneDec()
|
||||
)
|
||||
|
||||
// Params are governance parameters for the swap module
|
||||
type Params struct {
|
||||
AllowedPools AllowedPools `json:"allowed_pools" yaml:"allowed_pools"`
|
||||
SwapFee sdk.Dec `json:"swap_fee" yaml:"swap_fee"`
|
||||
}
|
||||
|
||||
// NewParams returns a new params object
|
||||
func NewParams(pairs AllowedPools, swapFee sdk.Dec) Params {
|
||||
return Params{
|
||||
AllowedPools: pairs,
|
||||
SwapFee: swapFee,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultParams returns default params for swap module
|
||||
func DefaultParams() Params {
|
||||
return NewParams(
|
||||
DefaultAllowedPools,
|
||||
DefaultSwapFee,
|
||||
)
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer
|
||||
func (p Params) String() string {
|
||||
return fmt.Sprintf(`Params:
|
||||
AllowedPools: %s
|
||||
SwapFee: %s`,
|
||||
p.AllowedPools, p.SwapFee)
|
||||
}
|
||||
|
||||
// ParamKeyTable Key declaration for parameters
|
||||
func ParamKeyTable() params.KeyTable {
|
||||
return params.NewKeyTable().RegisterParamSet(&Params{})
|
||||
}
|
||||
|
||||
// ParamSetAllowedPools implements the ParamSet interface and returns all the key/value pairs
|
||||
func (p *Params) ParamSetPairs() params.ParamSetPairs {
|
||||
return params.ParamSetPairs{
|
||||
params.NewParamSetPair(KeyAllowedPools, &p.AllowedPools, validateAllowedPoolsParams),
|
||||
params.NewParamSetPair(KeySwapFee, &p.SwapFee, validateSwapFee),
|
||||
}
|
||||
}
|
||||
|
||||
// Validate checks that the parameters have valid values.
|
||||
func (p Params) Validate() error {
|
||||
if err := validateAllowedPoolsParams(p.AllowedPools); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return validateSwapFee(p.SwapFee)
|
||||
}
|
||||
|
||||
func validateAllowedPoolsParams(i interface{}) error {
|
||||
p, ok := i.(AllowedPools)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
|
||||
return p.Validate()
|
||||
}
|
||||
|
||||
func validateSwapFee(i interface{}) error {
|
||||
swapFee, ok := i.(sdk.Dec)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid parameter type: %T", i)
|
||||
}
|
||||
|
||||
if swapFee.IsNil() || swapFee.IsNegative() || swapFee.GT(MaxSwapFee) {
|
||||
return fmt.Errorf(fmt.Sprintf("invalid swap fee: %s", swapFee))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
233
x/swap/types/params_test.go
Normal file
233
x/swap/types/params_test.go
Normal file
@ -0,0 +1,233 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/kava-labs/kava/x/swap/types"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
paramstypes "github.com/cosmos/cosmos-sdk/x/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func TestParams_UnmarshalJSON(t *testing.T) {
|
||||
pools := types.NewAllowedPools(
|
||||
types.NewAllowedPool("hard", "ukava"),
|
||||
types.NewAllowedPool("hard", "usdx"),
|
||||
)
|
||||
poolData, err := json.Marshal(pools)
|
||||
require.NoError(t, err)
|
||||
|
||||
fee, err := sdk.NewDecFromStr("0.5")
|
||||
require.NoError(t, err)
|
||||
feeData, err := json.Marshal(fee)
|
||||
require.NoError(t, err)
|
||||
|
||||
data := fmt.Sprintf(`{
|
||||
"allowed_pools": %s,
|
||||
"swap_fee": %s
|
||||
}`, string(poolData), string(feeData))
|
||||
|
||||
var params types.Params
|
||||
err = json.Unmarshal([]byte(data), ¶ms)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, pools, params.AllowedPools)
|
||||
assert.Equal(t, fee, params.SwapFee)
|
||||
}
|
||||
|
||||
func TestParams_MarshalYAML(t *testing.T) {
|
||||
pools := types.NewAllowedPools(
|
||||
types.NewAllowedPool("hard", "ukava"),
|
||||
types.NewAllowedPool("hard", "usdx"),
|
||||
)
|
||||
fee, err := sdk.NewDecFromStr("0.5")
|
||||
require.NoError(t, err)
|
||||
|
||||
p := types.Params{
|
||||
AllowedPools: pools,
|
||||
SwapFee: fee,
|
||||
}
|
||||
|
||||
data, err := yaml.Marshal(p)
|
||||
require.NoError(t, err)
|
||||
|
||||
var params map[string]interface{}
|
||||
err = yaml.Unmarshal(data, ¶ms)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, ok := params["allowed_pools"]
|
||||
require.True(t, ok)
|
||||
_, ok = params["swap_fee"]
|
||||
require.True(t, ok)
|
||||
}
|
||||
|
||||
func TestParams_Default(t *testing.T) {
|
||||
defaultParams := types.DefaultParams()
|
||||
|
||||
require.NoError(t, defaultParams.Validate())
|
||||
|
||||
assert.Equal(t, types.DefaultAllowedPools, defaultParams.AllowedPools)
|
||||
assert.Equal(t, types.DefaultSwapFee, defaultParams.SwapFee)
|
||||
|
||||
assert.Equal(t, 0, len(defaultParams.AllowedPools))
|
||||
assert.Equal(t, sdk.ZeroDec(), defaultParams.SwapFee)
|
||||
}
|
||||
|
||||
func TestParams_ParamSetPairs_AllowedPools(t *testing.T) {
|
||||
assert.Equal(t, []byte("AllowedPools"), types.KeyAllowedPools)
|
||||
defaultParams := types.DefaultParams()
|
||||
|
||||
var paramSetPair *paramstypes.ParamSetPair
|
||||
for _, pair := range defaultParams.ParamSetPairs() {
|
||||
if bytes.Compare(pair.Key, types.KeyAllowedPools) == 0 {
|
||||
paramSetPair = &pair
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, paramSetPair)
|
||||
|
||||
pairs, ok := paramSetPair.Value.(*types.AllowedPools)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, pairs, &defaultParams.AllowedPools)
|
||||
|
||||
assert.Nil(t, paramSetPair.ValidatorFn(*pairs))
|
||||
assert.EqualError(t, paramSetPair.ValidatorFn(struct{}{}), "invalid parameter type: struct {}")
|
||||
}
|
||||
|
||||
func TestParams_ParamSetPairs_SwapFee(t *testing.T) {
|
||||
assert.Equal(t, []byte("SwapFee"), types.KeySwapFee)
|
||||
defaultParams := types.DefaultParams()
|
||||
|
||||
var paramSetPair *paramstypes.ParamSetPair
|
||||
for _, pair := range defaultParams.ParamSetPairs() {
|
||||
if bytes.Compare(pair.Key, types.KeySwapFee) == 0 {
|
||||
paramSetPair = &pair
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, paramSetPair)
|
||||
|
||||
swapFee, ok := paramSetPair.Value.(*sdk.Dec)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, swapFee, &defaultParams.SwapFee)
|
||||
|
||||
assert.Nil(t, paramSetPair.ValidatorFn(*swapFee))
|
||||
assert.EqualError(t, paramSetPair.ValidatorFn(struct{}{}), "invalid parameter type: struct {}")
|
||||
}
|
||||
|
||||
func TestParams_Validation(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
key []byte
|
||||
testFn func(params *types.Params)
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "invalid denom",
|
||||
key: types.KeyAllowedPools,
|
||||
testFn: func(params *types.Params) {
|
||||
params.AllowedPools = types.NewAllowedPools(types.NewAllowedPool("UKAVA", "ukava"))
|
||||
},
|
||||
expectedErr: "invalid denom: UKAVA",
|
||||
},
|
||||
{
|
||||
name: "duplicate pools",
|
||||
key: types.KeyAllowedPools,
|
||||
testFn: func(params *types.Params) {
|
||||
params.AllowedPools = types.NewAllowedPools(types.NewAllowedPool("ukava", "ukava"))
|
||||
},
|
||||
expectedErr: "pool cannot have two tokens of the same type, received 'ukava' and 'ukava'",
|
||||
},
|
||||
{
|
||||
name: "nil swap fee",
|
||||
key: types.KeySwapFee,
|
||||
testFn: func(params *types.Params) {
|
||||
params.SwapFee = sdk.Dec{}
|
||||
},
|
||||
expectedErr: "invalid swap fee: <nil>",
|
||||
},
|
||||
{
|
||||
name: "negative swap fee",
|
||||
key: types.KeySwapFee,
|
||||
testFn: func(params *types.Params) {
|
||||
params.SwapFee = sdk.NewDec(-1)
|
||||
},
|
||||
expectedErr: "invalid swap fee: -1.000000000000000000",
|
||||
},
|
||||
{
|
||||
name: "swap fee greater than 1",
|
||||
key: types.KeySwapFee,
|
||||
testFn: func(params *types.Params) {
|
||||
params.SwapFee = sdk.MustNewDecFromStr("1.000000000000000001")
|
||||
},
|
||||
expectedErr: "invalid swap fee: 1.000000000000000001",
|
||||
},
|
||||
{
|
||||
name: "0 swap fee",
|
||||
key: types.KeySwapFee,
|
||||
testFn: func(params *types.Params) {
|
||||
params.SwapFee = sdk.ZeroDec()
|
||||
},
|
||||
expectedErr: "",
|
||||
},
|
||||
{
|
||||
name: "1 swap fee",
|
||||
key: types.KeySwapFee,
|
||||
testFn: func(params *types.Params) {
|
||||
params.SwapFee = sdk.OneDec()
|
||||
},
|
||||
expectedErr: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
params := types.DefaultParams()
|
||||
tc.testFn(¶ms)
|
||||
|
||||
err := params.Validate()
|
||||
|
||||
if tc.expectedErr == "" {
|
||||
assert.Nil(t, err)
|
||||
} else {
|
||||
assert.EqualError(t, err, tc.expectedErr)
|
||||
}
|
||||
|
||||
var paramSetPair *paramstypes.ParamSetPair
|
||||
for _, pair := range params.ParamSetPairs() {
|
||||
if bytes.Compare(pair.Key, tc.key) == 0 {
|
||||
paramSetPair = &pair
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, paramSetPair)
|
||||
value := reflect.ValueOf(paramSetPair.Value).Elem().Interface()
|
||||
|
||||
// assert validation error is same as param set validation
|
||||
assert.Equal(t, err, paramSetPair.ValidatorFn(value))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParams_String(t *testing.T) {
|
||||
params := types.NewParams(
|
||||
types.NewAllowedPools(
|
||||
types.NewAllowedPool("hard", "ukava"),
|
||||
types.NewAllowedPool("ukava", "usdx"),
|
||||
),
|
||||
sdk.MustNewDecFromStr("0.5"),
|
||||
)
|
||||
require.NoError(t, params.Validate())
|
||||
|
||||
output := params.String()
|
||||
assert.Contains(t, output, "hard/ukava")
|
||||
assert.Contains(t, output, "ukava/usdx")
|
||||
assert.Contains(t, output, "0.5")
|
||||
}
|
90
x/swap/types/pool.go
Normal file
90
x/swap/types/pool.go
Normal file
@ -0,0 +1,90 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// AllowedPool defines a tradable pool
|
||||
type AllowedPool struct {
|
||||
TokenA string `json:"token_a" yaml:"token_a"`
|
||||
TokenB string `json:"token_b" yaml:"token_b"`
|
||||
}
|
||||
|
||||
// NewAllowedPool returns a new AllowedPool object
|
||||
func NewAllowedPool(tokenA, tokenB string) AllowedPool {
|
||||
return AllowedPool{
|
||||
TokenA: tokenA,
|
||||
TokenB: tokenB,
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates allowedPool attributes and returns an error if invalid
|
||||
func (p AllowedPool) Validate() error {
|
||||
err := sdk.ValidateDenom(p.TokenA)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = sdk.ValidateDenom(p.TokenB)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if p.TokenA == p.TokenB {
|
||||
return fmt.Errorf(
|
||||
"pool cannot have two tokens of the same type, received '%s' and '%s'",
|
||||
p.TokenA, p.TokenB,
|
||||
)
|
||||
}
|
||||
|
||||
if p.TokenA > p.TokenB {
|
||||
return fmt.Errorf(
|
||||
"invalid token order: '%s' must come before '%s'",
|
||||
p.TokenB, p.TokenA,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Name returns a unique name for a allowedPool in alphabetical order
|
||||
func (p AllowedPool) Name() string {
|
||||
return fmt.Sprintf("%s/%s", p.TokenA, p.TokenB)
|
||||
}
|
||||
|
||||
// String pretty prints the allowedPool
|
||||
func (p AllowedPool) String() string {
|
||||
return fmt.Sprintf(`AllowedPool:
|
||||
Name: %s
|
||||
Token A: %s
|
||||
Token B: %s
|
||||
`, p.Name(), p.TokenA, p.TokenB)
|
||||
}
|
||||
|
||||
// AllowedPools is a slice of AllowedPool
|
||||
type AllowedPools []AllowedPool
|
||||
|
||||
// NewAllowedPools returns AllowedPools from the provided values
|
||||
func NewAllowedPools(allowedPools ...AllowedPool) AllowedPools {
|
||||
return AllowedPools(allowedPools)
|
||||
}
|
||||
|
||||
// Validate validates each allowedPool and returns an error if there are any duplicates
|
||||
func (p AllowedPools) Validate() error {
|
||||
seenAllowedPools := make(map[string]bool)
|
||||
for _, allowedPool := range p {
|
||||
err := allowedPool.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if seen := seenAllowedPools[allowedPool.Name()]; seen {
|
||||
return fmt.Errorf("duplicate pool: %s", allowedPool.Name())
|
||||
}
|
||||
seenAllowedPools[allowedPool.Name()] = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
185
x/swap/types/pool_test.go
Normal file
185
x/swap/types/pool_test.go
Normal file
@ -0,0 +1,185 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
types "github.com/kava-labs/kava/x/swap/types"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAllowedPool_Validation(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
allowedPool types.AllowedPool
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "blank token a",
|
||||
allowedPool: types.NewAllowedPool("", "ukava"),
|
||||
expectedErr: "invalid denom: ",
|
||||
},
|
||||
{
|
||||
name: "blank token b",
|
||||
allowedPool: types.NewAllowedPool("ukava", ""),
|
||||
expectedErr: "invalid denom: ",
|
||||
},
|
||||
{
|
||||
name: "invalid token a",
|
||||
allowedPool: types.NewAllowedPool("1ukava", "ukava"),
|
||||
expectedErr: "invalid denom: 1ukava",
|
||||
},
|
||||
{
|
||||
name: "invalid token b",
|
||||
allowedPool: types.NewAllowedPool("ukava", "1ukava"),
|
||||
expectedErr: "invalid denom: 1ukava",
|
||||
},
|
||||
{
|
||||
name: "no uppercase letters token a",
|
||||
allowedPool: types.NewAllowedPool("uKava", "ukava"),
|
||||
expectedErr: "invalid denom: uKava",
|
||||
},
|
||||
{
|
||||
name: "no uppercase letters token b",
|
||||
allowedPool: types.NewAllowedPool("ukava", "UKAVA"),
|
||||
expectedErr: "invalid denom: UKAVA",
|
||||
},
|
||||
{
|
||||
name: "matching tokens",
|
||||
allowedPool: types.NewAllowedPool("ukava", "ukava"),
|
||||
expectedErr: "pool cannot have two tokens of the same type, received 'ukava' and 'ukava'",
|
||||
},
|
||||
{
|
||||
name: "invalid token order",
|
||||
allowedPool: types.NewAllowedPool("usdx", "ukava"),
|
||||
expectedErr: "invalid token order: 'ukava' must come before 'usdx'",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.allowedPool.Validate()
|
||||
assert.EqualError(t, err, tc.expectedErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ensure no regression in case insentive token matching if
|
||||
// sdk.ValidateDenom ever allows upper case letters
|
||||
func TestAllowedPool_TokenMatch(t *testing.T) {
|
||||
allowedPool := types.NewAllowedPool("UKAVA", "ukava")
|
||||
err := allowedPool.Validate()
|
||||
assert.Error(t, err)
|
||||
|
||||
allowedPool = types.NewAllowedPool("hard", "haRd")
|
||||
err = allowedPool.Validate()
|
||||
assert.Error(t, err)
|
||||
|
||||
allowedPool = types.NewAllowedPool("Usdx", "uSdX")
|
||||
err = allowedPool.Validate()
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestAllowedPool_String(t *testing.T) {
|
||||
allowedPool := types.NewAllowedPool("hard", "ukava")
|
||||
require.NoError(t, allowedPool.Validate())
|
||||
|
||||
output := `AllowedPool:
|
||||
Name: hard/ukava
|
||||
Token A: hard
|
||||
Token B: ukava
|
||||
`
|
||||
assert.Equal(t, output, allowedPool.String())
|
||||
}
|
||||
|
||||
func TestAllowedPool_Name(t *testing.T) {
|
||||
testCases := []struct {
|
||||
tokens string
|
||||
name string
|
||||
}{
|
||||
{
|
||||
tokens: "atoken btoken",
|
||||
name: "atoken/btoken",
|
||||
},
|
||||
{
|
||||
tokens: "aaa aaaa",
|
||||
name: "aaa/aaaa",
|
||||
},
|
||||
{
|
||||
tokens: "aaaa aaab",
|
||||
name: "aaaa/aaab",
|
||||
},
|
||||
{
|
||||
tokens: "a001 a002",
|
||||
name: "a001/a002",
|
||||
},
|
||||
{
|
||||
tokens: "hard ukava",
|
||||
name: "hard/ukava",
|
||||
},
|
||||
{
|
||||
tokens: "bnb hard",
|
||||
name: "bnb/hard",
|
||||
},
|
||||
{
|
||||
tokens: "bnb xrpb",
|
||||
name: "bnb/xrpb",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.tokens, func(t *testing.T) {
|
||||
tokens := strings.Split(tc.tokens, " ")
|
||||
require.Equal(t, 2, len(tokens))
|
||||
|
||||
allowedPool := types.NewAllowedPool(tokens[0], tokens[1])
|
||||
require.NoError(t, allowedPool.Validate())
|
||||
|
||||
assert.Equal(t, tc.name, allowedPool.Name())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllowedPools_Validate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
allowedPools types.AllowedPools
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "invalid pool",
|
||||
allowedPools: types.NewAllowedPools(
|
||||
types.NewAllowedPool("hard", "ukava"),
|
||||
types.NewAllowedPool("HARD", "UKAVA"),
|
||||
),
|
||||
expectedErr: "invalid denom: HARD",
|
||||
},
|
||||
{
|
||||
name: "duplicate pool",
|
||||
allowedPools: types.NewAllowedPools(
|
||||
types.NewAllowedPool("hard", "ukava"),
|
||||
types.NewAllowedPool("hard", "ukava"),
|
||||
),
|
||||
expectedErr: "duplicate pool: hard/ukava",
|
||||
},
|
||||
{
|
||||
name: "duplicate pools",
|
||||
allowedPools: types.NewAllowedPools(
|
||||
types.NewAllowedPool("hard", "ukava"),
|
||||
types.NewAllowedPool("bnb", "usdx"),
|
||||
types.NewAllowedPool("btcb", "xrpb"),
|
||||
types.NewAllowedPool("bnb", "usdx"),
|
||||
),
|
||||
expectedErr: "duplicate pool: bnb/usdx",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.allowedPools.Validate()
|
||||
assert.EqualError(t, err, tc.expectedErr)
|
||||
})
|
||||
}
|
||||
}
|
6
x/swap/types/querier.go
Normal file
6
x/swap/types/querier.go
Normal file
@ -0,0 +1,6 @@
|
||||
package types
|
||||
|
||||
// Querier routes for the swap module
|
||||
const (
|
||||
QueryGetParams = "params"
|
||||
)
|
Loading…
Reference in New Issue
Block a user