diff --git a/app/app.go b/app/app.go index cf792d12b..09d07e79a 100644 --- a/app/app.go +++ b/app/app.go @@ -30,6 +30,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/supply" "github.com/irisnet/irishub/modules/asset" + "github.com/irisnet/irishub/modules/coinswap" "github.com/irisnet/irishub/modules/guardian" "github.com/irisnet/irishub/modules/htlc" "github.com/irisnet/irishub/modules/mint" @@ -62,6 +63,7 @@ var ( asset.AppModuleBasic{}, guardian.AppModuleBasic{}, htlc.AppModuleBasic{}, + coinswap.AppModuleBasic{}, ) // module account permissions @@ -74,6 +76,7 @@ var ( gov.ModuleName: {supply.Burner}, asset.ModuleName: {supply.Minter, supply.Burner}, htlc.ModuleName: nil, + coinswap.ModuleName: {supply.Minter, supply.Burner}, } ) @@ -117,6 +120,7 @@ type IrisApp struct { assetKeeper asset.Keeper guardianKeeper guardian.Keeper htlcKeeper htlc.Keeper + coinswapKeeper coinswap.Keeper // the module manager mm *module.Manager @@ -139,7 +143,7 @@ func NewIrisApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b bam.MainStoreKey, auth.StoreKey, staking.StoreKey, supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey, gov.StoreKey, params.StoreKey, evidence.StoreKey, asset.StoreKey, guardian.StoreKey, - htlc.StoreKey, + htlc.StoreKey, coinswap.StoreKey, ) tKeys := sdk.NewTransientStoreKeys(staking.TStoreKey, params.TStoreKey) @@ -163,6 +167,7 @@ func NewIrisApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b crisisSubspace := app.paramsKeeper.Subspace(crisis.DefaultParamspace) evidenceSubspace := app.paramsKeeper.Subspace(evidence.DefaultParamspace) assetSubspace := app.paramsKeeper.Subspace(asset.DefaultParamspace) + coinswapSubspace := app.paramsKeeper.Subspace(coinswap.DefaultParamspace) // add keepers app.accountKeeper = auth.NewAccountKeeper(app.cdc, keys[auth.StoreKey], authSubspace, auth.ProtoBaseAccount) @@ -217,6 +222,10 @@ func NewIrisApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b app.cdc, keys[htlc.StoreKey], app.supplyKeeper, htlc.DefaultCodespace, ) + app.coinswapKeeper = coinswap.NewKeeper( + app.cdc, keys[coinswap.StoreKey], app.bankKeeper, app.accountKeeper, app.supplyKeeper, coinswapSubspace, + ) + // NOTE: Any module instantiated in the module manager that is later modified // must be passed by reference here. app.mm = module.NewManager( @@ -234,6 +243,7 @@ func NewIrisApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b asset.NewAppModule(app.assetKeeper), guardian.NewAppModule(app.guardianKeeper), htlc.NewAppModule(app.htlcKeeper), + coinswap.NewAppModule(app.coinswapKeeper), ) // During begin block slashing happens after distr.BeginBlocker so that @@ -259,6 +269,7 @@ func NewIrisApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b slashing.ModuleName, gov.ModuleName, mint.ModuleName, supply.ModuleName, crisis.ModuleName, genutil.ModuleName, evidence.ModuleName, asset.ModuleName, guardian.ModuleName, htlc.ModuleName, + coinswap.ModuleName, ) app.mm.RegisterInvariants(&app.crisisKeeper) @@ -279,6 +290,7 @@ func NewIrisApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper), asset.NewAppModule(app.assetKeeper), htlc.NewAppModule(app.htlcKeeper), + coinswap.NewAppModule(app.coinswapKeeper), ) app.sm.RegisterStoreDecoders() diff --git a/config/config.go b/config/config.go index e6ada287e..0cb46bf47 100644 --- a/config/config.go +++ b/config/config.go @@ -1,8 +1,8 @@ package config const ( - Testnet = "testnet" - Mainnet = "mainnet" + Testnet = "testnet" + Mainnet = "mainnet" ) // Can be configured through environment variables diff --git a/modules/coinswap/alias.go b/modules/coinswap/alias.go index 6851cc1c3..20d035027 100644 --- a/modules/coinswap/alias.go +++ b/modules/coinswap/alias.go @@ -1,8 +1,27 @@ package coinswap import ( - "github.com/irisnet/irishub/app/v2/coinswap/internal/keeper" - "github.com/irisnet/irishub/app/v2/coinswap/internal/types" + "github.com/irisnet/irishub/modules/coinswap/internal/keeper" + "github.com/irisnet/irishub/modules/coinswap/internal/types" +) + +const ( + ModuleName = types.ModuleName + StoreKey = types.StoreKey + RouterKey = types.RouterKey + QuerierRoute = types.QuerierRoute + DefaultParamspace = types.DefaultParamspace + DefaultCodespace = types.DefaultCodespace + + EventTypeSwap = types.EventTypeSwap + EventTypeAddLiquidity = types.EventTypeAddLiquidity + EventTypeRemoveLiquidity = types.EventTypeRemoveLiquidity + AttributeValueCategory = types.AttributeValueCategory + AttributeValueAmount = types.AttributeValueAmount + AttributeValueSender = types.AttributeValueSender + AttributeValueRecipient = types.AttributeValueRecipient + AttributeValueIsBuyOrder = types.AttributeValueIsBuyOrder + AttributeValueTokenPair = types.AttributeValueTokenPair ) type ( @@ -17,31 +36,15 @@ type ( ) var ( - DefaultParamSpace = types.DefaultParamSpace - QueryLiquidity = types.QueryLiquidity - - RegisterCodec = types.RegisterCodec - - NewMsgSwapOrder = types.NewMsgSwapOrder - NewMsgAddLiquidity = types.NewMsgAddLiquidity - NewMsgRemoveLiquidity = types.NewMsgRemoveLiquidity - NewKeeper = keeper.NewKeeper - NewQuerier = keeper.NewQuerier - - ErrInvalidDeadline = types.ErrInvalidDeadline - ErrNotPositive = types.ErrNotPositive - ErrConstraintNotMet = types.ErrConstraintNotMet - - GetUniId = types.GetUniId - GetCoinMinDenomFromUniDenom = types.GetCoinMinDenomFromUniDenom - GetUniDenom = types.GetUniDenom - GetUniCoinType = types.GetUniCoinType - CheckUniDenom = types.CheckUniDenom - CheckUniId = types.CheckUniId + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier + RegisterCodec = types.RegisterCodec + ErrInvalidDeadline = types.ErrInvalidDeadline + ValidateParams = types.ValidateParams + DefaultParams = types.DefaultParams ) -const ( - DefaultCodespace = types.DefaultCodespace - ModuleName = types.ModuleName - FormatUniABSPrefix = types.FormatUniABSPrefix +// exported variables and functions +var ( + ModuleCdc = types.ModuleCdc ) diff --git a/modules/coinswap/client/rest/query.go b/modules/coinswap/client/rest/query.go new file mode 100644 index 000000000..1b98e233e --- /dev/null +++ b/modules/coinswap/client/rest/query.go @@ -0,0 +1,45 @@ +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/irisnet/irishub/modules/coinswap/internal/types" +) + +func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router) { + // Query liquidity + r.HandleFunc("/coinswap/liquidities/{id}", queryLiquidityHandlerFn(cliCtx)).Methods("GET") +} + +// queryLiquidityHandlerFn performs liquidity information query +func queryLiquidityHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id := vars["id"] + params := types.QueryLiquidityParams{ + ID: id, + } + + bz, err := cliCtx.Codec.MarshalJSON(params) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + route := fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryLiquidity) + res, height, err := cliCtx.QueryWithData(route, bz) + if err != nil { + rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error()) + return + } + + cliCtx = cliCtx.WithHeight(height) + rest.PostProcessResponse(w, cliCtx, res) + } +} diff --git a/modules/coinswap/client/rest/rest.go b/modules/coinswap/client/rest/rest.go new file mode 100644 index 000000000..894354968 --- /dev/null +++ b/modules/coinswap/client/rest/rest.go @@ -0,0 +1,52 @@ +package rest + +import ( + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/rest" +) + +// RegisterRoutes registers asset-related REST handlers to a router +func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) { + registerQueryRoutes(cliCtx, r) + registerTxRoutes(cliCtx, r) +} + +type AddLiquidityReq struct { + BaseTx rest.BaseReq `json:"base_tx" yaml:"base_tx"` + ID string `json:"id" yaml:"id"` // the unique liquidity id + MaxToken string `json:"max_token" yaml:"max_token"` // token to be deposited as liquidity with an upper bound for its amount + ExactStandardAmt string `json:"exact_standard_amt" yaml:"exact_standard_amt"` // exact amount of standard token being add to the liquidity pool + MinLiquidity string `json:"min_liquidity" yaml:"min_liquidity"` // lower bound UNI sender is willing to accept for deposited coins + Deadline string `json:"deadline" yaml:"deadline"` // deadline duration, e.g. 10m + Sender string `json:"sender" yaml:"sender"` +} + +type RemoveLiquidityReq struct { + BaseTx rest.BaseReq `json:"base_tx" yaml:"base_tx"` + ID string `json:"id" yaml:"id"` // the unique liquidity id + MinToken string `json:"min_token" yaml:"min_token"` // coin to be withdrawn with a lower bound for its amount + WithdrawLiquidity string `json:"withdraw_liquidity" yaml:"withdraw_liquidity"` // amount of UNI to be burned to withdraw liquidity from a reserve pool + MinStandardAmt string `json:"min_standard_amt" yaml:"min_standard_amt"` // minimum amount of the native asset the sender is willing to accept + Deadline string `json:"deadline" yaml:"deadline"` // deadline duration, e.g. 10m + Sender string `json:"sender" yaml:"sender"` +} + +type Input struct { + Address string `json:"address" yaml:"address"` + Coin sdk.Coin `json:"coin" yaml:"coin"` +} + +type Output struct { + Address string `json:"address" yaml:"address"` + Coin sdk.Coin `json:"coin" yaml:"coin"` +} + +type SwapOrderReq struct { + BaseTx rest.BaseReq `json:"base_tx" yaml:"base_tx"` + Input Input `json:"input" yaml:"input"` // the amount the sender is trading + Output Output `json:"output" yaml:"output"` // the amount the sender is receiving + Deadline string `json:"deadline" yaml:"deadline"` // deadline for the transaction to still be considered valid +} diff --git a/modules/coinswap/client/rest/tx.go b/modules/coinswap/client/rest/tx.go new file mode 100644 index 000000000..9d0208b66 --- /dev/null +++ b/modules/coinswap/client/rest/tx.go @@ -0,0 +1,218 @@ +package rest + +import ( + "net/http" + "time" + + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client/context" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/rest" + "github.com/cosmos/cosmos-sdk/x/auth/client/utils" + + "github.com/irisnet/irishub/modules/coinswap/internal/types" +) + +func registerTxRoutes(cliCtx context.CLIContext, r *mux.Router) { + r.HandleFunc("/coinswap/liquidities/{id}/deposit", addLiquidityHandlerFn(cliCtx)).Methods("POST") + r.HandleFunc("/coinswap/liquidities/{id}/withdraw", removeLiquidityHandlerFn(cliCtx)).Methods("POST") + r.HandleFunc("/coinswap/liquidities/buy", swapOrderHandlerFn(cliCtx, true)).Methods("POST") + r.HandleFunc("/coinswap/liquidities/sell", swapOrderHandlerFn(cliCtx, false)).Methods("POST") +} + +func addLiquidityHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + uniDenom := vars["id"] + + if err := types.CheckUniDenom(uniDenom); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + tokenDenom, err := types.GetCoinDenomFromUniDenom(uniDenom) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + var req AddLiquidityReq + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + return + } + + baseReq := req.BaseTx.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + senderAddress, e := sdk.AccAddressFromBech32(req.Sender) + if e != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, e.Error()) + return + } + + duration, e := time.ParseDuration(req.Deadline) + if e != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, e.Error()) + return + } + + status, e := cliCtx.Client.Status() + if e != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, e.Error()) + return + } + deadline := status.SyncInfo.LatestBlockTime.Add(duration) + + maxToken, ok := sdk.NewIntFromString(req.MaxToken) + if !ok || !maxToken.IsPositive() { + rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid max token amount: "+req.MaxToken) + return + } + + exactStandardAmt, ok := sdk.NewIntFromString(req.ExactStandardAmt) + if !ok { + rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid exact standard token amount: "+req.ExactStandardAmt) + return + } + + minLiquidity, ok := sdk.NewIntFromString(req.MinLiquidity) + if !ok { + rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid min liquidity amount: "+req.MinLiquidity) + return + } + + msg := types.NewMsgAddLiquidity(sdk.NewCoin(tokenDenom, maxToken), exactStandardAmt, minLiquidity, deadline.Unix(), senderAddress) + err = msg.ValidateBasic() + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseTx, []sdk.Msg{msg}) + } +} + +func removeLiquidityHandlerFn(cliCtx context.CLIContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + uniDenom := vars["id"] + + if err := types.CheckUniDenom(uniDenom); err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + var req RemoveLiquidityReq + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + return + } + + baseReq := req.BaseTx.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + senderAddress, e := sdk.AccAddressFromBech32(req.Sender) + if e != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, e.Error()) + return + } + + duration, e := time.ParseDuration(req.Deadline) + if e != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, e.Error()) + return + } + + status, e := cliCtx.Client.Status() + if e != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, e.Error()) + return + } + deadline := status.SyncInfo.LatestBlockTime.Add(duration) + + minToken, ok := sdk.NewIntFromString(req.MinToken) + if !ok { + rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid min token amount: "+req.MinToken) + return + } + + minStandard, ok := sdk.NewIntFromString(req.MinStandardAmt) + if !ok { + rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid min iris amount: "+req.MinStandardAmt) + return + } + + liquidityAmt, ok := sdk.NewIntFromString(req.WithdrawLiquidity) + if !ok || !liquidityAmt.IsPositive() { + rest.WriteErrorResponse(w, http.StatusBadRequest, "invalid liquidity amount: "+req.WithdrawLiquidity) + return + } + + msg := types.NewMsgRemoveLiquidity(minToken, sdk.NewCoin(uniDenom, liquidityAmt), minStandard, deadline.Unix(), senderAddress) + err := msg.ValidateBasic() + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseTx, []sdk.Msg{msg}) + } +} + +func swapOrderHandlerFn(cliCtx context.CLIContext, isBuyOrder bool) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var req SwapOrderReq + if !rest.ReadRESTReq(w, r, cliCtx.Codec, &req) { + return + } + + baseReq := req.BaseTx.Sanitize() + if !baseReq.ValidateBasic(w) { + return + } + + senderAddress, err := sdk.AccAddressFromBech32(req.Input.Address) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + var recipientAddress sdk.AccAddress + if len(req.Output.Address) > 0 { + recipientAddress, err = sdk.AccAddressFromBech32(req.Output.Address) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + } + + duration, err := time.ParseDuration(req.Deadline) + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + input := types.Input{Address: senderAddress, Coin: req.Input.Coin} + output := types.Output{Address: recipientAddress, Coin: req.Output.Coin} + + status, e := cliCtx.Client.Status() + if e != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, e.Error()) + return + } + deadline := status.SyncInfo.LatestBlockTime.Add(duration) + + msg := types.NewMsgSwapOrder(input, output, deadline.Unix(), isBuyOrder) + err = msg.ValidateBasic() + if err != nil { + rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error()) + return + } + + utils.WriteGenerateStdTxResponse(w, cliCtx, req.BaseTx, []sdk.Msg{msg}) + } +} diff --git a/modules/coinswap/genesis.go b/modules/coinswap/genesis.go index 762cec6ea..9e97c7d3f 100644 --- a/modules/coinswap/genesis.go +++ b/modules/coinswap/genesis.go @@ -2,19 +2,17 @@ package coinswap import ( "fmt" - "github.com/irisnet/irishub/app/v2/coinswap/internal/types" - sdk "github.com/irisnet/irishub/types" -) -// TODO: ... + sdk "github.com/cosmos/cosmos-sdk/types" +) // GenesisState - coinswap genesis state type GenesisState struct { - Params types.Params `json:"params"` + Params Params `json:"params"` } // NewGenesisState is the constructor function for GenesisState -func NewGenesisState(params types.Params) GenesisState { +func NewGenesisState(params Params) GenesisState { return GenesisState{ Params: params, } @@ -22,12 +20,11 @@ func NewGenesisState(params types.Params) GenesisState { // DefaultGenesisState creates a default GenesisState object func DefaultGenesisState() GenesisState { - return NewGenesisState(types.DefaultParams()) + return NewGenesisState(DefaultParams()) } // InitGenesis new coinswap genesis func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { - // if err := ValidateGenesis(data); err != nil { panic(fmt.Errorf("panic for ValidateGenesis,%v", err)) } @@ -41,7 +38,7 @@ func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { // ValidateGenesis - placeholder function func ValidateGenesis(data GenesisState) error { - if err := types.ValidateParams(data.Params); err != nil { + if err := ValidateParams(data.Params); err != nil { return err } return nil diff --git a/modules/coinswap/handler.go b/modules/coinswap/handler.go index f0cb35492..5398dd4ee 100644 --- a/modules/coinswap/handler.go +++ b/modules/coinswap/handler.go @@ -4,22 +4,21 @@ import ( "fmt" "time" - sdk "github.com/irisnet/irishub/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) // NewHandler returns a handler for "coinswap" type messages. func NewHandler(k Keeper) sdk.Handler { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + switch msg := msg.(type) { case MsgSwapOrder: - return HandleMsgSwapOrder(ctx, msg, k) - + return HandleMsgSwapOrder(ctx, k, msg) case MsgAddLiquidity: - return HandleMsgAddLiquidity(ctx, msg, k) - + return HandleMsgAddLiquidity(ctx, k, msg) case MsgRemoveLiquidity: - return HandleMsgRemoveLiquidity(ctx, msg, k) - + return HandleMsgRemoveLiquidity(ctx, k, msg) default: errMsg := fmt.Sprintf("unrecognized coinswap message type: %T", msg) return sdk.ErrUnknownRequest(errMsg).Result() @@ -28,50 +27,77 @@ func NewHandler(k Keeper) sdk.Handler { } // Handle MsgSwapOrder. -func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result { +func HandleMsgSwapOrder(ctx sdk.Context, k Keeper, msg MsgSwapOrder) sdk.Result { // check that deadline has not passed if ctx.BlockHeader().Time.After(time.Unix(msg.Deadline, 0)) { return ErrInvalidDeadline("deadline has passed for MsgSwapOrder").Result() } - tags, err := k.HandleSwap(ctx, msg) + + err := k.Swap(ctx, msg) if err != nil { return err.Result() } - return sdk.Result{Tags: tags} + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Input.Address.String()), + ), + ) + + return sdk.Result{ + Events: ctx.EventManager().Events(), + } } // Handle MsgAddLiquidity. If the reserve pool does not exist, it will be // created. The first liquidity provider sets the exchange rate. -func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.Result { +func HandleMsgAddLiquidity(ctx sdk.Context, k Keeper, msg MsgAddLiquidity) sdk.Result { // check that deadline has not passed if ctx.BlockHeader().Time.After(time.Unix(msg.Deadline, 0)) { return ErrInvalidDeadline("deadline has passed for MsgAddLiquidity").Result() } - tags, err := k.HandleAddLiquidity(ctx, msg) + err := k.AddLiquidity(ctx, msg) if err != nil { return err.Result() } + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender.String()), + ), + ) + return sdk.Result{ - Tags: tags, + Events: ctx.EventManager().Events(), } } // HandleMsgRemoveLiquidity handler for MsgRemoveLiquidity -func HandleMsgRemoveLiquidity(ctx sdk.Context, msg MsgRemoveLiquidity, k Keeper) sdk.Result { +func HandleMsgRemoveLiquidity(ctx sdk.Context, k Keeper, msg MsgRemoveLiquidity) sdk.Result { // check that deadline has not passed if ctx.BlockHeader().Time.After(time.Unix(msg.Deadline, 0)) { return ErrInvalidDeadline("deadline has passed for MsgRemoveLiquidity").Result() } - tags, err := k.HandleRemoveLiquidity(ctx, msg) + err := k.RemoveLiquidity(ctx, msg) if err != nil { return err.Result() } + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, AttributeValueCategory), + sdk.NewAttribute(sdk.AttributeKeySender, msg.Sender.String()), + ), + ) + return sdk.Result{ - Tags: tags, + Events: ctx.EventManager().Events(), } } diff --git a/modules/coinswap/internal/keeper/keeper.go b/modules/coinswap/internal/keeper/keeper.go index 99a943073..53f2053c5 100644 --- a/modules/coinswap/internal/keeper/keeper.go +++ b/modules/coinswap/internal/keeper/keeper.go @@ -4,11 +4,13 @@ import ( "fmt" "strconv" - "github.com/irisnet/irishub/app/v1/params" - "github.com/irisnet/irishub/app/v2/coinswap/internal/types" - "github.com/irisnet/irishub/codec" - sdk "github.com/irisnet/irishub/types" "github.com/tendermint/tendermint/crypto" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" + + "github.com/irisnet/irishub/modules/coinswap/internal/types" ) // Keeper of the coinswap store @@ -16,7 +18,8 @@ type Keeper struct { cdc *codec.Codec storeKey sdk.StoreKey bk types.BankKeeper - ak types.AuthKeeper + ak types.AccountKeeper + sk types.SupplyKeeper paramSpace params.Subspace } @@ -24,201 +27,200 @@ type Keeper struct { // - creating new ModuleAccounts for each trading pair // - burning minting liquidity coins // - sending to and from ModuleAccounts -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, bk types.BankKeeper, ak types.AuthKeeper, paramSpace params.Subspace) Keeper { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, bk types.BankKeeper, ak types.AccountKeeper, sk types.SupplyKeeper, paramSpace params.Subspace) Keeper { + // ensure coinswap module account is set + if addr := sk.GetModuleAddress(types.ModuleName); addr == nil { + panic(fmt.Sprintf("%s module account has not been set", types.ModuleName)) + } + return Keeper{ storeKey: key, bk: bk, ak: ak, + sk: sk, cdc: cdc, - paramSpace: paramSpace.WithTypeTable(types.ParamTypeTable()), + paramSpace: paramSpace.WithKeyTable(types.ParamKeyTable()), } } -func (k Keeper) HandleSwap(ctx sdk.Context, msg types.MsgSwapOrder) (sdk.Tags, sdk.Error) { - tags := sdk.EmptyTags() +func (k Keeper) Swap(ctx sdk.Context, msg types.MsgSwapOrder) sdk.Error { var amount sdk.Int var err sdk.Error - var isDoubleSwap = msg.Input.Coin.Denom != sdk.IrisAtto && msg.Output.Coin.Denom != sdk.IrisAtto + + standardDenom := k.GetParams(ctx).StandardDenom + isDoubleSwap := (msg.Input.Coin.Denom != standardDenom) && (msg.Output.Coin.Denom != standardDenom) if msg.IsBuyOrder && isDoubleSwap { amount, err = k.doubleTradeInputForExactOutput(ctx, msg.Input, msg.Output) } else if msg.IsBuyOrder && !isDoubleSwap { - amount, err = k.tradeInputForExactOutput(ctx, msg.Input, msg.Output) + amount, err = k.TradeInputForExactOutput(ctx, msg.Input, msg.Output) } else if !msg.IsBuyOrder && isDoubleSwap { amount, err = k.doubleTradeExactInputForOutput(ctx, msg.Input, msg.Output) } else if !msg.IsBuyOrder && !isDoubleSwap { - amount, err = k.tradeExactInputForOutput(ctx, msg.Input, msg.Output) + amount, err = k.TradeExactInputForOutput(ctx, msg.Input, msg.Output) } if err != nil { - return tags, err + return err } - tags = sdk.NewTags( - types.TagAmount, []byte(amount.String()), - types.TagSender, []byte(msg.Input.Address.String()), - types.TagRecipient, []byte(msg.Output.Address.String()), - types.TagIsBuyOrder, []byte(strconv.FormatBool(msg.IsBuyOrder)), - types.TagTokenPair, []byte(getTokenPairByDenom(msg.Input.Coin.Denom, msg.Output.Coin.Denom)), + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeSwap, + sdk.NewAttribute(types.AttributeValueAmount, amount.String()), + sdk.NewAttribute(types.AttributeValueSender, msg.Input.Address.String()), + sdk.NewAttribute(types.AttributeValueRecipient, msg.Output.Address.String()), + sdk.NewAttribute(types.AttributeValueIsBuyOrder, strconv.FormatBool(msg.IsBuyOrder)), + sdk.NewAttribute(types.AttributeValueTokenPair, getTokenPairByDenom(msg.Input.Coin.Denom, msg.Output.Coin.Denom)), + ), ) - return tags, nil + return nil } -func (k Keeper) HandleAddLiquidity(ctx sdk.Context, msg types.MsgAddLiquidity) (sdk.Tags, sdk.Error) { - tags := sdk.EmptyTags() - uniId, err := types.GetUniId(sdk.IrisAtto, msg.MaxToken.Denom) - if err != nil { - return tags, err - } - uniDenom, err := types.GetUniDenom(uniId) +func (k Keeper) AddLiquidity(ctx sdk.Context, msg types.MsgAddLiquidity) sdk.Error { + standardDenom := k.GetParams(ctx).StandardDenom + uniDenom, err := types.GetUniDenomFromDenom(msg.MaxToken.Denom) if err != nil { - return tags, err + return err } - reservePool := k.GetReservePool(ctx, uniId) - irisReserveAmt := reservePool.AmountOf(sdk.IrisAtto) + reservePool := k.GetReservePool(ctx, uniDenom) + standardReserveAmt := reservePool.AmountOf(standardDenom) tokenReserveAmt := reservePool.AmountOf(msg.MaxToken.Denom) - liquidity := reservePool.AmountOf(uniDenom) + liquidity := k.sk.GetSupply(ctx).GetTotal().AmountOf(uniDenom) var mintLiquidityAmt sdk.Int var depositToken sdk.Coin - var irisCoin = sdk.NewCoin(sdk.IrisAtto, msg.ExactIrisAmt) + var standardCoin = sdk.NewCoin(standardDenom, msg.ExactStandardAmt) // calculate amount of UNI to be minted for sender // and coin amount to be deposited if liquidity.IsZero() { - mintLiquidityAmt = msg.ExactIrisAmt + mintLiquidityAmt = msg.ExactStandardAmt depositToken = sdk.NewCoin(msg.MaxToken.Denom, msg.MaxToken.Amount) } else { - mintLiquidityAmt = (liquidity.Mul(msg.ExactIrisAmt)).Div(irisReserveAmt) + mintLiquidityAmt = (liquidity.Mul(msg.ExactStandardAmt)).Quo(standardReserveAmt) if mintLiquidityAmt.LT(msg.MinLiquidity) { - return tags, types.ErrConstraintNotMet(fmt.Sprintf("liquidity amount not met, user expected: no less than %s, actual: %s", msg.MinLiquidity.String(), mintLiquidityAmt.String())) + return types.ErrConstraintNotMet(fmt.Sprintf("liquidity amount not met, user expected: no less than %s, actual: %s", msg.MinLiquidity.String(), mintLiquidityAmt.String())) } - depositAmt := (tokenReserveAmt.Mul(msg.ExactIrisAmt)).Div(irisReserveAmt).AddRaw(1) + depositAmt := (tokenReserveAmt.Mul(msg.ExactStandardAmt)).Quo(standardReserveAmt).AddRaw(1) depositToken = sdk.NewCoin(msg.MaxToken.Denom, depositAmt) if depositAmt.GT(msg.MaxToken.Amount) { - return tags, types.ErrConstraintNotMet(fmt.Sprintf("token amount not met, user expected: no more than %s, actual: %s", msg.MaxToken.String(), depositToken.String())) + return types.ErrConstraintNotMet(fmt.Sprintf("token amount not met, user expected: no more than %s, actual: %s", msg.MaxToken.String(), depositToken.String())) } } - tags = sdk.NewTags( - types.TagSender, []byte(msg.Sender.String()), - types.TagTokenPair, []byte(getTokenPairByDenom(msg.MaxToken.Denom, sdk.IrisAtto)), + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeAddLiquidity, + sdk.NewAttribute(types.AttributeValueSender, msg.Sender.String()), + sdk.NewAttribute(types.AttributeValueTokenPair, getTokenPairByDenom(msg.MaxToken.Denom, standardDenom)), + ), ) - return tags, k.addLiquidity(ctx, msg.Sender, irisCoin, depositToken, uniId, mintLiquidityAmt) + + return k.addLiquidity(ctx, msg.Sender, standardCoin, depositToken, uniDenom, mintLiquidityAmt) } -func (k Keeper) addLiquidity(ctx sdk.Context, sender sdk.AccAddress, irisCoin, token sdk.Coin, uniId string, mintLiquidityAmt sdk.Int) sdk.Error { - depositedTokens := sdk.NewCoins(irisCoin, token) - poolAddr := getReservePoolAddr(uniId) +func (k Keeper) addLiquidity(ctx sdk.Context, sender sdk.AccAddress, standardCoin, token sdk.Coin, uniDenom string, mintLiquidityAmt sdk.Int) sdk.Error { + depositedTokens := sdk.NewCoins(standardCoin, token) + poolAddr := GetReservePoolAddr(uniDenom) // transfer deposited token into coinswaps Account - _, err := k.bk.SendCoins(ctx, sender, poolAddr, depositedTokens) - if err != nil { + if err := k.bk.SendCoins(ctx, sender, poolAddr, depositedTokens); err != nil { return err } - ctx.CoinFlowTags().AppendCoinFlowTag(ctx, sender.String(), poolAddr.String(), depositedTokens.String(), sdk.CoinSwapAddLiquidityFlow, "") - - uniDenom, err := types.GetUniDenom(uniId) - if err != nil { + mintToken := sdk.NewCoins(sdk.NewCoin(uniDenom, mintLiquidityAmt)) + if err := k.sk.MintCoins(ctx, types.ModuleName, mintToken); err != nil { + return err + } + // send half of the liquidity vouchers from module account to sender + if err := k.sk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, sender, mintToken); err != nil { return err } - // mint liquidity vouchers for reserve Pool - mintToken := sdk.NewCoins(sdk.NewCoin(uniDenom, mintLiquidityAmt)) - k.bk.AddCoins(ctx, poolAddr, mintToken) - ctx.CoinFlowTags().AppendCoinFlowTag(ctx, "", poolAddr.String(), mintToken.String(), sdk.MintTokenFlow, "") - - // mint liquidity vouchers for sender - k.bk.AddCoins(ctx, sender, mintToken) - ctx.CoinFlowTags().AppendCoinFlowTag(ctx, "", sender.String(), mintToken.String(), sdk.MintTokenFlow, "") return nil } -func (k Keeper) HandleRemoveLiquidity(ctx sdk.Context, msg types.MsgRemoveLiquidity) (sdk.Tags, sdk.Error) { - tags := sdk.EmptyTags() +func (k Keeper) RemoveLiquidity(ctx sdk.Context, msg types.MsgRemoveLiquidity) sdk.Error { + standardDenom := k.GetParams(ctx).StandardDenom uniDenom := msg.WithdrawLiquidity.Denom - uniId, err1 := sdk.GetCoinNameByDenom(uniDenom) - if err1 != nil { - return tags, types.ErrIllegalDenom(err1.Error()) - } - minTokenDenom, err := types.GetCoinMinDenomFromUniDenom(uniDenom) + + minTokenDenom, err := types.GetCoinDenomFromUniDenom(uniDenom) if err != nil { - return tags, err + return err } // check if reserve pool exists - reservePool := k.GetReservePool(ctx, uniId) + reservePool := k.GetReservePool(ctx, uniDenom) if reservePool == nil { - return tags, types.ErrReservePoolNotExists("") + return types.ErrReservePoolNotExists("") } - irisReserveAmt := reservePool.AmountOf(sdk.IrisAtto) + standardReserveAmt := reservePool.AmountOf(standardDenom) tokenReserveAmt := reservePool.AmountOf(minTokenDenom) - liquidityReserve := reservePool.AmountOf(uniDenom) - if irisReserveAmt.LT(msg.MinIrisAmt) { - return tags, types.ErrInsufficientFunds(fmt.Sprintf("insufficient %s funds, user expected: %s, actual: %s", sdk.IrisAtto, msg.MinIrisAmt.String(), irisReserveAmt.String())) + liquidityReserve := k.sk.GetSupply(ctx).GetTotal().AmountOf(uniDenom) + if standardReserveAmt.LT(msg.MinStandardAmt) { + return types.ErrInsufficientFunds(fmt.Sprintf("insufficient %s funds, user expected: %s, actual: %s", standardDenom, msg.MinStandardAmt.String(), standardReserveAmt.String())) } if tokenReserveAmt.LT(msg.MinToken) { - return tags, types.ErrInsufficientFunds(fmt.Sprintf("insufficient %s funds, user expected: %s, actual: %s", minTokenDenom, msg.MinToken.String(), tokenReserveAmt.String())) + return types.ErrInsufficientFunds(fmt.Sprintf("insufficient %s funds, user expected: %s, actual: %s", minTokenDenom, msg.MinToken.String(), tokenReserveAmt.String())) } if liquidityReserve.LT(msg.WithdrawLiquidity.Amount) { - return tags, types.ErrInsufficientFunds(fmt.Sprintf("insufficient %s funds, user expected: %s, actual: %s", uniDenom, msg.WithdrawLiquidity.Amount.String(), liquidityReserve.String())) + return types.ErrInsufficientFunds(fmt.Sprintf("insufficient %s funds, user expected: %s, actual: %s", uniDenom, msg.WithdrawLiquidity.Amount.String(), liquidityReserve.String())) } // calculate amount of UNI to be burned for sender // and coin amount to be returned - irisWithdrawnAmt := msg.WithdrawLiquidity.Amount.Mul(irisReserveAmt).Div(liquidityReserve) - tokenWithdrawnAmt := msg.WithdrawLiquidity.Amount.Mul(tokenReserveAmt).Div(liquidityReserve) + irisWithdrawnAmt := msg.WithdrawLiquidity.Amount.Mul(standardReserveAmt).Quo(liquidityReserve) + tokenWithdrawnAmt := msg.WithdrawLiquidity.Amount.Mul(tokenReserveAmt).Quo(liquidityReserve) - irisWithdrawCoin := sdk.NewCoin(sdk.IrisAtto, irisWithdrawnAmt) + irisWithdrawCoin := sdk.NewCoin(standardDenom, irisWithdrawnAmt) tokenWithdrawCoin := sdk.NewCoin(minTokenDenom, tokenWithdrawnAmt) deductUniCoin := msg.WithdrawLiquidity - if irisWithdrawCoin.Amount.LT(msg.MinIrisAmt) { - return tags, types.ErrConstraintNotMet(fmt.Sprintf("iris amount not met, user expected: no less than %s, actual: %s", sdk.NewCoin(sdk.IrisAtto, msg.MinIrisAmt).String(), irisWithdrawCoin.String())) + if irisWithdrawCoin.Amount.LT(msg.MinStandardAmt) { + return types.ErrConstraintNotMet(fmt.Sprintf("iris amount not met, user expected: no less than %s, actual: %s", sdk.NewCoin(standardDenom, msg.MinStandardAmt).String(), irisWithdrawCoin.String())) } if tokenWithdrawCoin.Amount.LT(msg.MinToken) { - return tags, types.ErrConstraintNotMet(fmt.Sprintf("token amount not met, user expected: no less than %s, actual: %s", sdk.NewCoin(minTokenDenom, msg.MinToken).String(), tokenWithdrawCoin.String())) + return types.ErrConstraintNotMet(fmt.Sprintf("token amount not met, user expected: no less than %s, actual: %s", sdk.NewCoin(minTokenDenom, msg.MinToken).String(), tokenWithdrawCoin.String())) } - poolAddr := getReservePoolAddr(uniId) - tags = sdk.NewTags( - types.TagSender, []byte(msg.Sender.String()), - types.TagTokenPair, []byte(getTokenPairByDenom(minTokenDenom, sdk.IrisAtto)), + poolAddr := GetReservePoolAddr(uniDenom) + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeRemoveLiquidity, + sdk.NewAttribute(types.AttributeValueSender, msg.Sender.String()), + sdk.NewAttribute(types.AttributeValueTokenPair, getTokenPairByDenom(minTokenDenom, standardDenom)), + ), ) - return tags, k.removeLiquidity(ctx, poolAddr, msg.Sender, deductUniCoin, irisWithdrawCoin, tokenWithdrawCoin) + + return k.removeLiquidity(ctx, poolAddr, msg.Sender, deductUniCoin, irisWithdrawCoin, tokenWithdrawCoin) } func (k Keeper) removeLiquidity(ctx sdk.Context, poolAddr, sender sdk.AccAddress, deductUniCoin, irisWithdrawCoin, tokenWithdrawCoin sdk.Coin) sdk.Error { - // burn liquidity from reserve Pool + deltaCoins := sdk.NewCoins(deductUniCoin) - _, _, err := k.bk.SubtractCoins(ctx, poolAddr, deltaCoins) - if err != nil { + + // send liquidity vouchers to be burned from sender account to module account + if err := k.sk.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, deltaCoins); err != nil { return err } - ctx.CoinFlowTags().AppendCoinFlowTag(ctx, poolAddr.String(), "", deltaCoins.String(), sdk.BurnFlow, "") - - // burn liquidity from account - _, _, err = k.bk.SubtractCoins(ctx, sender, deltaCoins) - if err != nil { + // burn liquidity vouchers of reserve pool form module account + if err := k.sk.BurnCoins(ctx, types.ModuleName, deltaCoins); err != nil { return err } - ctx.CoinFlowTags().AppendCoinFlowTag(ctx, sender.String(), "", deltaCoins.String(), sdk.BurnFlow, "") - // transfer withdrawn liquidity from coinswaps special account to sender's account + // transfer withdrawn liquidity from coinswaps reserve pool account to sender account coins := sdk.NewCoins(irisWithdrawCoin, tokenWithdrawCoin) - _, err = k.bk.SendCoins(ctx, poolAddr, sender, coins) - if err == nil { - ctx.CoinFlowTags().AppendCoinFlowTag(ctx, poolAddr.String(), sender.String(), coins.String(), sdk.CoinSwapRemoveLiquidityFlow, "") - } - return err + + return k.bk.SendCoins(ctx, poolAddr, sender, coins) } // GetReservePool returns the total balance of an reserve pool at the // provided denomination. -func (k Keeper) GetReservePool(ctx sdk.Context, uniId string) (coins sdk.Coins) { - swapPoolAccAddr := getReservePoolAddr(uniId) +func (k Keeper) GetReservePool(ctx sdk.Context, uniDenom string) (coins sdk.Coins) { + swapPoolAccAddr := GetReservePoolAddr(uniDenom) acc := k.ak.GetAccount(ctx, swapPoolAccAddr) if acc == nil { return nil @@ -243,19 +245,10 @@ func (k Keeper) Init(ctx sdk.Context) { k.paramSpace.SetParamSet(ctx, ¶mSet) } -func getReservePoolAddr(uniDenom string) sdk.AccAddress { +func GetReservePoolAddr(uniDenom string) sdk.AccAddress { return sdk.AccAddress(crypto.AddressHash([]byte(uniDenom))) } func getTokenPairByDenom(inputDenom, outputDenom string) string { - inputToken, err := sdk.GetCoinNameByDenom(inputDenom) - if err != nil { - panic(err) - } - outputToken, err := sdk.GetCoinNameByDenom(outputDenom) - if err != nil { - panic(err) - } - - return fmt.Sprintf("%s-%s", outputToken, inputToken) + return fmt.Sprintf("%s-%s", outputDenom, inputDenom) } diff --git a/modules/coinswap/internal/keeper/keeper_test.go b/modules/coinswap/internal/keeper/keeper_test.go index c55ca4d5a..f8c4e5c2e 100644 --- a/modules/coinswap/internal/keeper/keeper_test.go +++ b/modules/coinswap/internal/keeper/keeper_test.go @@ -1,67 +1,116 @@ -package keeper +package keeper_test import ( - "github.com/stretchr/testify/require" + "fmt" "testing" "time" - "github.com/irisnet/irishub/app/v2/coinswap/internal/types" - sdk "github.com/irisnet/irishub/types" + "github.com/stretchr/testify/suite" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/irisnet/irishub/modules/coinswap/internal/keeper" + "github.com/irisnet/irishub/modules/coinswap/internal/types" + "github.com/irisnet/irishub/simapp" +) + +const ( + denomStandard = types.StandardDenom + denomBTC = "btc" + denomETH = "eth" + unidenomBTC = types.FormatUniABSPrefix + "btc" + unidenomETH = types.FormatUniABSPrefix + "eth" ) // test that the params can be properly set and retrieved -func TestParams(t *testing.T) { - ctx, keeper, _ := createTestInput(t, sdk.NewInt(0), 0) +type KeeperTestSuite struct { + suite.Suite + + cdc *codec.Codec + ctx sdk.Context + app *simapp.SimApp +} + +func (suite *KeeperTestSuite) SetupTest() { + app := simapp.Setup(false) + + suite.cdc = app.Codec() + suite.ctx = app.BaseApp.NewContext(false, abci.Header{}) + suite.app = app +} +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) TestParams() { cases := []struct { params types.Params }{ {types.DefaultParams()}, - {types.NewParams(sdk.NewRat(5, 10))}, + {types.NewParams(sdk.NewDecWithPrec(5, 10), denomStandard)}, } - for _, tc := range cases { - keeper.SetParams(ctx, tc.params) + suite.app.CoinswapKeeper.SetParams(suite.ctx, tc.params) - feeParam := keeper.GetParams(ctx) - require.Equal(t, tc.params.Fee, feeParam.Fee) + feeParam := suite.app.CoinswapKeeper.GetParams(suite.ctx) + suite.Equal(tc.params.Fee, feeParam.Fee) } } -func TestKeeper_UpdateLiquidity(t *testing.T) { - total, _ := sdk.NewIntFromString("10000000000000000000") - ctx, keeper, accs := createTestInput(t, total, 1) - sender := accs[0].GetAddress() - denom1 := "btc-min" - denom2 := sdk.IrisAtto - uniId, _ := types.GetUniId(denom1, denom2) - poolAddr := getReservePoolAddr(uniId) +func (suite *KeeperTestSuite) TestKeeper_UpdateLiquidity() { + amountInit, _ := sdk.NewIntFromString("10000000000000000000") + + addrSender := sdk.AccAddress([]byte("addrSender")) + _ = suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addrSender) + _ = suite.app.BankKeeper.SetCoins( + suite.ctx, + addrSender, + sdk.NewCoins( + sdk.NewCoin(denomStandard, amountInit), + sdk.NewCoin(denomBTC, amountInit), + ), + ) + + uniDenom, _ := types.GetUniDenomFromDenoms(denomBTC, denomStandard) + suite.Equal(uniDenom, unidenomBTC) + poolAddr := keeper.GetReservePoolAddr(uniDenom) btcAmt, _ := sdk.NewIntFromString("1") - depositCoin := sdk.NewCoin("btc-min", btcAmt) + depositCoin := sdk.NewCoin(denomBTC, btcAmt) - irisAmt, _ := sdk.NewIntFromString("10000000000000000000") + standardAmt, _ := sdk.NewIntFromString("10000000000000000000") minReward := sdk.NewInt(1) deadline := time.Now().Add(1 * time.Minute) - msg := types.NewMsgAddLiquidity(depositCoin, irisAmt, minReward, deadline.Unix(), sender) - _, err := keeper.HandleAddLiquidity(ctx, msg) - //assert - require.Nil(t, err) - reservePoolBalances := keeper.ak.GetAccount(ctx, poolAddr).GetCoins() - require.Equal(t, "1btc-min,10000000000000000000iris-atto,10000000000000000000uni:btc-min", reservePoolBalances.String()) - senderBlances := keeper.ak.GetAccount(ctx, sender).GetCoins() - require.Equal(t, "9999999999999999999btc-min,10000000000000000000uni:btc-min", senderBlances.String()) + msg := types.NewMsgAddLiquidity(depositCoin, standardAmt, minReward, deadline.Unix(), addrSender) + + err := suite.app.CoinswapKeeper.AddLiquidity(suite.ctx, msg) + suite.Nil(err) + + reservePoolBalances := suite.app.AccountKeeper.GetAccount(suite.ctx, poolAddr).GetCoins() + moduleAccountBalances := suite.app.SupplyKeeper.GetSupply(suite.ctx).GetTotal() + suite.Equal(fmt.Sprintf("1%s,10000000000000000000%s", denomBTC, denomStandard), reservePoolBalances.String()) + suite.Equal("10000000000000000000", moduleAccountBalances.AmountOf(unidenomBTC).String()) + + senderBlances := suite.app.AccountKeeper.GetAccount(suite.ctx, addrSender).GetCoins() + suite.Equal(fmt.Sprintf("9999999999999999999%s,10000000000000000000%s", denomBTC, unidenomBTC), senderBlances.String()) withdraw, _ := sdk.NewIntFromString("10000000000000000000") - msgRemove := types.NewMsgRemoveLiquidity(sdk.NewInt(1), sdk.NewCoin("uni:btc-min", withdraw), - sdk.NewInt(1), ctx.BlockHeader().Time.Unix(), - sender) + msgRemove := types.NewMsgRemoveLiquidity( + sdk.NewInt(1), + sdk.NewCoin(unidenomBTC, withdraw), + sdk.NewInt(1), + suite.ctx.BlockHeader().Time.Unix(), + addrSender, + ) - _, err = keeper.HandleRemoveLiquidity(ctx, msgRemove) - require.Nil(t, err) + err = suite.app.CoinswapKeeper.RemoveLiquidity(suite.ctx, msgRemove) + suite.Nil(err) - poolAccout := keeper.ak.GetAccount(ctx, poolAddr) - acc := keeper.ak.GetAccount(ctx, sender) - require.Equal(t, "", poolAccout.GetCoins().String()) - require.Equal(t, "10000000000000000000btc-min,10000000000000000000iris-atto", acc.GetCoins().String()) + poolAccout := suite.app.AccountKeeper.GetAccount(suite.ctx, poolAddr) + acc := suite.app.AccountKeeper.GetAccount(suite.ctx, addrSender) + suite.Equal("", poolAccout.GetCoins().String()) + suite.Equal(fmt.Sprintf("10000000000000000000%s,10000000000000000000%s", denomBTC, denomStandard), acc.GetCoins().String()) } diff --git a/modules/coinswap/internal/keeper/querier.go b/modules/coinswap/internal/keeper/querier.go index e79c0877e..6accf7294 100644 --- a/modules/coinswap/internal/keeper/querier.go +++ b/modules/coinswap/internal/keeper/querier.go @@ -2,9 +2,12 @@ package keeper import ( "fmt" - "github.com/irisnet/irishub/app/v2/coinswap/internal/types" - sdk "github.com/irisnet/irishub/types" + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/irisnet/irishub/modules/coinswap/internal/types" ) // NewQuerier creates a querier for coinswap REST endpoints @@ -24,31 +27,35 @@ func NewQuerier(k Keeper) sdk.Querier { // upon success or an error if the query fails. func queryLiquidity(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { var params types.QueryLiquidityParams + standardDenom := k.GetParams(ctx).StandardDenom err := k.cdc.UnmarshalJSON(req.Data, ¶ms) if err != nil { return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) } - uniDenom, err := types.GetUniDenom(params.Id) - if err != nil { + if err := types.CheckUniDenom(params.ID); err != nil { return nil, sdk.ErrUnknownRequest(err.Error()) } - tokenDenom, err := types.GetCoinMinDenomFromUniDenom(uniDenom) + uniDenom := params.ID + + tokenDenom, err := types.GetCoinDenomFromUniDenom(uniDenom) if err != nil { return nil, sdk.ErrUnknownRequest(err.Error()) } - reservePool := k.GetReservePool(ctx, params.Id) + reservePool := k.GetReservePool(ctx, params.ID) + // all liquidity vouchers in module account + liquidities := k.sk.GetSupply(ctx).GetTotal() - iris := sdk.NewCoin(sdk.IrisAtto, reservePool.AmountOf(sdk.IrisAtto)) + standard := sdk.NewCoin(standardDenom, reservePool.AmountOf(standardDenom)) token := sdk.NewCoin(tokenDenom, reservePool.AmountOf(tokenDenom)) - liquidity := sdk.NewCoin(uniDenom, reservePool.AmountOf(uniDenom)) + liquidity := sdk.NewCoin(uniDenom, liquidities.AmountOf(uniDenom)) swapParams := k.GetParams(ctx) - fee := swapParams.Fee.DecimalString(types.MaxFeePrecision) + fee := swapParams.Fee.String() res := types.QueryLiquidityResponse{ - Iris: iris, + Standard: standard, Token: token, Liquidity: liquidity, Fee: fee, diff --git a/modules/coinswap/internal/keeper/querier_test.go b/modules/coinswap/internal/keeper/querier_test.go index 8b0d71456..b9a58e111 100644 --- a/modules/coinswap/internal/keeper/querier_test.go +++ b/modules/coinswap/internal/keeper/querier_test.go @@ -1,35 +1,32 @@ -package keeper +package keeper_test import ( "fmt" - "testing" - "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" - "github.com/irisnet/irishub/app/v2/coinswap/internal/types" - sdk "github.com/irisnet/irishub/types" + "github.com/irisnet/irishub/modules/coinswap/internal/keeper" + "github.com/irisnet/irishub/modules/coinswap/internal/types" ) -func TestNewQuerier(t *testing.T) { - ctx, keeper, _ := createTestInput(t, sdk.NewInt(100), 2) +func (suite *KeeperTestSuite) TestNewQuerier() { req := abci.RequestQuery{ Path: "", Data: []byte{}, } - querier := NewQuerier(keeper) + querier := keeper.NewQuerier(suite.app.CoinswapKeeper) // query with incorrect path - res, err := querier(ctx, []string{"other"}, req) - require.Error(t, err) - require.Nil(t, res) + res, err := querier(suite.ctx, []string{"other"}, req) + suite.Error(err) + suite.Nil(res) // query for non existent reserve pool should return an error req.Path = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryLiquidity) - req.Data = keeper.cdc.MustMarshalJSON("btc") - res, err = querier(ctx, []string{"liquidity"}, req) - require.Error(t, err) - require.Nil(t, res) + req.Data = suite.cdc.MustMarshalJSON("btc") + res, err = querier(suite.ctx, []string{"liquidity"}, req) + suite.Error(err) + suite.Nil(res) } diff --git a/modules/coinswap/internal/keeper/swap.go b/modules/coinswap/internal/keeper/swap.go index 63b5acc84..7c8ee04ca 100644 --- a/modules/coinswap/internal/keeper/swap.go +++ b/modules/coinswap/internal/keeper/swap.go @@ -2,30 +2,28 @@ package keeper import ( "fmt" - "github.com/irisnet/irishub/app/v2/coinswap/internal/types" - sdk "github.com/irisnet/irishub/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/irisnet/irishub/modules/coinswap/internal/types" ) func (k Keeper) swapCoins(ctx sdk.Context, sender, recipient sdk.AccAddress, coinSold, coinBought sdk.Coin) sdk.Error { - uniId, err := types.GetUniId(coinSold.Denom, coinBought.Denom) + uniDenom, err := types.GetUniDenomFromDenoms(coinSold.Denom, coinBought.Denom) if err != nil { return err } - poolAddr := getReservePoolAddr(uniId) - _, err = k.bk.SendCoins(ctx, sender, poolAddr, sdk.NewCoins(coinSold)) + poolAddr := GetReservePoolAddr(uniDenom) + err = k.bk.SendCoins(ctx, sender, poolAddr, sdk.NewCoins(coinSold)) if err != nil { return err } - ctx.CoinFlowTags().AppendCoinFlowTag(ctx, sender.String(), poolAddr.String(), coinSold.String(), sdk.CoinSwapInputFlow, "") - if recipient.Empty() { recipient = sender } - _, err = k.bk.SendCoins(ctx, poolAddr, recipient, sdk.NewCoins(coinBought)) - - ctx.CoinFlowTags().AppendCoinFlowTag(ctx, poolAddr.String(), recipient.String(), coinBought.String(), sdk.CoinSwapOutputFlow, "") + err = k.bk.SendCoins(ctx, poolAddr, recipient, sdk.NewCoins(coinBought)) return err } @@ -37,13 +35,13 @@ Calculate the amount of another token to be received based on the exact amount o @return : token amount that will to be received */ func (k Keeper) calculateWithExactInput(ctx sdk.Context, exactSoldCoin sdk.Coin, boughtTokenDenom string) (sdk.Int, sdk.Error) { - uniId, err := types.GetUniId(exactSoldCoin.Denom, boughtTokenDenom) + uniDenom, err := types.GetUniDenomFromDenoms(exactSoldCoin.Denom, boughtTokenDenom) if err != nil { return sdk.ZeroInt(), err } - reservePool := k.GetReservePool(ctx, uniId) + reservePool := k.GetReservePool(ctx, uniDenom) if reservePool == nil { - return sdk.ZeroInt(), types.ErrReservePoolNotExists(fmt.Sprintf("reserve pool for %s not found", uniId)) + return sdk.ZeroInt(), types.ErrReservePoolNotExists(fmt.Sprintf("reserve pool for %s not found", uniDenom)) } inputReserve := reservePool.AmountOf(exactSoldCoin.Denom) outputReserve := reservePool.AmountOf(boughtTokenDenom) @@ -56,19 +54,19 @@ func (k Keeper) calculateWithExactInput(ctx sdk.Context, exactSoldCoin sdk.Coin, } param := k.GetParams(ctx) - boughtTokenAmt := getInputPrice(exactSoldCoin.Amount, inputReserve, outputReserve, param.Fee) + boughtTokenAmt := GetInputPrice(exactSoldCoin.Amount, inputReserve, outputReserve, param.Fee) return boughtTokenAmt, nil } /** -Sell exact amount of a token for buying another, one of them must be iris +Sell exact amount of a token for buying another, one of them must be standard token @param input: exact amount of the token to be sold @param output: min amount of the token to be bought @param sender: address of the sender @param receipt: address of the receiver @return: actual amount of the token to be bought */ -func (k Keeper) tradeExactInputForOutput(ctx sdk.Context, input types.Input, output types.Output) (sdk.Int, sdk.Error) { +func (k Keeper) TradeExactInputForOutput(ctx sdk.Context, input types.Input, output types.Output) (sdk.Int, sdk.Error) { boughtTokenAmt, err := k.calculateWithExactInput(ctx, input.Coin, output.Coin.Denom) if err != nil { return sdk.ZeroInt(), err @@ -87,7 +85,7 @@ func (k Keeper) tradeExactInputForOutput(ctx sdk.Context, input types.Input, out } /** -Sell exact amount of a token for buying another, non of them are iris +Sell exact amount of a token for buying another, non of them are standard token @param input: exact amount of the token to be sold @param output: min amount of the token to be bought @param sender: address of the sender @@ -95,17 +93,18 @@ Sell exact amount of a token for buying another, non of them are iris @return: actual amount of the token to be bought */ func (k Keeper) doubleTradeExactInputForOutput(ctx sdk.Context, input types.Input, output types.Output) (sdk.Int, sdk.Error) { - nativeAmount, err := k.calculateWithExactInput(ctx, input.Coin, sdk.IrisAtto) + standardDenom := k.GetParams(ctx).StandardDenom + standardAmount, err := k.calculateWithExactInput(ctx, input.Coin, standardDenom) if err != nil { return sdk.ZeroInt(), err } - nativeCoin := sdk.NewCoin(sdk.IrisAtto, nativeAmount) - err = k.swapCoins(ctx, input.Address, output.Address, input.Coin, nativeCoin) + standardCoin := sdk.NewCoin(standardDenom, standardAmount) + err = k.swapCoins(ctx, input.Address, output.Address, input.Coin, standardCoin) if err != nil { return sdk.ZeroInt(), err } - boughtAmt, err := k.calculateWithExactInput(ctx, nativeCoin, output.Coin.Denom) + boughtAmt, err := k.calculateWithExactInput(ctx, standardCoin, output.Coin.Denom) if err != nil { return sdk.ZeroInt(), err } @@ -116,7 +115,7 @@ func (k Keeper) doubleTradeExactInputForOutput(ctx sdk.Context, input types.Inpu return sdk.ZeroInt(), types.ErrConstraintNotMet(fmt.Sprintf("insufficient amount of %s, user expected: %s, actual: %s", output.Coin.Denom, output.Coin.Amount.String(), boughtAmt.String())) } - err = k.swapCoins(ctx, input.Address, output.Address, nativeCoin, boughtToken) + err = k.swapCoins(ctx, input.Address, output.Address, standardCoin, boughtToken) if err != nil { return sdk.ZeroInt(), err } @@ -130,13 +129,13 @@ Calculate the amount of the token to be payed based on the exact amount of the t @return: actual amount of the token to be payed */ func (k Keeper) calculateWithExactOutput(ctx sdk.Context, exactBoughtCoin sdk.Coin, soldTokenDenom string) (sdk.Int, sdk.Error) { - uniId, err := types.GetUniId(exactBoughtCoin.Denom, soldTokenDenom) + uniDenom, err := types.GetUniDenomFromDenoms(exactBoughtCoin.Denom, soldTokenDenom) if err != nil { return sdk.ZeroInt(), types.ErrReservePoolNotExists(fmt.Sprintf("reserve pool not found: %s", err.Error())) } - reservePool := k.GetReservePool(ctx, uniId) + reservePool := k.GetReservePool(ctx, uniDenom) if reservePool == nil { - return sdk.ZeroInt(), types.ErrReservePoolNotExists(fmt.Sprintf("reserve pool for %s not found", uniId)) + return sdk.ZeroInt(), types.ErrReservePoolNotExists(fmt.Sprintf("reserve pool for %s not found", uniDenom)) } outputReserve := reservePool.AmountOf(exactBoughtCoin.Denom) inputReserve := reservePool.AmountOf(soldTokenDenom) @@ -152,19 +151,19 @@ func (k Keeper) calculateWithExactOutput(ctx sdk.Context, exactBoughtCoin sdk.Co } param := k.GetParams(ctx) - soldTokenAmt := getOutputPrice(exactBoughtCoin.Amount, inputReserve, outputReserve, param.Fee) + soldTokenAmt := GetOutputPrice(exactBoughtCoin.Amount, inputReserve, outputReserve, param.Fee) return soldTokenAmt, nil } /** -Buy exact amount of a token by specifying the max amount of another token, one of them must be iris +Buy exact amount of a token by specifying the max amount of another token, one of them must be standard token @param input : max amount of the token to be payed @param output : exact amount of the token to be bought @param sender : address of the sender @param receipt : address of the receiver @return : actual amount of the token to be payed */ -func (k Keeper) tradeInputForExactOutput(ctx sdk.Context, input types.Input, output types.Output) (sdk.Int, sdk.Error) { +func (k Keeper) TradeInputForExactOutput(ctx sdk.Context, input types.Input, output types.Output) (sdk.Int, sdk.Error) { soldTokenAmt, err := k.calculateWithExactOutput(ctx, output.Coin, input.Coin.Denom) if err != nil { return sdk.ZeroInt(), err @@ -183,7 +182,7 @@ func (k Keeper) tradeInputForExactOutput(ctx sdk.Context, input types.Input, out } /** -Buy exact amount of a token by specifying the max amount of another token, non of them are iris +Buy exact amount of a token by specifying the max amount of another token, non of them are standard token @param input : max amount of the token to be payed @param output : exact amount of the token to be bought @param sender : address of the sender @@ -191,13 +190,14 @@ Buy exact amount of a token by specifying the max amount of another token, non o @return : actual amount of the token to be payed */ func (k Keeper) doubleTradeInputForExactOutput(ctx sdk.Context, input types.Input, output types.Output) (sdk.Int, sdk.Error) { - soldIrisAmount, err := k.calculateWithExactOutput(ctx, output.Coin, sdk.IrisAtto) + standardDenom := k.GetParams(ctx).StandardDenom + soldStandardAmount, err := k.calculateWithExactOutput(ctx, output.Coin, standardDenom) if err != nil { return sdk.ZeroInt(), err } - soldIrisCoin := sdk.NewCoin(sdk.IrisAtto, soldIrisAmount) + soldStandardCoin := sdk.NewCoin(standardDenom, soldStandardAmount) - soldTokenAmt, err := k.calculateWithExactOutput(ctx, soldIrisCoin, input.Coin.Denom) + soldTokenAmt, err := k.calculateWithExactOutput(ctx, soldStandardCoin, input.Coin.Denom) if err != nil { return sdk.ZeroInt(), err } @@ -209,11 +209,11 @@ func (k Keeper) doubleTradeInputForExactOutput(ctx sdk.Context, input types.Inpu return sdk.ZeroInt(), types.ErrConstraintNotMet(fmt.Sprintf("insufficient amount of %s, user expected: %s, actual: %s", input.Coin.Denom, input.Coin.Amount.String(), soldTokenAmt.String())) } - err = k.swapCoins(ctx, input.Address, output.Address, soldTokenCoin, soldIrisCoin) + err = k.swapCoins(ctx, input.Address, output.Address, soldTokenCoin, soldStandardCoin) if err != nil { return sdk.ZeroInt(), err } - err = k.swapCoins(ctx, input.Address, output.Address, soldIrisCoin, output.Coin) + err = k.swapCoins(ctx, input.Address, output.Address, soldStandardCoin, output.Coin) if err != nil { return sdk.ZeroInt(), err } @@ -223,19 +223,19 @@ func (k Keeper) doubleTradeInputForExactOutput(ctx sdk.Context, input types.Inpu // getInputPrice returns the amount of coins bought (calculated) given the input amount being sold (exact) // The fee is included in the input coins being bought // https://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdf -func getInputPrice(inputAmt, inputReserve, outputReserve sdk.Int, fee sdk.Rat) sdk.Int { - deltaFee := sdk.OneRat().Sub(fee) - inputAmtWithFee := inputAmt.Mul(deltaFee.Num()) +func GetInputPrice(inputAmt, inputReserve, outputReserve sdk.Int, fee sdk.Dec) sdk.Int { + deltaFee := sdk.OneDec().Sub(fee) + inputAmtWithFee := inputAmt.Mul(sdk.NewIntFromBigInt(deltaFee.Int)) numerator := inputAmtWithFee.Mul(outputReserve) - denominator := inputReserve.Mul(deltaFee.Denom()).Add(inputAmtWithFee) - return numerator.Div(denominator) + denominator := inputReserve.Mul(sdk.NewIntWithDecimal(1, sdk.Precision)).Add(inputAmtWithFee) + return numerator.Quo(denominator) } // getOutputPrice returns the amount of coins sold (calculated) given the output amount being bought (exact) // The fee is included in the output coins being bought -func getOutputPrice(outputAmt, inputReserve, outputReserve sdk.Int, fee sdk.Rat) sdk.Int { - deltaFee := sdk.OneRat().Sub(fee) - numerator := inputReserve.Mul(outputAmt).Mul(deltaFee.Denom()) - denominator := (outputReserve.Sub(outputAmt)).Mul(deltaFee.Num()) - return numerator.Div(denominator).Add(sdk.OneInt()) +func GetOutputPrice(outputAmt, inputReserve, outputReserve sdk.Int, fee sdk.Dec) sdk.Int { + deltaFee := sdk.OneDec().Sub(fee) + numerator := inputReserve.Mul(outputAmt).Mul(sdk.NewIntWithDecimal(1, sdk.Precision)) + denominator := (outputReserve.Sub(outputAmt)).Mul(sdk.NewIntFromBigInt(deltaFee.Int)) + return numerator.Quo(denominator).Add(sdk.OneInt()) } diff --git a/modules/coinswap/internal/keeper/swap_test.go b/modules/coinswap/internal/keeper/swap_test.go index 5d542fc39..03c85871d 100644 --- a/modules/coinswap/internal/keeper/swap_test.go +++ b/modules/coinswap/internal/keeper/swap_test.go @@ -1,20 +1,16 @@ -package keeper +package keeper_test import ( "fmt" - "github.com/irisnet/irishub/app/v2/coinswap/internal/types" - sdk "github.com/irisnet/irishub/types" - "github.com/stretchr/testify/require" - "testing" "time" -) -var ( - native = sdk.IrisAtto -) + sdk "github.com/cosmos/cosmos-sdk/types" -func TestGetUniId(t *testing.T) { + "github.com/irisnet/irishub/modules/coinswap/internal/keeper" + "github.com/irisnet/irishub/modules/coinswap/internal/types" +) +func (suite *KeeperTestSuite) TestGetUniId() { cases := []struct { name string denom1 string @@ -22,21 +18,19 @@ func TestGetUniId(t *testing.T) { expectResult string expectPass bool }{ - {"denom1 is native", native, "btc-min", "uni:btc", true}, - {"denom2 is native", "btc-min", native, "uni:btc", true}, - {"denom1 equals denom2", "btc-min", "btc-min", "uni:btc", false}, - {"neither denom is native", "eth-min", "btc-min", "uni:btc", false}, + {"denom1 is denomStandard", denomStandard, denomBTC, unidenomBTC, true}, + {"denom2 is denomStandard", denomETH, denomStandard, unidenomETH, true}, + {"denom1 equals denom2", denomBTC, denomBTC, unidenomBTC, false}, + {"neither denom is denomStandard", denomETH, denomBTC, unidenomBTC, false}, } for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - uniId, err := types.GetUniId(tc.denom1, tc.denom2) - if tc.expectPass { - require.Equal(t, tc.expectResult, uniId) - } else { - require.NotNil(t, err) - } - }) + uniDenom, err := types.GetUniDenomFromDenoms(tc.denom1, tc.denom2) + if tc.expectPass { + suite.Equal(tc.expectResult, uniDenom) + } else { + suite.NotNil(err) + } } } @@ -44,76 +38,76 @@ type Data struct { delta sdk.Int x sdk.Int y sdk.Int - fee sdk.Rat + fee sdk.Dec } type SwapCase struct { data Data expect sdk.Int } -func TestGetInputPrice(t *testing.T) { +func (suite *KeeperTestSuite) TestGetInputPrice() { var datas = []SwapCase{ { - data: Data{delta: sdk.NewInt(100), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.NewRat(3, 1000)}, + data: Data{delta: sdk.NewInt(100), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.NewDecWithPrec(3, 3)}, expect: sdk.NewInt(90), }, { - data: Data{delta: sdk.NewInt(200), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.NewRat(3, 1000)}, + data: Data{delta: sdk.NewInt(200), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.NewDecWithPrec(3, 3)}, expect: sdk.NewInt(166), }, { - data: Data{delta: sdk.NewInt(300), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.NewRat(3, 1000)}, + data: Data{delta: sdk.NewInt(300), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.NewDecWithPrec(3, 3)}, expect: sdk.NewInt(230), }, { - data: Data{delta: sdk.NewInt(1000), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.NewRat(3, 1000)}, + data: Data{delta: sdk.NewInt(1000), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.NewDecWithPrec(3, 3)}, expect: sdk.NewInt(499), }, { - data: Data{delta: sdk.NewInt(1000), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.ZeroRat()}, + data: Data{delta: sdk.NewInt(1000), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.ZeroDec()}, expect: sdk.NewInt(500), }, } for _, tcase := range datas { data := tcase.data - actual := getInputPrice(data.delta, data.x, data.y, data.fee) + actual := keeper.GetInputPrice(data.delta, data.x, data.y, data.fee) fmt.Println(fmt.Sprintf("expect:%s,actual:%s", tcase.expect.String(), actual.String())) - require.Equal(t, tcase.expect, actual) + suite.Equal(tcase.expect, actual) } } -func TestGetOutputPrice(t *testing.T) { +func (suite *KeeperTestSuite) TestGetOutputPrice() { var datas = []SwapCase{ { - data: Data{delta: sdk.NewInt(100), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.NewRat(3, 1000)}, + data: Data{delta: sdk.NewInt(100), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.NewDecWithPrec(3, 3)}, expect: sdk.NewInt(112), }, { - data: Data{delta: sdk.NewInt(200), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.NewRat(3, 1000)}, + data: Data{delta: sdk.NewInt(200), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.NewDecWithPrec(3, 3)}, expect: sdk.NewInt(251), }, { - data: Data{delta: sdk.NewInt(300), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.NewRat(3, 1000)}, + data: Data{delta: sdk.NewInt(300), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.NewDecWithPrec(3, 3)}, expect: sdk.NewInt(430), }, { - data: Data{delta: sdk.NewInt(300), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.ZeroRat()}, + data: Data{delta: sdk.NewInt(300), x: sdk.NewInt(1000), y: sdk.NewInt(1000), fee: sdk.ZeroDec()}, expect: sdk.NewInt(429), }, } for _, tcase := range datas { data := tcase.data - actual := getOutputPrice(data.delta, data.x, data.y, data.fee) + actual := keeper.GetOutputPrice(data.delta, data.x, data.y, data.fee) fmt.Println(fmt.Sprintf("expect:%s,actual:%s", tcase.expect.String(), actual.String())) - require.Equal(t, tcase.expect, actual) + suite.Equal(tcase.expect, actual) } } -func TestKeeperSwap(t *testing.T) { - ctx, keeper, sender, reservePoolAddr, err, reservePoolBalances, senderBlances := createReservePool(t) +func (suite *KeeperTestSuite) TestKeeperSwap() { + sender, reservePoolAddr, err, reservePoolBalances, senderBlances := createReservePool(suite) - outputCoin := sdk.NewCoin("btc-min", sdk.NewInt(100)) - inputCoin := sdk.NewCoin(sdk.IrisAtto, sdk.NewInt(1000)) + outputCoin := sdk.NewCoin("btc", sdk.NewInt(100)) + inputCoin := sdk.NewCoin(denomStandard, sdk.NewInt(1000)) input := types.Input{ Address: sender, @@ -128,58 +122,76 @@ func TestKeeperSwap(t *testing.T) { msg1 := types.NewMsgSwapOrder(input, output, deadline1.Unix(), true) // first swap - _, err = keeper.HandleSwap(ctx, msg1) - require.Nil(t, err) - reservePoolBalances = keeper.ak.GetAccount(ctx, reservePoolAddr).GetCoins() - require.Equal(t, "900btc-min,1112iris-atto,1000uni:btc-min", reservePoolBalances.String()) - senderBlances = keeper.ak.GetAccount(ctx, sender).GetCoins() - require.Equal(t, "99999100btc-min,99998888iris-atto,1000uni:btc-min", senderBlances.String()) + err = suite.app.CoinswapKeeper.Swap(suite.ctx, msg1) + suite.Nil(err) + moduleAccountBalances := suite.app.SupplyKeeper.GetSupply(suite.ctx).GetTotal() + reservePoolBalances = suite.app.AccountKeeper.GetAccount(suite.ctx, reservePoolAddr).GetCoins() + suite.Equal(fmt.Sprintf("900%s,1112%s", denomBTC, denomStandard), reservePoolBalances.String()) + suite.Equal("1000", moduleAccountBalances.AmountOf(unidenomBTC).String()) + senderBlances = suite.app.AccountKeeper.GetAccount(suite.ctx, sender).GetCoins() + suite.Equal(fmt.Sprintf("99999100%s,99998888%s,1000%s", denomBTC, denomStandard, unidenomBTC), senderBlances.String()) // second swap - _, err = keeper.HandleSwap(ctx, msg1) - require.Nil(t, err) - reservePoolBalances = keeper.ak.GetAccount(ctx, reservePoolAddr).GetCoins() - require.Equal(t, "800btc-min,1252iris-atto,1000uni:btc-min", reservePoolBalances.String()) - senderBlances = keeper.ak.GetAccount(ctx, sender).GetCoins() - require.Equal(t, "99999200btc-min,99998748iris-atto,1000uni:btc-min", senderBlances.String()) + err = suite.app.CoinswapKeeper.Swap(suite.ctx, msg1) + suite.Nil(err) + moduleAccountBalances =suite.app.SupplyKeeper.GetSupply(suite.ctx).GetTotal() + reservePoolBalances = suite.app.AccountKeeper.GetAccount(suite.ctx, reservePoolAddr).GetCoins() + suite.Equal(fmt.Sprintf("800%s,1252%s", denomBTC, denomStandard), reservePoolBalances.String()) + suite.Equal("1000", moduleAccountBalances.AmountOf(unidenomBTC).String()) + senderBlances = suite.app.AccountKeeper.GetAccount(suite.ctx, sender).GetCoins() + suite.Equal(fmt.Sprintf("99999200%s,99998748%s,1000%s", denomBTC, denomStandard, unidenomBTC), senderBlances.String()) // third swap - _, err = keeper.HandleSwap(ctx, msg1) - require.Nil(t, err) - reservePoolBalances = keeper.ak.GetAccount(ctx, reservePoolAddr).GetCoins() - require.Equal(t, "700btc-min,1432iris-atto,1000uni:btc-min", reservePoolBalances.String()) + err = suite.app.CoinswapKeeper.Swap(suite.ctx, msg1) + suite.Nil(err) + moduleAccountBalances = suite.app.SupplyKeeper.GetSupply(suite.ctx).GetTotal() + reservePoolBalances = suite.app.AccountKeeper.GetAccount(suite.ctx, reservePoolAddr).GetCoins() + suite.Equal(fmt.Sprintf("700%s,1432%s", denomBTC, denomStandard), reservePoolBalances.String()) + suite.Equal("1000", moduleAccountBalances.AmountOf(unidenomBTC).String()) } -func createReservePool(t *testing.T) (sdk.Context, Keeper, sdk.AccAddress, sdk.AccAddress, sdk.Error, sdk.Coins, sdk.Coins) { - ctx, keeper, accs := createTestInput(t, sdk.NewInt(100000000), 1) - sender := accs[0].GetAddress() - denom1 := "btc-min" - denom2 := sdk.IrisAtto - uniId, _ := types.GetUniId(denom1, denom2) - reservePoolAddr := getReservePoolAddr(uniId) +func createReservePool(suite *KeeperTestSuite) (sdk.AccAddress, sdk.AccAddress, sdk.Error, sdk.Coins, sdk.Coins) { + amountInit, _ := sdk.NewIntFromString("100000000") + addrSender := sdk.AccAddress([]byte("addrSender")) + _ = suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, addrSender) + _ = suite.app.BankKeeper.SetCoins( + suite.ctx, + addrSender, + sdk.NewCoins( + sdk.NewCoin(denomStandard, amountInit), + sdk.NewCoin(denomBTC, amountInit), + ), + ) + + denom1 := denomBTC + denom2 := denomStandard + uniDenom, _ := types.GetUniDenomFromDenoms(denom1, denom2) + reservePoolAddr := keeper.GetReservePoolAddr(uniDenom) btcAmt, _ := sdk.NewIntFromString("1000") - depositCoin := sdk.NewCoin("btc-min", btcAmt) + depositCoin := sdk.NewCoin(denomBTC, btcAmt) - irisAmt, _ := sdk.NewIntFromString("1000") + standardAmt, _ := sdk.NewIntFromString("1000") minReward := sdk.NewInt(1) deadline := time.Now().Add(1 * time.Minute) - msg := types.NewMsgAddLiquidity(depositCoin, irisAmt, minReward, deadline.Unix(), sender) - _, err := keeper.HandleAddLiquidity(ctx, msg) + msg := types.NewMsgAddLiquidity(depositCoin, standardAmt, minReward, deadline.Unix(), addrSender) + err := suite.app.CoinswapKeeper.AddLiquidity(suite.ctx, msg) //assert - require.Nil(t, err) - reservePoolBalances := keeper.ak.GetAccount(ctx, reservePoolAddr).GetCoins() - require.Equal(t, "1000btc-min,1000iris-atto,1000uni:btc-min", reservePoolBalances.String()) - senderBlances := keeper.ak.GetAccount(ctx, sender).GetCoins() - require.Equal(t, "99999000btc-min,99999000iris-atto,1000uni:btc-min", senderBlances.String()) - return ctx, keeper, sender, reservePoolAddr, err, reservePoolBalances, senderBlances + suite.Nil(err) + moduleAccountBalances := suite.app.SupplyKeeper.GetSupply(suite.ctx).GetTotal() + reservePoolBalances := suite.app.AccountKeeper.GetAccount(suite.ctx, reservePoolAddr).GetCoins() + suite.Equal(fmt.Sprintf("1000%s,1000%s", denomBTC, denomStandard), reservePoolBalances.String()) + suite.Equal("1000", moduleAccountBalances.AmountOf(unidenomBTC).String()) + senderBlances := suite.app.AccountKeeper.GetAccount(suite.ctx, addrSender).GetCoins() + suite.Equal(fmt.Sprintf("99999000%s,99999000%s,1000%s", denomBTC, denomStandard, unidenomBTC), senderBlances.String()) + return addrSender, reservePoolAddr, err, reservePoolBalances, senderBlances } -func TestTradeInputForExactOutput(t *testing.T) { - ctx, keeper, sender, poolAddr, _, poolBalances, senderBlances := createReservePool(t) +func (suite *KeeperTestSuite) TestTradeInputForExactOutput() { + sender, poolAddr, _, poolBalances, senderBlances := createReservePool(suite) - outputCoin := sdk.NewCoin("btc-min", sdk.NewInt(100)) - inputCoin := sdk.NewCoin(sdk.IrisAtto, sdk.NewInt(100000)) + outputCoin := sdk.NewCoin(denomBTC, sdk.NewInt(100)) + inputCoin := sdk.NewCoin(denomStandard, sdk.NewInt(100000)) input := types.Input{ Address: sender, Coin: inputCoin, @@ -189,34 +201,34 @@ func TestTradeInputForExactOutput(t *testing.T) { } initSupplyOutput := poolBalances.AmountOf(outputCoin.Denom) - maxCnt := int(initSupplyOutput.Div(outputCoin.Amount).Int64()) + maxCnt := int(initSupplyOutput.Quo(outputCoin.Amount).Int64()) for i := 1; i < 100; i++ { - amt, err := keeper.tradeInputForExactOutput(ctx, input, output) + amt, err := suite.app.CoinswapKeeper.TradeInputForExactOutput(suite.ctx, input, output) if i == maxCnt { - require.NotNil(t, err) + suite.NotNil(err) break } - ifNil(t, err) + ifNil(suite, err) bought := sdk.NewCoins(outputCoin) - sold := sdk.NewCoins(sdk.NewCoin(sdk.IrisAtto, amt)) + sold := sdk.NewCoins(sdk.NewCoin(denomStandard, amt)) pb := poolBalances.Add(sold).Sub(bought) sb := senderBlances.Add(bought).Sub(sold) - assertResult(t, keeper, ctx, poolAddr, sender, pb, sb) + assertResult(suite, poolAddr, sender, pb, sb) poolBalances = pb senderBlances = sb } } -func TestTradeExactInputForOutput(t *testing.T) { - ctx, keeper, sender, poolAddr, _, poolBalances, senderBlances := createReservePool(t) +func (suite *KeeperTestSuite) TestTradeExactInputForOutput() { + sender, poolAddr, _, poolBalances, senderBlances := createReservePool(suite) - outputCoin := sdk.NewCoin("btc-min", sdk.NewInt(0)) - inputCoin := sdk.NewCoin(sdk.IrisAtto, sdk.NewInt(100)) + outputCoin := sdk.NewCoin(denomBTC, sdk.NewInt(0)) + inputCoin := sdk.NewCoin(denomStandard, sdk.NewInt(100)) input := types.Input{ Address: sender, Coin: inputCoin, @@ -226,33 +238,33 @@ func TestTradeExactInputForOutput(t *testing.T) { } for i := 1; i < 1000; i++ { - amt, err := keeper.tradeExactInputForOutput(ctx, input, output) - ifNil(t, err) + amt, err := suite.app.CoinswapKeeper.TradeExactInputForOutput(suite.ctx, input, output) + ifNil(suite, err) sold := sdk.NewCoins(inputCoin) - bought := sdk.NewCoins(sdk.NewCoin("btc-min", amt)) + bought := sdk.NewCoins(sdk.NewCoin(denomBTC, amt)) pb := poolBalances.Add(sold).Sub(bought) sb := senderBlances.Add(bought).Sub(sold) - assertResult(t, keeper, ctx, poolAddr, sender, pb, sb) + assertResult(suite, poolAddr, sender, pb, sb) poolBalances = pb senderBlances = sb } } -func assertResult(t *testing.T, keeper Keeper, ctx sdk.Context, reservePoolAddr, sender sdk.AccAddress, expectPoolBalance, expectSenderBalance sdk.Coins) { - reservePoolBalances := keeper.ak.GetAccount(ctx, reservePoolAddr).GetCoins() - require.Equal(t, expectPoolBalance.String(), reservePoolBalances.String()) - senderBlances := keeper.ak.GetAccount(ctx, sender).GetCoins() - require.Equal(t, expectSenderBalance.String(), senderBlances.String()) +func assertResult(suite *KeeperTestSuite, reservePoolAddr, sender sdk.AccAddress, expectPoolBalance, expectSenderBalance sdk.Coins) { + reservePoolBalances := suite.app.AccountKeeper.GetAccount(suite.ctx, reservePoolAddr).GetCoins() + suite.Equal(expectPoolBalance.String(), reservePoolBalances.String()) + senderBlances := suite.app.AccountKeeper.GetAccount(suite.ctx, sender).GetCoins() + suite.Equal(expectSenderBalance.String(), senderBlances.String()) } -func ifNil(t *testing.T, err sdk.Error) { +func ifNil(suite *KeeperTestSuite, err sdk.Error) { msg := "" if err != nil { msg = err.Error() } - require.Nil(t, err, msg) + suite.Nil(err, msg) } diff --git a/modules/coinswap/internal/keeper/test_common.go b/modules/coinswap/internal/keeper/test_common.go deleted file mode 100644 index 8f901ff6b..000000000 --- a/modules/coinswap/internal/keeper/test_common.go +++ /dev/null @@ -1,84 +0,0 @@ -package keeper - -import ( - "github.com/irisnet/irishub/app/protocol" - "testing" - - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/secp256k1" - "github.com/tendermint/tendermint/libs/log" - dbm "github.com/tendermint/tm-db" - - "github.com/stretchr/testify/require" - - "github.com/irisnet/irishub/app/v1/auth" - "github.com/irisnet/irishub/app/v1/bank" - "github.com/irisnet/irishub/app/v1/params" - "github.com/irisnet/irishub/app/v2/coinswap/internal/types" - "github.com/irisnet/irishub/codec" - "github.com/irisnet/irishub/store" - sdk "github.com/irisnet/irishub/types" -) - -// create a codec used only for testing -func makeTestCodec() *codec.Codec { - var cdc = codec.New() - - bank.RegisterCodec(cdc) - auth.RegisterCodec(cdc) - types.RegisterCodec(cdc) - sdk.RegisterCodec(cdc) - codec.RegisterCrypto(cdc) - - return cdc -} - -func createTestInput(t *testing.T, amt sdk.Int, nAccs int64) (sdk.Context, Keeper, []auth.Account) { - keyAcc := protocol.KeyAccount - keyParams := protocol.KeyParams - tkeyParams := protocol.TkeyParams - keyCoinswap := sdk.NewKVStoreKey(types.StoreKey) - - db := dbm.NewMemDB() - ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) - ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) - ms.MountStoreWithDB(keyCoinswap, sdk.StoreTypeIAVL, db) - err := ms.LoadLatestVersion() - require.Nil(t, err) - - cdc := makeTestCodec() - ctx := sdk.NewContext(ms, abci.Header{ChainID: "coinswap-chain"}, false, log.NewNopLogger()) - - pk := params.NewKeeper(cdc, keyParams, tkeyParams) - ak := auth.NewAccountKeeper(cdc, keyAcc, auth.ProtoBaseAccount) - bk := bank.NewBaseKeeper(cdc, ak) - - initialCoins := sdk.Coins{ - sdk.NewCoin(sdk.IrisAtto, amt), - sdk.NewCoin("btc-min", amt), - } - initialCoins = initialCoins.Sort() - accs := createTestAccs(ctx, int(nAccs), initialCoins, &ak) - - keeper := NewKeeper(cdc, keyCoinswap, bk, ak, pk.Subspace(types.DefaultParamSpace)) - keeper.SetParams(ctx, types.DefaultParams()) - - return ctx, keeper, accs -} - -func createTestAccs(ctx sdk.Context, numAccs int, initialCoins sdk.Coins, ak *auth.AccountKeeper) (accs []auth.Account) { - for i := 0; i < numAccs; i++ { - privKey := secp256k1.GenPrivKey() - pubKey := privKey.PubKey() - addr := sdk.AccAddress(pubKey.Address()) - acc := auth.NewBaseAccountWithAddress(addr) - acc.Coins = initialCoins - acc.PubKey = pubKey - acc.AccountNumber = uint64(i) - ak.SetAccount(ctx, &acc) - accs = append(accs, &acc) - } - return -} diff --git a/modules/coinswap/internal/types/codec.go b/modules/coinswap/internal/types/codec.go index 545dbf007..55fd3a68b 100644 --- a/modules/coinswap/internal/types/codec.go +++ b/modules/coinswap/internal/types/codec.go @@ -1,9 +1,12 @@ package types import ( - "github.com/irisnet/irishub/codec" + "github.com/cosmos/cosmos-sdk/codec" ) +// module codec +var ModuleCdc *codec.Codec + // RegisterCodec registers concrete types on the codec. func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgSwapOrder{}, "irishub/coinswap/MsgSwapOrder", nil) @@ -12,12 +15,8 @@ func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(&Params{}, "irishub/coinswap/Params", nil) } -// ModuleCdc generic sealed codec to be used throughout module -var ModuleCdc *codec.Codec - func init() { ModuleCdc = codec.New() RegisterCodec(ModuleCdc) - codec.RegisterCrypto(ModuleCdc) ModuleCdc.Seal() } diff --git a/modules/coinswap/internal/types/errors.go b/modules/coinswap/internal/types/errors.go index 21505694a..378374470 100644 --- a/modules/coinswap/internal/types/errors.go +++ b/modules/coinswap/internal/types/errors.go @@ -2,7 +2,7 @@ package types import ( - sdk "github.com/irisnet/irishub/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) const ( @@ -14,8 +14,7 @@ const ( CodeNotPositive sdk.CodeType = 104 CodeConstraintNotMet sdk.CodeType = 105 CodeIllegalDenom sdk.CodeType = 106 - CodeIllegalUniId sdk.CodeType = 107 - CodeReservePoolInsufficientFunds sdk.CodeType = 108 + CodeReservePoolInsufficientFunds sdk.CodeType = 107 ) func ErrReservePoolNotExists(msg string) sdk.Error { @@ -32,13 +31,6 @@ func ErrEqualDenom(msg string) sdk.Error { return sdk.NewError(DefaultCodespace, CodeEqualDenom, "input and output denomination are equal") } -func ErrIllegalUniId(msg string) sdk.Error { - if msg != "" { - return sdk.NewError(DefaultCodespace, CodeIllegalUniId, msg) - } - return sdk.NewError(DefaultCodespace, CodeIllegalUniId, "illegal liquidity id") -} - func ErrIllegalDenom(msg string) sdk.Error { if msg != "" { return sdk.NewError(DefaultCodespace, CodeIllegalDenom, msg) diff --git a/modules/coinswap/internal/types/events.go b/modules/coinswap/internal/types/events.go new file mode 100644 index 000000000..c7882880b --- /dev/null +++ b/modules/coinswap/internal/types/events.go @@ -0,0 +1,15 @@ +package types + +// coinswap module event types +const ( + EventTypeSwap = "swap" + EventTypeAddLiquidity = "add_liquidity" + EventTypeRemoveLiquidity = "remove_liquidity" + + AttributeValueCategory = ModuleName + AttributeValueAmount = "amount" + AttributeValueSender = "sender" + AttributeValueRecipient = "recipient" + AttributeValueIsBuyOrder = "is_buy_order" + AttributeValueTokenPair = "token_pair" +) diff --git a/modules/coinswap/internal/types/expected_keepers.go b/modules/coinswap/internal/types/expected_keepers.go index e73891a1f..a8fb40cec 100644 --- a/modules/coinswap/internal/types/expected_keepers.go +++ b/modules/coinswap/internal/types/expected_keepers.go @@ -1,18 +1,25 @@ package types import ( - "github.com/irisnet/irishub/app/v1/auth" - sdk "github.com/irisnet/irishub/types" + sdk "github.com/cosmos/cosmos-sdk/types" + auth "github.com/cosmos/cosmos-sdk/x/auth/exported" + supply "github.com/cosmos/cosmos-sdk/x/supply/exported" ) -type BankKeeper interface { - SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) (sdk.Tags, sdk.Error) - - AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) +// SupplyKeeper defines the expected supply keeper +type SupplyKeeper interface { + GetModuleAddress(name string) sdk.AccAddress + SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) sdk.Error + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) sdk.Error + MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) sdk.Error + BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) sdk.Error + GetSupply(ctx sdk.Context) (supply supply.SupplyI) +} - SubtractCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Tags, sdk.Error) +type BankKeeper interface { + SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) sdk.Error } -type AuthKeeper interface { +type AccountKeeper interface { GetAccount(ctx sdk.Context, addr sdk.AccAddress) auth.Account } diff --git a/modules/coinswap/internal/types/msgs.go b/modules/coinswap/internal/types/msgs.go index 44b37eed5..9fd98a162 100644 --- a/modules/coinswap/internal/types/msgs.go +++ b/modules/coinswap/internal/types/msgs.go @@ -1,8 +1,9 @@ package types import ( - sdk "github.com/irisnet/irishub/types" "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" ) var ( @@ -12,8 +13,8 @@ var ( ) const ( - FormatUniABSPrefix = sdk.FormatUniABSPrefix - FormatUniId = FormatUniABSPrefix + "%s" + FormatUniABSPrefix = "uni:" + FormatUniDenom = "uni:%s" ) /* --------------------------------------------------------------------------- */ @@ -26,27 +27,26 @@ const ( // A calculated coin has the desired denomination and bounded amount // the sender is willing to buy or sell in this order. type Input struct { - Address sdk.AccAddress `json:"address"` - Coin sdk.Coin `json:"coin"` + Address sdk.AccAddress `json:"address" yaml:"address"` + Coin sdk.Coin `json:"coin" yaml:"coin"` } type Output struct { - Address sdk.AccAddress `json:"address"` - Coin sdk.Coin `json:"coin"` + Address sdk.AccAddress `json:"address" yaml:"address"` + Coin sdk.Coin `json:"coin" yaml:"coin"` } type MsgSwapOrder struct { - Input Input `json:"input"` // the amount the sender is trading - Output Output `json:"output"` // the amount the sender is receiving - Deadline int64 `json:"deadline"` // deadline for the transaction to still be considered valid - IsBuyOrder bool `json:"is_buy_order"` // boolean indicating whether the order should be treated as a buy or sell + Input Input `json:"input" yaml:"input"` // the amount the sender is trading + Output Output `json:"output" yaml:"output"` // the amount the sender is receiving + Deadline int64 `json:"deadline" yaml:"deadline"` // deadline for the transaction to still be considered valid + IsBuyOrder bool `json:"is_buy_order" yaml:"is_buy_order"` // boolean indicating whether the order should be treated as a buy or sell } // NewMsgSwapOrder creates a new MsgSwapOrder object. func NewMsgSwapOrder( input Input, output Output, deadline int64, isBuyOrder bool, ) MsgSwapOrder { - return MsgSwapOrder{ Input: input, Output: output, @@ -103,25 +103,24 @@ func (msg MsgSwapOrder) GetSigners() []sdk.AccAddress { // MsgAddLiquidity - struct for adding liquidity to a reserve pool type MsgAddLiquidity struct { - MaxToken sdk.Coin `json:"max_token"` // coin to be deposited as liquidity with an upper bound for its amount - ExactIrisAmt sdk.Int `json:"exact_iris_amt"` // exact amount of native asset being add to the liquidity pool - MinLiquidity sdk.Int `json:"min_liquidity"` // lower bound UNI sender is willing to accept for deposited coins - Deadline int64 `json:"deadline"` - Sender sdk.AccAddress `json:"sender"` + MaxToken sdk.Coin `json:"max_token" yaml:"max_token"` // coin to be deposited as liquidity with an upper bound for its amount + ExactStandardAmt sdk.Int `json:"exact_standard_amt" yaml:"exact_standard_amt"` // exact amount of native asset being add to the liquidity pool + MinLiquidity sdk.Int `json:"min_liquidity" yaml:"min_liquidity"` // lower bound UNI sender is willing to accept for deposited coins + Deadline int64 `json:"deadline" yaml:"deadline"` + Sender sdk.AccAddress `json:"sender" yaml:"sender"` } // NewMsgAddLiquidity creates a new MsgAddLiquidity object. func NewMsgAddLiquidity( - maxToken sdk.Coin, exactIrisAmt, minLiquidity sdk.Int, + maxToken sdk.Coin, exactStandardAmt, minLiquidity sdk.Int, deadline int64, sender sdk.AccAddress, ) MsgAddLiquidity { - return MsgAddLiquidity{ - MaxToken: maxToken, - ExactIrisAmt: exactIrisAmt, - MinLiquidity: minLiquidity, - Deadline: deadline, - Sender: sender, + MaxToken: maxToken, + ExactStandardAmt: exactStandardAmt, + MinLiquidity: minLiquidity, + Deadline: deadline, + Sender: sender, } } @@ -136,16 +135,16 @@ func (msg MsgAddLiquidity) ValidateBasic() sdk.Error { if !(msg.MaxToken.IsValid() && msg.MaxToken.IsPositive()) { return sdk.ErrInvalidCoins("max token is invalid: " + msg.MaxToken.String()) } - if msg.MaxToken.Denom == sdk.IrisAtto { - return sdk.ErrInvalidCoins("max token must be non-iris token") + if msg.MaxToken.Denom == StandardDenom { + return sdk.ErrInvalidCoins("max token must not be native token") } if strings.HasPrefix(msg.MaxToken.Denom, FormatUniABSPrefix) { return sdk.ErrInvalidCoins("max token must be non-liquidity token") } - if msg.ExactIrisAmt.IsNil() || !msg.ExactIrisAmt.IsPositive() { - return ErrNotPositive("iris amount must be positive") + if !msg.ExactStandardAmt.IsPositive() { + return ErrNotPositive("standard token amount must be positive") } - if msg.MinLiquidity.IsNil() || msg.MinLiquidity.IsNegative() { + if msg.MinLiquidity.IsNegative() { return ErrNotPositive("minimum liquidity can not be negative") } if msg.Deadline <= 0 { @@ -173,23 +172,23 @@ func (msg MsgAddLiquidity) GetSigners() []sdk.AccAddress { // MsgRemoveLiquidity - struct for removing liquidity from a reserve pool type MsgRemoveLiquidity struct { - MinToken sdk.Int `json:"min_token"` // coin to be withdrawn with a lower bound for its amount - WithdrawLiquidity sdk.Coin `json:"withdraw_liquidity"` // amount of UNI to be burned to withdraw liquidity from a reserve pool - MinIrisAmt sdk.Int `json:"min_iris_amt"` // minimum amount of the native asset the sender is willing to accept - Deadline int64 `json:"deadline"` - Sender sdk.AccAddress `json:"sender"` + MinToken sdk.Int `json:"min_token" yaml:"min_token"` // coin to be withdrawn with a lower bound for its amount + WithdrawLiquidity sdk.Coin `json:"withdraw_liquidity" yaml:"withdraw_liquidity"` // amount of UNI to be burned to withdraw liquidity from a reserve pool + MinStandardAmt sdk.Int `json:"min_standard_amt" yaml:"min_standard_amt"` // minimum amount of the native asset the sender is willing to accept + Deadline int64 `json:"deadline" yaml:"deadline"` + Sender sdk.AccAddress `json:"sender" yaml:"sender"` } // NewMsgRemoveLiquidity creates a new MsgRemoveLiquidity object func NewMsgRemoveLiquidity( - minToken sdk.Int, withdrawLiquidity sdk.Coin, minIrisAmt sdk.Int, + minToken sdk.Int, withdrawLiquidity sdk.Coin, minStandardAmt sdk.Int, deadline int64, sender sdk.AccAddress, ) MsgRemoveLiquidity { return MsgRemoveLiquidity{ MinToken: minToken, WithdrawLiquidity: withdrawLiquidity, - MinIrisAmt: minIrisAmt, + MinStandardAmt: minStandardAmt, Deadline: deadline, Sender: sender, } @@ -203,7 +202,7 @@ func (msg MsgRemoveLiquidity) Type() string { return MsgTypeRemoveLiquidity } // ValidateBasic Implements Msg. func (msg MsgRemoveLiquidity) ValidateBasic() sdk.Error { - if msg.MinToken.IsNil() || msg.MinToken.IsNegative() { + if msg.MinToken.IsNegative() { return sdk.ErrInvalidCoins("minimum token amount can not be negative") } if !msg.WithdrawLiquidity.IsValid() || !msg.WithdrawLiquidity.IsPositive() { @@ -212,8 +211,8 @@ func (msg MsgRemoveLiquidity) ValidateBasic() sdk.Error { if err := CheckUniDenom(msg.WithdrawLiquidity.Denom); err != nil { return err } - if msg.MinIrisAmt.IsNil() || msg.MinIrisAmt.IsNegative() { - return ErrNotPositive("minimum iris amount can not be negative") + if msg.MinStandardAmt.IsNegative() { + return ErrNotPositive("minimum standard token amount can not be negative") } if msg.Deadline <= 0 { return ErrInvalidDeadline("deadline for MsgRemoveLiquidity not initialized") diff --git a/modules/coinswap/internal/types/msgs_test.go b/modules/coinswap/internal/types/msgs_test.go index 79f4827d8..ea77bc54c 100644 --- a/modules/coinswap/internal/types/msgs_test.go +++ b/modules/coinswap/internal/types/msgs_test.go @@ -2,10 +2,35 @@ package types import ( "testing" + "time" "github.com/stretchr/testify/require" - sdk "github.com/irisnet/irishub/types" + "github.com/tendermint/tendermint/crypto/ed25519" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// nolint: deadcode unused +var ( + amt = sdk.NewInt(100) + + senderPk = ed25519.GenPrivKey().PubKey() + recipientPk = ed25519.GenPrivKey().PubKey() + sender = sdk.AccAddress(senderPk.Address()) + recipient = sdk.AccAddress(recipientPk.Address()) + + denom0 = "atom" + denom1 = "btc" + unidenom = FormatUniABSPrefix + "btc" + + input = sdk.NewCoin(denom0, sdk.NewInt(1000)) + output = sdk.NewCoin(denom1, sdk.NewInt(500)) + withdrawLiquidity = sdk.NewCoin(unidenom, sdk.NewInt(500)) + deadline = time.Now().Unix() + + emptyAddr sdk.AccAddress + emptyTime int64 ) // test ValidateBasic for MsgSwapOrder @@ -130,7 +155,7 @@ func TestMsgRemoveLiquidity(t *testing.T) { {"no withdraw coin", NewMsgRemoveLiquidity(amt, sdk.Coin{}, sdk.OneInt(), deadline, sender), false}, {"zero withdraw coin", NewMsgRemoveLiquidity(amt, sdk.NewCoin(unidenom, sdk.ZeroInt()), sdk.OneInt(), deadline, sender), false}, {"invalid minimum token amount", NewMsgRemoveLiquidity(sdk.NewInt(-100), withdrawLiquidity, sdk.OneInt(), deadline, sender), false}, - {"invalid minimum iris amount", NewMsgRemoveLiquidity(amt, withdrawLiquidity, sdk.NewInt(-100), deadline, sender), false}, + {"invalid minimum standard token amount", NewMsgRemoveLiquidity(amt, withdrawLiquidity, sdk.NewInt(-100), deadline, sender), false}, {"deadline not initialized", NewMsgRemoveLiquidity(amt, withdrawLiquidity, sdk.OneInt(), emptyTime, sender), false}, {"empty sender", NewMsgRemoveLiquidity(amt, withdrawLiquidity, sdk.OneInt(), deadline, emptyAddr), false}, {"valid MsgRemoveLiquidity", NewMsgRemoveLiquidity(amt, withdrawLiquidity, sdk.OneInt(), deadline, sender), true}, diff --git a/modules/coinswap/internal/types/params.go b/modules/coinswap/internal/types/params.go index c711223f4..0402bd2b1 100644 --- a/modules/coinswap/internal/types/params.go +++ b/modules/coinswap/internal/types/params.go @@ -2,108 +2,72 @@ package types import ( "fmt" - "github.com/irisnet/irishub/app/v1/params" - "github.com/irisnet/irishub/codec" - sdk "github.com/irisnet/irishub/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" ) const ( // DefaultParamSpace for coinswap - DefaultParamSpace = ModuleName - MaxFeePrecision = 10 + DefaultParamspace = ModuleName + StandardDenom = sdk.DefaultBondDenom ) // Parameter store keys var ( - feeKey = []byte("fee") + KeyFee = []byte("Fee") + KeyStandardDenom = []byte("StandardDenom") ) // Params defines the fee and native denomination for coinswap type Params struct { - Fee sdk.Rat `json:"fee"` + Fee sdk.Dec `json:"fee" yaml:"fee"` + StandardDenom string `json:"standard_denom" yaml:"standard_denom"` } // NewParams coinswap params constructor -func NewParams(fee sdk.Rat) Params { +func NewParams(fee sdk.Dec, feeDenom string) Params { return Params{ - Fee: fee, + Fee: fee, + StandardDenom: feeDenom, } } // ParamTypeTable returns the TypeTable for coinswap module -func ParamTypeTable() params.TypeTable { - return params.NewTypeTable().RegisterParamSet(&Params{}) -} - -// String returns a human readable string representation of the parameters. -func (p Params) String() string { - return fmt.Sprintf(`Coinswap Params: - Fee: %s`, p.Fee.String(), - ) -} - -// GetParamSpace Implements params.ParamStruct -func (p *Params) GetParamSpace() string { - return DefaultParamSpace +func ParamKeyTable() params.KeyTable { + return params.NewKeyTable().RegisterParamSet(&Params{}) } // KeyValuePairs Implements params.KeyValuePairs -func (p *Params) KeyValuePairs() params.KeyValuePairs { - return params.KeyValuePairs{ - {Key: feeKey, Value: &p.Fee}, - } -} - -// Validate Implements params.Validate -func (p *Params) Validate(key string, value string) (interface{}, sdk.Error) { - switch key { - case string(feeKey): - fee, err := sdk.NewRatFromDecimal(value, MaxFeePrecision) - if err != nil { - return nil, err - } - if err := validateFee(fee); err != nil { - return nil, err - } - return fee, nil - default: - return nil, sdk.NewError(params.DefaultCodespace, params.CodeInvalidKey, fmt.Sprintf("%s is not found", key)) +func (p *Params) ParamSetPairs() params.ParamSetPairs { + return params.ParamSetPairs{ + { + Key: KeyFee, + Value: &p.Fee, + }, + { + Key: KeyStandardDenom, + Value: &p.StandardDenom, + }, } } -// StringFromBytes Implements params.StringFromBytes -func (p *Params) StringFromBytes(cdc *codec.Codec, key string, bytes []byte) (string, error) { - switch key { - default: - return "", fmt.Errorf("%s is not existed", key) - } -} - -// ReadOnly Implements params.ReadOnly -func (p *Params) ReadOnly() bool { - return false -} - // DefaultParams returns the default coinswap module parameters func DefaultParams() Params { - fee := sdk.NewRat(3, 1000) + fee := sdk.NewDecWithPrec(3, 3) return Params{ - Fee: fee, + Fee: fee, + StandardDenom: StandardDenom, } } // ValidateParams validates a set of params func ValidateParams(p Params) error { - return validateFee(p.Fee) -} - -func validateFee(fee sdk.Rat) sdk.Error { - if !fee.GT(sdk.ZeroRat()) { - return sdk.ParseParamsErr(fmt.Errorf("fee is not positive: %s", fee.String())) + if !p.Fee.GT(sdk.ZeroDec()) || !p.Fee.LT(sdk.OneDec()) { + return fmt.Errorf("fee must be positive and less than 1: %s", p.Fee.String()) } - - if !fee.LT(sdk.OneRat()) { - return sdk.ParseParamsErr(fmt.Errorf("fee must be less than 1: %s", fee.String())) + if p.StandardDenom == "" { + return fmt.Errorf("coinswap parameter standard denom can't be an empty string") } return nil } diff --git a/modules/coinswap/internal/types/params_test.go b/modules/coinswap/internal/types/params_test.go index 225395a3b..1dfac9b2c 100644 --- a/modules/coinswap/internal/types/params_test.go +++ b/modules/coinswap/internal/types/params_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/require" - sdk "github.com/irisnet/irishub/types" + sdk "github.com/cosmos/cosmos-sdk/types" ) func TestValidateParams(t *testing.T) { @@ -14,16 +14,18 @@ func TestValidateParams(t *testing.T) { err := ValidateParams(defaultParams) require.Nil(t, err) + require.Panics(t, func() { sdk.NewDecWithPrec(1, 19) }, "should panic") + require.Panics(t, func() { sdk.NewDecWithPrec(1, -1) }, "should panic") + // all cases should return an error invalidTests := []struct { name string params Params result bool }{ - {"fee == 0 ", NewParams(sdk.ZeroRat()), false}, - {"fee < 1", NewParams(sdk.NewRat(1000, 100)), false}, - {"fee numerator < 0", NewParams(sdk.NewRat(-1, 10)), false}, - {"fee denominator < 0", NewParams(sdk.NewRat(1, -10)), false}, + {"fee == 0 ", NewParams(sdk.ZeroDec(), StandardDenom), false}, + {"fee < 1", NewParams(sdk.NewDecWithPrec(1000, 2), StandardDenom), false}, + {"fee numerator < 0", NewParams(sdk.NewDecWithPrec(-1, 1), StandardDenom), false}, } for _, tc := range invalidTests { diff --git a/modules/coinswap/internal/types/querier.go b/modules/coinswap/internal/types/querier.go index a84ba02e4..605a74271 100644 --- a/modules/coinswap/internal/types/querier.go +++ b/modules/coinswap/internal/types/querier.go @@ -1,6 +1,8 @@ package types -import "github.com/irisnet/irishub/types" +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) const ( // QueryLiquidity liquidity query endpoint supported by the coinswap querier @@ -9,13 +11,13 @@ const ( // QueryLiquidityParams is the query parameters for 'custom/swap/liquidity' type QueryLiquidityParams struct { - Id string + ID string `json:"id" yaml:"id"` // same as uniDenom } // QueryLiquidityResponse is the query response for 'custom/swap/liquidity' type QueryLiquidityResponse struct { - Iris types.Coin `json:"iris"` - Token types.Coin `json:"token"` - Liquidity types.Coin `json:"liquidity"` - Fee string `json:"fee"` + Standard sdk.Coin `json:"standard" yaml:"standard"` + Token sdk.Coin `json:"token" yaml:"token"` + Liquidity sdk.Coin `json:"liquidity" yaml:"liquidity"` + Fee string `json:"fee" yaml:"fee"` } diff --git a/modules/coinswap/internal/types/tags.go b/modules/coinswap/internal/types/tags.go deleted file mode 100644 index 4108aa720..000000000 --- a/modules/coinswap/internal/types/tags.go +++ /dev/null @@ -1,10 +0,0 @@ -// nolint -package types - -var ( - TagAmount = "amount" - TagSender = "sender" - TagRecipient = "recipient" - TagIsBuyOrder = "is-buy-order" - TagTokenPair = "token-pair" -) diff --git a/modules/coinswap/internal/types/test_common.go b/modules/coinswap/internal/types/test_common.go deleted file mode 100644 index fcd38a910..000000000 --- a/modules/coinswap/internal/types/test_common.go +++ /dev/null @@ -1,30 +0,0 @@ -package types - -import ( - "github.com/tendermint/tendermint/crypto/ed25519" - "time" - - sdk "github.com/irisnet/irishub/types" -) - -// nolint: deadcode unused -var ( - amt = sdk.NewInt(100) - - senderPk = ed25519.GenPrivKey().PubKey() - recipientPk = ed25519.GenPrivKey().PubKey() - sender = sdk.AccAddress(senderPk.Address()) - recipient = sdk.AccAddress(recipientPk.Address()) - - denom0 = "atom-min" - denom1 = "btc-min" - unidenom = FormatUniABSPrefix + "btc-min" - - input = sdk.NewCoin(denom0, sdk.NewInt(1000)) - output = sdk.NewCoin(denom1, sdk.NewInt(500)) - withdrawLiquidity = sdk.NewCoin(unidenom, sdk.NewInt(500)) - deadline = time.Now().Unix() - - emptyAddr sdk.AccAddress - emptyTime int64 -) diff --git a/modules/coinswap/internal/types/utils.go b/modules/coinswap/internal/types/utils.go index d1786197d..a89498651 100644 --- a/modules/coinswap/internal/types/utils.go +++ b/modules/coinswap/internal/types/utils.go @@ -2,83 +2,48 @@ package types import ( "fmt" - sdk "github.com/irisnet/irishub/types" "strings" + + sdk "github.com/cosmos/cosmos-sdk/types" ) -// GetUniId returns the unique uni id for the provided denominations. -// The uni id is in the format of 'u-coin-name' which the denomination -// is not iris-atto. -func GetUniId(denom1, denom2 string) (string, sdk.Error) { +// GetUniDenomFromDenoms returns the uni denom for the provided denominations. +func GetUniDenomFromDenoms(denom1, denom2 string) (string, sdk.Error) { if denom1 == denom2 { return "", ErrEqualDenom("denomnations for forming uni id are equal") } - if denom1 != sdk.IrisAtto && denom2 != sdk.IrisAtto { - return "", ErrIllegalDenom(fmt.Sprintf("illegal denomnations for forming uni id, must have one native denom: %s", sdk.IrisAtto)) + if denom1 != StandardDenom && denom2 != StandardDenom { + return "", ErrIllegalDenom(fmt.Sprintf("illegal denomnations for forming uni id, must have one native denom: %s", StandardDenom)) } - denom := denom1 - if denom == sdk.IrisAtto { - denom = denom2 - } - coinName, err := sdk.GetCoinNameByDenom(denom) - if err != nil { - return "", ErrIllegalDenom(err.Error()) + if denom1 == StandardDenom { + return fmt.Sprintf(FormatUniDenom, denom2), nil } - return fmt.Sprintf(FormatUniId, coinName), nil + + return fmt.Sprintf(FormatUniDenom, denom1), nil } -// GetCoinMinDenomFromUniDenom returns the token denom by uni denom -func GetCoinMinDenomFromUniDenom(uniDenom string) (string, sdk.Error) { - err := CheckUniDenom(uniDenom) - if err != nil { - return "", err +// GetUniDenomFromDenom returns the uni denom for the provided denomination. +func GetUniDenomFromDenom(denom string) (string, sdk.Error) { + if denom == StandardDenom { + return "", ErrIllegalDenom("illegal denomnation for forming uni denom, must not be NativeDenom") } - return strings.TrimPrefix(uniDenom, FormatUniABSPrefix), nil + return fmt.Sprintf(FormatUniDenom, denom), nil } -// GetUniCoinType returns the uni coin type -func GetUniCoinType(uniId string) (sdk.CoinType, sdk.Error) { - uniDenom, err := GetUniDenom(uniId) - if err != nil { - return sdk.CoinType{}, err +// GetCoinDenomFromUniDenom returns the token denom by uni denom +func GetCoinDenomFromUniDenom(uniDenom string) (string, sdk.Error) { + if err := CheckUniDenom(uniDenom); err != nil { + return "", err } - units := make(sdk.Units, 2) - units[0] = sdk.NewUnit(uniId, 0) - units[1] = sdk.NewUnit(uniDenom, sdk.AttoScale) // the uni denom has the same decimal with iris-atto - return sdk.CoinType{ - Name: uniId, - MinUnit: units[1], - Units: units, - }, nil + return strings.TrimPrefix(uniDenom, FormatUniABSPrefix), nil } // CheckUniDenom returns nil if the uni denom is valid func CheckUniDenom(uniDenom string) sdk.Error { - if !sdk.IsCoinMinDenomValid(uniDenom) || !strings.HasPrefix(uniDenom, FormatUniABSPrefix) { + if !strings.HasPrefix(uniDenom, FormatUniABSPrefix) { return ErrIllegalDenom(fmt.Sprintf("illegal liquidity denomnation: %s", uniDenom)) } return nil } - -// CheckUniId returns nil if the uni id is valid -func CheckUniId(uniId string) sdk.Error { - if !sdk.IsCoinNameValid(uniId) || !strings.HasPrefix(uniId, FormatUniABSPrefix) { - return ErrIllegalUniId(fmt.Sprintf("illegal liquidity id: %s", uniId)) - } - return nil -} - -// GetUniDenom returns uni denom if the uni id is valid -func GetUniDenom(uniId string) (string, sdk.Error) { - if err := CheckUniId(uniId); err != nil { - return "", err - } - - uniDenom, err := sdk.GetCoinMinDenom(uniId) - if err != nil { - return "", ErrIllegalUniId(fmt.Sprintf("illegal liquidity id: %s", uniId)) - } - return uniDenom, nil -} diff --git a/modules/coinswap/module.go b/modules/coinswap/module.go new file mode 100644 index 000000000..9608eac93 --- /dev/null +++ b/modules/coinswap/module.go @@ -0,0 +1,158 @@ +package coinswap + +import ( + "encoding/json" + "math/rand" + + "github.com/gorilla/mux" + "github.com/spf13/cobra" + + abci "github.com/tendermint/tendermint/abci/types" + + "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" + sim "github.com/cosmos/cosmos-sdk/x/simulation" + + "github.com/irisnet/irishub/modules/coinswap/client/rest" + "github.com/irisnet/irishub/modules/coinswap/simulation" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleSimulation = AppModuleSimulation{} +) + +// AppModuleBasic defines the basic application module used by the coinswap module. +type AppModuleBasic struct{} + +// Name returns the coinswap module's name. +func (AppModuleBasic) Name() string { + return ModuleName +} + +// RegisterCodec registers the coinswap module's types for the given codec. +func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { + RegisterCodec(cdc) +} + +// DefaultGenesis returns default genesis state as raw bytes for the coinswap module. +func (AppModuleBasic) DefaultGenesis() json.RawMessage { + return ModuleCdc.MustMarshalJSON(DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the coinswap module. +func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { + var data GenesisState + err := ModuleCdc.UnmarshalJSON(bz, &data) + if err != nil { + return err + } + return ValidateGenesis(data) +} + +// RegisterRESTRoutes registers the REST routes for the coinswap module. +func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) { + rest.RegisterRoutes(ctx, rtr) +} + +// GetTxCmd returns no root tx command for the coinswap module. +func (AppModuleBasic) GetTxCmd(cdc *codec.Codec) *cobra.Command { + return nil +} + +// GetQueryCmd returns the root query command for the coinswap module. +func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { + return nil +} + +//____________________________________________________________________________ + +// AppModuleSimulation defines the module simulation functions used by the coinswap module. +type AppModuleSimulation struct{} + +// RegisterStoreDecoder registers a decoder for coinswap module's types. +func (AppModuleSimulation) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[StoreKey] = simulation.DecodeStore +} + +// GenerateGenesisState creates a randomized GenState of the coinswap module. +func (AppModuleSimulation) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// RandomizedParams creates randomized coinswap param changes for the simulator. +func (AppModuleSimulation) RandomizedParams(r *rand.Rand) []sim.ParamChange { + return simulation.ParamChanges(r) +} + +//____________________________________________________________________________ + +// AppModule implements an application module for the coinswap module. +type AppModule struct { + AppModuleBasic + AppModuleSimulation + keeper Keeper +} + +// NewAppModule creates a new AppModule object +func NewAppModule(keeper Keeper) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{}, + AppModuleSimulation: AppModuleSimulation{}, + keeper: keeper, + } +} + +// Name returns the coinswap module's name. +func (AppModule) Name() string { + return ModuleName +} + +// RegisterInvariants registers the coinswap module invariants. +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// Route returns the message routing key for the coinswap module. +func (AppModule) Route() string { + return RouterKey +} + +// NewHandler returns an sdk.Handler for the coinswap module. +func (am AppModule) NewHandler() sdk.Handler { + return NewHandler(am.keeper) +} + +// QuerierRoute returns the coinswap module's querier route name. +func (AppModule) QuerierRoute() string { + return QuerierRoute +} + +// NewQuerierHandler returns the coinswap module sdk.Querier. +func (am AppModule) NewQuerierHandler() sdk.Querier { + return NewQuerier(am.keeper) +} + +// InitGenesis performs genesis initialization for the coinswap module. It returns +// no validator updates. +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 returns the exported genesis state as raw bytes for the coinswap module. +func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { + gs := ExportGenesis(ctx, am.keeper) + return ModuleCdc.MustMarshalJSON(gs) +} + +// BeginBlock returns the begin blocker for the coinswap module. +func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock returns the end blocker for the coinswap module. It returns no validator updates. +func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} diff --git a/modules/coinswap/simulation/decoder.go b/modules/coinswap/simulation/decoder.go new file mode 100644 index 000000000..3f24ca994 --- /dev/null +++ b/modules/coinswap/simulation/decoder.go @@ -0,0 +1,12 @@ +package simulation + +import ( + cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/codec" +) + +// DecodeStore unmarshals the KVPair's Value to the corresponding htlc type +func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { + return "" +} diff --git a/modules/coinswap/simulation/genesis.go b/modules/coinswap/simulation/genesis.go new file mode 100644 index 000000000..761d21e6d --- /dev/null +++ b/modules/coinswap/simulation/genesis.go @@ -0,0 +1,7 @@ +package simulation + +import "github.com/cosmos/cosmos-sdk/types/module" + +// RandomizedGenState generates a random GenesisState for HTLC +func RandomizedGenState(simState *module.SimulationState) { +} diff --git a/modules/coinswap/simulation/params.go b/modules/coinswap/simulation/params.go new file mode 100644 index 000000000..bcdb07275 --- /dev/null +++ b/modules/coinswap/simulation/params.go @@ -0,0 +1,13 @@ +package simulation + +import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/x/simulation" +) + +// ParamChanges defines the parameters that can be modified by param change proposals +// on the simulation +func ParamChanges(r *rand.Rand) []simulation.ParamChange { + return nil +} diff --git a/simapp/app.go b/simapp/app.go index 7558005d4..56ae16019 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -30,6 +30,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/supply" "github.com/irisnet/irishub/modules/asset" + "github.com/irisnet/irishub/modules/coinswap" "github.com/irisnet/irishub/modules/guardian" "github.com/irisnet/irishub/modules/htlc" "github.com/irisnet/irishub/modules/mint" @@ -62,6 +63,7 @@ var ( evidence.AppModuleBasic{}, guardian.AppModuleBasic{}, htlc.AppModuleBasic{}, + coinswap.AppModuleBasic{}, ) // module account permissions @@ -74,6 +76,7 @@ var ( gov.ModuleName: {supply.Burner}, asset.ModuleName: {supply.Minter, supply.Burner}, htlc.ModuleName: nil, + coinswap.ModuleName: {supply.Minter, supply.Burner}, } ) @@ -118,6 +121,7 @@ type SimApp struct { AssetKeeper asset.Keeper GuardianKeeper guardian.Keeper HTLCKeeper htlc.Keeper + CoinswapKeeper coinswap.Keeper // the module manager mm *module.Manager @@ -141,7 +145,7 @@ func NewSimApp( keys := sdk.NewKVStoreKeys( bam.MainStoreKey, auth.StoreKey, staking.StoreKey, supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey, gov.StoreKey, params.StoreKey, evidence.StoreKey, - asset.StoreKey, guardian.StoreKey, htlc.StoreKey, + asset.StoreKey, guardian.StoreKey, htlc.StoreKey, coinswap.StoreKey, ) tkeys := sdk.NewTransientStoreKeys(params.TStoreKey) @@ -166,6 +170,7 @@ func NewSimApp( app.subspaces[crisis.ModuleName] = app.ParamsKeeper.Subspace(crisis.DefaultParamspace) app.subspaces[evidence.ModuleName] = app.ParamsKeeper.Subspace(evidence.DefaultParamspace) app.subspaces[asset.ModuleName] = app.ParamsKeeper.Subspace(asset.DefaultParamspace) + app.subspaces[coinswap.ModuleName] = app.ParamsKeeper.Subspace(coinswap.DefaultParamspace) // add keepers app.AccountKeeper = auth.NewAccountKeeper( @@ -193,7 +198,9 @@ func NewSimApp( app.subspaces[crisis.ModuleName], invCheckPeriod, app.SupplyKeeper, auth.FeeCollectorName, ) app.HTLCKeeper = htlc.NewKeeper(app.cdc, keys[htlc.StoreKey], app.SupplyKeeper, htlc.DefaultCodespace) - + app.CoinswapKeeper = coinswap.NewKeeper( + app.cdc, keys[coinswap.StoreKey], app.BankKeeper, app.AccountKeeper, app.SupplyKeeper, app.subspaces[coinswap.ModuleName], + ) // create evidence keeper with router evidenceKeeper := evidence.NewKeeper( app.cdc, keys[evidence.StoreKey], app.subspaces[evidence.ModuleName], evidence.DefaultCodespace, @@ -245,6 +252,7 @@ func NewSimApp( asset.NewAppModule(app.AssetKeeper), guardian.NewAppModule(app.GuardianKeeper), htlc.NewAppModule(app.HTLCKeeper), + coinswap.NewAppModule(app.CoinswapKeeper), ) // During begin block slashing happens after distr.BeginBlocker so that @@ -269,6 +277,7 @@ func NewSimApp( slashing.ModuleName, gov.ModuleName, mint.ModuleName, supply.ModuleName, crisis.ModuleName, genutil.ModuleName, evidence.ModuleName, asset.ModuleName, guardian.ModuleName, htlc.ModuleName, + coinswap.ModuleName, ) app.mm.RegisterInvariants(&app.CrisisKeeper) @@ -289,6 +298,7 @@ func NewSimApp( slashing.NewAppModule(app.SlashingKeeper, app.StakingKeeper), asset.NewAppModule(app.AssetKeeper), htlc.NewAppModule(app.HTLCKeeper), + coinswap.NewAppModule(app.CoinswapKeeper), ) app.sm.RegisterStoreDecoders()