From b6aceacdea13f3a7d2854c190bed1274ef428acb Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Thu, 30 May 2019 15:57:16 -0700 Subject: [PATCH 01/15] init scaffolding --- x/uniswap/ante.go | 1 + x/uniswap/keeper.go | 1 + x/uniswap/module.go | 4 +++ x/uniswap/msgs.go | 68 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+) create mode 100644 x/uniswap/ante.go create mode 100644 x/uniswap/keeper.go create mode 100644 x/uniswap/module.go create mode 100644 x/uniswap/msgs.go diff --git a/x/uniswap/ante.go b/x/uniswap/ante.go new file mode 100644 index 00000000000..7d1e96cd269 --- /dev/null +++ b/x/uniswap/ante.go @@ -0,0 +1 @@ +package uniswap diff --git a/x/uniswap/keeper.go b/x/uniswap/keeper.go new file mode 100644 index 00000000000..7d1e96cd269 --- /dev/null +++ b/x/uniswap/keeper.go @@ -0,0 +1 @@ +package uniswap diff --git a/x/uniswap/module.go b/x/uniswap/module.go new file mode 100644 index 00000000000..dfc9073f068 --- /dev/null +++ b/x/uniswap/module.go @@ -0,0 +1,4 @@ +package uniswap + +// name of this module +const ModuleName = "uniswap" diff --git a/x/uniswap/msgs.go b/x/uniswap/msgs.go new file mode 100644 index 00000000000..64531d91123 --- /dev/null +++ b/x/uniswap/msgs.go @@ -0,0 +1,68 @@ +package uniswap + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const RouterKey = ModuleName + +// MsgCreateExchange - add a new trading pair +type MsgCreateExchange struct { + NewCoin string +} + +var _ sdk.Msg = MsgCreateExchange{} + +// NewCreateExchange - . +func NewMsgCreateExchange(newCoin string) MsgCreateExchange { + return MsgSend{NewCoin: newCoin} +} + +// Route Implements Msg. +func (msg MsgCreateExchange) Route() string { return RouterKey } + +// Type Implements Msg. +func (msg MsgCreateExchange) Type() string { return "create_exchange" } + +// ValidateBasic Implements Msg. +func (msg MsgCreateExchange) ValidateBasic() sdk.Error { + if !(len(msg.NewCoin) > 0) { + return errors.New("must provide coin denomination") + } + return nil +} + +// GetSignBytes Implements Msg. +func (msg MsgCreateExchange) GetSignBytes() []byte { + return sdk.MustSortJSON(moduleCdc.MustMarshalJSON(msg)) +} + +// GetSigners Implements Msg. +func (msg MsgCreateExchange) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{} +} + +type MsgSwapOrder struct { + SwapDenom string // The desired denomination either to be bought or sold + Coin sdk.Coin // The specified amount to be either bought or sold + Bound sdk.Int // If buy order, maximum amount of coins to be sold; otherwise minimum amount of coins to be bought + Deadline time.Time // deadline for the transaction to still be considered valid + Recipient sdk.AccAddress // address output coin is being sent to + IsBuyOrder bool // boolean indicating whether the order should be treated as a buy or sell +} + +type MsgAddLiquidity struct { + DepositAmount sdk.Int // exact amount of native asset being add to the liquidity pool + ExchangeDenom string // denomination of the exchange being added to + MinLiquidity sdk.Int // lower bound UNI sender is willing to accept for deposited coins + MaxCoins sdk.Int // maximum amount of the coin the sender is willing to deposit. + Deadline time.Time +} + +type MsgRemoveLiquidity struct { + WithdrawAmount sdk.Int // amount of UNI to be burned to withdraw liquidity from an exchange + ExchangeDenom string // denomination of the exchange being withdrawn from + MinNative sdk.Int // minimum amount of the native asset the sender is willing to accept + MinCoins sdk.Int // minimum amount of the exchange coin the sender is willing to accept + Deadline time.Time +} From d2b7ba5bf1ae2d62f2f742057aa59944b504c8da Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Wed, 5 Jun 2019 16:43:13 -0700 Subject: [PATCH 02/15] base files added for uniswap module --- x/uniswap/codec.go | 18 +++ x/uniswap/errors.go | 28 +++++ x/uniswap/expected_keepers.go | 10 ++ x/uniswap/genesis.go | 41 ++++++ x/uniswap/handler.go | 45 +++++++ x/uniswap/keeper.go | 148 ++++++++++++++++++++++ x/uniswap/{ante.go => keeper_test.go} | 0 x/uniswap/key.go | 35 ++++++ x/uniswap/module.go | 112 +++++++++++++++++ x/uniswap/msgs.go | 171 ++++++++++++++++++++++---- x/uniswap/msgs_test.go | 31 +++++ x/uniswap/test_common.go | 15 +++ 12 files changed, 627 insertions(+), 27 deletions(-) create mode 100644 x/uniswap/codec.go create mode 100644 x/uniswap/errors.go create mode 100644 x/uniswap/expected_keepers.go create mode 100644 x/uniswap/genesis.go create mode 100644 x/uniswap/handler.go rename x/uniswap/{ante.go => keeper_test.go} (100%) create mode 100644 x/uniswap/key.go create mode 100644 x/uniswap/msgs_test.go create mode 100644 x/uniswap/test_common.go diff --git a/x/uniswap/codec.go b/x/uniswap/codec.go new file mode 100644 index 00000000000..79d8e2e5a28 --- /dev/null +++ b/x/uniswap/codec.go @@ -0,0 +1,18 @@ +package uniswap + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +// Register concrete types on codec codec +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterConcrete(MsgSwapOrder{}, "cosmos-sdk/MsgSwapOrder", nil) + cdc.RegisterConcrete(MsgAddLiquidity{}, "cosmos-sdk/MsgAddLiquidity", nil) + cdc.RegisterConcrete(MsgRemoveLiquidity{}, "cosmos-sdk/MsgRemoveLiquidity", nil) +} + +var moduleCdc = codec.New() + +func init() { + RegisterCodec(moduleCdc) +} diff --git a/x/uniswap/errors.go b/x/uniswap/errors.go new file mode 100644 index 00000000000..f784dcff1ac --- /dev/null +++ b/x/uniswap/errors.go @@ -0,0 +1,28 @@ +package uniswap + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + DefaultCodespace sdk.CodespaceType = ModuleName + + CodeNoDenom sdk.CodeType = 1 + CodeNoSigner sdk.CodeType = 2 + CodeExchangeAlreadyExists = 3 +) + +func ErrNoDenom(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeNoDenom, "denomination is empty") +} + +func ErrNoSigner(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeNoSigner, "signer is nil") +} + +func ErrExchangeAlreadyExists(codespace sdk.CodespaceType, msg string) sdk.Error { + if msg != "" { + return sdk.NewError(codespace, CodeExchangeAlreadyExists, msg) + } + return sdk.NewError(codespace, CodeExchangeAlreadyExists, "exchange already exists") +} diff --git a/x/uniswap/expected_keepers.go b/x/uniswap/expected_keepers.go new file mode 100644 index 00000000000..1db0900255e --- /dev/null +++ b/x/uniswap/expected_keepers.go @@ -0,0 +1,10 @@ +package uniswap + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// BankKeeper defines the expected coin keeper +type BankKeeper interface { + AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Error) +} diff --git a/x/uniswap/genesis.go b/x/uniswap/genesis.go new file mode 100644 index 00000000000..1024d26df4a --- /dev/null +++ b/x/uniswap/genesis.go @@ -0,0 +1,41 @@ +package uniswap + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// TODO + +// GenesisState - uniswap genesis state +type GenesisState struct { + NativeAssetDenom string +} + +// NewGenesisState creates a new GenesisState object +func NewGenesisState(nativeAssetDenom string) GenesisState { + return GenesisState{ + NativeAssetDenom: nativeAssetDenom, + } +} + +// DefaultGenesisState creates a default GenesisState object +func DefaultGenesisState() GenesisState { + return GenesisState{ + NativeAssetDenom: sdk.DefaultBondDenom, + } +} + +// InitGenesis new uniswap genesis +func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { + +} + +// ExportGenesis returns a GenesisState for a given context and keeper. +func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { + return NewGenesisState(sdk.DefaultBondDenom) +} + +// ValidateGenesis - placeholder function +func ValidateGenesis(data GenesisState) error { + return nil +} diff --git a/x/uniswap/handler.go b/x/uniswap/handler.go new file mode 100644 index 00000000000..f77bf98e537 --- /dev/null +++ b/x/uniswap/handler.go @@ -0,0 +1,45 @@ +package uniswap + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// NewHandler routes the messages to the handlers +func NewHandler(k Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + switch msg := msg.(type) { + case MsgSwapOrder: + return HandleMsgSwapOrder(ctx, msg, k) + case MsgAddLiquidity: + return HandleMsgAddLiquidity(ctx, msg, k) + case MsgRemoveLiquidity: + return HandleMsgRemoveLiquidity(ctx, msg, k) + default: + errMsg := fmt.Sprintf("unrecognized uniswap message type: %T", msg) + return sdk.ErrUnknownRequest(errMsg).Result() + } + } +} + +// HandleMsgSwapOrder handler for MsgSwapOrder +func HandleMsgSwapOrder(ctx sdk.Context, msg types.MsgSwapOrder, k Keeper, +) sdk.Result { + +} + +// HandleMsgAddLiquidity handler for MsgAddLiquidity +func HandleMsgAddLiquidity(ctx sdk.Context, msg types.MsgAddLiqudity, k Keeper, +) sdk.Result { + // check if exchange already exists + totalLiquidity, err := k.GetExchange(ctx, msg.Denom) + if err != nil { + k.CreateExchange(ctx, msg.Denom) + } + +} + +// HandleMsgRemoveLiquidity handler for MsgRemoveLiquidity +func HandleMsgRemoveLiquidity(ctx sdk.Context, msg types.MsgRemoveLiquidity, k Keeper, +) sdk.Result { + +} diff --git a/x/uniswap/keeper.go b/x/uniswap/keeper.go index 7d1e96cd269..5348df32651 100644 --- a/x/uniswap/keeper.go +++ b/x/uniswap/keeper.go @@ -1 +1,149 @@ package uniswap + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/tendermint/tendermint/libs/log" +) + +var ( + fee sdk.Dec // TODO: replace with fee param +) + +type Keeper struct { + // The key used to access the store TODO update + storeKey sdk.StoreKey + + // The reference to the CoinKeeper to modify balances after swaps or liquidity is deposited/withdrawn + ck BankKeeper + + // The codec codec for binary encoding/decoding. + cdc *codec.Codec +} + +// NewKeeper returns a uniswap keeper. It handles: +// - creating new exchanges +// - facilitating swaps +// - users adding liquidity to exchanges +// - users removing liquidity to exchanges +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, ck BankKeeper) Keeper { + fee, err := sdk.NewDecFromStr("0.003") + if err != nil { + panic("could not construct fee") + } + + return Keeper{ + storeKey: key, + ck: ck, + cdc: cdc, + } +} + +// Logger returns a module-specific logger. +func (keeper Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/uniswap") +} + +// CreateExchange initializes a new exchange pair between the new coin and the native asset +func (keeper Keeper) CreateExchange(ctx sdk.Context, newCoinDenom string) { + store := ctx.KVStore(keeper.storeKey) + key := GetExchangeKey() + bz := store.Get(key) + if bz != nil { + panic("exchange already exists") + } + store.Set(key, sdk.NewDec(0)) +} + +// Deposit adds the specified amount of UNI to the associated account +func (keeper Keeper) Deposit(ctx sdk.Context, amt sdk.Dec, addr sdk.AccAddress) { + var balance sdk.Dec + + store := ctx.KVStore(keeper.storeKey) + key := GetUNIBalancesKey(addr) + bz := store.Get(key) + if bz != nil { + balance = keeper.decodeBalance(bz) + } + + balance.Add(amt) + store.Set(key, keeper.encodeBalance(balance)) + + return +} + +// Withdraw removes the specified amount of UNI from the associated account +func (keeper Keeper) Withdraw(ctx sdk.Context, amt sdk.Dec, addr sdk.AccAddress) { + var balance sdk.Dec + + store := ctx.KVStore(keeper.storeKey) + key := GetUNIBalancesKey(addr) + bz := store.Get(key) + if bz != nil { + balance = keeper.decodeBalance(bz) + } + + balance.Sub(amt) + store.Set(key, keeper.encodeBalance(balance)) +} + +// GetInputAmount 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 (keeper Keeper) GetInputAmount(ctx sdk.Context, outputAmt sdk.Dec, inputDenom, outputDenom string) sdk.Dec { + store := ctx.KVStore(keeper.storeKey) + inputReserve := store.Get(GetExchangeKey(inputDenom)) + if inputReserve == nil { + panic("exchange for input denomination does not exist") + } + outputReserve := store.Get(GetExchangeKey(outputDenom)) + if outputReserve == nil { + panic("exchange for output denomination does not exist") + } + + // TODO: verify + feeAmt := outputAmt.Mul(fee) + numerator := inputReserve.Mul(outputReserve) + denominator := outputReserve.Sub(outputAmt.Add(feeAmt)) + return numerator/denominator + 1 +} + +// GetOutputAmount returns the amount of coins bought (calculated) given the output amount being sold (exact) +// The fee is included in the input coins being bought +func (keeper Keeper) GetOutputAmount(ctx sdk.Context, inputAmt sdk.Dec, inputDenom, outputDenom string) sdk.Dec { + store := ctx.KVStore(keeper.storeKey) + inputReserve := store.Get(GetExchangeKey(inputDenom)) + if inputReserve == nil { + panic("exchange for input denomination does not exist") + } + outputReserve := store.Get(GetExchangeKey(outputDenom)) + if outputReserve == nil { + panic("exchange for output denomination does not exist") + } + + // TODO: verify + feeAmt := inputAmt.Mul(fee) + inputAmtWithFee := inputReserve.Add(feeAmt) + numerator := inputAmtWithFee * outputReserve + denominator := inputReserve + inputAmt.Sub(feeAmt) + return numerator / denominator +} + +// ----------------------------------------------------------------------------- +// Misc. + +func (keeper Keeper) decodeBalance(bz []byte) (balance sdk.Dec) { + err := keeper.cdc.UnmarshalBinaryBare(bz, &balance) + if err != nil { + panic(err) + } + return +} + +func (keeper Keeper) encodeBalance(balance sdk.Dec) (bz []byte) { + bz, err := keeper.cdc.MarshalBinaryBare(balance) + if err != nil { + panic(err) + } + return +} diff --git a/x/uniswap/ante.go b/x/uniswap/keeper_test.go similarity index 100% rename from x/uniswap/ante.go rename to x/uniswap/keeper_test.go diff --git a/x/uniswap/key.go b/x/uniswap/key.go new file mode 100644 index 00000000000..5ef04fe31c3 --- /dev/null +++ b/x/uniswap/key.go @@ -0,0 +1,35 @@ +package uniswap + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// keys +var ( + ExchangePrefix = []byte{0x00} // key for exchange liquidity + UNIBalancesPrefix = []byte{0x01} // key for UNI balances +) + +const ( + // ModuleName is the name of the module + ModuleName = "uniswap" + + // QuerierRoute is the querier route for the uniswap store + QuerierRoute = ModuleName + + // RouterKey is the message route for the uniswap module + RouterKey = ModuleName + + // StoreKey is the default store key for uniswap + StoreKey = ModuleName +) + +// GetExchangeKey gets the key for an exchanges total liquidity +func GetExchangeKey(denom string) []byte { + return append(ExchangePrefix, []byte(denom)...) +} + +// GetUNIBalancesKey gets the key for an addresses UNI balance +func GetUNIBalancesKey(addr sdk.AccAddress) []byte { + return append(UNIBalancesPrefix, addr.Bytes()...) +} diff --git a/x/uniswap/module.go b/x/uniswap/module.go index dfc9073f068..dad592db8d8 100644 --- a/x/uniswap/module.go +++ b/x/uniswap/module.go @@ -1,4 +1,116 @@ package uniswap +import ( + "encoding/json" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + abci "github.com/tendermint/tendermint/abci/types" +) + +var ( + _ sdk.AppModule = AppModule{} + _ sdk.AppModuleBasic = AppModuleBasic{} +) + // name of this module const ModuleName = "uniswap" + +// AppModuleBasic app module basics object +type AppModuleBasic struct{} + +// Name defines module name +func (AppModuleBasic) Name() string { + return ModuleName +} + +// RegisterCodec registers module codec +func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { + RegisterCodec(cdc) +} + +// DefaultGenesis default genesis state +func (AppModuleBasic) DefaultGenesis() json.RawMessage { + return moduleCdc.MustMarshalJSON(DefaultGenesisState()) +} + +// ValidateGenesis module validate genesis +func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { + var data GenesisState + err := moduleCdc.UnmarshalJSON(bz, &data) + if err != nil { + return err + } + return ValidateGenesis(data) +} + +//___________________________ + +// AppModule supply app module +type AppModule struct { + AppModuleBasic + keeper Keeper +} + +// NewAppModule creates a new AppModule object +func NewAppModule(keeper Keeper) sdk.AppModule { + + return sdk.NewGenesisOnlyAppModule(AppModule{ + AppModuleBasic: AppModuleBasic{}, + keeper: keeper, + }) +} + +// Name defines module name +func (AppModule) Name() string { + return ModuleName +} + +// RegisterInvariants registers the nft module invariants +func (am AppModule) RegisterInvariants(ir sdk.InvariantRouter) { + RegisterInvariants(ir, am.keeper) +} + +// Route module message route name +func (AppModule) Route() string { + return RouterKey +} + +// NewHandler module handler +//func (am AppModule) NewHandler() sdk.Handler { +// return NewHandler(am.keeper) +//} + +// QuerierRoute module querier route name +func (AppModule) QuerierRoute() string { + return QuerierRoute +} + +// NewQuerierHandler module querier +//func (am AppModule) NewQuerierHandler() sdk.Querier { +// return NewQuerier(am.keeper) +//} + +// InitGenesis supply module init-genesis +func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState GenesisState + ModuleCdc.MustUnmarshalJSON(data, &genesisState) + InitGenesis(ctx, am.keeper, genesisState) + return []abci.ValidatorUpdate{} +} + +// ExportGenesis module export genesis +func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { + gs := ExportGenesis(ctx, am.keeper) + return ModuleCdc.MustMarshalJSON(gs) +} + +// BeginBlock module begin-block +func (AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) sdk.Tags { + return sdk.EmptyTags() +} + +// EndBlock module end-block +func (am AppModule) EndBlock(ctx sdk.Context, _ abci.RequestEndBlock) ([]abci.ValidatorUpdate, sdk.Tags) { + return []abci.ValidatorUpdate{}, sdk.EmptyTags() +} diff --git a/x/uniswap/msgs.go b/x/uniswap/msgs.go index 64531d91123..55602dc8d42 100644 --- a/x/uniswap/msgs.go +++ b/x/uniswap/msgs.go @@ -2,67 +2,184 @@ package uniswap import ( sdk "github.com/cosmos/cosmos-sdk/types" + "time" ) -const RouterKey = ModuleName +var ( + _ sdk.Msg = MsgSwapOrder{} + _ sdk.Msg = MsgAddLiquidity{} + _ sdk.Msg = MsgRemoveLiquidity{} +) + +/* --------------------------------------------------------------------------- */ +// MsgSwapOrder +/* --------------------------------------------------------------------------- */ -// MsgCreateExchange - add a new trading pair -type MsgCreateExchange struct { - NewCoin string +// MsgSwap Order - struct for swapping a coin +type MsgSwapOrder struct { + SwapDenom string // The desired denomination either to be bought or sold + Coin sdk.Coin // The specified amount to be either bought or sold + Bound sdk.Int // If buy order, maximum amount of coins to be sold; otherwise minimum amount of coins to be bought + Deadline time.Time // deadline for the transaction to still be considered valid + Sender sdk.AccAddress // address swapping coin + Recipient sdk.AccAddress // address output coin is being sent to + IsBuyOrder bool // boolean indicating whether the order should be treated as a buy or sell } -var _ sdk.Msg = MsgCreateExchange{} +// NewMsgSwapOrder is a constructor function for MsgSwapOrder +func NewMsgSwapOrder( + swapDenom string, coin sdk.Coin, bound sdk.Int, deadline time.Time, + sender, recipient sdk.AccAddress, isBuyOrder bool, +) MsgSwapOrder { -// NewCreateExchange - . -func NewMsgCreateExchange(newCoin string) MsgCreateExchange { - return MsgSend{NewCoin: newCoin} + return MsgSwapOrder{ + SwapDenom: swapDenom, + Coin: coin, + Bound: bound, + Deadline: deadline, + Sender: sender, + Recipient: recipient, + IsBuyOrder: isBuyOrder, + } } -// Route Implements Msg. -func (msg MsgCreateExchange) Route() string { return RouterKey } +// Type Implements Msg +func (msg MsgSwapOrder) Route() string { return RouterKey } -// Type Implements Msg. -func (msg MsgCreateExchange) Type() string { return "create_exchange" } +// Type Implements Msg +func (msg MsgSwapOrder) Type() string { return "swap_order" } // ValidateBasic Implements Msg. -func (msg MsgCreateExchange) ValidateBasic() sdk.Error { - if !(len(msg.NewCoin) > 0) { - return errors.New("must provide coin denomination") +func (msg MsgSwapOrder) ValidateBasic() sdk.Error { + if msg.SwapDenom == "" { + return ErrNoDenom(DefaultCodespace) + } + if msg.Sender.Empty() { + return sdk.ErrInvalidAddress("invalid sender address") + } + if msg.Recipient.Empty() { + return sdk.ErrInvalidAddress("invalid recipient address") + } + if msg.Coin.IsZero() { + return ErrZeroCoin() } - return nil } // GetSignBytes Implements Msg. -func (msg MsgCreateExchange) GetSignBytes() []byte { +func (msg MsgSwapOrder) GetSignBytes() []byte { return sdk.MustSortJSON(moduleCdc.MustMarshalJSON(msg)) } // GetSigners Implements Msg. -func (msg MsgCreateExchange) GetSigners() []sdk.AccAddress { - return []sdk.AccAddress{} +func (msg MsgSwapOrder) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Sender} } -type MsgSwapOrder struct { - SwapDenom string // The desired denomination either to be bought or sold - Coin sdk.Coin // The specified amount to be either bought or sold - Bound sdk.Int // If buy order, maximum amount of coins to be sold; otherwise minimum amount of coins to be bought - Deadline time.Time // deadline for the transaction to still be considered valid - Recipient sdk.AccAddress // address output coin is being sent to - IsBuyOrder bool // boolean indicating whether the order should be treated as a buy or sell -} +/* --------------------------------------------------------------------------- */ +// MsgAddLiquidity +/* --------------------------------------------------------------------------- */ +// MsgAddLiquidity - struct for adding liquidity to an exchange type MsgAddLiquidity struct { DepositAmount sdk.Int // exact amount of native asset being add to the liquidity pool ExchangeDenom string // denomination of the exchange being added to MinLiquidity sdk.Int // lower bound UNI sender is willing to accept for deposited coins MaxCoins sdk.Int // maximum amount of the coin the sender is willing to deposit. Deadline time.Time + Sender sdk.AccAddress +} + +// NewMsgAddLiquidity is a constructor function for MsgAddLiquidity +func NewMsgAddLiquidity( + depositAmount, minLiquidity, maxCoins sdk.Int, + exchangeDenom string, deadline time.Time, sender sdk.AccAddress, +) MsgAddLiquidity { + + return MsgAddLiquidity{ + DepositAmount: depositAmount, + ExchangeDenom: exchangeDenom, + MinLiquidity: minLiquidity, + MaxCoins: maxCoins, + Deadline: deadline, + Sender: sender, + } } +// Type Implements Msg +func (msg MsgAddLiquidity) Route() string { return RouterKey } + +// Type Implements Msg +func (msg MsgAddLiquidity) Type() string { return "add_liquidity" } + +// ValidateBasic Implements Msg. +func (msg MsgAddLiquidity) ValidateBasic() sdk.Error { + if msg.Sender.Empty() { + return sdk.InvalidAddress("invalid sender address") + } +} + +// GetSignBytes Implements Msg. +func (msg MsgAddLiquidity) GetSignBytes() []byte { + return sdk.MustSortJSON(moduleCdc.MustMarshalJSON(msg)) +} + +// GetSigners Implements Msg. +func (msg MsgAddLiquidity) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Sender} +} + +/* --------------------------------------------------------------------------- */ +// MsgRemoveLiquidity +/* --------------------------------------------------------------------------- */ + +// MsgRemoveLiquidity - struct for removing liquidity from an exchange type MsgRemoveLiquidity struct { WithdrawAmount sdk.Int // amount of UNI to be burned to withdraw liquidity from an exchange ExchangeDenom string // denomination of the exchange being withdrawn from MinNative sdk.Int // minimum amount of the native asset the sender is willing to accept MinCoins sdk.Int // minimum amount of the exchange coin the sender is willing to accept Deadline time.Time + Sender sdk.AccAddress +} + +// NewMsgRemoveLiquidity is a contructor function for MsgRemoveLiquidity +func NewMsgRemoveLiquidity( + withdrawAmount, minNative, minCoins sdk.Int, + exchangeDenom string, deadline time.Time, sender sdk.AccAddress, +) MsgRemoveLiquidity { + + return MsgRemoveLiquidity{ + WithdrawAmount: withdrawAmount, + ExchangeDenom: exchangeDenom, + MinNative: minNative, + MinCoins: minCoins, + Deadline: deadline, + Sender: sender, + } +} + +// Type Implements Msg +func (msg MsgRemoveLiquidity) Route() string { return RouterKey } + +// Type Implements Msg +func (msg MsgRemoveLiquidity) Type() string { return "remove_liquidity" } + +// ValidateBasic Implements Msg. +func (msg MsgRemoveLiquidity) ValidateBasic() sdk.Error { + if msg.ExchangeDenom == "" { + return ErrNoDenom(DefaultCodespace) + } + if msg.Sender.Empty() { + return sdk.InvalidAddress("invalid sender address") + } +} + +// GetSignBytes Implements Msg. +func (msg MsgRemoveLiquidity) GetSignBytes() []byte { + return sdk.MustSortJSON(moduleCdc.MustMarshalJSON(msg)) +} + +// GetSigners Implements Msg. +func (msg MsgRemoveLiquidity) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Sender} } diff --git a/x/uniswap/msgs_test.go b/x/uniswap/msgs_test.go new file mode 100644 index 00000000000..c6744d3a55e --- /dev/null +++ b/x/uniswap/msgs_test.go @@ -0,0 +1,31 @@ +package uniswap + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// test ValidateBasic for MsgSwapOrder +func TestMsgSwapOrder(t *testing.T) { + tests := []struct { + newCoinDenom string + signer sdk.AccAddress + expectPass bool + }{ + {emptyDenom, addr, false}, + {denom, emptyAddr, false}, + {denom, addr, true}, + } + + for i, tc := range tests { + msg := NewMsgCreateExchange(tc.newCoinDenom, tc.signer) + if tc.expectPass { + require.Nil(t, msg.ValidateBasic(), "test index: %v", i) + } else { + require.NotNil(t, msg.ValidateBasic(), "test index: %v", i) + } + } +} diff --git a/x/uniswap/test_common.go b/x/uniswap/test_common.go new file mode 100644 index 00000000000..04a93b19e94 --- /dev/null +++ b/x/uniswap/test_common.go @@ -0,0 +1,15 @@ +package uniswap + +import ( + "github.com/tendermint/tendermint/crypto/ed25519" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + pk = ed25519.GenPrivKey().PubKey() + addr = sdk.AccAddress(pk.Address()) + denom = "atom" + emptyAddr sdk.AccAddress + emptyDenom string +) From 7609e0551eea36b2ceba23639ba180fc8770db45 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Thu, 6 Jun 2019 15:46:27 -0700 Subject: [PATCH 03/15] modifiy module layout to follow standard --- x/uniswap/{ => keeper}/keeper.go | 0 x/uniswap/{ => keeper}/keeper_test.go | 0 x/uniswap/{ => keeper}/key.go | 0 x/uniswap/{ => types}/codec.go | 2 +- x/uniswap/{ => types}/errors.go | 2 +- x/uniswap/{ => types}/msgs.go | 42 ++++++++++++++++++++++++--- x/uniswap/{ => types}/msgs_test.go | 2 +- x/uniswap/{ => types}/test_common.go | 2 +- 8 files changed, 42 insertions(+), 8 deletions(-) rename x/uniswap/{ => keeper}/keeper.go (100%) rename x/uniswap/{ => keeper}/keeper_test.go (100%) rename x/uniswap/{ => keeper}/key.go (100%) rename x/uniswap/{ => types}/codec.go (96%) rename x/uniswap/{ => types}/errors.go (97%) rename x/uniswap/{ => types}/msgs.go (87%) rename x/uniswap/{ => types}/msgs_test.go (97%) rename x/uniswap/{ => types}/test_common.go (94%) diff --git a/x/uniswap/keeper.go b/x/uniswap/keeper/keeper.go similarity index 100% rename from x/uniswap/keeper.go rename to x/uniswap/keeper/keeper.go diff --git a/x/uniswap/keeper_test.go b/x/uniswap/keeper/keeper_test.go similarity index 100% rename from x/uniswap/keeper_test.go rename to x/uniswap/keeper/keeper_test.go diff --git a/x/uniswap/key.go b/x/uniswap/keeper/key.go similarity index 100% rename from x/uniswap/key.go rename to x/uniswap/keeper/key.go diff --git a/x/uniswap/codec.go b/x/uniswap/types/codec.go similarity index 96% rename from x/uniswap/codec.go rename to x/uniswap/types/codec.go index 79d8e2e5a28..5602c6bab4e 100644 --- a/x/uniswap/codec.go +++ b/x/uniswap/types/codec.go @@ -1,4 +1,4 @@ -package uniswap +package types import ( "github.com/cosmos/cosmos-sdk/codec" diff --git a/x/uniswap/errors.go b/x/uniswap/types/errors.go similarity index 97% rename from x/uniswap/errors.go rename to x/uniswap/types/errors.go index f784dcff1ac..f9f2eb16ce2 100644 --- a/x/uniswap/errors.go +++ b/x/uniswap/types/errors.go @@ -1,4 +1,4 @@ -package uniswap +package types import ( sdk "github.com/cosmos/cosmos-sdk/types" diff --git a/x/uniswap/msgs.go b/x/uniswap/types/msgs.go similarity index 87% rename from x/uniswap/msgs.go rename to x/uniswap/types/msgs.go index 55602dc8d42..d7a24984426 100644 --- a/x/uniswap/msgs.go +++ b/x/uniswap/types/msgs.go @@ -1,4 +1,4 @@ -package uniswap +package types import ( sdk "github.com/cosmos/cosmos-sdk/types" @@ -54,15 +54,24 @@ func (msg MsgSwapOrder) ValidateBasic() sdk.Error { if msg.SwapDenom == "" { return ErrNoDenom(DefaultCodespace) } + if msg.Coin.IsZero() { + return sdk.ErrInsufficientCoins("coin has zero value") + } + if msg.Coin.Denom == "" { + return sdk.ErrInvalidCoins("coin has no denomination") + } + if msg.Coin.Denom == msg.SwapDenom { + return ErrEqualDenom(DefaultCodespace) + } + if !msg.Bound.IsPositive() { + return Err(DefaultCodespace) + } if msg.Sender.Empty() { return sdk.ErrInvalidAddress("invalid sender address") } if msg.Recipient.Empty() { return sdk.ErrInvalidAddress("invalid recipient address") } - if msg.Coin.IsZero() { - return ErrZeroCoin() - } } // GetSignBytes Implements Msg. @@ -113,9 +122,25 @@ func (msg MsgAddLiquidity) Type() string { return "add_liquidity" } // ValidateBasic Implements Msg. func (msg MsgAddLiquidity) ValidateBasic() sdk.Error { + if !msg.DepositAmount.IsPositive() { + return ErrInsufficientAmount("deposit amount provided is not positive") + } + if msg.ExchangeDenom == "" { + return ErrNoDenom(DefaultCodespace) + } + if !msg.MinLiquidity.IsPositive() { + return + } + if !msg.MaxCoins.IsPositive() { + return + } + if msg.Deadline { + + } if msg.Sender.Empty() { return sdk.InvalidAddress("invalid sender address") } + } // GetSignBytes Implements Msg. @@ -166,9 +191,18 @@ func (msg MsgRemoveLiquidity) Type() string { return "remove_liquidity" } // ValidateBasic Implements Msg. func (msg MsgRemoveLiquidity) ValidateBasic() sdk.Error { + if !msg.WithdrawAmount.IsPositive() { + return ErrInsufficientAmount("withdraw amount is not positive") + } if msg.ExchangeDenom == "" { return ErrNoDenom(DefaultCodespace) } + if !msg.MinNative.IsPositive() { + return + } + if !msg.MinCoins.IsPositive() { + return + } if msg.Sender.Empty() { return sdk.InvalidAddress("invalid sender address") } diff --git a/x/uniswap/msgs_test.go b/x/uniswap/types/msgs_test.go similarity index 97% rename from x/uniswap/msgs_test.go rename to x/uniswap/types/msgs_test.go index c6744d3a55e..cba00419a71 100644 --- a/x/uniswap/msgs_test.go +++ b/x/uniswap/types/msgs_test.go @@ -1,4 +1,4 @@ -package uniswap +package types import ( "testing" diff --git a/x/uniswap/test_common.go b/x/uniswap/types/test_common.go similarity index 94% rename from x/uniswap/test_common.go rename to x/uniswap/types/test_common.go index 04a93b19e94..e8d2b3f5997 100644 --- a/x/uniswap/test_common.go +++ b/x/uniswap/types/test_common.go @@ -1,4 +1,4 @@ -package uniswap +package types import ( "github.com/tendermint/tendermint/crypto/ed25519" From 9e56eee060cb3c4e592c26ffed380bd387fecd3c Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Fri, 7 Jun 2019 13:32:58 -0700 Subject: [PATCH 04/15] added tests for msgs --- x/uniswap/types/codec.go | 9 ++-- x/uniswap/types/errors.go | 35 ++++++++++--- x/uniswap/types/key.go | 15 ++++++ x/uniswap/types/msgs.go | 69 ++++++++++++++----------- x/uniswap/types/msgs_test.go | 93 +++++++++++++++++++++++++++++----- x/uniswap/types/test_common.go | 23 +++++++-- 6 files changed, 188 insertions(+), 56 deletions(-) create mode 100644 x/uniswap/types/key.go diff --git a/x/uniswap/types/codec.go b/x/uniswap/types/codec.go index 5602c6bab4e..0a674ac50e6 100644 --- a/x/uniswap/types/codec.go +++ b/x/uniswap/types/codec.go @@ -4,15 +4,18 @@ import ( "github.com/cosmos/cosmos-sdk/codec" ) -// Register concrete types on codec codec +// Register concrete types on codec func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgSwapOrder{}, "cosmos-sdk/MsgSwapOrder", nil) cdc.RegisterConcrete(MsgAddLiquidity{}, "cosmos-sdk/MsgAddLiquidity", nil) cdc.RegisterConcrete(MsgRemoveLiquidity{}, "cosmos-sdk/MsgRemoveLiquidity", nil) } -var moduleCdc = codec.New() +// module codec +var ModuleCdc = codec.New() func init() { - RegisterCodec(moduleCdc) + ModuleCdc = codec.New() + RegisterCodec(ModuleCdc) + ModuleCdc.Seal() } diff --git a/x/uniswap/types/errors.go b/x/uniswap/types/errors.go index f9f2eb16ce2..8c16a8c72ed 100644 --- a/x/uniswap/types/errors.go +++ b/x/uniswap/types/errors.go @@ -7,22 +7,43 @@ import ( const ( DefaultCodespace sdk.CodespaceType = ModuleName - CodeNoDenom sdk.CodeType = 1 - CodeNoSigner sdk.CodeType = 2 - CodeExchangeAlreadyExists = 3 + CodeNoDenom sdk.CodeType = 101 + CodeExchangeAlreadyExists sdk.CodeType = 102 + CodeEqualDenom sdk.CodeType = 103 + CodeInvalidBound sdk.CodeType = 104 + CodeInvalidDeadline sdk.CodeType = 105 + CodeInsufficientAmount sdk.CodeType = 106 ) func ErrNoDenom(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeNoDenom, "denomination is empty") } -func ErrNoSigner(codespace sdk.CodespaceType) sdk.Error { - return sdk.NewError(codespace, CodeNoSigner, "signer is nil") -} - func ErrExchangeAlreadyExists(codespace sdk.CodespaceType, msg string) sdk.Error { if msg != "" { return sdk.NewError(codespace, CodeExchangeAlreadyExists, msg) } return sdk.NewError(codespace, CodeExchangeAlreadyExists, "exchange already exists") } + +func ErrEqualDenom(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeEqualDenom, "coin and swap denomination are equal") +} + +func ErrInvalidBound(codespace sdk.CodespaceType, msg string) sdk.Error { + if msg != "" { + return sdk.NewError(codespace, CodeInvalidBound, msg) + } + return sdk.NewError(codespace, CodeInvalidBound, "bound is not positive") +} + +func ErrInvalidDeadline(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDeadline, "deadline not initialized") +} + +func ErrInsufficientAmount(codespace sdk.CodespaceType, msg string) sdk.Error { + if msg != "" { + return sdk.NewError(codespace, CodeInsufficientAmount, msg) + } + return sdk.NewError(codespace, CodeInsufficientAmount, "insufficient amount provided") +} diff --git a/x/uniswap/types/key.go b/x/uniswap/types/key.go new file mode 100644 index 00000000000..889c8dad9e2 --- /dev/null +++ b/x/uniswap/types/key.go @@ -0,0 +1,15 @@ +package types + +const ( + // ModuleName is the name of the module + ModuleName = "uniswap" + + // QuerierRoute is the querier route for the uniswap store + QuerierRoute = ModuleName + + // RouterKey is the message route for the uniswap module + RouterKey = ModuleName + + // StoreKey is the default store key for uniswap + StoreKey = ModuleName +) diff --git a/x/uniswap/types/msgs.go b/x/uniswap/types/msgs.go index d7a24984426..b689f8ff956 100644 --- a/x/uniswap/types/msgs.go +++ b/x/uniswap/types/msgs.go @@ -18,7 +18,7 @@ var ( // MsgSwap Order - struct for swapping a coin type MsgSwapOrder struct { SwapDenom string // The desired denomination either to be bought or sold - Coin sdk.Coin // The specified amount to be either bought or sold + Amount sdk.Coins // The specified amount to be either bought or sold Bound sdk.Int // If buy order, maximum amount of coins to be sold; otherwise minimum amount of coins to be bought Deadline time.Time // deadline for the transaction to still be considered valid Sender sdk.AccAddress // address swapping coin @@ -28,13 +28,13 @@ type MsgSwapOrder struct { // NewMsgSwapOrder is a constructor function for MsgSwapOrder func NewMsgSwapOrder( - swapDenom string, coin sdk.Coin, bound sdk.Int, deadline time.Time, + swapDenom string, amt sdk.Coins, bound sdk.Int, deadline time.Time, sender, recipient sdk.AccAddress, isBuyOrder bool, ) MsgSwapOrder { return MsgSwapOrder{ SwapDenom: swapDenom, - Coin: coin, + Amount: amt, Bound: bound, Deadline: deadline, Sender: sender, @@ -43,7 +43,7 @@ func NewMsgSwapOrder( } } -// Type Implements Msg +// Route Implements Msg func (msg MsgSwapOrder) Route() string { return RouterKey } // Type Implements Msg @@ -54,17 +54,21 @@ func (msg MsgSwapOrder) ValidateBasic() sdk.Error { if msg.SwapDenom == "" { return ErrNoDenom(DefaultCodespace) } - if msg.Coin.IsZero() { - return sdk.ErrInsufficientCoins("coin has zero value") + // initially only support trading 1 coin only + if len(msg.Amount) != 1 { + return sdk.ErrInvalidCoins("must provide a single coin") } - if msg.Coin.Denom == "" { - return sdk.ErrInvalidCoins("coin has no denomination") + if !msg.Amount.IsValid() { + return sdk.ErrInvalidCoins("coin is invalid: " + msg.Amount.String()) } - if msg.Coin.Denom == msg.SwapDenom { + if msg.Amount[0].Denom == msg.SwapDenom { return ErrEqualDenom(DefaultCodespace) } if !msg.Bound.IsPositive() { - return Err(DefaultCodespace) + return ErrInvalidBound(DefaultCodespace, "") + } + if msg.Deadline.IsZero() { + return ErrInvalidDeadline(DefaultCodespace) } if msg.Sender.Empty() { return sdk.ErrInvalidAddress("invalid sender address") @@ -72,11 +76,12 @@ func (msg MsgSwapOrder) ValidateBasic() sdk.Error { if msg.Recipient.Empty() { return sdk.ErrInvalidAddress("invalid recipient address") } + return nil } // GetSignBytes Implements Msg. func (msg MsgSwapOrder) GetSignBytes() []byte { - return sdk.MustSortJSON(moduleCdc.MustMarshalJSON(msg)) + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) } // GetSigners Implements Msg. @@ -90,8 +95,8 @@ func (msg MsgSwapOrder) GetSigners() []sdk.AccAddress { // MsgAddLiquidity - struct for adding liquidity to an exchange type MsgAddLiquidity struct { - DepositAmount sdk.Int // exact amount of native asset being add to the liquidity pool ExchangeDenom string // denomination of the exchange being added to + DepositAmount sdk.Int // exact amount of native asset being add to the liquidity pool MinLiquidity sdk.Int // lower bound UNI sender is willing to accept for deposited coins MaxCoins sdk.Int // maximum amount of the coin the sender is willing to deposit. Deadline time.Time @@ -100,8 +105,8 @@ type MsgAddLiquidity struct { // NewMsgAddLiquidity is a constructor function for MsgAddLiquidity func NewMsgAddLiquidity( - depositAmount, minLiquidity, maxCoins sdk.Int, - exchangeDenom string, deadline time.Time, sender sdk.AccAddress, + exchangeDenom string, depositAmount, minLiquidity, maxCoins sdk.Int, + deadline time.Time, sender sdk.AccAddress, ) MsgAddLiquidity { return MsgAddLiquidity{ @@ -123,29 +128,29 @@ func (msg MsgAddLiquidity) Type() string { return "add_liquidity" } // ValidateBasic Implements Msg. func (msg MsgAddLiquidity) ValidateBasic() sdk.Error { if !msg.DepositAmount.IsPositive() { - return ErrInsufficientAmount("deposit amount provided is not positive") + return ErrInsufficientAmount(DefaultCodespace, "deposit amount provided is not positive") } if msg.ExchangeDenom == "" { return ErrNoDenom(DefaultCodespace) } if !msg.MinLiquidity.IsPositive() { - return + return ErrInvalidBound(DefaultCodespace, "minimum liquidity is not positive") } if !msg.MaxCoins.IsPositive() { - return + return ErrInvalidBound(DefaultCodespace, "maxmimum coins is not positive") } - if msg.Deadline { - + if msg.Deadline.IsZero() { + return ErrInvalidDeadline(DefaultCodespace) } if msg.Sender.Empty() { - return sdk.InvalidAddress("invalid sender address") + return sdk.ErrInvalidAddress("invalid sender address") } - + return nil } // GetSignBytes Implements Msg. func (msg MsgAddLiquidity) GetSignBytes() []byte { - return sdk.MustSortJSON(moduleCdc.MustMarshalJSON(msg)) + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) } // GetSigners Implements Msg. @@ -159,8 +164,8 @@ func (msg MsgAddLiquidity) GetSigners() []sdk.AccAddress { // MsgRemoveLiquidity - struct for removing liquidity from an exchange type MsgRemoveLiquidity struct { - WithdrawAmount sdk.Int // amount of UNI to be burned to withdraw liquidity from an exchange ExchangeDenom string // denomination of the exchange being withdrawn from + WithdrawAmount sdk.Int // amount of UNI to be burned to withdraw liquidity from an exchange MinNative sdk.Int // minimum amount of the native asset the sender is willing to accept MinCoins sdk.Int // minimum amount of the exchange coin the sender is willing to accept Deadline time.Time @@ -169,8 +174,8 @@ type MsgRemoveLiquidity struct { // NewMsgRemoveLiquidity is a contructor function for MsgRemoveLiquidity func NewMsgRemoveLiquidity( - withdrawAmount, minNative, minCoins sdk.Int, - exchangeDenom string, deadline time.Time, sender sdk.AccAddress, + exchangeDenom string, withdrawAmount, minNative, minCoins sdk.Int, + deadline time.Time, sender sdk.AccAddress, ) MsgRemoveLiquidity { return MsgRemoveLiquidity{ @@ -192,25 +197,29 @@ func (msg MsgRemoveLiquidity) Type() string { return "remove_liquidity" } // ValidateBasic Implements Msg. func (msg MsgRemoveLiquidity) ValidateBasic() sdk.Error { if !msg.WithdrawAmount.IsPositive() { - return ErrInsufficientAmount("withdraw amount is not positive") + return ErrInsufficientAmount(DefaultCodespace, "withdraw amount is not positive") } if msg.ExchangeDenom == "" { return ErrNoDenom(DefaultCodespace) } if !msg.MinNative.IsPositive() { - return + return ErrInvalidBound(DefaultCodespace, "minimum native is not positive") } if !msg.MinCoins.IsPositive() { - return + return ErrInvalidBound(DefaultCodespace, "minimum coins is not positive") + } + if msg.Deadline.IsZero() { + return ErrInvalidDeadline(DefaultCodespace) } if msg.Sender.Empty() { - return sdk.InvalidAddress("invalid sender address") + return sdk.ErrInvalidAddress("invalid sender address") } + return nil } // GetSignBytes Implements Msg. func (msg MsgRemoveLiquidity) GetSignBytes() []byte { - return sdk.MustSortJSON(moduleCdc.MustMarshalJSON(msg)) + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) } // GetSigners Implements Msg. diff --git a/x/uniswap/types/msgs_test.go b/x/uniswap/types/msgs_test.go index cba00419a71..5000443efb4 100644 --- a/x/uniswap/types/msgs_test.go +++ b/x/uniswap/types/msgs_test.go @@ -11,21 +11,88 @@ import ( // test ValidateBasic for MsgSwapOrder func TestMsgSwapOrder(t *testing.T) { tests := []struct { - newCoinDenom string - signer sdk.AccAddress - expectPass bool + name string + msg MsgSwapOrder + expectPass bool }{ - {emptyDenom, addr, false}, - {denom, emptyAddr, false}, - {denom, addr, true}, + {"empty swap denomination", NewMsgSwapOrder(emptyDenom, amount, bound, deadline, sender, recipient, true), false}, + {"empty coin", NewMsgSwapOrder(denom0, sdk.NewCoins(sdk.NewCoin(denom1, zero)), bound, deadline, sender, recipient, true), false}, + {"no coin", NewMsgSwapOrder(denom0, sdk.Coins{}, bound, deadline, sender, recipient, true), false}, + {"too many coins", NewMsgSwapOrder(denom0, sdk.NewCoins(coin, sdk.NewCoin(denom2, baseValue)), bound, deadline, sender, recipient, true), false}, + {"swap and coin denomination are equal", NewMsgSwapOrder(denom0, sdk.NewCoins(sdk.NewCoin(denom0, baseValue)), bound, deadline, sender, recipient, true), false}, + {"bound is not positive", NewMsgSwapOrder(denom0, amount, zero, deadline, sender, recipient, true), false}, + {"deadline not initialized", NewMsgSwapOrder(denom0, amount, bound, emptyTime, sender, recipient, true), false}, + {"no sender", NewMsgSwapOrder(denom0, amount, bound, deadline, emptyAddr, recipient, true), false}, + {"no recipient", NewMsgSwapOrder(denom0, amount, bound, deadline, sender, emptyAddr, true), false}, + {"valid MsgSwapOrder", NewMsgSwapOrder(denom0, amount, bound, deadline, sender, recipient, true), true}, + {"sender and recipient are same", NewMsgSwapOrder(denom0, amount, bound, deadline, sender, sender, true), true}, } - for i, tc := range tests { - msg := NewMsgCreateExchange(tc.newCoinDenom, tc.signer) - if tc.expectPass { - require.Nil(t, msg.ValidateBasic(), "test index: %v", i) - } else { - require.NotNil(t, msg.ValidateBasic(), "test index: %v", i) - } + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.expectPass { + require.Nil(t, err) + } else { + require.NotNil(t, err) + } + }) } } + +// test ValidateBasic for MsgAddLiquidity +func TestMsgAddLiquidity(t *testing.T) { + tests := []struct { + name string + msg MsgAddLiquidity + expectPass bool + }{ + {"invalid withdraw amount", NewMsgAddLiquidity(denom1, zero, one, one, deadline, sender), false}, + {"empty exchange denom", NewMsgAddLiquidity(emptyDenom, baseValue, one, one, deadline, sender), false}, + {"invalid minumum liquidity bound", NewMsgAddLiquidity(denom1, baseValue, zero, one, deadline, sender), false}, + {"invalid maximum coins bound", NewMsgAddLiquidity(denom1, baseValue, one, zero, deadline, sender), false}, + {"deadline not initialized", NewMsgAddLiquidity(denom1, baseValue, one, one, emptyTime, sender), false}, + {"empty sender", NewMsgAddLiquidity(denom1, baseValue, one, one, deadline, emptyAddr), false}, + {"valid MsgAddLiquidity", NewMsgAddLiquidity(denom1, baseValue, one, one, deadline, sender), true}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.expectPass { + require.Nil(t, err) + } else { + require.NotNil(t, err) + } + }) + } +} + +// test ValidateBasic for MsgRemoveLiquidity +func TestMsgRemoveLiquidity(t *testing.T) { + tests := []struct { + name string + msg MsgRemoveLiquidity + expectPass bool + }{ + {"invalid deposit amount", NewMsgRemoveLiquidity(denom1, zero, one, one, deadline, sender), false}, + {"empty exchange denom", NewMsgRemoveLiquidity(emptyDenom, baseValue, one, one, deadline, sender), false}, + {"invalid minimum native bound", NewMsgRemoveLiquidity(denom1, baseValue, zero, one, deadline, sender), false}, + {"invalid minumum coins bound", NewMsgRemoveLiquidity(denom1, baseValue, one, zero, deadline, sender), false}, + {"deadline not initialized", NewMsgRemoveLiquidity(denom1, baseValue, one, one, emptyTime, sender), false}, + {"empty sender", NewMsgRemoveLiquidity(denom1, baseValue, one, one, deadline, emptyAddr), false}, + {"valid MsgRemoveLiquidity", NewMsgRemoveLiquidity(denom1, baseValue, one, one, deadline, sender), true}, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := tc.msg.ValidateBasic() + if tc.expectPass { + require.Nil(t, err) + } else { + require.NotNil(t, err) + } + }) + } + +} diff --git a/x/uniswap/types/test_common.go b/x/uniswap/types/test_common.go index e8d2b3f5997..d874c63b58c 100644 --- a/x/uniswap/types/test_common.go +++ b/x/uniswap/types/test_common.go @@ -2,14 +2,31 @@ package types import ( "github.com/tendermint/tendermint/crypto/ed25519" + "time" sdk "github.com/cosmos/cosmos-sdk/types" ) var ( - pk = ed25519.GenPrivKey().PubKey() - addr = sdk.AccAddress(pk.Address()) - denom = "atom" + zero = sdk.NewInt(0) + one = sdk.NewInt(1) + baseValue = sdk.NewInt(100) + + sender_pk = ed25519.GenPrivKey().PubKey() + recipient_pk = ed25519.GenPrivKey().PubKey() + sender = sdk.AccAddress(sender_pk.Address()) + recipient = sdk.AccAddress(recipient_pk.Address()) + + denom0 = "atom" + denom1 = "btc" + denom2 = "eth" + + coin = sdk.NewCoin(denom1, sdk.NewInt(1000)) + amount = sdk.NewCoins(coin) + bound = sdk.NewInt(100) + deadline = time.Now() + emptyAddr sdk.AccAddress emptyDenom string + emptyTime time.Time ) From 43f29f00de40e81e597d9025849f8246a64e6ace Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Fri, 7 Jun 2019 14:39:32 -0700 Subject: [PATCH 05/15] fixes in keeper --- x/uniswap/{ => keeper}/expected_keepers.go | 2 +- x/uniswap/keeper/keeper.go | 87 ++++++++++++---------- x/uniswap/keeper/keeper_test.go | 2 +- x/uniswap/keeper/key.go | 11 +-- x/uniswap/types/key.go | 6 -- 5 files changed, 49 insertions(+), 59 deletions(-) rename x/uniswap/{ => keeper}/expected_keepers.go (93%) diff --git a/x/uniswap/expected_keepers.go b/x/uniswap/keeper/expected_keepers.go similarity index 93% rename from x/uniswap/expected_keepers.go rename to x/uniswap/keeper/expected_keepers.go index 1db0900255e..d29640f36dd 100644 --- a/x/uniswap/expected_keepers.go +++ b/x/uniswap/keeper/expected_keepers.go @@ -1,4 +1,4 @@ -package uniswap +package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" diff --git a/x/uniswap/keeper/keeper.go b/x/uniswap/keeper/keeper.go index 5348df32651..505bf587d2a 100644 --- a/x/uniswap/keeper/keeper.go +++ b/x/uniswap/keeper/keeper.go @@ -1,4 +1,4 @@ -package uniswap +package keeper import ( "github.com/cosmos/cosmos-sdk/codec" @@ -7,8 +7,12 @@ import ( "github.com/tendermint/tendermint/libs/log" ) +// fee = 1 - p +// p = feeN / feeD var ( - fee sdk.Dec // TODO: replace with fee param + // TODO: replace with fee param + feeN sdk.Int // numerator for determining fee + feeD sdk.Int // denominator for determining fee ) type Keeper struct { @@ -28,11 +32,9 @@ type Keeper struct { // - users adding liquidity to exchanges // - users removing liquidity to exchanges func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, ck BankKeeper) Keeper { - fee, err := sdk.NewDecFromStr("0.003") - if err != nil { - panic("could not construct fee") - } - + // TODO: replace with param + feeN = sdk.NewInt(997) + feeD = sdk.NewInt(100) return Keeper{ storeKey: key, ck: ck, @@ -48,91 +50,94 @@ func (keeper Keeper) Logger(ctx sdk.Context) log.Logger { // CreateExchange initializes a new exchange pair between the new coin and the native asset func (keeper Keeper) CreateExchange(ctx sdk.Context, newCoinDenom string) { store := ctx.KVStore(keeper.storeKey) - key := GetExchangeKey() + key := GetExchangeKey(newCoinDenom) bz := store.Get(key) if bz != nil { panic("exchange already exists") } - store.Set(key, sdk.NewDec(0)) + + store.Set(key, keeper.encode(sdk.NewInt(0))) } // Deposit adds the specified amount of UNI to the associated account -func (keeper Keeper) Deposit(ctx sdk.Context, amt sdk.Dec, addr sdk.AccAddress) { - var balance sdk.Dec +func (keeper Keeper) Deposit(ctx sdk.Context, amt sdk.Int, addr sdk.AccAddress) { + var balance sdk.Int store := ctx.KVStore(keeper.storeKey) key := GetUNIBalancesKey(addr) bz := store.Get(key) if bz != nil { - balance = keeper.decodeBalance(bz) + balance = keeper.decode(bz) } balance.Add(amt) - store.Set(key, keeper.encodeBalance(balance)) + store.Set(key, keeper.encode(balance)) return } // Withdraw removes the specified amount of UNI from the associated account -func (keeper Keeper) Withdraw(ctx sdk.Context, amt sdk.Dec, addr sdk.AccAddress) { - var balance sdk.Dec +func (keeper Keeper) Withdraw(ctx sdk.Context, amt sdk.Int, addr sdk.AccAddress) { + var balance sdk.Int store := ctx.KVStore(keeper.storeKey) key := GetUNIBalancesKey(addr) bz := store.Get(key) if bz != nil { - balance = keeper.decodeBalance(bz) + balance = keeper.decode(bz) } balance.Sub(amt) - store.Set(key, keeper.encodeBalance(balance)) + store.Set(key, keeper.encode(balance)) } // GetInputAmount 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 (keeper Keeper) GetInputAmount(ctx sdk.Context, outputAmt sdk.Dec, inputDenom, outputDenom string) sdk.Dec { +// https://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdfhttps://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdf +func (keeper Keeper) GetInputAmount(ctx sdk.Context, outputAmt sdk.Int, inputDenom, outputDenom string) sdk.Int { store := ctx.KVStore(keeper.storeKey) - inputReserve := store.Get(GetExchangeKey(inputDenom)) - if inputReserve == nil { + bz := store.Get(GetExchangeKey(inputDenom)) + if bz == nil { panic("exchange for input denomination does not exist") } - outputReserve := store.Get(GetExchangeKey(outputDenom)) - if outputReserve == nil { + inputReserve := keeper.decode(bz) + bz = store.Get(GetExchangeKey(outputDenom)) + if bz == nil { panic("exchange for output denomination does not exist") } + outputReserve := keeper.decode(bz) - // TODO: verify - feeAmt := outputAmt.Mul(fee) - numerator := inputReserve.Mul(outputReserve) - denominator := outputReserve.Sub(outputAmt.Add(feeAmt)) - return numerator/denominator + 1 + numerator := inputReserve.Mul(outputReserve).Mul(feeD) + denominator := (outputReserve.Sub(outputAmt)).Mul(feeN) + return numerator.Quo(denominator).Add(sdk.NewInt(1)) } -// GetOutputAmount returns the amount of coins bought (calculated) given the output amount being sold (exact) +// GetOutputAmount returns the amount of coins bought (calculated) given the input amount being sold (exact) // The fee is included in the input coins being bought -func (keeper Keeper) GetOutputAmount(ctx sdk.Context, inputAmt sdk.Dec, inputDenom, outputDenom string) sdk.Dec { +// https://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdf +func (keeper Keeper) GetOutputAmount(ctx sdk.Context, inputAmt sdk.Int, inputDenom, outputDenom string) sdk.Int { store := ctx.KVStore(keeper.storeKey) - inputReserve := store.Get(GetExchangeKey(inputDenom)) - if inputReserve == nil { + bz := store.Get(GetExchangeKey(inputDenom)) + if bz == nil { panic("exchange for input denomination does not exist") } - outputReserve := store.Get(GetExchangeKey(outputDenom)) - if outputReserve == nil { + inputReserve := keeper.decode(bz) + bz = store.Get(GetExchangeKey(outputDenom)) + if bz == nil { panic("exchange for output denomination does not exist") } + outputReserve := keeper.decode(bz) - // TODO: verify - feeAmt := inputAmt.Mul(fee) - inputAmtWithFee := inputReserve.Add(feeAmt) - numerator := inputAmtWithFee * outputReserve - denominator := inputReserve + inputAmt.Sub(feeAmt) - return numerator / denominator + inputAmtWithFee := inputAmt.Mul(feeN) + numerator := inputAmtWithFee.Mul(outputReserve) + denominator := inputReserve.Mul(feeD).Add(inputAmtWithFee) + return numerator.Quo(denominator) } // ----------------------------------------------------------------------------- // Misc. -func (keeper Keeper) decodeBalance(bz []byte) (balance sdk.Dec) { +func (keeper Keeper) decode(bz []byte) (balance sdk.Int) { err := keeper.cdc.UnmarshalBinaryBare(bz, &balance) if err != nil { panic(err) @@ -140,7 +145,7 @@ func (keeper Keeper) decodeBalance(bz []byte) (balance sdk.Dec) { return } -func (keeper Keeper) encodeBalance(balance sdk.Dec) (bz []byte) { +func (keeper Keeper) encode(balance sdk.Int) (bz []byte) { bz, err := keeper.cdc.MarshalBinaryBare(balance) if err != nil { panic(err) diff --git a/x/uniswap/keeper/keeper_test.go b/x/uniswap/keeper/keeper_test.go index 7d1e96cd269..b55569d4a44 100644 --- a/x/uniswap/keeper/keeper_test.go +++ b/x/uniswap/keeper/keeper_test.go @@ -1 +1 @@ -package uniswap +package keeper diff --git a/x/uniswap/keeper/key.go b/x/uniswap/keeper/key.go index 5ef04fe31c3..d81e7769f4d 100644 --- a/x/uniswap/keeper/key.go +++ b/x/uniswap/keeper/key.go @@ -1,4 +1,4 @@ -package uniswap +package keeper import ( sdk "github.com/cosmos/cosmos-sdk/types" @@ -13,15 +13,6 @@ var ( const ( // ModuleName is the name of the module ModuleName = "uniswap" - - // QuerierRoute is the querier route for the uniswap store - QuerierRoute = ModuleName - - // RouterKey is the message route for the uniswap module - RouterKey = ModuleName - - // StoreKey is the default store key for uniswap - StoreKey = ModuleName ) // GetExchangeKey gets the key for an exchanges total liquidity diff --git a/x/uniswap/types/key.go b/x/uniswap/types/key.go index 889c8dad9e2..a8252011cc1 100644 --- a/x/uniswap/types/key.go +++ b/x/uniswap/types/key.go @@ -4,12 +4,6 @@ const ( // ModuleName is the name of the module ModuleName = "uniswap" - // QuerierRoute is the querier route for the uniswap store - QuerierRoute = ModuleName - // RouterKey is the message route for the uniswap module RouterKey = ModuleName - - // StoreKey is the default store key for uniswap - StoreKey = ModuleName ) From 930f5edb23bd02183012d65263cb6a3c19cceba2 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Fri, 7 Jun 2019 16:39:20 -0700 Subject: [PATCH 06/15] work continued on keeper design --- x/uniswap/keeper/keeper.go | 22 +++++++++++++++++----- x/uniswap/keeper/key.go | 5 +++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/x/uniswap/keeper/keeper.go b/x/uniswap/keeper/keeper.go index 505bf587d2a..4e476dcaf5e 100644 --- a/x/uniswap/keeper/keeper.go +++ b/x/uniswap/keeper/keeper.go @@ -7,16 +7,16 @@ import ( "github.com/tendermint/tendermint/libs/log" ) +// TODO: replace with params // fee = 1 - p // p = feeN / feeD var ( - // TODO: replace with fee param feeN sdk.Int // numerator for determining fee feeD sdk.Int // denominator for determining fee ) type Keeper struct { - // The key used to access the store TODO update + // The key used to access the uniswap store storeKey sdk.StoreKey // The reference to the CoinKeeper to modify balances after swaps or liquidity is deposited/withdrawn @@ -44,13 +44,13 @@ func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, ck BankKeeper) Keeper { // Logger returns a module-specific logger. func (keeper Keeper) Logger(ctx sdk.Context) log.Logger { - return ctx.Logger().With("module", "x/uniswap") + return ctx.Logger().With("module", ModuleName) } // CreateExchange initializes a new exchange pair between the new coin and the native asset -func (keeper Keeper) CreateExchange(ctx sdk.Context, newCoinDenom string) { +func (keeper Keeper) CreateExchange(ctx sdk.Context, exchangeDenom string) { store := ctx.KVStore(keeper.storeKey) - key := GetExchangeKey(newCoinDenom) + key := GetExchangeKey(exchangeDenom) bz := store.Get(key) if bz != nil { panic("exchange already exists") @@ -91,6 +91,18 @@ func (keeper Keeper) Withdraw(ctx sdk.Context, amt sdk.Int, addr sdk.AccAddress) store.Set(key, keeper.encode(balance)) } +// GetTotalLiquidity returns the total UNI currently in existence +func (keeper Keeper) GetTotalLiquidity(ctx sdk.Context) sdk.Int { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(TotalLiquidityKey) + if bz == nil { + panic("error retrieving total UNI liquidity") + } + + totalLiquidity := keeper.decode(bz) + return totalLiquidity +} + // GetInputAmount returns the amount of coins sold (calculated) given the output amount being bought (exact) // The fee is included in the output coins being bought // https://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdfhttps://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdf diff --git a/x/uniswap/keeper/key.go b/x/uniswap/keeper/key.go index d81e7769f4d..ae7ce3c9019 100644 --- a/x/uniswap/keeper/key.go +++ b/x/uniswap/keeper/key.go @@ -6,8 +6,9 @@ import ( // keys var ( - ExchangePrefix = []byte{0x00} // key for exchange liquidity - UNIBalancesPrefix = []byte{0x01} // key for UNI balances + ExchangePrefix = []byte{0x00} // prefix for exchange liquidity keys + UNIBalancesPrefix = []byte{0x01} // prefix for UNI balances keys + TotalLiquidityKey = []byte{0x02} // key for total UNI liquidity ) const ( From c04b2397fb03a14676c4b030c0ebe02a71e956a6 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Mon, 10 Jun 2019 16:21:53 -0700 Subject: [PATCH 07/15] added fee params, reflected pr comments --- x/uniswap/alias.go | 18 ++ x/uniswap/genesis.go | 25 ++- x/uniswap/handler.go | 75 +++++++- x/uniswap/internal/keeper/keeper.go | 136 ++++++++++++++ .../{ => internal}/keeper/keeper_test.go | 0 .../key.go => internal/keeper/keys.go} | 7 +- x/uniswap/internal/keeper/querier.go | 1 + x/uniswap/{ => internal}/types/codec.go | 0 x/uniswap/{ => internal}/types/errors.go | 0 .../{types/key.go => internal/types/keys.go} | 5 + x/uniswap/{ => internal}/types/msgs.go | 48 ++--- x/uniswap/{ => internal}/types/msgs_test.go | 0 x/uniswap/internal/types/params.go | 64 +++++++ x/uniswap/internal/types/tags.go | 1 + x/uniswap/{ => internal}/types/test_common.go | 2 +- x/uniswap/keeper/expected_keepers.go | 10 -- x/uniswap/keeper/keeper.go | 166 ------------------ x/uniswap/module.go | 46 ++--- 18 files changed, 361 insertions(+), 243 deletions(-) create mode 100644 x/uniswap/alias.go create mode 100644 x/uniswap/internal/keeper/keeper.go rename x/uniswap/{ => internal}/keeper/keeper_test.go (100%) rename x/uniswap/{keeper/key.go => internal/keeper/keys.go} (79%) create mode 100644 x/uniswap/internal/keeper/querier.go rename x/uniswap/{ => internal}/types/codec.go (100%) rename x/uniswap/{ => internal}/types/errors.go (100%) rename x/uniswap/{types/key.go => internal/types/keys.go} (74%) rename x/uniswap/{ => internal}/types/msgs.go (73%) rename x/uniswap/{ => internal}/types/msgs_test.go (100%) create mode 100644 x/uniswap/internal/types/params.go create mode 100644 x/uniswap/internal/types/tags.go rename x/uniswap/{ => internal}/types/test_common.go (97%) delete mode 100644 x/uniswap/keeper/expected_keepers.go delete mode 100644 x/uniswap/keeper/keeper.go diff --git a/x/uniswap/alias.go b/x/uniswap/alias.go new file mode 100644 index 00000000000..51d35a1307a --- /dev/null +++ b/x/uniswap/alias.go @@ -0,0 +1,18 @@ +package uniswap + +import ( + "github.com/cosmos/cosmos-sdk/x/uniswap/internal/keeper" + "github.com/cosmos/cosmos-sdk/x/uniswap/internal/types" +) + +type ( + Keeper = keeper.Keeper + NativeAsset = types.NativeAsset + MsgSwapOrder = types.MsgSwapOrder + MsgAddLiquidity = types.MsgAddLiquidity + MsgRemoveLiquidity = types.MsgRemoveLiquidity +) + +const ( + ModuleName = types.ModuleName +) diff --git a/x/uniswap/genesis.go b/x/uniswap/genesis.go index 1024d26df4a..b5a4c9fbd14 100644 --- a/x/uniswap/genesis.go +++ b/x/uniswap/genesis.go @@ -1,41 +1,50 @@ package uniswap import ( + "fmt" + "strings" + sdk "github.com/cosmos/cosmos-sdk/types" ) -// TODO - // GenesisState - uniswap genesis state type GenesisState struct { - NativeAssetDenom string + NativeAssetDenom string `json:"native_asset_denom"` + FeeParams FeeParams `json:"fee_params"` } -// NewGenesisState creates a new GenesisState object -func NewGenesisState(nativeAssetDenom string) GenesisState { +// NewGenesisState is the constructor function for GenesisState +func NewGenesisState(nativeAssetDenom string, feeParams FeeParams) GenesisState { return GenesisState{ NativeAssetDenom: nativeAssetDenom, + FeeParams: feeParams, } } // DefaultGenesisState creates a default GenesisState object func DefaultGenesisState() GenesisState { - return GenesisState{ - NativeAssetDenom: sdk.DefaultBondDenom, - } + return NewGenesisState(sdk.DefaultBondDenom, DefaultParams()) } // InitGenesis new uniswap genesis +// TODO func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { } // ExportGenesis returns a GenesisState for a given context and keeper. +// TODO func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { return NewGenesisState(sdk.DefaultBondDenom) } // ValidateGenesis - placeholder function func ValidateGenesis(data GenesisState) error { + if strings.TrimSpace(data.NativeAssetDenom) == "" { + fmt.Errorf("no native asset denomination provided") + } + if err := data.FeeParams.Validate(); err != nil { + return err + } return nil } diff --git a/x/uniswap/handler.go b/x/uniswap/handler.go index f77bf98e537..6cd4d839e9b 100644 --- a/x/uniswap/handler.go +++ b/x/uniswap/handler.go @@ -1,6 +1,7 @@ package uniswap import ( + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -22,24 +23,82 @@ func NewHandler(k Keeper) sdk.Handler { } // HandleMsgSwapOrder handler for MsgSwapOrder -func HandleMsgSwapOrder(ctx sdk.Context, msg types.MsgSwapOrder, k Keeper, -) sdk.Result { +func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result { + if msg.IsBuyOrder { + inputAmount := k.GetInputAmount(ctx, msg.SwapDenom, msg.Amount) + } else { + outputAmount := k.GetOutputAmount(ctx, msg.SwapDenom, msg.Amount) + + } + + return sdk.Result{} } // HandleMsgAddLiquidity handler for MsgAddLiquidity -func HandleMsgAddLiquidity(ctx sdk.Context, msg types.MsgAddLiqudity, k Keeper, -) sdk.Result { - // check if exchange already exists - totalLiquidity, err := k.GetExchange(ctx, msg.Denom) +// If the exchange does not exist, it will be created. +func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.Result { + // create exchange if it does not exist + coinLiquidity, err := k.GetExchange(ctx, msg.ExchangeDenom) if err != nil { k.CreateExchange(ctx, msg.Denom) } + nativeLiqudiity, err := k.GetExchange(ctx, NativeAsset) + if err != nil { + panic("error retrieving native asset total liquidity") + } + + totalLiquidity, err := k.GetTotalLiquidity(ctx) + if err != nil { + panic("error retrieving total UNI liquidity") + } + + MintedUNI := (totalLiquidity.Mul(msg.DepositAmount)).Quo(nativeLiquidity) + coinDeposited := (totalLiquidity.Mul(msg.DepositAmount)).Quo(nativeLiquidity) + nativeCoin := sdk.NewCoin(NativeAsset, msg.DepositAmount) + exchangeCoin := sdk.NewCoin(msg.ExchangeDenom, coinDeposited) + k.SendCoins(ctx, msg.Sender, exchangeAcc, nativeCoin) + k.SendCoins(ctx, msg.Sender, exchangeAcc, exchangeCoin) + k.Deposit(ctx, msg.Sender, UNIMinted) + + return sdk.Result{} } // HandleMsgRemoveLiquidity handler for MsgRemoveLiquidity -func HandleMsgRemoveLiquidity(ctx sdk.Context, msg types.MsgRemoveLiquidity, k Keeper, -) sdk.Result { +func HandleMsgRemoveLiquidity(ctx sdk.Context, msg MsgRemoveLiquidity, k Keeper) sdk.Result { + // check if exchange exists + totalLiquidity, err := k.GetExchange(ctx, msg.ExchangeDenom) + if err != nil { + return err + } + + return sdk.Result{} +} + +// GetInputAmount returns the amount of coins sold (calculated) given the output amount being bought (exact) +// The fee is included in the output coins being bought +// https://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdfhttps://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdf +func getInputAmount(ctx sdk.Context, k Keeper, outputAmt sdk.Int, inputDenom, outputDenom string) sdk.Int { + inputReserve := k.GetExchange(inputDenom) + outputReserve := k.GetExchange(outputDenom) + params := k.GetFeeParams(ctx) + + numerator := inputReserve.Mul(outputReserve).Mul(params.FeeD) + denominator := (outputReserve.Sub(outputAmt)).Mul(parans.FeeN) + return numerator.Quo(denominator).Add(sdk.OneInt()) +} + +// GetOutputAmount 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 getOutputAmount(ctx sdk.Context, k Keeper, inputAmt sdk.Int, inputDenom, outputDenom string) sdk.Int { + inputReserve := k.GetExchange(inputDenom) + outputReserve := k.GetExchange(outputDenom) + params := k.GetFeeParams(ctx) + inputAmtWithFee := inputAmt.Mul(params.FeeN) + numerator := inputAmtWithFee.Mul(outputReserve) + denominator := inputReserve.Mul(params.FeeD).Add(inputAmtWithFee) + return numerator.Quo(denominator) } diff --git a/x/uniswap/internal/keeper/keeper.go b/x/uniswap/internal/keeper/keeper.go new file mode 100644 index 00000000000..abed6c54966 --- /dev/null +++ b/x/uniswap/internal/keeper/keeper.go @@ -0,0 +1,136 @@ +package keeper + +import ( + "fmt" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/uniswap/internal/types" + + "github.com/tendermint/tendermint/libs/log" +) + +// Uniswap Keeper +type Keeper struct { + // The key used to access the uniswap store + storeKey sdk.StoreKey + + // The reference to the CoinKeeper to modify balances after swaps or liquidity is deposited/withdrawn + ck bank.Keeper + + // The codec codec for binary encoding/decoding. + cdc *codec.Codec + + // The reference to the Paramstore to get and set uniswap specific params + paramSpace params.Subspace +} + +// NewKeeper returns a uniswap keeper. It handles: +// - creating new exchanges +// - facilitating swaps +// - users adding liquidity to exchanges +// - users removing liquidity to exchanges +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, ck bank.Keeper, paramSpace params.Subspace) Keeper { + return Keeper{ + storeKey: key, + ck: ck, + cdc: cdc, + paramSpace: paramSpace.WithKeyTable(types.ParamKeyTable()), + } +} + +// Logger returns a module-specific logger. +func (keeper Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", types.ModuleName) +} + +// CreateExchange initializes a new exchange pair between the new coin and the native asset +func (keeper Keeper) CreateExchange(ctx sdk.Context, exchangeDenom string) { + store := ctx.KVStore(keeper.storeKey) + key := GetExchangeKey(exchangeDenom) + bz := store.Get(key) + if bz != nil { + panic("exchange already exists") + } + + store.Set(key, keeper.encode(sdk.ZeroInt())) +} + +// GetUNIForAddress returns the total UNI at the provided address +func (keeper Keeper) GetUNIForAddress(ctx sdk.Context, addr sdk.AccAddress) sdk.Int { + var balance sdk.Int + + store := ctx.KVStore(keeper.storeKey) + key := GetUNIBalancesKey(addr) + bz := store.Get(key) + if bz != nil { + balance = keeper.decode(bz) + } + + return balance +} + +// SetUNIForAddress sets the provided UNI at the given address +func (keeper Keeper) SetUNIForAddress(ctx sdk.Context, amt sdk.Int, addr sdk.AccAddress) { + store := ctx.KVStore(keeper.storeKey) + key := GetUNIBalancesKey(addr) + store.Set(key, keeper.encode(amt)) +} + +// GetTotalUNI returns the total UNI currently in existence +func (keeper Keeper) GetTotalUNI(ctx sdk.Context) sdk.Int { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(TotalUNIKey) + if bz == nil { + return sdk.ZeroInt() + } + + totalUNI := keeper.decode(bz) + return totalUNI +} + +// SetTotalLiquidity sets the total UNI +func (keeper Keeper) SetTotalUNI(ctx sdk.Context, totalUNI sdk.Int) { + store := ctx.KVStore(keeper.storeKey) + store.Set(TotalUNIKey, keeper.encode(totalUNI)) +} + +// GetExchange returns the total balance of an exchange at the provided denomination +func (keeper Keeper) GetExchange(ctx sdk.Context, denom string) sdk.Int { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(GetExchangeKey(denom)) + if bz == nil { + panic(fmt.Sprintf("exchange for denomination: %s does not exist")) + } + return keeper.decode(bz) +} + +// GetFeeParams returns the current FeeParams from the global param store +func (keeper Keeper) GetFeeParams(ctx sdk.Context) (feeParams types.FeeParams) { + keeper.paramSpace.Get(ctx, types.ParamStoreKeyFeeParams, &feeParams) + return feeParams +} + +func (keeper Keeper) setFeeParams(ctx sdk.Context, feeParams types.FeeParams) { + keeper.paramSpace.Set(ctx, types.ParamStoreKeyFeeParams, &feeParams) +} + +// ----------------------------------------------------------------------------- +// Misc. + +func (keeper Keeper) decode(bz []byte) (balance sdk.Int) { + err := keeper.cdc.UnmarshalBinaryBare(bz, &balance) + if err != nil { + panic(err) + } + return +} + +func (keeper Keeper) encode(balance sdk.Int) (bz []byte) { + bz, err := keeper.cdc.MarshalBinaryBare(balance) + if err != nil { + panic(err) + } + return +} diff --git a/x/uniswap/keeper/keeper_test.go b/x/uniswap/internal/keeper/keeper_test.go similarity index 100% rename from x/uniswap/keeper/keeper_test.go rename to x/uniswap/internal/keeper/keeper_test.go diff --git a/x/uniswap/keeper/key.go b/x/uniswap/internal/keeper/keys.go similarity index 79% rename from x/uniswap/keeper/key.go rename to x/uniswap/internal/keeper/keys.go index ae7ce3c9019..27f7e6005cb 100644 --- a/x/uniswap/keeper/key.go +++ b/x/uniswap/internal/keeper/keys.go @@ -8,12 +8,7 @@ import ( var ( ExchangePrefix = []byte{0x00} // prefix for exchange liquidity keys UNIBalancesPrefix = []byte{0x01} // prefix for UNI balances keys - TotalLiquidityKey = []byte{0x02} // key for total UNI liquidity -) - -const ( - // ModuleName is the name of the module - ModuleName = "uniswap" + TotalUNIKey = []byte{0x02} // key for total ) // GetExchangeKey gets the key for an exchanges total liquidity diff --git a/x/uniswap/internal/keeper/querier.go b/x/uniswap/internal/keeper/querier.go new file mode 100644 index 00000000000..b55569d4a44 --- /dev/null +++ b/x/uniswap/internal/keeper/querier.go @@ -0,0 +1 @@ +package keeper diff --git a/x/uniswap/types/codec.go b/x/uniswap/internal/types/codec.go similarity index 100% rename from x/uniswap/types/codec.go rename to x/uniswap/internal/types/codec.go diff --git a/x/uniswap/types/errors.go b/x/uniswap/internal/types/errors.go similarity index 100% rename from x/uniswap/types/errors.go rename to x/uniswap/internal/types/errors.go diff --git a/x/uniswap/types/key.go b/x/uniswap/internal/types/keys.go similarity index 74% rename from x/uniswap/types/key.go rename to x/uniswap/internal/types/keys.go index a8252011cc1..82cdd42f003 100644 --- a/x/uniswap/types/key.go +++ b/x/uniswap/internal/types/keys.go @@ -7,3 +7,8 @@ const ( // RouterKey is the message route for the uniswap module RouterKey = ModuleName ) + +// native asset to the module +type ( + NativeAsset string +) diff --git a/x/uniswap/types/msgs.go b/x/uniswap/internal/types/msgs.go similarity index 73% rename from x/uniswap/types/msgs.go rename to x/uniswap/internal/types/msgs.go index b689f8ff956..262b0472441 100644 --- a/x/uniswap/types/msgs.go +++ b/x/uniswap/internal/types/msgs.go @@ -1,8 +1,10 @@ package types import ( - sdk "github.com/cosmos/cosmos-sdk/types" + "strings" "time" + + sdk "github.com/cosmos/cosmos-sdk/types" ) var ( @@ -17,13 +19,13 @@ var ( // MsgSwap Order - struct for swapping a coin type MsgSwapOrder struct { - SwapDenom string // The desired denomination either to be bought or sold - Amount sdk.Coins // The specified amount to be either bought or sold - Bound sdk.Int // If buy order, maximum amount of coins to be sold; otherwise minimum amount of coins to be bought - Deadline time.Time // deadline for the transaction to still be considered valid - Sender sdk.AccAddress // address swapping coin - Recipient sdk.AccAddress // address output coin is being sent to - IsBuyOrder bool // boolean indicating whether the order should be treated as a buy or sell + SwapDenom string `json:"swap_denom"` // The desired denomination either to be bought or sold + Amount sdk.Coins `json:"amount"` // The specified amount to be either bought or sold + Bound sdk.Int `json:"bound"` // If buy order, maximum amount of coins to be sold; otherwise minimum amount of coins to be bought + Deadline time.Time `json:"deadline"` // deadline for the transaction to still be considered valid + Sender sdk.AccAddress `json:"sender"` // address swapping coin + Recipient sdk.AccAddress `json:"recipient"` // address output coin is being sent to + IsBuyOrder bool `json:"is_buy_order"` // boolean indicating whether the order should be treated as a buy or sell } // NewMsgSwapOrder is a constructor function for MsgSwapOrder @@ -51,7 +53,7 @@ func (msg MsgSwapOrder) Type() string { return "swap_order" } // ValidateBasic Implements Msg. func (msg MsgSwapOrder) ValidateBasic() sdk.Error { - if msg.SwapDenom == "" { + if strings.TrimSpace(msg.SwapDenom) == "" { return ErrNoDenom(DefaultCodespace) } // initially only support trading 1 coin only @@ -95,12 +97,12 @@ func (msg MsgSwapOrder) GetSigners() []sdk.AccAddress { // MsgAddLiquidity - struct for adding liquidity to an exchange type MsgAddLiquidity struct { - ExchangeDenom string // denomination of the exchange being added to - DepositAmount sdk.Int // exact amount of native asset being add to the liquidity pool - MinLiquidity sdk.Int // lower bound UNI sender is willing to accept for deposited coins - MaxCoins sdk.Int // maximum amount of the coin the sender is willing to deposit. - Deadline time.Time - Sender sdk.AccAddress + ExchangeDenom string `json:"exchange_denom"` // denomination of the exchange being added to + DepositAmount sdk.Int `json:"deposit_amount"` // 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 + MaxCoins sdk.Int `json:"max_coins"` // maximum amount of the coin the sender is willing to deposit. + Deadline time.Time `json:"deadline"` + Sender sdk.AccAddress `json:"sender"` } // NewMsgAddLiquidity is a constructor function for MsgAddLiquidity @@ -130,7 +132,7 @@ func (msg MsgAddLiquidity) ValidateBasic() sdk.Error { if !msg.DepositAmount.IsPositive() { return ErrInsufficientAmount(DefaultCodespace, "deposit amount provided is not positive") } - if msg.ExchangeDenom == "" { + if strings.TrimSpace(msg.ExchangeDenom) == "" { return ErrNoDenom(DefaultCodespace) } if !msg.MinLiquidity.IsPositive() { @@ -164,12 +166,12 @@ func (msg MsgAddLiquidity) GetSigners() []sdk.AccAddress { // MsgRemoveLiquidity - struct for removing liquidity from an exchange type MsgRemoveLiquidity struct { - ExchangeDenom string // denomination of the exchange being withdrawn from - WithdrawAmount sdk.Int // amount of UNI to be burned to withdraw liquidity from an exchange - MinNative sdk.Int // minimum amount of the native asset the sender is willing to accept - MinCoins sdk.Int // minimum amount of the exchange coin the sender is willing to accept - Deadline time.Time - Sender sdk.AccAddress + ExchangeDenom string `json:"exchange_denom"` // denomination of the exchange being withdrawn from + WithdrawAmount sdk.Int `json:"withdraw_amount"` // amount of UNI to be burned to withdraw liquidity from an exchange + MinNative sdk.Int `json:"min_native"` // minimum amount of the native asset the sender is willing to accept + MinCoins sdk.Int `json:"min_coins"` // minimum amount of the exchange coin the sender is willing to accept + Deadline time.Time `json:"deadline"` + Sender sdk.AccAddress `json:"sender"` } // NewMsgRemoveLiquidity is a contructor function for MsgRemoveLiquidity @@ -199,7 +201,7 @@ func (msg MsgRemoveLiquidity) ValidateBasic() sdk.Error { if !msg.WithdrawAmount.IsPositive() { return ErrInsufficientAmount(DefaultCodespace, "withdraw amount is not positive") } - if msg.ExchangeDenom == "" { + if strings.TrimSpace(msg.ExchangeDenom) == "" { return ErrNoDenom(DefaultCodespace) } if !msg.MinNative.IsPositive() { diff --git a/x/uniswap/types/msgs_test.go b/x/uniswap/internal/types/msgs_test.go similarity index 100% rename from x/uniswap/types/msgs_test.go rename to x/uniswap/internal/types/msgs_test.go diff --git a/x/uniswap/internal/types/params.go b/x/uniswap/internal/types/params.go new file mode 100644 index 00000000000..ca3abe7b98d --- /dev/null +++ b/x/uniswap/internal/types/params.go @@ -0,0 +1,64 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" +) + +// TODO: come up with better naming for these params? +// Parameter store keys +var ( + ParamStoreKeyFeeParams = []byte("feeparams") +) + +// uniswap parameters +// Fee = 1 - (FeeN - FeeD) +type FeeParams struct { + FeeN sdk.Int `json:"fee_numerator"` // fee numerator + FeeD sdk.Int `json:"fee_denominator"` // fee denominator +} + +// ParamKeyTable creates a ParamTable for uniswap module. +func ParamKeyTable() params.KeyTable { + return params.NewKeyTable( + ParamStoreKeyFeeParams, FeeParams{}, + ) +} + +// NewFeeParams is a constructor for the FeeParams struct +func NewFeeParams(feeN, feeD sdk.Int) FeeParams { + return FeeParams{ + FeeN: feeN, + FeeD: feeD, + } +} + +// DefaultParams returns the default uniswap module parameters +func DefaultParams() FeeParams { + return FeeParams{ + FeeN: sdk.NewInt(997), + FeeD: sdk.NewInt(1000), + } +} + +// ValidateParams validates the set params +func ValidateParams(p FeeParams) error { + if p.FeeN.GT(p.FeeD) { + return fmt.Errorf("fee numerator must be less than or equal to fee denominator") + } + if p.FeeD.Mod(sdk.NewInt(10)) != sdk.NewInt(0) { + return fmt.Errorf("fee denominator must be multiple of 10") + } + return nil +} + +func (p FeeParams) String() string { + return fmt.Sprintf(`Uniswap Params: + Fee Numerator: %s + Fee Denominator: %s +`, + p.FeeN, p.FeeD, + ) +} diff --git a/x/uniswap/internal/types/tags.go b/x/uniswap/internal/types/tags.go new file mode 100644 index 00000000000..ab1254f4c2b --- /dev/null +++ b/x/uniswap/internal/types/tags.go @@ -0,0 +1 @@ +package types diff --git a/x/uniswap/types/test_common.go b/x/uniswap/internal/types/test_common.go similarity index 97% rename from x/uniswap/types/test_common.go rename to x/uniswap/internal/types/test_common.go index d874c63b58c..13b8ea34ba4 100644 --- a/x/uniswap/types/test_common.go +++ b/x/uniswap/internal/types/test_common.go @@ -27,6 +27,6 @@ var ( deadline = time.Now() emptyAddr sdk.AccAddress - emptyDenom string + emptyDenom = " " emptyTime time.Time ) diff --git a/x/uniswap/keeper/expected_keepers.go b/x/uniswap/keeper/expected_keepers.go deleted file mode 100644 index d29640f36dd..00000000000 --- a/x/uniswap/keeper/expected_keepers.go +++ /dev/null @@ -1,10 +0,0 @@ -package keeper - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - -// BankKeeper defines the expected coin keeper -type BankKeeper interface { - AddCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) (sdk.Coins, sdk.Error) -} diff --git a/x/uniswap/keeper/keeper.go b/x/uniswap/keeper/keeper.go deleted file mode 100644 index 4e476dcaf5e..00000000000 --- a/x/uniswap/keeper/keeper.go +++ /dev/null @@ -1,166 +0,0 @@ -package keeper - -import ( - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - - "github.com/tendermint/tendermint/libs/log" -) - -// TODO: replace with params -// fee = 1 - p -// p = feeN / feeD -var ( - feeN sdk.Int // numerator for determining fee - feeD sdk.Int // denominator for determining fee -) - -type Keeper struct { - // The key used to access the uniswap store - storeKey sdk.StoreKey - - // The reference to the CoinKeeper to modify balances after swaps or liquidity is deposited/withdrawn - ck BankKeeper - - // The codec codec for binary encoding/decoding. - cdc *codec.Codec -} - -// NewKeeper returns a uniswap keeper. It handles: -// - creating new exchanges -// - facilitating swaps -// - users adding liquidity to exchanges -// - users removing liquidity to exchanges -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, ck BankKeeper) Keeper { - // TODO: replace with param - feeN = sdk.NewInt(997) - feeD = sdk.NewInt(100) - return Keeper{ - storeKey: key, - ck: ck, - cdc: cdc, - } -} - -// Logger returns a module-specific logger. -func (keeper Keeper) Logger(ctx sdk.Context) log.Logger { - return ctx.Logger().With("module", ModuleName) -} - -// CreateExchange initializes a new exchange pair between the new coin and the native asset -func (keeper Keeper) CreateExchange(ctx sdk.Context, exchangeDenom string) { - store := ctx.KVStore(keeper.storeKey) - key := GetExchangeKey(exchangeDenom) - bz := store.Get(key) - if bz != nil { - panic("exchange already exists") - } - - store.Set(key, keeper.encode(sdk.NewInt(0))) -} - -// Deposit adds the specified amount of UNI to the associated account -func (keeper Keeper) Deposit(ctx sdk.Context, amt sdk.Int, addr sdk.AccAddress) { - var balance sdk.Int - - store := ctx.KVStore(keeper.storeKey) - key := GetUNIBalancesKey(addr) - bz := store.Get(key) - if bz != nil { - balance = keeper.decode(bz) - } - - balance.Add(amt) - store.Set(key, keeper.encode(balance)) - - return -} - -// Withdraw removes the specified amount of UNI from the associated account -func (keeper Keeper) Withdraw(ctx sdk.Context, amt sdk.Int, addr sdk.AccAddress) { - var balance sdk.Int - - store := ctx.KVStore(keeper.storeKey) - key := GetUNIBalancesKey(addr) - bz := store.Get(key) - if bz != nil { - balance = keeper.decode(bz) - } - - balance.Sub(amt) - store.Set(key, keeper.encode(balance)) -} - -// GetTotalLiquidity returns the total UNI currently in existence -func (keeper Keeper) GetTotalLiquidity(ctx sdk.Context) sdk.Int { - store := ctx.KVStore(keeper.storeKey) - bz := store.Get(TotalLiquidityKey) - if bz == nil { - panic("error retrieving total UNI liquidity") - } - - totalLiquidity := keeper.decode(bz) - return totalLiquidity -} - -// GetInputAmount returns the amount of coins sold (calculated) given the output amount being bought (exact) -// The fee is included in the output coins being bought -// https://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdfhttps://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdf -func (keeper Keeper) GetInputAmount(ctx sdk.Context, outputAmt sdk.Int, inputDenom, outputDenom string) sdk.Int { - store := ctx.KVStore(keeper.storeKey) - bz := store.Get(GetExchangeKey(inputDenom)) - if bz == nil { - panic("exchange for input denomination does not exist") - } - inputReserve := keeper.decode(bz) - bz = store.Get(GetExchangeKey(outputDenom)) - if bz == nil { - panic("exchange for output denomination does not exist") - } - outputReserve := keeper.decode(bz) - - numerator := inputReserve.Mul(outputReserve).Mul(feeD) - denominator := (outputReserve.Sub(outputAmt)).Mul(feeN) - return numerator.Quo(denominator).Add(sdk.NewInt(1)) -} - -// GetOutputAmount 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 (keeper Keeper) GetOutputAmount(ctx sdk.Context, inputAmt sdk.Int, inputDenom, outputDenom string) sdk.Int { - store := ctx.KVStore(keeper.storeKey) - bz := store.Get(GetExchangeKey(inputDenom)) - if bz == nil { - panic("exchange for input denomination does not exist") - } - inputReserve := keeper.decode(bz) - bz = store.Get(GetExchangeKey(outputDenom)) - if bz == nil { - panic("exchange for output denomination does not exist") - } - outputReserve := keeper.decode(bz) - - inputAmtWithFee := inputAmt.Mul(feeN) - numerator := inputAmtWithFee.Mul(outputReserve) - denominator := inputReserve.Mul(feeD).Add(inputAmtWithFee) - return numerator.Quo(denominator) -} - -// ----------------------------------------------------------------------------- -// Misc. - -func (keeper Keeper) decode(bz []byte) (balance sdk.Int) { - err := keeper.cdc.UnmarshalBinaryBare(bz, &balance) - if err != nil { - panic(err) - } - return -} - -func (keeper Keeper) encode(balance sdk.Int) (bz []byte) { - bz, err := keeper.cdc.MarshalBinaryBare(balance) - if err != nil { - panic(err) - } - return -} diff --git a/x/uniswap/module.go b/x/uniswap/module.go index dad592db8d8..b698b84feba 100644 --- a/x/uniswap/module.go +++ b/x/uniswap/module.go @@ -3,6 +3,10 @@ package uniswap import ( "encoding/json" + "github.com/gorilla/mux" + "github.com/spf13/cobra" + + "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" abci "github.com/tendermint/tendermint/abci/types" @@ -13,9 +17,6 @@ var ( _ sdk.AppModuleBasic = AppModuleBasic{} ) -// name of this module -const ModuleName = "uniswap" - // AppModuleBasic app module basics object type AppModuleBasic struct{} @@ -31,22 +32,31 @@ func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { // DefaultGenesis default genesis state func (AppModuleBasic) DefaultGenesis() json.RawMessage { - return moduleCdc.MustMarshalJSON(DefaultGenesisState()) + return ModuleCdc.MustMarshalJSON(DefaultGenesisState()) } // ValidateGenesis module validate genesis func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { var data GenesisState - err := moduleCdc.UnmarshalJSON(bz, &data) + err := ModuleCdc.UnmarshalJSON(bz, &data) if err != nil { return err } return ValidateGenesis(data) } -//___________________________ +// RegisterRESTRoutes registers rest routes +func (AppModuleBasic) RegisterRESTRoutes(_ context.CLIContext, _ *mux.Router) { + +} -// AppModule supply app module +// GetTxCmd returns the root tx command of this module +func (AppModuleBasic) GetTxCmd(_ *codec.Codec) *cobra.Command { return nil } + +// GetQueryCmd returns the root query command of this module +func (AppModuleBasic) GetQueryCmd(_ *codec.Codec) *cobra.Command { return nil } + +// AppModule uniswap app module type AppModule struct { AppModuleBasic keeper Keeper @@ -54,19 +64,13 @@ type AppModule struct { // NewAppModule creates a new AppModule object func NewAppModule(keeper Keeper) sdk.AppModule { - return sdk.NewGenesisOnlyAppModule(AppModule{ AppModuleBasic: AppModuleBasic{}, keeper: keeper, }) } -// Name defines module name -func (AppModule) Name() string { - return ModuleName -} - -// RegisterInvariants registers the nft module invariants +// RegisterInvariants registers the uniswap module invariants func (am AppModule) RegisterInvariants(ir sdk.InvariantRouter) { RegisterInvariants(ir, am.keeper) } @@ -77,9 +81,9 @@ func (AppModule) Route() string { } // NewHandler module handler -//func (am AppModule) NewHandler() sdk.Handler { -// return NewHandler(am.keeper) -//} +func (am AppModule) NewHandler() sdk.Handler { + return NewHandler(am.keeper) +} // QuerierRoute module querier route name func (AppModule) QuerierRoute() string { @@ -87,11 +91,11 @@ func (AppModule) QuerierRoute() string { } // NewQuerierHandler module querier -//func (am AppModule) NewQuerierHandler() sdk.Querier { -// return NewQuerier(am.keeper) -//} +func (am AppModule) NewQuerierHandler() sdk.Querier { + return NewQuerier(am.keeper) +} -// InitGenesis supply module init-genesis +// InitGenesis uniswap module init-genesis func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { var genesisState GenesisState ModuleCdc.MustUnmarshalJSON(data, &genesisState) From 91da176ec35d47e28790a52a9e8a0e6d6cb4fdf5 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Tue, 11 Jun 2019 10:49:22 -0700 Subject: [PATCH 08/15] updated keeper --- x/uniswap/internal/keeper/keeper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/uniswap/internal/keeper/keeper.go b/x/uniswap/internal/keeper/keeper.go index abed6c54966..e9901cdde73 100644 --- a/x/uniswap/internal/keeper/keeper.go +++ b/x/uniswap/internal/keeper/keeper.go @@ -101,7 +101,7 @@ func (keeper Keeper) GetExchange(ctx sdk.Context, denom string) sdk.Int { store := ctx.KVStore(keeper.storeKey) bz := store.Get(GetExchangeKey(denom)) if bz == nil { - panic(fmt.Sprintf("exchange for denomination: %s does not exist")) + panic(fmt.Sprintf("exchange for denomination: %s does not exist"), denom) } return keeper.decode(bz) } From a8437b050f4ca3c96497dceb661e8dd32a127078 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Tue, 11 Jun 2019 13:19:20 -0700 Subject: [PATCH 09/15] updating handler --- x/uniswap/handler.go | 64 +++++++++++++++++++++++++---- x/uniswap/internal/keeper/keeper.go | 16 ++++---- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/x/uniswap/handler.go b/x/uniswap/handler.go index 6cd4d839e9b..ef27bb9df9d 100644 --- a/x/uniswap/handler.go +++ b/x/uniswap/handler.go @@ -2,7 +2,9 @@ package uniswap import ( "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/supply/types" ) // NewHandler routes the messages to the handlers @@ -49,18 +51,32 @@ func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.R panic("error retrieving native asset total liquidity") } - totalLiquidity, err := k.GetTotalLiquidity(ctx) + totalUNI, err := k.GetTotalUNI(ctx) if err != nil { - panic("error retrieving total UNI liquidity") + panic("error retrieving total UNI") } - MintedUNI := (totalLiquidity.Mul(msg.DepositAmount)).Quo(nativeLiquidity) - coinDeposited := (totalLiquidity.Mul(msg.DepositAmount)).Quo(nativeLiquidity) + // calculate amount of UNI to be minted for sender + // and coin amount to be deposited + MintedUNI := (totalUNI.Mul(msg.DepositAmount)).Quo(nativeLiquidity) + coinDeposited := (totalUNI.Mul(msg.DepositAmount)).Quo(nativeLiquidity) nativeCoin := sdk.NewCoin(NativeAsset, msg.DepositAmount) exchangeCoin := sdk.NewCoin(msg.ExchangeDenom, coinDeposited) - k.SendCoins(ctx, msg.Sender, exchangeAcc, nativeCoin) - k.SendCoins(ctx, msg.Sender, exchangeAcc, exchangeCoin) - k.Deposit(ctx, msg.Sender, UNIMinted) + + // transfer deposited liquidity into uniswaps ModuleAccount + err := k.SendCoinsAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(nativeCoin, coinDeposited)) + if err != nil { + return err + } + + // set updated total UNI + totalUNI = totalUNI.Add(MintedUNI) + k.SetTotalUNI(ctx, totalUNI) + + // update senders account with minted UNI + UNIBalance := k.GetUNIForAddress(ctx, msg.Sender) + UNIBalance = UNIBalance.Add(MintedUNI) + k.SetUNIForAddress(ctx, UNIBalance) return sdk.Result{} } @@ -68,11 +84,43 @@ func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.R // HandleMsgRemoveLiquidity handler for MsgRemoveLiquidity func HandleMsgRemoveLiquidity(ctx sdk.Context, msg MsgRemoveLiquidity, k Keeper) sdk.Result { // check if exchange exists - totalLiquidity, err := k.GetExchange(ctx, msg.ExchangeDenom) + coinLiquidity, err := k.GetExchange(ctx, msg.ExchangeDenom) + if err != nil { + panic(fmt.Sprintf("error retrieving total liquidity for denomination: %s", msg.ExchangeDenom)) + } + + nativeLiquidity, err := k.GetExchange(ctx, NativeAsset) + if err != nil { + panic("error retrieving native asset total liquidity") + } + + totalUNI, err := k.GetTotalUNI(ctx) + if err != nil { + panic("error retrieving total UNI") + } + + // calculate amount of UNI to be burned for sender + // and coin amount to be returned + nativeWithdrawn := msg.WithdrawAmount.Mul(nativeLiquidity).Quo(totalUNI) + coinWithdrawn := msg.WithdrawAmount.Mul(coinLiqudity).Quo(totalUNI) + nativeCoin := sdk.NewCoin(nativeDenom, nativeWithdrawn) + exchangeCoin = sdk.NewCoin(msg.ExchangeDenom, coinWithdrawn) + + // transfer withdrawn liquidity from uniswaps ModuleAccount to sender's account + err := k.SendCoinsModuleToAccount(ctx, msg.Sender, ModuleName, sdk.NewCoins(nativeCoin, coinDeposited)) if err != nil { return err } + // set updated total UNI + totalUNI = totalUNI.Add(MintedUNI) + k.SetTotalUNI(ctx, totalUNI) + + // update senders account with minted UNI + UNIBalance := k.GetUNIForAddress(ctx, msg.Sender) + UNIBalance = UNIBalance.Add(MintedUNI) + k.SetUNIForAddress(ctx, UNIBalance) + return sdk.Result{} } diff --git a/x/uniswap/internal/keeper/keeper.go b/x/uniswap/internal/keeper/keeper.go index e9901cdde73..3be99bcceb2 100644 --- a/x/uniswap/internal/keeper/keeper.go +++ b/x/uniswap/internal/keeper/keeper.go @@ -4,8 +4,8 @@ import ( "fmt" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/params" + supply "github.com/cosmos/cosmos-sdk/x/supply/keeper" "github.com/cosmos/cosmos-sdk/x/uniswap/internal/types" "github.com/tendermint/tendermint/libs/log" @@ -16,8 +16,8 @@ type Keeper struct { // The key used to access the uniswap store storeKey sdk.StoreKey - // The reference to the CoinKeeper to modify balances after swaps or liquidity is deposited/withdrawn - ck bank.Keeper + // The reference to the SupplyKeeper to hold coins for this module + types.SupplyKeeper // The codec codec for binary encoding/decoding. cdc *codec.Codec @@ -31,12 +31,12 @@ type Keeper struct { // - facilitating swaps // - users adding liquidity to exchanges // - users removing liquidity to exchanges -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, ck bank.Keeper, paramSpace params.Subspace) Keeper { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, supplyKeeper supply.Keeper, paramSpace params.Subspace) Keeper { return Keeper{ - storeKey: key, - ck: ck, - cdc: cdc, - paramSpace: paramSpace.WithKeyTable(types.ParamKeyTable()), + storeKey: key, + supplyKeeper: supplyKeeper, + cdc: cdc, + paramSpace: paramSpace.WithKeyTable(types.ParamKeyTable()), } } From 8796822ed01f6536440e479e098873bb481dd0e1 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Thu, 13 Jun 2019 17:17:37 -0700 Subject: [PATCH 10/15] added querier and basic querier test --- x/uniswap/handler.go | 12 +++- x/uniswap/internal/keeper/keeper.go | 6 +- x/uniswap/internal/keeper/querier.go | 77 +++++++++++++++++++++++ x/uniswap/internal/keeper/querier_test.go | 54 ++++++++++++++++ x/uniswap/internal/keeper/test_common.go | 46 ++++++++++++++ x/uniswap/internal/types/errors.go | 5 +- x/uniswap/internal/types/keys.go | 10 ++- x/uniswap/internal/types/msgs.go | 6 +- x/uniswap/internal/types/querier.go | 41 ++++++++++++ 9 files changed, 244 insertions(+), 13 deletions(-) create mode 100644 x/uniswap/internal/keeper/querier_test.go create mode 100644 x/uniswap/internal/keeper/test_common.go create mode 100644 x/uniswap/internal/types/querier.go diff --git a/x/uniswap/handler.go b/x/uniswap/handler.go index ef27bb9df9d..ebe318b09a9 100644 --- a/x/uniswap/handler.go +++ b/x/uniswap/handler.go @@ -28,10 +28,16 @@ func NewHandler(k Keeper) sdk.Handler { func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result { if msg.IsBuyOrder { inputAmount := k.GetInputAmount(ctx, msg.SwapDenom, msg.Amount) + coinSold = sdk.NewCoin(msg.ExchangeDenom, inputAmount) + k.supplyKeeper.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(coinSold)) + k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, ModuleName, msg.Sender, sdk.NewCoins(msg.Amount)) } else { outputAmount := k.GetOutputAmount(ctx, msg.SwapDenom, msg.Amount) + coinBought := sdk.NewCoin(msg.ExchangeDenom, outputAmount) + k.supplyKeeper.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(msg.Amount)) + k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, ModuleName, msg.Sender, sdk.NewCoins(coinBought)) } return sdk.Result{} @@ -41,7 +47,7 @@ func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result // If the exchange does not exist, it will be created. func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.Result { // create exchange if it does not exist - coinLiquidity, err := k.GetExchange(ctx, msg.ExchangeDenom) + coinLiquidity := k.GetExchange(ctx, msg.ExchangeDenom) if err != nil { k.CreateExchange(ctx, msg.Denom) } @@ -64,7 +70,7 @@ func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.R exchangeCoin := sdk.NewCoin(msg.ExchangeDenom, coinDeposited) // transfer deposited liquidity into uniswaps ModuleAccount - err := k.SendCoinsAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(nativeCoin, coinDeposited)) + err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(nativeCoin, coinDeposited)) if err != nil { return err } @@ -107,7 +113,7 @@ func HandleMsgRemoveLiquidity(ctx sdk.Context, msg MsgRemoveLiquidity, k Keeper) exchangeCoin = sdk.NewCoin(msg.ExchangeDenom, coinWithdrawn) // transfer withdrawn liquidity from uniswaps ModuleAccount to sender's account - err := k.SendCoinsModuleToAccount(ctx, msg.Sender, ModuleName, sdk.NewCoins(nativeCoin, coinDeposited)) + err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, msg.Sender, ModuleName, sdk.NewCoins(nativeCoin, coinDeposited)) if err != nil { return err } diff --git a/x/uniswap/internal/keeper/keeper.go b/x/uniswap/internal/keeper/keeper.go index 3be99bcceb2..90f8d794f69 100644 --- a/x/uniswap/internal/keeper/keeper.go +++ b/x/uniswap/internal/keeper/keeper.go @@ -51,7 +51,7 @@ func (keeper Keeper) CreateExchange(ctx sdk.Context, exchangeDenom string) { key := GetExchangeKey(exchangeDenom) bz := store.Get(key) if bz != nil { - panic("exchange already exists") + panic(fmt.Sprintf("exchange %s already exists", exchangeDenom)) } store.Set(key, keeper.encode(sdk.ZeroInt())) @@ -101,7 +101,7 @@ func (keeper Keeper) GetExchange(ctx sdk.Context, denom string) sdk.Int { store := ctx.KVStore(keeper.storeKey) bz := store.Get(GetExchangeKey(denom)) if bz == nil { - panic(fmt.Sprintf("exchange for denomination: %s does not exist"), denom) + panic(fmt.Sprintf("exchange %s does not exist"), denom) } return keeper.decode(bz) } @@ -112,7 +112,7 @@ func (keeper Keeper) GetFeeParams(ctx sdk.Context) (feeParams types.FeeParams) { return feeParams } -func (keeper Keeper) setFeeParams(ctx sdk.Context, feeParams types.FeeParams) { +func (keeper Keeper) SetFeeParams(ctx sdk.Context, feeParams types.FeeParams) { keeper.paramSpace.Set(ctx, types.ParamStoreKeyFeeParams, &feeParams) } diff --git a/x/uniswap/internal/keeper/querier.go b/x/uniswap/internal/keeper/querier.go index b55569d4a44..7ab38f6d34f 100644 --- a/x/uniswap/internal/keeper/querier.go +++ b/x/uniswap/internal/keeper/querier.go @@ -1 +1,78 @@ package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// NewQuerier creates a querier for uniswap REST endpoints +func NewQuerier(k Keeper) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) (res []byte, err sdk.Error) { + switch path[0] { + case types.QueryBalance: + return queryBalance(ctx, req, k) + case types.QueryLiquidity: + return queryLiquidity(ctx, req, k) + case types.Parameters: + return queryLiquidity(ctx, req, k) + default: + return nil, sdk.ErrUnknownRequest(fmt.Sprintf("%s is not a valid query request path", req.Path)) + } + } +} + +// queryBalance returns the provided addresses UNI balance upon success +// or an error if the query fails. +func queryBalance(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params QueryBalance + err := k.cdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + } + + balance := k.GetUNIForAddress(params.Address) + + bz, err := codec.MarshalJSONIndent(k.cdc, balance) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + return bz, nil +} + +// queryLiquidity returns the total liquidity avaliable at the provided exchange +// upon success or an error if the query fails. +func queryLiquidity(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + var params QueryLiquidity + err := k.cdc.UnmarshalJSON(req.Data, ¶ms) + if err != nil { + return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) + } + + liquidity := k.GetExchange(ctx, params.Denom) + + bz, err := codec.MarshalJSONIndent(k.cdc, liquidity) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + return bz, nil +} + +// queryParameters returns uniswap module parameter queried for upon success +// or an error if the query fails +func queryParameters(ctx sdk.Context, path []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { + switch path[0] { + case ParamFee: + bz, err := codec.MarshalJSONIndent(k.cdc, k.GetFeeParams(ctx)) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + return bz, nil + case ParamNativeAsset: + bz, err := codec.MarshalJSONIndent(k.cdc, k.GetNativeAsset()) + if err != nil { + return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) + } + return bz, nil + default: + return nil, sdk.ErrUnknownRequest(fmt.Sprintf("%s is not a valid query request path", req.Path)) + } +} diff --git a/x/uniswap/internal/keeper/querier_test.go b/x/uniswap/internal/keeper/querier_test.go new file mode 100644 index 00000000000..3e8d0911fee --- /dev/null +++ b/x/uniswap/internal/keeper/querier_test.go @@ -0,0 +1,54 @@ +package keeper + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/uniswap/internal/types" +) + +func TestNewQuerier(t *testing.T) { + cdc := codec.New() + ctx, keeper := createNewInput() + + req := abci.RequestQuery{ + Path: "", + Data: []byte{}, + } + + querier := NewQuerier(keeper) + + // query for non existent address should return an error + req.Path = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryBalance) + req.Data = keeper.cdc.MustMarshalJSON(addr) + res, err := querier(ctx, []string{"balance"}, req) + require.NotNil(t, err) + require.Nil(res) + + // query for non existent exchange 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.NotNil(t, err) + require.Nil(res) + + // query for set fee params + var feeParams types.FeeParams + req.Path = fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, types.QueryParameters, types.ParamFee) + req.Data = []byte{} + res, err = querier(ctx, []string{types.QueryParameters, types.ParamFee}, req) + keeper.cdc.UnmarshalJSON(res, &feeParams) + require.Nil(t, err) + require.Equal(t, feeParams, types.DefaultParams()) + + // query for set native asset param + req.Path = fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, types.QueryParameters, types.ParamNativeAsset) + res, err = querier(ctx, []string{types.QueryParameters, types.ParamNativeAsset}, req) + require.Nil(t, err) +} + +// TODO: Add tests for valid UNI balance queries and valid liquidity queries diff --git a/x/uniswap/internal/keeper/test_common.go b/x/uniswap/internal/keeper/test_common.go new file mode 100644 index 00000000000..2c23b68e8f3 --- /dev/null +++ b/x/uniswap/internal/keeper/test_common.go @@ -0,0 +1,46 @@ +package keeper + +import ( + "testing" + + abci "github.com/tendermint/tendermint/abci/types" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/params" +) + +func createTestInput(t *testing.T) (sdk.Context, Keeper) { + keyAcc := sdk.NewKVStoreKey(auth.StoreKey) + keyParams := sdk.NewKVStoreKey(params.StoreKey) + tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) + keySupply := sdk.NewKVStoreKey(supply.StoreKey) + keyUniswap := sdk.NewKVStoreKey(uniswap.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(keySupply, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyUniswap, sdk.StoreTypeIAVL, db) + err := ms.LoadLatestVersion() + require.Nil(t, err) + + ctx := sdk.NewContext(ms, abci.Header{ChainID: "uniswap-chain"}, false, log.NewNopLogger()) + + pk := params.NewKeeper(types.ModuleCdc, keyParams, tkeyParams, params.Defaultcodespace) + ak := auth.NewAccountKeeper(types.ModuleCdc, keyAcc, paramsKeeper.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount) + bk := bank.NewBaseKeeper(ak, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace) + + sk := supply.NewKeeper(cdc, keySupply, ak, bk, suppy.DefaultCodespace) + keeper := NewKeeper(cdc, keyUniswap, sk, DefaultCodespace) + feeParams := types.NewFeeParams() + keeper.SetFeeParams(ctx, types.DefaultParams()) + + return ctx, keeper +} diff --git a/x/uniswap/internal/types/errors.go b/x/uniswap/internal/types/errors.go index 8c16a8c72ed..a37c847cc9d 100644 --- a/x/uniswap/internal/types/errors.go +++ b/x/uniswap/internal/types/errors.go @@ -1,6 +1,7 @@ package types import ( + "fmt" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -37,8 +38,8 @@ func ErrInvalidBound(codespace sdk.CodespaceType, msg string) sdk.Error { return sdk.NewError(codespace, CodeInvalidBound, "bound is not positive") } -func ErrInvalidDeadline(codespace sdk.CodespaceType) sdk.Error { - return sdk.NewError(codespace, CodeInvalidDeadline, "deadline not initialized") +func ErrInvalidDeadline(codespace sdk.CodespaceType, msgName string) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDeadline, fmt.Sprintf("deadline for %s not initialized", msgName)) } func ErrInsufficientAmount(codespace sdk.CodespaceType, msg string) sdk.Error { diff --git a/x/uniswap/internal/types/keys.go b/x/uniswap/internal/types/keys.go index 82cdd42f003..135b9ef39f7 100644 --- a/x/uniswap/internal/types/keys.go +++ b/x/uniswap/internal/types/keys.go @@ -1,11 +1,17 @@ package types const ( - // ModuleName is the name of the module + // ModuleName is the name of the module. ModuleName = "uniswap" - // RouterKey is the message route for the uniswap module + // RouterKey is the message route for the uniswap module. RouterKey = ModuleName + + // StoreKey is the default store key for the uniswap module. + StoreKey = ModuleName + + // QuerierRoute is the querier route for the uniswap module. + QuerierRoute = StoreKey ) // native asset to the module diff --git a/x/uniswap/internal/types/msgs.go b/x/uniswap/internal/types/msgs.go index 262b0472441..5a8f94dbf08 100644 --- a/x/uniswap/internal/types/msgs.go +++ b/x/uniswap/internal/types/msgs.go @@ -70,7 +70,7 @@ func (msg MsgSwapOrder) ValidateBasic() sdk.Error { return ErrInvalidBound(DefaultCodespace, "") } if msg.Deadline.IsZero() { - return ErrInvalidDeadline(DefaultCodespace) + return ErrInvalidDeadline(DefaultCodespace, "MsgSwapOrder") } if msg.Sender.Empty() { return sdk.ErrInvalidAddress("invalid sender address") @@ -142,7 +142,7 @@ func (msg MsgAddLiquidity) ValidateBasic() sdk.Error { return ErrInvalidBound(DefaultCodespace, "maxmimum coins is not positive") } if msg.Deadline.IsZero() { - return ErrInvalidDeadline(DefaultCodespace) + return ErrInvalidDeadline(DefaultCodespace, "MsgAddLiquidity") } if msg.Sender.Empty() { return sdk.ErrInvalidAddress("invalid sender address") @@ -211,7 +211,7 @@ func (msg MsgRemoveLiquidity) ValidateBasic() sdk.Error { return ErrInvalidBound(DefaultCodespace, "minimum coins is not positive") } if msg.Deadline.IsZero() { - return ErrInvalidDeadline(DefaultCodespace) + return ErrInvalidDeadline(DefaultCodespace, "MsgRemoveLiquidity") } if msg.Sender.Empty() { return sdk.ErrInvalidAddress("invalid sender address") diff --git a/x/uniswap/internal/types/querier.go b/x/uniswap/internal/types/querier.go new file mode 100644 index 00000000000..37958853cc2 --- /dev/null +++ b/x/uniswap/internal/types/querier.go @@ -0,0 +1,41 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // Query endpoints supported by the uniswap querier + QueryBalance = "balance" + QueryLiquidity = "liquidity" + QueryParameters = "parameters" + + ParamFee = "fee" + ParamNativeAsset = "nativeAsset" +) + +// QueryBalanceParams defines the params for the query: +// - 'custom/uniswap/balance' +type QueryBalanceParams struct { + Address sdk.AccAddress +} + +// NewQueryBalanceParams is a constructor function for QueryBalanceParams +func NewQueryBalanceParams(address sdk.AccAddress) QueryBalanceParams { + return QueryBalanceParams{ + Address: address, + } +} + +// QueryLiquidity defines the params for the query: +// - 'custom/uniswap/liquidity' +type QueryLiquidityParams struct { + Denom string +} + +// NewQueryLiquidityParams is a constructor function for QueryLiquidityParams +func NewQueryLiquidityParams(denom string) QueryLiquidityParams { + return QueryLiquidityParams{ + Denom: denom, + } +} From 5e366fd0d223b20bdab94d7416a8c25b44ab163a Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Fri, 14 Jun 2019 14:14:54 -0700 Subject: [PATCH 11/15] update msgs fields to input/output --- x/uniswap/handler.go | 34 ++++++---- x/uniswap/internal/types/msgs.go | 90 ++++++++++--------------- x/uniswap/internal/types/msgs_test.go | 53 ++++++++------- x/uniswap/internal/types/params.go | 83 ++++++++++++++++------- x/uniswap/internal/types/test_common.go | 12 ++-- 5 files changed, 150 insertions(+), 122 deletions(-) diff --git a/x/uniswap/handler.go b/x/uniswap/handler.go index ebe318b09a9..7917bdb9d47 100644 --- a/x/uniswap/handler.go +++ b/x/uniswap/handler.go @@ -26,18 +26,28 @@ func NewHandler(k Keeper) sdk.Handler { // HandleMsgSwapOrder handler for MsgSwapOrder func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result { + var caclulatedAmount sdk.Int + if msg.IsBuyOrder { - inputAmount := k.GetInputAmount(ctx, msg.SwapDenom, msg.Amount) - coinSold = sdk.NewCoin(msg.ExchangeDenom, inputAmount) + calculatedAmount := k.GetInputAmount(ctx, msg.SwapDenom, msg.Amount) + // ensure the maximum amount sender is willing to sell is less than + // the calculated amount + if msg.Input.Amount.LT(calculatedAmount) { + return types.ErrInvalidBound(DefaultCodespace, "maximum amount (%d) to be sold was exceeded (%d)", msg.Input.Amount, calculatedAmount).Result() + } + k.supplyKeeper.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(sdk.NewCoin(msg.Input.Denom, calculatedAmount))) + k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, ModuleName, msg.Sender, sdk.NewCoins(msg.Output)) - k.supplyKeeper.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(coinSold)) - k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, ModuleName, msg.Sender, sdk.NewCoins(msg.Amount)) } else { outputAmount := k.GetOutputAmount(ctx, msg.SwapDenom, msg.Amount) - coinBought := sdk.NewCoin(msg.ExchangeDenom, outputAmount) + // ensure the minimum amount the sender is willing to buy is greater than + // the calculated amount + if msg.Output.Amount.GT(outputAmount) { + return sdk.ErrInvalidBound(DefaultCodespace, "minimum amount (%d) to be sold was not met (%d)", msg.Output.Amount, calculatedAmount).Result() + } + k.supplyKeeper.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(msg.Input)) + k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, ModuleName, msg.Sender, sdk.NewCoins(sdk.NewCoin(msg.Output.Denom, calculatedAmount))) - k.supplyKeeper.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(msg.Amount)) - k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, ModuleName, msg.Sender, sdk.NewCoins(coinBought)) } return sdk.Result{} @@ -47,7 +57,7 @@ func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result // If the exchange does not exist, it will be created. func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.Result { // create exchange if it does not exist - coinLiquidity := k.GetExchange(ctx, msg.ExchangeDenom) + coinLiquidity := k.GetExchange(ctx, msg.Deposit.Denom) if err != nil { k.CreateExchange(ctx, msg.Denom) } @@ -67,7 +77,7 @@ func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.R MintedUNI := (totalUNI.Mul(msg.DepositAmount)).Quo(nativeLiquidity) coinDeposited := (totalUNI.Mul(msg.DepositAmount)).Quo(nativeLiquidity) nativeCoin := sdk.NewCoin(NativeAsset, msg.DepositAmount) - exchangeCoin := sdk.NewCoin(msg.ExchangeDenom, coinDeposited) + exchangeCoin := sdk.NewCoin(msg.Deposit.Denom, coinDeposited) // transfer deposited liquidity into uniswaps ModuleAccount err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(nativeCoin, coinDeposited)) @@ -90,9 +100,9 @@ func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.R // HandleMsgRemoveLiquidity handler for MsgRemoveLiquidity func HandleMsgRemoveLiquidity(ctx sdk.Context, msg MsgRemoveLiquidity, k Keeper) sdk.Result { // check if exchange exists - coinLiquidity, err := k.GetExchange(ctx, msg.ExchangeDenom) + coinLiquidity, err := k.GetExchange(ctx, msg.Withdraw.Denom) if err != nil { - panic(fmt.Sprintf("error retrieving total liquidity for denomination: %s", msg.ExchangeDenom)) + panic(fmt.Sprintf("error retrieving total liquidity for denomination: %s", msg.Withdraw.Denom)) } nativeLiquidity, err := k.GetExchange(ctx, NativeAsset) @@ -110,7 +120,7 @@ func HandleMsgRemoveLiquidity(ctx sdk.Context, msg MsgRemoveLiquidity, k Keeper) nativeWithdrawn := msg.WithdrawAmount.Mul(nativeLiquidity).Quo(totalUNI) coinWithdrawn := msg.WithdrawAmount.Mul(coinLiqudity).Quo(totalUNI) nativeCoin := sdk.NewCoin(nativeDenom, nativeWithdrawn) - exchangeCoin = sdk.NewCoin(msg.ExchangeDenom, coinWithdrawn) + exchangeCoin = sdk.NewCoin(msg.Withdraw.Denom, coinWithdrawn) // transfer withdrawn liquidity from uniswaps ModuleAccount to sender's account err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, msg.Sender, ModuleName, sdk.NewCoins(nativeCoin, coinDeposited)) diff --git a/x/uniswap/internal/types/msgs.go b/x/uniswap/internal/types/msgs.go index 5a8f94dbf08..c589a6b6e81 100644 --- a/x/uniswap/internal/types/msgs.go +++ b/x/uniswap/internal/types/msgs.go @@ -1,7 +1,6 @@ package types import ( - "strings" "time" sdk "github.com/cosmos/cosmos-sdk/types" @@ -18,26 +17,28 @@ var ( /* --------------------------------------------------------------------------- */ // MsgSwap Order - struct for swapping a coin +// Input and Output can either be exact or calculated. +// An exact coin has the senders desired buy or sell amount. +// A calculated coin has the desired denomination and bounded amount +// the sender is willing to buy or sell in this order. type MsgSwapOrder struct { - SwapDenom string `json:"swap_denom"` // The desired denomination either to be bought or sold - Amount sdk.Coins `json:"amount"` // The specified amount to be either bought or sold - Bound sdk.Int `json:"bound"` // If buy order, maximum amount of coins to be sold; otherwise minimum amount of coins to be bought + Input sdk.Coin `json:"input"` // the amount the sender is trading + Output sdk.Coin `json:"output"` // the amount the sender is recieivng Deadline time.Time `json:"deadline"` // deadline for the transaction to still be considered valid Sender sdk.AccAddress `json:"sender"` // address swapping coin Recipient sdk.AccAddress `json:"recipient"` // address output coin is being sent to IsBuyOrder bool `json:"is_buy_order"` // boolean indicating whether the order should be treated as a buy or sell } -// NewMsgSwapOrder is a constructor function for MsgSwapOrder +// NewMsgSwapOrder creates a new MsgSwapOrder object. func NewMsgSwapOrder( - swapDenom string, amt sdk.Coins, bound sdk.Int, deadline time.Time, + input, output sdk.Coin, deadline time.Time, sender, recipient sdk.AccAddress, isBuyOrder bool, ) MsgSwapOrder { return MsgSwapOrder{ - SwapDenom: swapDenom, - Amount: amt, - Bound: bound, + Input: input, + Output: output, Deadline: deadline, Sender: sender, Recipient: recipient, @@ -45,30 +46,23 @@ func NewMsgSwapOrder( } } -// Route Implements Msg +// Route Implements Msg. func (msg MsgSwapOrder) Route() string { return RouterKey } -// Type Implements Msg +// Type Implements Msg. func (msg MsgSwapOrder) Type() string { return "swap_order" } // ValidateBasic Implements Msg. func (msg MsgSwapOrder) ValidateBasic() sdk.Error { - if strings.TrimSpace(msg.SwapDenom) == "" { - return ErrNoDenom(DefaultCodespace) + if !msg.Input.IsValid() { + return sdk.ErrInvalidCoins("coin is invalid: " + msg.Input.String()) } - // initially only support trading 1 coin only - if len(msg.Amount) != 1 { - return sdk.ErrInvalidCoins("must provide a single coin") + if !msg.Output.IsValid() { + return sdk.ErrInvalidCoins("coin is invalid: " + msg.Output.String()) } - if !msg.Amount.IsValid() { - return sdk.ErrInvalidCoins("coin is invalid: " + msg.Amount.String()) - } - if msg.Amount[0].Denom == msg.SwapDenom { + if msg.Input.Denom == msg.Output.Denom { return ErrEqualDenom(DefaultCodespace) } - if !msg.Bound.IsPositive() { - return ErrInvalidBound(DefaultCodespace, "") - } if msg.Deadline.IsZero() { return ErrInvalidDeadline(DefaultCodespace, "MsgSwapOrder") } @@ -97,50 +91,45 @@ func (msg MsgSwapOrder) GetSigners() []sdk.AccAddress { // MsgAddLiquidity - struct for adding liquidity to an exchange type MsgAddLiquidity struct { - ExchangeDenom string `json:"exchange_denom"` // denomination of the exchange being added to + Deposit sdk.Coin `json:"deposit"` // coin to be deposited as liquidity with an upper bound for its amount DepositAmount sdk.Int `json:"deposit_amount"` // 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 - MaxCoins sdk.Int `json:"max_coins"` // maximum amount of the coin the sender is willing to deposit. + MinReward sdk.Int `json:"min_reward"` // lower bound UNI sender is willing to accept for deposited coins Deadline time.Time `json:"deadline"` Sender sdk.AccAddress `json:"sender"` } -// NewMsgAddLiquidity is a constructor function for MsgAddLiquidity +// NewMsgAddLiquidity creates a new MsgAddLiquidity object. func NewMsgAddLiquidity( - exchangeDenom string, depositAmount, minLiquidity, maxCoins sdk.Int, + deposit sdk.Coin, depositAmount, minReward sdk.Int, deadline time.Time, sender sdk.AccAddress, ) MsgAddLiquidity { return MsgAddLiquidity{ + Deposit: deposit, DepositAmount: depositAmount, - ExchangeDenom: exchangeDenom, - MinLiquidity: minLiquidity, - MaxCoins: maxCoins, + MinReward: minReward, Deadline: deadline, Sender: sender, } } -// Type Implements Msg +// Type Implements Msg. func (msg MsgAddLiquidity) Route() string { return RouterKey } -// Type Implements Msg +// Type Implements Msg. func (msg MsgAddLiquidity) Type() string { return "add_liquidity" } // ValidateBasic Implements Msg. func (msg MsgAddLiquidity) ValidateBasic() sdk.Error { + if !msg.Deposit.IsValid() { + return sdk.ErrInvalidCoins("coin is invalid: " + msg.Deposit.IsValid()) + } if !msg.DepositAmount.IsPositive() { return ErrInsufficientAmount(DefaultCodespace, "deposit amount provided is not positive") } - if strings.TrimSpace(msg.ExchangeDenom) == "" { - return ErrNoDenom(DefaultCodespace) - } - if !msg.MinLiquidity.IsPositive() { + if !msg.MinReward.IsPositive() { return ErrInvalidBound(DefaultCodespace, "minimum liquidity is not positive") } - if !msg.MaxCoins.IsPositive() { - return ErrInvalidBound(DefaultCodespace, "maxmimum coins is not positive") - } if msg.Deadline.IsZero() { return ErrInvalidDeadline(DefaultCodespace, "MsgAddLiquidity") } @@ -166,34 +155,32 @@ func (msg MsgAddLiquidity) GetSigners() []sdk.AccAddress { // MsgRemoveLiquidity - struct for removing liquidity from an exchange type MsgRemoveLiquidity struct { - ExchangeDenom string `json:"exchange_denom"` // denomination of the exchange being withdrawn from + Withdraw sdk.Coin `json:"withdraw"` // coin to be withdrawn with a lower bound for its amount WithdrawAmount sdk.Int `json:"withdraw_amount"` // amount of UNI to be burned to withdraw liquidity from an exchange MinNative sdk.Int `json:"min_native"` // minimum amount of the native asset the sender is willing to accept - MinCoins sdk.Int `json:"min_coins"` // minimum amount of the exchange coin the sender is willing to accept Deadline time.Time `json:"deadline"` Sender sdk.AccAddress `json:"sender"` } -// NewMsgRemoveLiquidity is a contructor function for MsgRemoveLiquidity +// NewMsgRemoveLiquidity creates a new MsgRemoveLiquidity object func NewMsgRemoveLiquidity( - exchangeDenom string, withdrawAmount, minNative, minCoins sdk.Int, + withdraw sdk.Coin, withdrawAmount, minNative sdk.Int, deadline time.Time, sender sdk.AccAddress, ) MsgRemoveLiquidity { return MsgRemoveLiquidity{ + Withdraw: withdraw, WithdrawAmount: withdrawAmount, - ExchangeDenom: exchangeDenom, MinNative: minNative, - MinCoins: minCoins, Deadline: deadline, Sender: sender, } } -// Type Implements Msg +// Type Implements Msg. func (msg MsgRemoveLiquidity) Route() string { return RouterKey } -// Type Implements Msg +// Type Implements Msg. func (msg MsgRemoveLiquidity) Type() string { return "remove_liquidity" } // ValidateBasic Implements Msg. @@ -201,15 +188,12 @@ func (msg MsgRemoveLiquidity) ValidateBasic() sdk.Error { if !msg.WithdrawAmount.IsPositive() { return ErrInsufficientAmount(DefaultCodespace, "withdraw amount is not positive") } - if strings.TrimSpace(msg.ExchangeDenom) == "" { - return ErrNoDenom(DefaultCodespace) + if !msg.Withdraw.IsValid() { + return sdk.ErrInvalidCoins("coin is invalid: " + msg.Withdraw.String()) } if !msg.MinNative.IsPositive() { return ErrInvalidBound(DefaultCodespace, "minimum native is not positive") } - if !msg.MinCoins.IsPositive() { - return ErrInvalidBound(DefaultCodespace, "minimum coins is not positive") - } if msg.Deadline.IsZero() { return ErrInvalidDeadline(DefaultCodespace, "MsgRemoveLiquidity") } diff --git a/x/uniswap/internal/types/msgs_test.go b/x/uniswap/internal/types/msgs_test.go index 5000443efb4..65046de06b2 100644 --- a/x/uniswap/internal/types/msgs_test.go +++ b/x/uniswap/internal/types/msgs_test.go @@ -15,17 +15,18 @@ func TestMsgSwapOrder(t *testing.T) { msg MsgSwapOrder expectPass bool }{ - {"empty swap denomination", NewMsgSwapOrder(emptyDenom, amount, bound, deadline, sender, recipient, true), false}, - {"empty coin", NewMsgSwapOrder(denom0, sdk.NewCoins(sdk.NewCoin(denom1, zero)), bound, deadline, sender, recipient, true), false}, - {"no coin", NewMsgSwapOrder(denom0, sdk.Coins{}, bound, deadline, sender, recipient, true), false}, - {"too many coins", NewMsgSwapOrder(denom0, sdk.NewCoins(coin, sdk.NewCoin(denom2, baseValue)), bound, deadline, sender, recipient, true), false}, - {"swap and coin denomination are equal", NewMsgSwapOrder(denom0, sdk.NewCoins(sdk.NewCoin(denom0, baseValue)), bound, deadline, sender, recipient, true), false}, - {"bound is not positive", NewMsgSwapOrder(denom0, amount, zero, deadline, sender, recipient, true), false}, - {"deadline not initialized", NewMsgSwapOrder(denom0, amount, bound, emptyTime, sender, recipient, true), false}, - {"no sender", NewMsgSwapOrder(denom0, amount, bound, deadline, emptyAddr, recipient, true), false}, - {"no recipient", NewMsgSwapOrder(denom0, amount, bound, deadline, sender, emptyAddr, true), false}, - {"valid MsgSwapOrder", NewMsgSwapOrder(denom0, amount, bound, deadline, sender, recipient, true), true}, - {"sender and recipient are same", NewMsgSwapOrder(denom0, amount, bound, deadline, sender, sender, true), true}, + {"no input coin", NewMsgSwapOrder(sdk.Coin{}, output, deadline, sender, recipient, true), false}, + {"zero input coin", NewMsgSwapOrder(sdk.NewCoin(denom0, zero), output, deadline, sender, recipient, true), false}, + {"invalid input denom", NewMsgSwapOrder(sdk.NewCoin(emptyDenom, amt), output, deadline, sender, recipient, true), false}, + {"no output coin", NewMsgSwapOrder(input, sdk.Coin{}, deadline, sender, recipient, false), false}, + {"zero output coin", NewMsgSwapOrder(input, sdk.NewCoin(denom1, zero), deadline, sender, recipient, true), false}, + {"invalid output denom", NewMsgSwapOrder(input, sdk.NewCoin(emptyDenom, amt), deadline, sender, recipient, true), false}, + {"swap and coin denomination are equal", NewMsgSwapOrder(input, sdk.NewCoin(denom0, amt), deadline, sender, recipient, true), false}, + {"deadline not initialized", NewMsgSwapOrder(input, output, emptyTime, sender, recipient, true), false}, + {"no sender", NewMsgSwapOrder(input, output, deadline, emptyAddr, recipient, true), false}, + {"no recipient", NewMsgSwapOrder(input, output, deadline, sender, emptyAddr, true), false}, + {"valid MsgSwapOrder", NewMsgSwapOrder(input, output, deadline, sender, recipient, true), true}, + {"sender and recipient are same", NewMsgSwapOrder(input, output, deadline, sender, sender, true), true}, } for _, tc := range tests { @@ -47,13 +48,14 @@ func TestMsgAddLiquidity(t *testing.T) { msg MsgAddLiquidity expectPass bool }{ - {"invalid withdraw amount", NewMsgAddLiquidity(denom1, zero, one, one, deadline, sender), false}, - {"empty exchange denom", NewMsgAddLiquidity(emptyDenom, baseValue, one, one, deadline, sender), false}, - {"invalid minumum liquidity bound", NewMsgAddLiquidity(denom1, baseValue, zero, one, deadline, sender), false}, - {"invalid maximum coins bound", NewMsgAddLiquidity(denom1, baseValue, one, zero, deadline, sender), false}, - {"deadline not initialized", NewMsgAddLiquidity(denom1, baseValue, one, one, emptyTime, sender), false}, - {"empty sender", NewMsgAddLiquidity(denom1, baseValue, one, one, deadline, emptyAddr), false}, - {"valid MsgAddLiquidity", NewMsgAddLiquidity(denom1, baseValue, one, one, deadline, sender), true}, + {"no deposit coin", NewMsgAddLiquidity(sdk.Coin{}, amt, one, deadline, sender), false}, + {"zero deposit coin", NewMsgAddLiquidity(sdk.NewCoin(denom1, zero), amt, one, deadline, sender), false}, + {"invalid deposit denom", NewMsgAddLiquidity(sdk.NewCoin(emptyDenom, amt), amt, one, deadline, sender), false}, + {"invalid withdraw amount", NewMsgAddLiquidity(input, zero, one, deadline, sender), false}, + {"invalid minumum reward bound", NewMsgAddLiquidity(input, amt, zero, deadline, sender), false}, + {"deadline not initialized", NewMsgAddLiquidity(input, amt, one, emptyTime, sender), false}, + {"empty sender", NewMsgAddLiquidity(input, amt, one, deadline, emptyAddr), false}, + {"valid MsgAddLiquidity", NewMsgAddLiquidity(input, amt, one, deadline, sender), true}, } for _, tc := range tests { @@ -75,13 +77,14 @@ func TestMsgRemoveLiquidity(t *testing.T) { msg MsgRemoveLiquidity expectPass bool }{ - {"invalid deposit amount", NewMsgRemoveLiquidity(denom1, zero, one, one, deadline, sender), false}, - {"empty exchange denom", NewMsgRemoveLiquidity(emptyDenom, baseValue, one, one, deadline, sender), false}, - {"invalid minimum native bound", NewMsgRemoveLiquidity(denom1, baseValue, zero, one, deadline, sender), false}, - {"invalid minumum coins bound", NewMsgRemoveLiquidity(denom1, baseValue, one, zero, deadline, sender), false}, - {"deadline not initialized", NewMsgRemoveLiquidity(denom1, baseValue, one, one, emptyTime, sender), false}, - {"empty sender", NewMsgRemoveLiquidity(denom1, baseValue, one, one, deadline, emptyAddr), false}, - {"valid MsgRemoveLiquidity", NewMsgRemoveLiquidity(denom1, baseValue, one, one, deadline, sender), true}, + {"no withdraw coin", NewMsgRemoveLiquidity(sdk.Coin{}, amt, one, deadline, sender), false}, + {"zero withdraw coin", NewMsgRemoveLiquidity(sdk.NewCoin(denom1, zero), amt, one, deadline, sender), false}, + {"invalid withdraw denom", NewMsgRemoveLiquidity(sdk.NewCoin(emptyDenom, amt), amt, one, deadline, sender), false}, + {"invalid deposit amount", NewMsgRemoveLiquidity(input, zero, one, deadline, sender), false}, + {"invalid minimum native bound", NewMsgRemoveLiquidity(input, amt, zero, deadline, sender), false}, + {"deadline not initialized", NewMsgRemoveLiquidity(input, amt, one, emptyTime, sender), false}, + {"empty sender", NewMsgRemoveLiquidity(input, amt, one, deadline, emptyAddr), false}, + {"valid MsgRemoveLiquidity", NewMsgRemoveLiquidity(input, amt, one, deadline, sender), true}, } for _, tc := range tests { diff --git a/x/uniswap/internal/types/params.go b/x/uniswap/internal/types/params.go index ca3abe7b98d..a06af222e10 100644 --- a/x/uniswap/internal/types/params.go +++ b/x/uniswap/internal/types/params.go @@ -2,6 +2,7 @@ package types import ( "fmt" + "strings" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" @@ -10,24 +11,43 @@ import ( // TODO: come up with better naming for these params? // Parameter store keys var ( - ParamStoreKeyFeeParams = []byte("feeparams") + ParamStoreKeyFeeParams = []byte("feeparams") + ParamStoreKeyNativeAssetParams = []byte("nativeAssetparam") ) -// uniswap parameters -// Fee = 1 - (FeeN - FeeD) -type FeeParams struct { - FeeN sdk.Int `json:"fee_numerator"` // fee numerator - FeeD sdk.Int `json:"fee_denominator"` // fee denominator -} - // ParamKeyTable creates a ParamTable for uniswap module. func ParamKeyTable() params.KeyTable { return params.NewKeyTable( + ParamStoreKeyNativeAssetParams, NativeAssetParams{}, ParamStoreKeyFeeParams, FeeParams{}, ) } -// NewFeeParams is a constructor for the FeeParams struct +// NativeAssetParams represents the native asset used in the uniswap module. +type NativeAssetParams struct { + Denom string +} + +// NewNativeAssetParams creates a new NativeAssetParams object +func NewNativeAssetParams(denom string) NativeAssetParams { + return NativeAssetParams{ + Denom: denom, + } +} + +func (p NativeAssetParams) String() string { + return fmt.Sprintf("Native Asset: %s\n", p.Denom) +} + +// FeeParams represents the numerator and denominator for calculating +// the swap fee. +// Fee = 1 - (FeeN / FeeD) +type FeeParams struct { + FeeN sdk.Int `json:"fee_numerator"` // fee numerator + FeeD sdk.Int `json:"fee_denominator"` // fee denominator +} + +// NewFeeParams creates a new FeeParams object func NewFeeParams(feeN, feeD sdk.Int) FeeParams { return FeeParams{ FeeN: feeN, @@ -35,30 +55,43 @@ func NewFeeParams(feeN, feeD sdk.Int) FeeParams { } } +func (p FeeParams) String() string { + return fmt.Sprintf(`Fee Params: + Fee Numerator: %s + Fee Denominator: %s +`, + p.FeeN, p.FeeD, + ) +} + +type Params struct { + NativeAssetParams NativeAssetParams `json:"native_asset"` + FeeParams FeeParams `json:"fee"` +} + // DefaultParams returns the default uniswap module parameters -func DefaultParams() FeeParams { - return FeeParams{ - FeeN: sdk.NewInt(997), - FeeD: sdk.NewInt(1000), +func DefaultParams() Params { + return Params{ + FeeParams: FeeParams{ + FeeN: sdk.NewInt(997), + FeeD: sdk.NewInt(1000), + }, + NativeAssetParams: NativeAssetParams{ + Denom: sdk.DefaultBondDenom, + }, } } // ValidateParams validates the set params -func ValidateParams(p FeeParams) error { - if p.FeeN.GT(p.FeeD) { +func ValidateParams(p Params) error { + if p.FeeParams.FeeN.GT(p.FeeD) { return fmt.Errorf("fee numerator must be less than or equal to fee denominator") } - if p.FeeD.Mod(sdk.NewInt(10)) != sdk.NewInt(0) { + if p.FeeParams.FeeD.Mod(sdk.NewInt(10)) != sdk.NewInt(0) { return fmt.Errorf("fee denominator must be multiple of 10") } + if strings.TrimSpace(p.NativeAssetParams.Denom) != "" { + return fmt.Errorf("native asset denomination must not be empty") + } return nil } - -func (p FeeParams) String() string { - return fmt.Sprintf(`Uniswap Params: - Fee Numerator: %s - Fee Denominator: %s -`, - p.FeeN, p.FeeD, - ) -} diff --git a/x/uniswap/internal/types/test_common.go b/x/uniswap/internal/types/test_common.go index 13b8ea34ba4..9d1fdedc9cc 100644 --- a/x/uniswap/internal/types/test_common.go +++ b/x/uniswap/internal/types/test_common.go @@ -8,9 +8,9 @@ import ( ) var ( - zero = sdk.NewInt(0) - one = sdk.NewInt(1) - baseValue = sdk.NewInt(100) + zero = sdk.NewInt(0) + one = sdk.NewInt(1) + amt = sdk.NewInt(100) sender_pk = ed25519.GenPrivKey().PubKey() recipient_pk = ed25519.GenPrivKey().PubKey() @@ -19,11 +19,9 @@ var ( denom0 = "atom" denom1 = "btc" - denom2 = "eth" - coin = sdk.NewCoin(denom1, sdk.NewInt(1000)) - amount = sdk.NewCoins(coin) - bound = sdk.NewInt(100) + input = sdk.NewCoin(denom0, sdk.NewInt(1000)) + output = sdk.NewCoin(denom1, sdk.NewInt(500)) deadline = time.Now() emptyAddr sdk.AccAddress From 7cfb8c18dc1872cc4ebab81fd0006d4234a511bf Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Fri, 14 Jun 2019 17:15:43 -0700 Subject: [PATCH 12/15] rename exchange to reserve pool, updated handler with err checks --- x/uniswap/handler.go | 92 +++++++++++++++----- x/uniswap/internal/keeper/keeper.go | 38 ++++---- x/uniswap/internal/keeper/keys.go | 8 +- x/uniswap/internal/keeper/querier.go | 8 +- x/uniswap/internal/keeper/querier_test.go | 2 +- x/uniswap/internal/types/codec.go | 2 +- x/uniswap/internal/types/errors.go | 39 +++------ x/uniswap/internal/types/expected_keepers.go | 10 +++ x/uniswap/internal/types/msgs.go | 32 +++---- x/uniswap/internal/types/params.go | 2 +- 10 files changed, 141 insertions(+), 92 deletions(-) create mode 100644 x/uniswap/internal/types/expected_keepers.go diff --git a/x/uniswap/handler.go b/x/uniswap/handler.go index 7917bdb9d47..0ffccb80dde 100644 --- a/x/uniswap/handler.go +++ b/x/uniswap/handler.go @@ -28,6 +28,11 @@ func NewHandler(k Keeper) sdk.Handler { func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result { var caclulatedAmount sdk.Int + // check that deadline has not passed + if msg.ctx.BlockTime.After(msg.Deadline) { + return ErrInvalidDeadline(DefaultCodespace, "deadline has passed for MsgSwapOrder") + } + if msg.IsBuyOrder { calculatedAmount := k.GetInputAmount(ctx, msg.SwapDenom, msg.Amount) // ensure the maximum amount sender is willing to sell is less than @@ -35,8 +40,21 @@ func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result if msg.Input.Amount.LT(calculatedAmount) { return types.ErrInvalidBound(DefaultCodespace, "maximum amount (%d) to be sold was exceeded (%d)", msg.Input.Amount, calculatedAmount).Result() } - k.supplyKeeper.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(sdk.NewCoin(msg.Input.Denom, calculatedAmount))) - k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, ModuleName, msg.Sender, sdk.NewCoins(msg.Output)) + + coinSold := sdk.NewCoins(sdk.NewCoin(msg.Input.Denom, calculatedAmount)) + if !s.bk.HasCoins(ctx, msg.Sender, coinSold) { + return ErrInsufficientAmount(DefaultCodespace, "sender account does not have sufficient funds to fulfill the swap order").Result() + } + + err := k.sk.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, coinSold) + if err != nil { + return err.Result() + } + + err = k.sk.SendCoinsFromModuleToAccount(ctx, ModuleName, msg.Sender, sdk.NewCoins(msg.Output)) + if err != nil { + return err.Result() + } } else { outputAmount := k.GetOutputAmount(ctx, msg.SwapDenom, msg.Amount) @@ -45,8 +63,21 @@ func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result if msg.Output.Amount.GT(outputAmount) { return sdk.ErrInvalidBound(DefaultCodespace, "minimum amount (%d) to be sold was not met (%d)", msg.Output.Amount, calculatedAmount).Result() } - k.supplyKeeper.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(msg.Input)) - k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, ModuleName, msg.Sender, sdk.NewCoins(sdk.NewCoin(msg.Output.Denom, calculatedAmount))) + + coinSold := sdk.NewCoins(msg.Input) + if !s.bk.HasCoins(ctx, msg.Sender, coinSold) { + return ErrInsifficientAmount(DefaultCodespace, "sender account does not have sufficient funds to fulfill the swap order").Result() + } + + err := k.sk.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(msg.Input)) + if err != nil { + return err.Result() + } + + err = k.sk.SendCoinsFromModuleToAccount(ctx, ModuleName, msg.Sender, sdk.NewCoins(sdk.NewCoin(msg.Output.Denom, calculatedAmount))) + if err != nil { + return err.Result() + } } @@ -54,15 +85,20 @@ func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result } // HandleMsgAddLiquidity handler for MsgAddLiquidity -// If the exchange does not exist, it will be created. +// If the reserve pool does not exist, it will be created. func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.Result { - // create exchange if it does not exist - coinLiquidity := k.GetExchange(ctx, msg.Deposit.Denom) + // check that deadline has not passed + if msg.ctx.BlockTime.After(msg.Deadline) { + return ErrInvalidDeadline(DefaultCodespace, "deadline has passed for MsgAddLiquidity") + } + + // create reserve pool if it does not exist + coinLiquidity := k.GetReservePool(ctx, msg.Deposit.Denom) if err != nil { - k.CreateExchange(ctx, msg.Denom) + k.CreateReservePool(ctx, msg.Denom) } - nativeLiqudiity, err := k.GetExchange(ctx, NativeAsset) + nativeLiqudiity, err := k.GetReservePool(ctx, NativeAsset) if err != nil { panic("error retrieving native asset total liquidity") } @@ -75,14 +111,19 @@ func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.R // calculate amount of UNI to be minted for sender // and coin amount to be deposited MintedUNI := (totalUNI.Mul(msg.DepositAmount)).Quo(nativeLiquidity) - coinDeposited := (totalUNI.Mul(msg.DepositAmount)).Quo(nativeLiquidity) - nativeCoin := sdk.NewCoin(NativeAsset, msg.DepositAmount) - exchangeCoin := sdk.NewCoin(msg.Deposit.Denom, coinDeposited) + coinAmountDeposited := (totalUNI.Mul(msg.DepositAmount)).Quo(nativeLiquidity) + nativeCoinDeposited := sdk.NewCoin(NativeAsset, msg.DepositAmount) + coinDeposited := sdk.NewCoin(msg.Deposit.Denom, coinAmountDeposited) + + coins := sdk.NewCoins(nativeCoinDeposited, coinDeposited) + if !s.bk.HasCoins(ctx, msg.Sender, coins) { + return ErrInsufficientCoins(DefaultCodespace, "sender does not have sufficient funds to add liquidity").Result() + } // transfer deposited liquidity into uniswaps ModuleAccount - err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(nativeCoin, coinDeposited)) + err := k.sk.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, coins) if err != nil { - return err + return err.Result() } // set updated total UNI @@ -99,13 +140,18 @@ func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.R // HandleMsgRemoveLiquidity handler for MsgRemoveLiquidity func HandleMsgRemoveLiquidity(ctx sdk.Context, msg MsgRemoveLiquidity, k Keeper) sdk.Result { - // check if exchange exists - coinLiquidity, err := k.GetExchange(ctx, msg.Withdraw.Denom) + // check that deadline has not passed + if msg.ctx.BlockTime.After(msg.Deadline) { + return ErrInvalidDeadline(DefaultCodespace, "deadline has passed for MsgRemoveLiquidity") + } + + // check if reserve pool exists + coinLiquidity, err := k.GetReservePool(ctx, msg.Withdraw.Denom) if err != nil { panic(fmt.Sprintf("error retrieving total liquidity for denomination: %s", msg.Withdraw.Denom)) } - nativeLiquidity, err := k.GetExchange(ctx, NativeAsset) + nativeLiquidity, err := k.GetReservePool(ctx, NativeAsset) if err != nil { panic("error retrieving native asset total liquidity") } @@ -123,9 +169,9 @@ func HandleMsgRemoveLiquidity(ctx sdk.Context, msg MsgRemoveLiquidity, k Keeper) exchangeCoin = sdk.NewCoin(msg.Withdraw.Denom, coinWithdrawn) // transfer withdrawn liquidity from uniswaps ModuleAccount to sender's account - err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, msg.Sender, ModuleName, sdk.NewCoins(nativeCoin, coinDeposited)) + err := k.sk.SendCoinsFromModuleToAccount(ctx, msg.Sender, ModuleName, sdk.NewCoins(nativeCoin, coinDeposited)) if err != nil { - return err + return err.Result() } // set updated total UNI @@ -144,8 +190,8 @@ func HandleMsgRemoveLiquidity(ctx sdk.Context, msg MsgRemoveLiquidity, k Keeper) // The fee is included in the output coins being bought // https://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdfhttps://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdf func getInputAmount(ctx sdk.Context, k Keeper, outputAmt sdk.Int, inputDenom, outputDenom string) sdk.Int { - inputReserve := k.GetExchange(inputDenom) - outputReserve := k.GetExchange(outputDenom) + inputReserve := k.GetReservePool(inputDenom) + outputReserve := k.GetReservePool(outputDenom) params := k.GetFeeParams(ctx) numerator := inputReserve.Mul(outputReserve).Mul(params.FeeD) @@ -157,8 +203,8 @@ func getInputAmount(ctx sdk.Context, k Keeper, outputAmt sdk.Int, inputDenom, ou // 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 getOutputAmount(ctx sdk.Context, k Keeper, inputAmt sdk.Int, inputDenom, outputDenom string) sdk.Int { - inputReserve := k.GetExchange(inputDenom) - outputReserve := k.GetExchange(outputDenom) + inputReserve := k.GetReservePool(inputDenom) + outputReserve := k.GetReservePool(outputDenom) params := k.GetFeeParams(ctx) inputAmtWithFee := inputAmt.Mul(params.FeeN) diff --git a/x/uniswap/internal/keeper/keeper.go b/x/uniswap/internal/keeper/keeper.go index 90f8d794f69..bff397b1252 100644 --- a/x/uniswap/internal/keeper/keeper.go +++ b/x/uniswap/internal/keeper/keeper.go @@ -16,8 +16,11 @@ type Keeper struct { // The key used to access the uniswap store storeKey sdk.StoreKey + // The reference to the BankKeeper to retrieve account balances + bk types.BankKeeper + // The reference to the SupplyKeeper to hold coins for this module - types.SupplyKeeper + sk types.SupplyKeeper // The codec codec for binary encoding/decoding. cdc *codec.Codec @@ -27,16 +30,17 @@ type Keeper struct { } // NewKeeper returns a uniswap keeper. It handles: -// - creating new exchanges +// - creating new reserve pools // - facilitating swaps -// - users adding liquidity to exchanges -// - users removing liquidity to exchanges -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, supplyKeeper supply.Keeper, paramSpace params.Subspace) Keeper { +// - users adding liquidity to a reserve pool +// - users removing liquidity from a reserve pool +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, bk types.BankKeeper, sk supply.Keeper, paramSpace params.Subspace) Keeper { return Keeper{ - storeKey: key, - supplyKeeper: supplyKeeper, - cdc: cdc, - paramSpace: paramSpace.WithKeyTable(types.ParamKeyTable()), + storeKey: key, + bk: bk, + sk: sk, + cdc: cdc, + paramSpace: paramSpace.WithKeyTable(types.ParamKeyTable()), } } @@ -45,13 +49,13 @@ func (keeper Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", types.ModuleName) } -// CreateExchange initializes a new exchange pair between the new coin and the native asset -func (keeper Keeper) CreateExchange(ctx sdk.Context, exchangeDenom string) { +// CreateReservePool initializes a new reserve pool for the new denomination +func (keeper Keeper) CreateReservePool(ctx sdk.Context, denom string) { store := ctx.KVStore(keeper.storeKey) - key := GetExchangeKey(exchangeDenom) + key := GetReservePoolKey(denom) bz := store.Get(key) if bz != nil { - panic(fmt.Sprintf("exchange %s already exists", exchangeDenom)) + panic(fmt.Sprintf("reserve pool for %s already exists", denom)) } store.Set(key, keeper.encode(sdk.ZeroInt())) @@ -96,12 +100,12 @@ func (keeper Keeper) SetTotalUNI(ctx sdk.Context, totalUNI sdk.Int) { store.Set(TotalUNIKey, keeper.encode(totalUNI)) } -// GetExchange returns the total balance of an exchange at the provided denomination -func (keeper Keeper) GetExchange(ctx sdk.Context, denom string) sdk.Int { +// GetReservePool returns the total balance of an reserve pool at the provided denomination +func (keeper Keeper) GetReservePool(ctx sdk.Context, denom string) sdk.Int { store := ctx.KVStore(keeper.storeKey) - bz := store.Get(GetExchangeKey(denom)) + bz := store.Get(GetReservePoolKey(denom)) if bz == nil { - panic(fmt.Sprintf("exchange %s does not exist"), denom) + panic(fmt.Sprintf("reserve pool for %s does not exist"), denom) } return keeper.decode(bz) } diff --git a/x/uniswap/internal/keeper/keys.go b/x/uniswap/internal/keeper/keys.go index 27f7e6005cb..4ce25e379c6 100644 --- a/x/uniswap/internal/keeper/keys.go +++ b/x/uniswap/internal/keeper/keys.go @@ -6,14 +6,14 @@ import ( // keys var ( - ExchangePrefix = []byte{0x00} // prefix for exchange liquidity keys + ReservePoolPrefix = []byte{0x00} // prefix for reserve pool keys UNIBalancesPrefix = []byte{0x01} // prefix for UNI balances keys TotalUNIKey = []byte{0x02} // key for total ) -// GetExchangeKey gets the key for an exchanges total liquidity -func GetExchangeKey(denom string) []byte { - return append(ExchangePrefix, []byte(denom)...) +// GetReservePoolKey gets the key for a reserve pool's total liquidity +func GetReservePoolKey(denom string) []byte { + return append(ReservePoolPrefix, []byte(denom)...) } // GetUNIBalancesKey gets the key for an addresses UNI balance diff --git a/x/uniswap/internal/keeper/querier.go b/x/uniswap/internal/keeper/querier.go index 7ab38f6d34f..a6e2d1f321f 100644 --- a/x/uniswap/internal/keeper/querier.go +++ b/x/uniswap/internal/keeper/querier.go @@ -12,8 +12,8 @@ func NewQuerier(k Keeper) sdk.Querier { return queryBalance(ctx, req, k) case types.QueryLiquidity: return queryLiquidity(ctx, req, k) - case types.Parameters: - return queryLiquidity(ctx, req, k) + case types.QueryParameters: + return queryParameters(ctx, req, k) default: return nil, sdk.ErrUnknownRequest(fmt.Sprintf("%s is not a valid query request path", req.Path)) } @@ -38,7 +38,7 @@ func queryBalance(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk return bz, nil } -// queryLiquidity returns the total liquidity avaliable at the provided exchange +// queryLiquidity returns the total liquidity avaliable for the provided denomination // upon success or an error if the query fails. func queryLiquidity(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { var params QueryLiquidity @@ -47,7 +47,7 @@ func queryLiquidity(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, s return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) } - liquidity := k.GetExchange(ctx, params.Denom) + liquidity := k.GetReservePool(ctx, params.Denom) bz, err := codec.MarshalJSONIndent(k.cdc, liquidity) if err != nil { diff --git a/x/uniswap/internal/keeper/querier_test.go b/x/uniswap/internal/keeper/querier_test.go index 3e8d0911fee..4bc1410f95a 100644 --- a/x/uniswap/internal/keeper/querier_test.go +++ b/x/uniswap/internal/keeper/querier_test.go @@ -29,7 +29,7 @@ func TestNewQuerier(t *testing.T) { require.NotNil(t, err) require.Nil(res) - // query for non existent exchange should return an error + // 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) diff --git a/x/uniswap/internal/types/codec.go b/x/uniswap/internal/types/codec.go index 0a674ac50e6..5a85422ace8 100644 --- a/x/uniswap/internal/types/codec.go +++ b/x/uniswap/internal/types/codec.go @@ -4,7 +4,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" ) -// Register concrete types on codec +// RegisterCodec registers concrete types on the codec. func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(MsgSwapOrder{}, "cosmos-sdk/MsgSwapOrder", nil) cdc.RegisterConcrete(MsgAddLiquidity{}, "cosmos-sdk/MsgAddLiquidity", nil) diff --git a/x/uniswap/internal/types/errors.go b/x/uniswap/internal/types/errors.go index a37c847cc9d..1fe273b779b 100644 --- a/x/uniswap/internal/types/errors.go +++ b/x/uniswap/internal/types/errors.go @@ -1,50 +1,39 @@ package types import ( - "fmt" sdk "github.com/cosmos/cosmos-sdk/types" ) const ( DefaultCodespace sdk.CodespaceType = ModuleName - CodeNoDenom sdk.CodeType = 101 - CodeExchangeAlreadyExists sdk.CodeType = 102 - CodeEqualDenom sdk.CodeType = 103 - CodeInvalidBound sdk.CodeType = 104 - CodeInvalidDeadline sdk.CodeType = 105 - CodeInsufficientAmount sdk.CodeType = 106 + CodeReservePoolAlreadyExists sdk.CodeType = 101 + CodeEqualDenom sdk.CodeType = 102 + CodeInvalidDeadline sdk.CodeType = 103 + CodeNotPositive sdk.CodeType = 104 ) -func ErrNoDenom(codespace sdk.CodespaceType) sdk.Error { - return sdk.NewError(codespace, CodeNoDenom, "denomination is empty") -} - -func ErrExchangeAlreadyExists(codespace sdk.CodespaceType, msg string) sdk.Error { +func ErrReservePoolAlreadyExists(codespace sdk.CodespaceType, msg string) sdk.Error { if msg != "" { - return sdk.NewError(codespace, CodeExchangeAlreadyExists, msg) + return sdk.NewError(codespace, CodeReservePoolAlreadyExists, msg) } - return sdk.NewError(codespace, CodeExchangeAlreadyExists, "exchange already exists") + return sdk.NewError(codespace, CodeReservePoolAlreadyExists, "reserve pool already exists") } func ErrEqualDenom(codespace sdk.CodespaceType) sdk.Error { - return sdk.NewError(codespace, CodeEqualDenom, "coin and swap denomination are equal") + return sdk.NewError(codespace, CodeEqualDenom, "input and output denomination are equal") } -func ErrInvalidBound(codespace sdk.CodespaceType, msg string) sdk.Error { +func ErrInvalidDeadline(codespace sdk.CodespaceType, msg string) sdk.Error { if msg != "" { - return sdk.NewError(codespace, CodeInvalidBound, msg) + return sdk.NewError(codespace, CodeInvalidDeadline, msg) } - return sdk.NewError(codespace, CodeInvalidBound, "bound is not positive") -} - -func ErrInvalidDeadline(codespace sdk.CodespaceType, msgName string) sdk.Error { - return sdk.NewError(codespace, CodeInvalidDeadline, fmt.Sprintf("deadline for %s not initialized", msgName)) + return sdk.NewError(codespace, CodeInvalidDeadline, "invalid deadline") } -func ErrInsufficientAmount(codespace sdk.CodespaceType, msg string) sdk.Error { +func ErrNotPositive(codespace sdk.CodespaceType, msg string) sdk.Error { if msg != "" { - return sdk.NewError(codespace, CodeInsufficientAmount, msg) + return sdk.NewError(codespace, CodeNotPositive, msg) } - return sdk.NewError(codespace, CodeInsufficientAmount, "insufficient amount provided") + return sdk.NewError(codespace, CodeNotPositive, "amount is not positive") } diff --git a/x/uniswap/internal/types/expected_keepers.go b/x/uniswap/internal/types/expected_keepers.go new file mode 100644 index 00000000000..17e35b7b8a2 --- /dev/null +++ b/x/uniswap/internal/types/expected_keepers.go @@ -0,0 +1,10 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// BankKeeper defines the expected bank keeper +type BankKeeper interface { + HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool +} diff --git a/x/uniswap/internal/types/msgs.go b/x/uniswap/internal/types/msgs.go index c589a6b6e81..9488cd4013f 100644 --- a/x/uniswap/internal/types/msgs.go +++ b/x/uniswap/internal/types/msgs.go @@ -22,11 +22,11 @@ var ( // A calculated coin has the desired denomination and bounded amount // the sender is willing to buy or sell in this order. type MsgSwapOrder struct { - Input sdk.Coin `json:"input"` // the amount the sender is trading - Output sdk.Coin `json:"output"` // the amount the sender is recieivng - Deadline time.Time `json:"deadline"` // deadline for the transaction to still be considered valid - Sender sdk.AccAddress `json:"sender"` // address swapping coin - Recipient sdk.AccAddress `json:"recipient"` // address output coin is being sent to + Input sdk.Coin `json:"input"` // the amount the sender is trading + Output sdk.Coin `json:"output"` // the amount the sender is recieivng + Deadline time.Time `json:"deadline"` // deadline for the transaction to still be considered valid + Sender sdk.AccAddress `json:"sender"` + Recipient sdk.AccAddress `json:"recipient"` IsBuyOrder bool `json:"is_buy_order"` // boolean indicating whether the order should be treated as a buy or sell } @@ -64,7 +64,7 @@ func (msg MsgSwapOrder) ValidateBasic() sdk.Error { return ErrEqualDenom(DefaultCodespace) } if msg.Deadline.IsZero() { - return ErrInvalidDeadline(DefaultCodespace, "MsgSwapOrder") + return ErrInvalidDeadline(DefaultCodespace, "deadline for MsgSwapOrder not initialized") } if msg.Sender.Empty() { return sdk.ErrInvalidAddress("invalid sender address") @@ -89,7 +89,7 @@ func (msg MsgSwapOrder) GetSigners() []sdk.AccAddress { // MsgAddLiquidity /* --------------------------------------------------------------------------- */ -// MsgAddLiquidity - struct for adding liquidity to an exchange +// MsgAddLiquidity - struct for adding liquidity to a reserve pool type MsgAddLiquidity struct { Deposit sdk.Coin `json:"deposit"` // coin to be deposited as liquidity with an upper bound for its amount DepositAmount sdk.Int `json:"deposit_amount"` // exact amount of native asset being add to the liquidity pool @@ -122,16 +122,16 @@ func (msg MsgAddLiquidity) Type() string { return "add_liquidity" } // ValidateBasic Implements Msg. func (msg MsgAddLiquidity) ValidateBasic() sdk.Error { if !msg.Deposit.IsValid() { - return sdk.ErrInvalidCoins("coin is invalid: " + msg.Deposit.IsValid()) + return sdk.ErrInvalidCoins("coin is invalid: " + msg.Deposit.String()) } if !msg.DepositAmount.IsPositive() { - return ErrInsufficientAmount(DefaultCodespace, "deposit amount provided is not positive") + return ErrNotPositive(DefaultCodespace, "deposit amount provided is not positive") } if !msg.MinReward.IsPositive() { - return ErrInvalidBound(DefaultCodespace, "minimum liquidity is not positive") + return ErrNotPositive(DefaultCodespace, "minimum liquidity is not positive") } if msg.Deadline.IsZero() { - return ErrInvalidDeadline(DefaultCodespace, "MsgAddLiquidity") + return ErrInvalidDeadline(DefaultCodespace, "deadline for MsgAddLiquidity not initialized") } if msg.Sender.Empty() { return sdk.ErrInvalidAddress("invalid sender address") @@ -153,10 +153,10 @@ func (msg MsgAddLiquidity) GetSigners() []sdk.AccAddress { // MsgRemoveLiquidity /* --------------------------------------------------------------------------- */ -// MsgRemoveLiquidity - struct for removing liquidity from an exchange +// MsgRemoveLiquidity - struct for removing liquidity from a reserve pool type MsgRemoveLiquidity struct { Withdraw sdk.Coin `json:"withdraw"` // coin to be withdrawn with a lower bound for its amount - WithdrawAmount sdk.Int `json:"withdraw_amount"` // amount of UNI to be burned to withdraw liquidity from an exchange + WithdrawAmount sdk.Int `json:"withdraw_amount"` // amount of UNI to be burned to withdraw liquidity from a reserve pool MinNative sdk.Int `json:"min_native"` // minimum amount of the native asset the sender is willing to accept Deadline time.Time `json:"deadline"` Sender sdk.AccAddress `json:"sender"` @@ -186,16 +186,16 @@ func (msg MsgRemoveLiquidity) Type() string { return "remove_liquidity" } // ValidateBasic Implements Msg. func (msg MsgRemoveLiquidity) ValidateBasic() sdk.Error { if !msg.WithdrawAmount.IsPositive() { - return ErrInsufficientAmount(DefaultCodespace, "withdraw amount is not positive") + return ErrNotPositive(DefaultCodespace, "withdraw amount is not positive") } if !msg.Withdraw.IsValid() { return sdk.ErrInvalidCoins("coin is invalid: " + msg.Withdraw.String()) } if !msg.MinNative.IsPositive() { - return ErrInvalidBound(DefaultCodespace, "minimum native is not positive") + return ErrNotPositive(DefaultCodespace, "minimum native amount is not positive") } if msg.Deadline.IsZero() { - return ErrInvalidDeadline(DefaultCodespace, "MsgRemoveLiquidity") + return ErrInvalidDeadline(DefaultCodespace, "deadline for MsgRemoveLiquidity not initialized") } if msg.Sender.Empty() { return sdk.ErrInvalidAddress("invalid sender address") diff --git a/x/uniswap/internal/types/params.go b/x/uniswap/internal/types/params.go index a06af222e10..d11c42303eb 100644 --- a/x/uniswap/internal/types/params.go +++ b/x/uniswap/internal/types/params.go @@ -84,7 +84,7 @@ func DefaultParams() Params { // ValidateParams validates the set params func ValidateParams(p Params) error { - if p.FeeParams.FeeN.GT(p.FeeD) { + if p.FeeParams.FeeN.GT(p.FeeParams.FeeD) { return fmt.Errorf("fee numerator must be less than or equal to fee denominator") } if p.FeeParams.FeeD.Mod(sdk.NewInt(10)) != sdk.NewInt(0) { From 5bb46247db897545ced76c39ab4b812a3d6114e6 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Mon, 17 Jun 2019 18:19:57 -0700 Subject: [PATCH 13/15] updated params --- x/uniswap/handler.go | 4 +- x/uniswap/internal/keeper/keeper.go | 16 ++-- x/uniswap/internal/keeper/querier.go | 6 +- x/uniswap/internal/keeper/querier_test.go | 23 ++++-- x/uniswap/internal/keeper/test_common.go | 21 ++++- x/uniswap/internal/types/codec.go | 2 +- x/uniswap/internal/types/keys.go | 5 -- x/uniswap/internal/types/msgs_test.go | 36 ++++----- x/uniswap/internal/types/params.go | 97 +++++++++-------------- x/uniswap/internal/types/params_test.go | 37 +++++++++ x/uniswap/internal/types/querier.go | 2 +- x/uniswap/internal/types/test_common.go | 4 +- 12 files changed, 149 insertions(+), 104 deletions(-) create mode 100644 x/uniswap/internal/types/params_test.go diff --git a/x/uniswap/handler.go b/x/uniswap/handler.go index 0ffccb80dde..e2cee8b5004 100644 --- a/x/uniswap/handler.go +++ b/x/uniswap/handler.go @@ -188,7 +188,8 @@ func HandleMsgRemoveLiquidity(ctx sdk.Context, msg MsgRemoveLiquidity, k Keeper) // GetInputAmount returns the amount of coins sold (calculated) given the output amount being bought (exact) // The fee is included in the output coins being bought -// https://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdfhttps://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdf +// https://github.com/runtimeverification/verified-smart-contracts/blob/uniswap/uniswap/x-y-k.pdf +// TODO: replace FeeD and FeeN with updated formula using fee as sdk.Dec func getInputAmount(ctx sdk.Context, k Keeper, outputAmt sdk.Int, inputDenom, outputDenom string) sdk.Int { inputReserve := k.GetReservePool(inputDenom) outputReserve := k.GetReservePool(outputDenom) @@ -202,6 +203,7 @@ func getInputAmount(ctx sdk.Context, k Keeper, outputAmt sdk.Int, inputDenom, ou // GetOutputAmount 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 +// TODO: replace FeeD and FeeN with updated formula using fee as sdk.Dec func getOutputAmount(ctx sdk.Context, k Keeper, inputAmt sdk.Int, inputDenom, outputDenom string) sdk.Int { inputReserve := k.GetReservePool(inputDenom) outputReserve := k.GetReservePool(outputDenom) diff --git a/x/uniswap/internal/keeper/keeper.go b/x/uniswap/internal/keeper/keeper.go index bff397b1252..d7bc935dadf 100644 --- a/x/uniswap/internal/keeper/keeper.go +++ b/x/uniswap/internal/keeper/keeper.go @@ -110,14 +110,20 @@ func (keeper Keeper) GetReservePool(ctx sdk.Context, denom string) sdk.Int { return keeper.decode(bz) } +// GetNativeDenom returns the native denomination for this module from the global param store +func (keeper Keeper) GetNativeDenom(ctx sdk.Context) (nativeDenom string) { + keeper.paramSpace.Get(ctx, types.KeyNativeDenom, &nativeDenom) + return +} + // GetFeeParams returns the current FeeParams from the global param store -func (keeper Keeper) GetFeeParams(ctx sdk.Context) (feeParams types.FeeParams) { - keeper.paramSpace.Get(ctx, types.ParamStoreKeyFeeParams, &feeParams) - return feeParams +func (keeper Keeper) GetFeeParams(ctx sdk.Context) (feeParams sdk.Dec) { + keeper.paramSpace.Get(ctx, types.KeyFee, &feeParams) + return } -func (keeper Keeper) SetFeeParams(ctx sdk.Context, feeParams types.FeeParams) { - keeper.paramSpace.Set(ctx, types.ParamStoreKeyFeeParams, &feeParams) +func (keeper Keeper) SetFeeParam(ctx sdk.Context, feeParams sdk.Dec) { + keeper.paramSpace.Set(ctx, types.KeyFee, &feeParams) } // ----------------------------------------------------------------------------- diff --git a/x/uniswap/internal/keeper/querier.go b/x/uniswap/internal/keeper/querier.go index a6e2d1f321f..12ca6952c4c 100644 --- a/x/uniswap/internal/keeper/querier.go +++ b/x/uniswap/internal/keeper/querier.go @@ -61,13 +61,13 @@ func queryLiquidity(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, s func queryParameters(ctx sdk.Context, path []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { switch path[0] { case ParamFee: - bz, err := codec.MarshalJSONIndent(k.cdc, k.GetFeeParams(ctx)) + bz, err := codec.MarshalJSONIndent(k.cdc, k.GetFeeParam(ctx)) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) } return bz, nil - case ParamNativeAsset: - bz, err := codec.MarshalJSONIndent(k.cdc, k.GetNativeAsset()) + case ParamNativeDenom: + bz, err := codec.MarshalJSONIndent(k.cdc, k.GetNativeDenom(ctx)) if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) } diff --git a/x/uniswap/internal/keeper/querier_test.go b/x/uniswap/internal/keeper/querier_test.go index 4bc1410f95a..6eaed59d3c2 100644 --- a/x/uniswap/internal/keeper/querier_test.go +++ b/x/uniswap/internal/keeper/querier_test.go @@ -22,6 +22,12 @@ func TestNewQuerier(t *testing.T) { querier := NewQuerier(keeper) + // query with incorrect path + req.Path = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, "other") + res, err := querier(ctx, []string("other"), req) + require.NotNil(t, err) + require.Nil(res) + // query for non existent address should return an error req.Path = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryBalance) req.Data = keeper.cdc.MustMarshalJSON(addr) @@ -36,19 +42,22 @@ func TestNewQuerier(t *testing.T) { require.NotNil(t, err) require.Nil(res) - // query for set fee params - var feeParams types.FeeParams + // query for fee params + var fee sdk.Dec req.Path = fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, types.QueryParameters, types.ParamFee) req.Data = []byte{} res, err = querier(ctx, []string{types.QueryParameters, types.ParamFee}, req) - keeper.cdc.UnmarshalJSON(res, &feeParams) + keeper.cdc.UnmarshalJSON(res, &fee) require.Nil(t, err) - require.Equal(t, feeParams, types.DefaultParams()) + require.Equal(t, fee, types.DefaultParams().Fee) - // query for set native asset param - req.Path = fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, types.QueryParameters, types.ParamNativeAsset) - res, err = querier(ctx, []string{types.QueryParameters, types.ParamNativeAsset}, req) + // query for native denom param + var nativeDenom string + req.Path = fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, types.QueryParameters, types.ParamNativeDenom) + res, err = querier(ctx, []string{types.QueryParameters, types.ParamNativeDenom}, req) + keeper.cdc.UnmsrahlJSON(res, &nativeDenom) require.Nil(t, err) + require.Equal(t, nativeDenom, types.DefaultParams().NativeDenom) } // TODO: Add tests for valid UNI balance queries and valid liquidity queries diff --git a/x/uniswap/internal/keeper/test_common.go b/x/uniswap/internal/keeper/test_common.go index 2c23b68e8f3..ac9dcee49fb 100644 --- a/x/uniswap/internal/keeper/test_common.go +++ b/x/uniswap/internal/keeper/test_common.go @@ -14,7 +14,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/params" ) -func createTestInput(t *testing.T) (sdk.Context, Keeper) { +func createTestInput(t *testing.Ti, amt sdk.Int, nAccs int64) (sdk.Context, Keeper) { keyAcc := sdk.NewKVStoreKey(auth.StoreKey) keyParams := sdk.NewKVStoreKey(params.StoreKey) tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) @@ -37,10 +37,27 @@ func createTestInput(t *testing.T) (sdk.Context, Keeper) { ak := auth.NewAccountKeeper(types.ModuleCdc, keyAcc, paramsKeeper.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount) bk := bank.NewBaseKeeper(ak, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace) + initialCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, amt)) + accs := createTestAccs(ctx, int(nAccs), initialCoins, &ak) + sk := supply.NewKeeper(cdc, keySupply, ak, bk, suppy.DefaultCodespace) keeper := NewKeeper(cdc, keyUniswap, sk, DefaultCodespace) feeParams := types.NewFeeParams() keeper.SetFeeParams(ctx, types.DefaultParams()) - return ctx, keeper + 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) + } + return } diff --git a/x/uniswap/internal/types/codec.go b/x/uniswap/internal/types/codec.go index 5a85422ace8..cd698f7b5b0 100644 --- a/x/uniswap/internal/types/codec.go +++ b/x/uniswap/internal/types/codec.go @@ -12,7 +12,7 @@ func RegisterCodec(cdc *codec.Codec) { } // module codec -var ModuleCdc = codec.New() +var ModuleCdc *codec.Codec func init() { ModuleCdc = codec.New() diff --git a/x/uniswap/internal/types/keys.go b/x/uniswap/internal/types/keys.go index 135b9ef39f7..3e04517776b 100644 --- a/x/uniswap/internal/types/keys.go +++ b/x/uniswap/internal/types/keys.go @@ -13,8 +13,3 @@ const ( // QuerierRoute is the querier route for the uniswap module. QuerierRoute = StoreKey ) - -// native asset to the module -type ( - NativeAsset string -) diff --git a/x/uniswap/internal/types/msgs_test.go b/x/uniswap/internal/types/msgs_test.go index 65046de06b2..b41b20f561f 100644 --- a/x/uniswap/internal/types/msgs_test.go +++ b/x/uniswap/internal/types/msgs_test.go @@ -16,10 +16,10 @@ func TestMsgSwapOrder(t *testing.T) { expectPass bool }{ {"no input coin", NewMsgSwapOrder(sdk.Coin{}, output, deadline, sender, recipient, true), false}, - {"zero input coin", NewMsgSwapOrder(sdk.NewCoin(denom0, zero), output, deadline, sender, recipient, true), false}, + {"zero input coin", NewMsgSwapOrder(sdk.NewCoin(denom0, sdk.ZeroInt()), output, deadline, sender, recipient, true), false}, {"invalid input denom", NewMsgSwapOrder(sdk.NewCoin(emptyDenom, amt), output, deadline, sender, recipient, true), false}, {"no output coin", NewMsgSwapOrder(input, sdk.Coin{}, deadline, sender, recipient, false), false}, - {"zero output coin", NewMsgSwapOrder(input, sdk.NewCoin(denom1, zero), deadline, sender, recipient, true), false}, + {"zero output coin", NewMsgSwapOrder(input, sdk.NewCoin(denom1, sdk.ZeroInt()), deadline, sender, recipient, true), false}, {"invalid output denom", NewMsgSwapOrder(input, sdk.NewCoin(emptyDenom, amt), deadline, sender, recipient, true), false}, {"swap and coin denomination are equal", NewMsgSwapOrder(input, sdk.NewCoin(denom0, amt), deadline, sender, recipient, true), false}, {"deadline not initialized", NewMsgSwapOrder(input, output, emptyTime, sender, recipient, true), false}, @@ -48,14 +48,14 @@ func TestMsgAddLiquidity(t *testing.T) { msg MsgAddLiquidity expectPass bool }{ - {"no deposit coin", NewMsgAddLiquidity(sdk.Coin{}, amt, one, deadline, sender), false}, - {"zero deposit coin", NewMsgAddLiquidity(sdk.NewCoin(denom1, zero), amt, one, deadline, sender), false}, - {"invalid deposit denom", NewMsgAddLiquidity(sdk.NewCoin(emptyDenom, amt), amt, one, deadline, sender), false}, - {"invalid withdraw amount", NewMsgAddLiquidity(input, zero, one, deadline, sender), false}, - {"invalid minumum reward bound", NewMsgAddLiquidity(input, amt, zero, deadline, sender), false}, - {"deadline not initialized", NewMsgAddLiquidity(input, amt, one, emptyTime, sender), false}, - {"empty sender", NewMsgAddLiquidity(input, amt, one, deadline, emptyAddr), false}, - {"valid MsgAddLiquidity", NewMsgAddLiquidity(input, amt, one, deadline, sender), true}, + {"no deposit coin", NewMsgAddLiquidity(sdk.Coin{}, amt, sdk.OneInt(), deadline, sender), false}, + {"zero deposit coin", NewMsgAddLiquidity(sdk.NewCoin(denom1, sdk.ZeroInt()), amt, sdk.OneInt(), deadline, sender), false}, + {"invalid deposit denom", NewMsgAddLiquidity(sdk.NewCoin(emptyDenom, amt), amt, sdk.OneInt(), deadline, sender), false}, + {"invalid withdraw amount", NewMsgAddLiquidity(input, sdk.ZeroInt(), sdk.OneInt(), deadline, sender), false}, + {"invalid minumum reward bound", NewMsgAddLiquidity(input, amt, sdk.ZeroInt(), deadline, sender), false}, + {"deadline not initialized", NewMsgAddLiquidity(input, amt, sdk.OneInt(), emptyTime, sender), false}, + {"empty sender", NewMsgAddLiquidity(input, amt, sdk.OneInt(), deadline, emptyAddr), false}, + {"valid MsgAddLiquidity", NewMsgAddLiquidity(input, amt, sdk.OneInt(), deadline, sender), true}, } for _, tc := range tests { @@ -77,14 +77,14 @@ func TestMsgRemoveLiquidity(t *testing.T) { msg MsgRemoveLiquidity expectPass bool }{ - {"no withdraw coin", NewMsgRemoveLiquidity(sdk.Coin{}, amt, one, deadline, sender), false}, - {"zero withdraw coin", NewMsgRemoveLiquidity(sdk.NewCoin(denom1, zero), amt, one, deadline, sender), false}, - {"invalid withdraw denom", NewMsgRemoveLiquidity(sdk.NewCoin(emptyDenom, amt), amt, one, deadline, sender), false}, - {"invalid deposit amount", NewMsgRemoveLiquidity(input, zero, one, deadline, sender), false}, - {"invalid minimum native bound", NewMsgRemoveLiquidity(input, amt, zero, deadline, sender), false}, - {"deadline not initialized", NewMsgRemoveLiquidity(input, amt, one, emptyTime, sender), false}, - {"empty sender", NewMsgRemoveLiquidity(input, amt, one, deadline, emptyAddr), false}, - {"valid MsgRemoveLiquidity", NewMsgRemoveLiquidity(input, amt, one, deadline, sender), true}, + {"no withdraw coin", NewMsgRemoveLiquidity(sdk.Coin{}, amt, sdk.OneInt(), deadline, sender), false}, + {"zero withdraw coin", NewMsgRemoveLiquidity(sdk.NewCoin(denom1, sdk.ZeroInt()), amt, sdk.OneInt(), deadline, sender), false}, + {"invalid withdraw denom", NewMsgRemoveLiquidity(sdk.NewCoin(emptyDenom, amt), amt, sdk.OneInt(), deadline, sender), false}, + {"invalid deposit amount", NewMsgRemoveLiquidity(input, sdk.ZeroInt(), sdk.OneInt(), deadline, sender), false}, + {"invalid minimum native bound", NewMsgRemoveLiquidity(input, amt, sdk.ZeroInt(), deadline, sender), false}, + {"deadline not initialized", NewMsgRemoveLiquidity(input, amt, sdk.OneInt(), emptyTime, sender), false}, + {"empty sender", NewMsgRemoveLiquidity(input, amt, sdk.OneInt(), deadline, emptyAddr), false}, + {"valid MsgRemoveLiquidity", NewMsgRemoveLiquidity(input, amt, sdk.OneInt(), deadline, sender), true}, } for _, tc := range tests { diff --git a/x/uniswap/internal/types/params.go b/x/uniswap/internal/types/params.go index d11c42303eb..1137deca9ca 100644 --- a/x/uniswap/internal/types/params.go +++ b/x/uniswap/internal/types/params.go @@ -8,90 +8,71 @@ import ( "github.com/cosmos/cosmos-sdk/x/params" ) -// TODO: come up with better naming for these params? +const ( + DefaultParamspace = ModuleName +) + // Parameter store keys var ( - ParamStoreKeyFeeParams = []byte("feeparams") - ParamStoreKeyNativeAssetParams = []byte("nativeAssetparam") + KeyNativeDenom = []byte("nativeDenom") + KeyFee = []byte("fee") ) -// ParamKeyTable creates a ParamTable for uniswap module. -func ParamKeyTable() params.KeyTable { - return params.NewKeyTable( - ParamStoreKeyNativeAssetParams, NativeAssetParams{}, - ParamStoreKeyFeeParams, FeeParams{}, - ) +// Params defines the fee and native denomination for uniswap +type Params struct { + NativeDenom string `json:"native_denom"` + Fee sdk.Dec `json:"fee"` } -// NativeAssetParams represents the native asset used in the uniswap module. -type NativeAssetParams struct { - Denom string -} +func NewParams(nativeDenom string, fee sdk.Dec) Params { -// NewNativeAssetParams creates a new NativeAssetParams object -func NewNativeAssetParams(denom string) NativeAssetParams { - return NativeAssetParams{ - Denom: denom, + return Params{ + NativeDenom: nativeDenom, + Fee: fee, } } -func (p NativeAssetParams) String() string { - return fmt.Sprintf("Native Asset: %s\n", p.Denom) -} - -// FeeParams represents the numerator and denominator for calculating -// the swap fee. -// Fee = 1 - (FeeN / FeeD) -type FeeParams struct { - FeeN sdk.Int `json:"fee_numerator"` // fee numerator - FeeD sdk.Int `json:"fee_denominator"` // fee denominator -} +// Implements params.ParamSet. +func (p *Params) ParamSetPair() params.ParamSetPairs { -// NewFeeParams creates a new FeeParams object -func NewFeeParams(feeN, feeD sdk.Int) FeeParams { - return FeeParams{ - FeeN: feeN, - FeeD: feeD, + return params.ParamSetPairs{ + {KeyNativeDenom, &p.NativeDenom}, + {KeyFee, &p.Fee}, } } -func (p FeeParams) String() string { - return fmt.Sprintf(`Fee Params: - Fee Numerator: %s - Fee Denominator: %s -`, - p.FeeN, p.FeeD, +// String returns a human readable string representation of the parameters. +func (p Params) String() string { + return fmt.Sprintf(`Params: +Native Denom: %s +Fee: %s`, p.NativeDenom, p.Fee, ) } -type Params struct { - NativeAssetParams NativeAssetParams `json:"native_asset"` - FeeParams FeeParams `json:"fee"` -} - // DefaultParams returns the default uniswap module parameters func DefaultParams() Params { + fee, err := sdk.NewDecFromStr("0.03") + if err != nil { + panic(err) + } + return Params{ - FeeParams: FeeParams{ - FeeN: sdk.NewInt(997), - FeeD: sdk.NewInt(1000), - }, - NativeAssetParams: NativeAssetParams{ - Denom: sdk.DefaultBondDenom, - }, + NativeDenom: sdk.DefaultBondDenom, + Fee: fee, } } -// ValidateParams validates the set params +// ValidateParams validates a set of params func ValidateParams(p Params) error { - if p.FeeParams.FeeN.GT(p.FeeParams.FeeD) { - return fmt.Errorf("fee numerator must be less than or equal to fee denominator") + // TODO: ensure equivalent sdk.validateDenom validation + if strings.TrimSpace(p.NativeDenom) != "" { + return fmt.Errorf("native denomination must not be empty") } - if p.FeeParams.FeeD.Mod(sdk.NewInt(10)) != sdk.NewInt(0) { - return fmt.Errorf("fee denominator must be multiple of 10") + if !p.Fee.IsPositive() { + return fmt.Errorf("fee is not positive: %v", p.Fee) } - if strings.TrimSpace(p.NativeAssetParams.Denom) != "" { - return fmt.Errorf("native asset denomination must not be empty") + if !p.Fee.LT(sdk.OneDec()) { + return fmt.Errorf("fee must be less than one: %v", p.Fee) } return nil } diff --git a/x/uniswap/internal/types/params_test.go b/x/uniswap/internal/types/params_test.go new file mode 100644 index 00000000000..2a1209877e3 --- /dev/null +++ b/x/uniswap/internal/types/params_test.go @@ -0,0 +1,37 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestValidateParams(t *testing.T) { + // check that valid case work + defaultParams := DefaultParams() + err := ValidateParams(defaultParams) + require.Nil(t, err) + + // all cases should return an error + invalidTests := []struct { + name string + params Params + }{ + {"empty native denom", NewParams(" ", defaultParams.Fee)}, + {"native denom with caps", NewParams("aTom", defaultParams.Fee)}, + {"native denom too short", NewParams("a", defaultParams.Fee)}, + {"native denom too long", NewParams("a very long coin denomination", defaultParams.Fee)}, + {"fee is one", NewParams(defaultParams.NativeDenom, sdk.NewDec(1))}, + {"fee above one", NewParams(defaultParams.NativeDenom, sdk.NewDec(2))}, + {"fee is negative", NewParams(defaultParams.NativeDenom, sdk.NewDec(-1))}, + } + + for _, tc := range invalidTests { + t.Run(tc.name, func(t *testing.T) { + err := ValidateParams(tc.params) + require.NotNil(t, err) + }) + } +} diff --git a/x/uniswap/internal/types/querier.go b/x/uniswap/internal/types/querier.go index 37958853cc2..586fef48e5a 100644 --- a/x/uniswap/internal/types/querier.go +++ b/x/uniswap/internal/types/querier.go @@ -11,7 +11,7 @@ const ( QueryParameters = "parameters" ParamFee = "fee" - ParamNativeAsset = "nativeAsset" + ParamNativeDenom = "nativeDenom" ) // QueryBalanceParams defines the params for the query: diff --git a/x/uniswap/internal/types/test_common.go b/x/uniswap/internal/types/test_common.go index 9d1fdedc9cc..225d1b0d59c 100644 --- a/x/uniswap/internal/types/test_common.go +++ b/x/uniswap/internal/types/test_common.go @@ -8,9 +8,7 @@ import ( ) var ( - zero = sdk.NewInt(0) - one = sdk.NewInt(1) - amt = sdk.NewInt(100) + amt = sdk.NewInt(100) sender_pk = ed25519.GenPrivKey().PubKey() recipient_pk = ed25519.GenPrivKey().PubKey() From 9e84bf87308003ba98d51f1d31049442c4c9054d Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Wed, 19 Jun 2019 18:08:57 -0700 Subject: [PATCH 14/15] reflect pr comments, fix some build issues in keeper/ --- x/uniswap/handler.go | 16 +++--- x/uniswap/internal/keeper/keeper.go | 55 +++++++------------- x/uniswap/internal/keeper/querier.go | 31 ++++++----- x/uniswap/internal/keeper/querier_test.go | 16 +++--- x/uniswap/internal/keeper/test_common.go | 35 ++++++++++--- x/uniswap/internal/types/expected_keepers.go | 6 +++ x/uniswap/internal/types/msgs.go | 16 +++++- x/uniswap/internal/types/msgs_test.go | 4 -- x/uniswap/internal/types/querier.go | 30 ----------- x/uniswap/internal/types/test_common.go | 5 +- 10 files changed, 102 insertions(+), 112 deletions(-) diff --git a/x/uniswap/handler.go b/x/uniswap/handler.go index e2cee8b5004..307016e2aa5 100644 --- a/x/uniswap/handler.go +++ b/x/uniswap/handler.go @@ -35,9 +35,9 @@ func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result if msg.IsBuyOrder { calculatedAmount := k.GetInputAmount(ctx, msg.SwapDenom, msg.Amount) - // ensure the maximum amount sender is willing to sell is less than - // the calculated amount - if msg.Input.Amount.LT(calculatedAmount) { + // ensure the calculated amount is less than or equal to the amount + // the sender is willing to pay. + if !calculatedAmount.LTE(msg.Input.Amount) { return types.ErrInvalidBound(DefaultCodespace, "maximum amount (%d) to be sold was exceeded (%d)", msg.Input.Amount, calculatedAmount).Result() } @@ -57,10 +57,10 @@ func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result } } else { - outputAmount := k.GetOutputAmount(ctx, msg.SwapDenom, msg.Amount) - // ensure the minimum amount the sender is willing to buy is greater than - // the calculated amount - if msg.Output.Amount.GT(outputAmount) { + calculatedAmount := k.GetOutputAmount(ctx, msg.SwapDenom, msg.Amount) + // ensure the calculated amount is greater than the minimum amount + // the sender is willing to buy. + if !calculatedAmount.GTE(msg.Output.Amount) { return sdk.ErrInvalidBound(DefaultCodespace, "minimum amount (%d) to be sold was not met (%d)", msg.Output.Amount, calculatedAmount).Result() } @@ -98,7 +98,7 @@ func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.R k.CreateReservePool(ctx, msg.Denom) } - nativeLiqudiity, err := k.GetReservePool(ctx, NativeAsset) + nativeLiqudity, err := k.GetReservePool(ctx, NativeAsset) if err != nil { panic("error retrieving native asset total liquidity") } diff --git a/x/uniswap/internal/keeper/keeper.go b/x/uniswap/internal/keeper/keeper.go index d7bc935dadf..1bc54729f79 100644 --- a/x/uniswap/internal/keeper/keeper.go +++ b/x/uniswap/internal/keeper/keeper.go @@ -2,10 +2,10 @@ package keeper import ( "fmt" + "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/params" - supply "github.com/cosmos/cosmos-sdk/x/supply/keeper" "github.com/cosmos/cosmos-sdk/x/uniswap/internal/types" "github.com/tendermint/tendermint/libs/log" @@ -34,7 +34,7 @@ type Keeper struct { // - facilitating swaps // - users adding liquidity to a reserve pool // - users removing liquidity from a reserve pool -func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, bk types.BankKeeper, sk supply.Keeper, paramSpace params.Subspace) Keeper { +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, bk types.BankKeeper, sk types.SupplyKeeper, paramSpace params.Subspace) Keeper { return Keeper{ storeKey: key, bk: bk, @@ -58,56 +58,56 @@ func (keeper Keeper) CreateReservePool(ctx sdk.Context, denom string) { panic(fmt.Sprintf("reserve pool for %s already exists", denom)) } - store.Set(key, keeper.encode(sdk.ZeroInt())) + store.Set(key, keeper.cdc.MustMarshalBinaryBare(sdk.ZeroInt())) } // GetUNIForAddress returns the total UNI at the provided address -func (keeper Keeper) GetUNIForAddress(ctx sdk.Context, addr sdk.AccAddress) sdk.Int { - var balance sdk.Int - +func (keeper Keeper) GetUNIForAddress(ctx sdk.Context, addr sdk.AccAddress) (balance sdk.Int) { store := ctx.KVStore(keeper.storeKey) key := GetUNIBalancesKey(addr) bz := store.Get(key) if bz != nil { - balance = keeper.decode(bz) + keeper.cdc.MustUnmarshalBinaryBare(bz, &balance) } - return balance + return } // SetUNIForAddress sets the provided UNI at the given address func (keeper Keeper) SetUNIForAddress(ctx sdk.Context, amt sdk.Int, addr sdk.AccAddress) { store := ctx.KVStore(keeper.storeKey) key := GetUNIBalancesKey(addr) - store.Set(key, keeper.encode(amt)) + store.Set(key, keeper.cdc.MustMarshalBinaryBare(amt)) } // GetTotalUNI returns the total UNI currently in existence -func (keeper Keeper) GetTotalUNI(ctx sdk.Context) sdk.Int { +func (keeper Keeper) GetTotalUNI(ctx sdk.Context) (totalUNI sdk.Int) { store := ctx.KVStore(keeper.storeKey) bz := store.Get(TotalUNIKey) if bz == nil { return sdk.ZeroInt() } - totalUNI := keeper.decode(bz) - return totalUNI + keeper.cdc.MustUnmarshalBinaryBare(bz, &totalUNI) + return } // SetTotalLiquidity sets the total UNI func (keeper Keeper) SetTotalUNI(ctx sdk.Context, totalUNI sdk.Int) { store := ctx.KVStore(keeper.storeKey) - store.Set(TotalUNIKey, keeper.encode(totalUNI)) + store.Set(TotalUNIKey, keeper.cdc.MustMarshalBinaryBare(totalUNI)) } // GetReservePool returns the total balance of an reserve pool at the provided denomination -func (keeper Keeper) GetReservePool(ctx sdk.Context, denom string) sdk.Int { +func (keeper Keeper) GetReservePool(ctx sdk.Context, denom string) (balance sdk.Int) { store := ctx.KVStore(keeper.storeKey) bz := store.Get(GetReservePoolKey(denom)) if bz == nil { - panic(fmt.Sprintf("reserve pool for %s does not exist"), denom) + panic(fmt.Sprintf("reserve pool for %s does not exist", denom)) } - return keeper.decode(bz) + + keeper.cdc.MustUnmarshalBinaryBare(bz, &balance) + return } // GetNativeDenom returns the native denomination for this module from the global param store @@ -116,8 +116,8 @@ func (keeper Keeper) GetNativeDenom(ctx sdk.Context) (nativeDenom string) { return } -// GetFeeParams returns the current FeeParams from the global param store -func (keeper Keeper) GetFeeParams(ctx sdk.Context) (feeParams sdk.Dec) { +// GetFeeParam returns the current FeeParam from the global param store +func (keeper Keeper) GetFeeParam(ctx sdk.Context) (feeParams sdk.Dec) { keeper.paramSpace.Get(ctx, types.KeyFee, &feeParams) return } @@ -125,22 +125,3 @@ func (keeper Keeper) GetFeeParams(ctx sdk.Context) (feeParams sdk.Dec) { func (keeper Keeper) SetFeeParam(ctx sdk.Context, feeParams sdk.Dec) { keeper.paramSpace.Set(ctx, types.KeyFee, &feeParams) } - -// ----------------------------------------------------------------------------- -// Misc. - -func (keeper Keeper) decode(bz []byte) (balance sdk.Int) { - err := keeper.cdc.UnmarshalBinaryBare(bz, &balance) - if err != nil { - panic(err) - } - return -} - -func (keeper Keeper) encode(balance sdk.Int) (bz []byte) { - bz, err := keeper.cdc.MarshalBinaryBare(balance) - if err != nil { - panic(err) - } - return -} diff --git a/x/uniswap/internal/keeper/querier.go b/x/uniswap/internal/keeper/querier.go index 12ca6952c4c..b71be2a672d 100644 --- a/x/uniswap/internal/keeper/querier.go +++ b/x/uniswap/internal/keeper/querier.go @@ -1,7 +1,12 @@ package keeper import ( + "fmt" + + abci "github.com/tendermint/tendermint/abci/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/uniswap/internal/types" ) // NewQuerier creates a querier for uniswap REST endpoints @@ -13,7 +18,7 @@ func NewQuerier(k Keeper) sdk.Querier { case types.QueryLiquidity: return queryLiquidity(ctx, req, k) case types.QueryParameters: - return queryParameters(ctx, req, k) + return queryParameters(ctx, path[1:], req, k) default: return nil, sdk.ErrUnknownRequest(fmt.Sprintf("%s is not a valid query request path", req.Path)) } @@ -23,15 +28,15 @@ func NewQuerier(k Keeper) sdk.Querier { // queryBalance returns the provided addresses UNI balance upon success // or an error if the query fails. func queryBalance(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { - var params QueryBalance - err := k.cdc.UnmarshalJSON(req.Data, ¶ms) + var address sdk.AccAddress + err := k.cdc.UnmarshalJSON(req.Data, &address) if err != nil { return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) } - balance := k.GetUNIForAddress(params.Address) + balance := k.GetUNIForAddress(ctx, address) - bz, err := codec.MarshalJSONIndent(k.cdc, balance) + bz, err := k.cdc.MarshalJSONIndent(balance, "", " ") if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) } @@ -41,15 +46,15 @@ func queryBalance(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk // queryLiquidity returns the total liquidity avaliable for the provided denomination // upon success or an error if the query fails. func queryLiquidity(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { - var params QueryLiquidity - err := k.cdc.UnmarshalJSON(req.Data, ¶ms) + var denom string + err := k.cdc.UnmarshalJSON(req.Data, &denom) if err != nil { return nil, sdk.ErrUnknownRequest(sdk.AppendMsgToErr("incorrectly formatted request data", err.Error())) } - liquidity := k.GetReservePool(ctx, params.Denom) + liquidity := k.GetReservePool(ctx, denom) - bz, err := codec.MarshalJSONIndent(k.cdc, liquidity) + bz, err := k.cdc.MarshalJSONIndent(liquidity, "", " ") if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) } @@ -60,14 +65,14 @@ func queryLiquidity(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, s // or an error if the query fails func queryParameters(ctx sdk.Context, path []string, req abci.RequestQuery, k Keeper) ([]byte, sdk.Error) { switch path[0] { - case ParamFee: - bz, err := codec.MarshalJSONIndent(k.cdc, k.GetFeeParam(ctx)) + case types.ParamFee: + bz, err := k.cdc.MarshalJSONIndent(k.GetFeeParam(ctx), "", " ") if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) } return bz, nil - case ParamNativeDenom: - bz, err := codec.MarshalJSONIndent(k.cdc, k.GetNativeDenom(ctx)) + case types.ParamNativeDenom: + bz, err := k.cdc.MarshalJSONIndent(k.GetNativeDenom(ctx), "", " ") if err != nil { return nil, sdk.ErrInternal(sdk.AppendMsgToErr("could not marshal result to JSON", err.Error())) } diff --git a/x/uniswap/internal/keeper/querier_test.go b/x/uniswap/internal/keeper/querier_test.go index 6eaed59d3c2..971f30ce081 100644 --- a/x/uniswap/internal/keeper/querier_test.go +++ b/x/uniswap/internal/keeper/querier_test.go @@ -12,8 +12,8 @@ import ( ) func TestNewQuerier(t *testing.T) { - cdc := codec.New() - ctx, keeper := createNewInput() + cdc := makeTestCodec() + ctx, keeper, accs := createTestInput(t, sdk.NewInt(100), 2) req := abci.RequestQuery{ Path: "", @@ -24,23 +24,23 @@ func TestNewQuerier(t *testing.T) { // query with incorrect path req.Path = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, "other") - res, err := querier(ctx, []string("other"), req) + res, err := querier(ctx, []string{"other"}, req) require.NotNil(t, err) - require.Nil(res) + require.Nil(t, res) // query for non existent address should return an error req.Path = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryBalance) - req.Data = keeper.cdc.MustMarshalJSON(addr) - res, err := querier(ctx, []string{"balance"}, req) + req.Data = keeper.cdc.MustMarshalJSON(accs[0].Address) + res, err = querier(ctx, []string{"balance"}, req) require.NotNil(t, err) - require.Nil(res) + require.Nil(t, 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.NotNil(t, err) - require.Nil(res) + require.Nil(t, res) // query for fee params var fee sdk.Dec diff --git a/x/uniswap/internal/keeper/test_common.go b/x/uniswap/internal/keeper/test_common.go index ac9dcee49fb..2e62adab5b5 100644 --- a/x/uniswap/internal/keeper/test_common.go +++ b/x/uniswap/internal/keeper/test_common.go @@ -4,22 +4,42 @@ import ( "testing" abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto/secp256k1" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/store" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/uniswap/internal/types" + //supply "github.com/cosmos/cosmos-sdk/x/supply/types" ) -func createTestInput(t *testing.Ti, amt sdk.Int, nAccs int64) (sdk.Context, Keeper) { +// create a codec used only for testing +func makeTestCodec() *codec.Codec { + var cdc = codec.New() + + bank.RegisterCodec(cdc) + auth.RegisterCodec(cdc) + supply.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 := sdk.NewKVStoreKey(auth.StoreKey) keyParams := sdk.NewKVStoreKey(params.StoreKey) tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) keySupply := sdk.NewKVStoreKey(supply.StoreKey) - keyUniswap := sdk.NewKVStoreKey(uniswap.StoreKey) + keyUniswap := sdk.NewKVStoreKey(types.StoreKey) db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) @@ -31,19 +51,20 @@ func createTestInput(t *testing.Ti, amt sdk.Int, nAccs int64) (sdk.Context, Keep err := ms.LoadLatestVersion() require.Nil(t, err) + cdc := makeTestCodec() ctx := sdk.NewContext(ms, abci.Header{ChainID: "uniswap-chain"}, false, log.NewNopLogger()) - pk := params.NewKeeper(types.ModuleCdc, keyParams, tkeyParams, params.Defaultcodespace) - ak := auth.NewAccountKeeper(types.ModuleCdc, keyAcc, paramsKeeper.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount) + pk := params.NewKeeper(types.ModuleCdc, keyParams, tkeyParams, params.DefaultCodespace) + ak := auth.NewAccountKeeper(types.ModuleCdc, keyAcc, pk.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount) bk := bank.NewBaseKeeper(ak, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace) initialCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, amt)) accs := createTestAccs(ctx, int(nAccs), initialCoins, &ak) sk := supply.NewKeeper(cdc, keySupply, ak, bk, suppy.DefaultCodespace) - keeper := NewKeeper(cdc, keyUniswap, sk, DefaultCodespace) - feeParams := types.NewFeeParams() - keeper.SetFeeParams(ctx, types.DefaultParams()) + keeper := NewKeeper(cdc, keyUniswap, bk, sk, types.DefaultCodespace) + params := types.DefaultParams() + keeper.SetFeeParam(ctx, params.Fee) return ctx, keeper, accs } diff --git a/x/uniswap/internal/types/expected_keepers.go b/x/uniswap/internal/types/expected_keepers.go index 17e35b7b8a2..723b0e87376 100644 --- a/x/uniswap/internal/types/expected_keepers.go +++ b/x/uniswap/internal/types/expected_keepers.go @@ -8,3 +8,9 @@ import ( type BankKeeper interface { HasCoins(ctx sdk.Context, addr sdk.AccAddress, amt sdk.Coins) bool } + +// SupplyKeeper defines the expected supply keeper +type SupplyKeeper interface { + 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 +} diff --git a/x/uniswap/internal/types/msgs.go b/x/uniswap/internal/types/msgs.go index 9488cd4013f..318b0f4be09 100644 --- a/x/uniswap/internal/types/msgs.go +++ b/x/uniswap/internal/types/msgs.go @@ -55,10 +55,16 @@ func (msg MsgSwapOrder) Type() string { return "swap_order" } // ValidateBasic Implements Msg. func (msg MsgSwapOrder) ValidateBasic() sdk.Error { if !msg.Input.IsValid() { - return sdk.ErrInvalidCoins("coin is invalid: " + msg.Input.String()) + return sdk.ErrInvalidCoins("input coin is invalid: " + msg.Input.String()) + } + if msg.Input.IsZero() { + return sdk.ErrInvalidCoins("input coin is zero: " + msg.Input.String()) } if !msg.Output.IsValid() { - return sdk.ErrInvalidCoins("coin is invalid: " + msg.Output.String()) + return sdk.ErrInvalidCoins("output coin is invalid: " + msg.Output.String()) + } + if msg.Output.IsZero() { + return sdk.ErrInvalidCoins("output coin is zero: " + msg.Output.String()) } if msg.Input.Denom == msg.Output.Denom { return ErrEqualDenom(DefaultCodespace) @@ -124,6 +130,9 @@ func (msg MsgAddLiquidity) ValidateBasic() sdk.Error { if !msg.Deposit.IsValid() { return sdk.ErrInvalidCoins("coin is invalid: " + msg.Deposit.String()) } + if msg.Deposit.IsZero() { + return sdk.ErrInvalidCoins("coin is zero: " + msg.Deposit.String()) + } if !msg.DepositAmount.IsPositive() { return ErrNotPositive(DefaultCodespace, "deposit amount provided is not positive") } @@ -191,6 +200,9 @@ func (msg MsgRemoveLiquidity) ValidateBasic() sdk.Error { if !msg.Withdraw.IsValid() { return sdk.ErrInvalidCoins("coin is invalid: " + msg.Withdraw.String()) } + if msg.Withdraw.IsZero() { + return sdk.ErrInvalidCoins("coin is zero: " + msg.Withdraw.String()) + } if !msg.MinNative.IsPositive() { return ErrNotPositive(DefaultCodespace, "minimum native amount is not positive") } diff --git a/x/uniswap/internal/types/msgs_test.go b/x/uniswap/internal/types/msgs_test.go index b41b20f561f..6851b3d2d3d 100644 --- a/x/uniswap/internal/types/msgs_test.go +++ b/x/uniswap/internal/types/msgs_test.go @@ -17,10 +17,8 @@ func TestMsgSwapOrder(t *testing.T) { }{ {"no input coin", NewMsgSwapOrder(sdk.Coin{}, output, deadline, sender, recipient, true), false}, {"zero input coin", NewMsgSwapOrder(sdk.NewCoin(denom0, sdk.ZeroInt()), output, deadline, sender, recipient, true), false}, - {"invalid input denom", NewMsgSwapOrder(sdk.NewCoin(emptyDenom, amt), output, deadline, sender, recipient, true), false}, {"no output coin", NewMsgSwapOrder(input, sdk.Coin{}, deadline, sender, recipient, false), false}, {"zero output coin", NewMsgSwapOrder(input, sdk.NewCoin(denom1, sdk.ZeroInt()), deadline, sender, recipient, true), false}, - {"invalid output denom", NewMsgSwapOrder(input, sdk.NewCoin(emptyDenom, amt), deadline, sender, recipient, true), false}, {"swap and coin denomination are equal", NewMsgSwapOrder(input, sdk.NewCoin(denom0, amt), deadline, sender, recipient, true), false}, {"deadline not initialized", NewMsgSwapOrder(input, output, emptyTime, sender, recipient, true), false}, {"no sender", NewMsgSwapOrder(input, output, deadline, emptyAddr, recipient, true), false}, @@ -50,7 +48,6 @@ func TestMsgAddLiquidity(t *testing.T) { }{ {"no deposit coin", NewMsgAddLiquidity(sdk.Coin{}, amt, sdk.OneInt(), deadline, sender), false}, {"zero deposit coin", NewMsgAddLiquidity(sdk.NewCoin(denom1, sdk.ZeroInt()), amt, sdk.OneInt(), deadline, sender), false}, - {"invalid deposit denom", NewMsgAddLiquidity(sdk.NewCoin(emptyDenom, amt), amt, sdk.OneInt(), deadline, sender), false}, {"invalid withdraw amount", NewMsgAddLiquidity(input, sdk.ZeroInt(), sdk.OneInt(), deadline, sender), false}, {"invalid minumum reward bound", NewMsgAddLiquidity(input, amt, sdk.ZeroInt(), deadline, sender), false}, {"deadline not initialized", NewMsgAddLiquidity(input, amt, sdk.OneInt(), emptyTime, sender), false}, @@ -79,7 +76,6 @@ func TestMsgRemoveLiquidity(t *testing.T) { }{ {"no withdraw coin", NewMsgRemoveLiquidity(sdk.Coin{}, amt, sdk.OneInt(), deadline, sender), false}, {"zero withdraw coin", NewMsgRemoveLiquidity(sdk.NewCoin(denom1, sdk.ZeroInt()), amt, sdk.OneInt(), deadline, sender), false}, - {"invalid withdraw denom", NewMsgRemoveLiquidity(sdk.NewCoin(emptyDenom, amt), amt, sdk.OneInt(), deadline, sender), false}, {"invalid deposit amount", NewMsgRemoveLiquidity(input, sdk.ZeroInt(), sdk.OneInt(), deadline, sender), false}, {"invalid minimum native bound", NewMsgRemoveLiquidity(input, amt, sdk.ZeroInt(), deadline, sender), false}, {"deadline not initialized", NewMsgRemoveLiquidity(input, amt, sdk.OneInt(), emptyTime, sender), false}, diff --git a/x/uniswap/internal/types/querier.go b/x/uniswap/internal/types/querier.go index 586fef48e5a..300f8f7fe9b 100644 --- a/x/uniswap/internal/types/querier.go +++ b/x/uniswap/internal/types/querier.go @@ -1,9 +1,5 @@ package types -import ( - sdk "github.com/cosmos/cosmos-sdk/types" -) - const ( // Query endpoints supported by the uniswap querier QueryBalance = "balance" @@ -13,29 +9,3 @@ const ( ParamFee = "fee" ParamNativeDenom = "nativeDenom" ) - -// QueryBalanceParams defines the params for the query: -// - 'custom/uniswap/balance' -type QueryBalanceParams struct { - Address sdk.AccAddress -} - -// NewQueryBalanceParams is a constructor function for QueryBalanceParams -func NewQueryBalanceParams(address sdk.AccAddress) QueryBalanceParams { - return QueryBalanceParams{ - Address: address, - } -} - -// QueryLiquidity defines the params for the query: -// - 'custom/uniswap/liquidity' -type QueryLiquidityParams struct { - Denom string -} - -// NewQueryLiquidityParams is a constructor function for QueryLiquidityParams -func NewQueryLiquidityParams(denom string) QueryLiquidityParams { - return QueryLiquidityParams{ - Denom: denom, - } -} diff --git a/x/uniswap/internal/types/test_common.go b/x/uniswap/internal/types/test_common.go index 225d1b0d59c..4182b6dd94c 100644 --- a/x/uniswap/internal/types/test_common.go +++ b/x/uniswap/internal/types/test_common.go @@ -22,7 +22,6 @@ var ( output = sdk.NewCoin(denom1, sdk.NewInt(500)) deadline = time.Now() - emptyAddr sdk.AccAddress - emptyDenom = " " - emptyTime time.Time + emptyAddr sdk.AccAddress + emptyTime time.Time ) From cacbff3ade637038cdbf46aeb5c265485376dfe1 Mon Sep 17 00:00:00 2001 From: Colin Axner Date: Thu, 20 Jun 2019 17:18:33 -0700 Subject: [PATCH 15/15] fixed some more build errors --- x/uniswap/alias.go | 9 +++- x/uniswap/genesis.go | 25 ++++------- x/uniswap/handler.go | 55 +++++++++++------------ x/uniswap/internal/keeper/keeper.go | 7 +++ x/uniswap/internal/keeper/querier_test.go | 8 ++-- x/uniswap/internal/keeper/test_common.go | 6 ++- x/uniswap/internal/types/params.go | 9 ++-- x/uniswap/module.go | 7 +-- 8 files changed, 68 insertions(+), 58 deletions(-) diff --git a/x/uniswap/alias.go b/x/uniswap/alias.go index 51d35a1307a..0f5d5acf9a1 100644 --- a/x/uniswap/alias.go +++ b/x/uniswap/alias.go @@ -7,12 +7,17 @@ import ( type ( Keeper = keeper.Keeper - NativeAsset = types.NativeAsset MsgSwapOrder = types.MsgSwapOrder MsgAddLiquidity = types.MsgAddLiquidity MsgRemoveLiquidity = types.MsgRemoveLiquidity ) +var ( + ErrInvalidDeadline = types.ErrInvalidDeadline + ErrNotPositive = types.ErrNotPositive +) + const ( - ModuleName = types.ModuleName + DefaultCodespace = types.DefaultCodespace + ModuleName = types.ModuleName ) diff --git a/x/uniswap/genesis.go b/x/uniswap/genesis.go index b5a4c9fbd14..835c2f6b0f8 100644 --- a/x/uniswap/genesis.go +++ b/x/uniswap/genesis.go @@ -1,49 +1,42 @@ package uniswap import ( - "fmt" - "strings" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/uniswap/internal/types" ) +// TODO: ... + // GenesisState - uniswap genesis state type GenesisState struct { - NativeAssetDenom string `json:"native_asset_denom"` - FeeParams FeeParams `json:"fee_params"` + Params types.Params `json:"params"` } // NewGenesisState is the constructor function for GenesisState -func NewGenesisState(nativeAssetDenom string, feeParams FeeParams) GenesisState { +func NewGenesisState(params types.Params) GenesisState { return GenesisState{ - NativeAssetDenom: nativeAssetDenom, - FeeParams: feeParams, + Params: params, } } // DefaultGenesisState creates a default GenesisState object func DefaultGenesisState() GenesisState { - return NewGenesisState(sdk.DefaultBondDenom, DefaultParams()) + return NewGenesisState(types.DefaultParams()) } // InitGenesis new uniswap genesis -// TODO func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { } // ExportGenesis returns a GenesisState for a given context and keeper. -// TODO func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { - return NewGenesisState(sdk.DefaultBondDenom) + return NewGenesisState(types.DefaultParams()) } // ValidateGenesis - placeholder function func ValidateGenesis(data GenesisState) error { - if strings.TrimSpace(data.NativeAssetDenom) == "" { - fmt.Errorf("no native asset denomination provided") - } - if err := data.FeeParams.Validate(); err != nil { + if err := types.ValidateParams(data.Params); err != nil { return err } return nil diff --git a/x/uniswap/handler.go b/x/uniswap/handler.go index 307016e2aa5..3a68a211ec4 100644 --- a/x/uniswap/handler.go +++ b/x/uniswap/handler.go @@ -4,7 +4,6 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/supply/types" ) // NewHandler routes the messages to the handlers @@ -29,21 +28,21 @@ func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result var caclulatedAmount sdk.Int // check that deadline has not passed - if msg.ctx.BlockTime.After(msg.Deadline) { - return ErrInvalidDeadline(DefaultCodespace, "deadline has passed for MsgSwapOrder") + if ctx.BlockHeader().Time.After(msg.Deadline) { + return ErrInvalidDeadline(DefaultCodespace, "deadline has passed for MsgSwapOrder").Result() } if msg.IsBuyOrder { - calculatedAmount := k.GetInputAmount(ctx, msg.SwapDenom, msg.Amount) + calculatedAmount := getInputAmount(ctx, k, msg.Output.Amount, msg.Input.Denom, msg.Input.Denom) // ensure the calculated amount is less than or equal to the amount // the sender is willing to pay. if !calculatedAmount.LTE(msg.Input.Amount) { - return types.ErrInvalidBound(DefaultCodespace, "maximum amount (%d) to be sold was exceeded (%d)", msg.Input.Amount, calculatedAmount).Result() + return ErrNotPositive(DefaultCodespace, fmt.Sprintf("maximum amount (%d) to be sold was exceeded (%d)", msg.Input.Amount, calculatedAmount)).Result() } coinSold := sdk.NewCoins(sdk.NewCoin(msg.Input.Denom, calculatedAmount)) - if !s.bk.HasCoins(ctx, msg.Sender, coinSold) { - return ErrInsufficientAmount(DefaultCodespace, "sender account does not have sufficient funds to fulfill the swap order").Result() + if !k.bk.HasCoins(ctx, msg.Sender, coinSold) { + return sdk.ErrInsufficientCoins("sender account does not have sufficient funds to fulfill the swap order").Result() } err := k.sk.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, coinSold) @@ -57,16 +56,17 @@ func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result } } else { - calculatedAmount := k.GetOutputAmount(ctx, msg.SwapDenom, msg.Amount) + calculatedAmount := getOutputAmount(ctx, k, msg.Input.Amount, msg.Input.Denom, msg.Output.Denom) // ensure the calculated amount is greater than the minimum amount // the sender is willing to buy. if !calculatedAmount.GTE(msg.Output.Amount) { - return sdk.ErrInvalidBound(DefaultCodespace, "minimum amount (%d) to be sold was not met (%d)", msg.Output.Amount, calculatedAmount).Result() + // TODO: add custom error for these + return Err(DefaultCodespace, "minimum amount (%d) to be sold was not met (%d)", msg.Output.Amount, calculatedAmount).Result() } coinSold := sdk.NewCoins(msg.Input) - if !s.bk.HasCoins(ctx, msg.Sender, coinSold) { - return ErrInsifficientAmount(DefaultCodespace, "sender account does not have sufficient funds to fulfill the swap order").Result() + if !k.bk.HasCoins(ctx, msg.Sender, coinSold) { + return sdk.ErrInsufficientCoins("sender account does not have sufficient funds to fulfill the swap order").Result() } err := k.sk.SendCoinsFromAccountToModule(ctx, msg.Sender, ModuleName, sdk.NewCoins(msg.Input)) @@ -88,36 +88,31 @@ func HandleMsgSwapOrder(ctx sdk.Context, msg MsgSwapOrder, k Keeper) sdk.Result // If the reserve pool does not exist, it will be created. func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.Result { // check that deadline has not passed - if msg.ctx.BlockTime.After(msg.Deadline) { - return ErrInvalidDeadline(DefaultCodespace, "deadline has passed for MsgAddLiquidity") + if ctx.BlockHeader().Time.After(msg.Deadline) { + return ErrInvalidDeadline(DefaultCodespace, "deadline has passed for MsgAddLiquidity").Result() } // create reserve pool if it does not exist - coinLiquidity := k.GetReservePool(ctx, msg.Deposit.Denom) - if err != nil { - k.CreateReservePool(ctx, msg.Denom) - } - - nativeLiqudity, err := k.GetReservePool(ctx, NativeAsset) - if err != nil { - panic("error retrieving native asset total liquidity") + var coinLiquidity sdk.Int + if !k.HasReservePool(ctx, msg.Deposit.Denom) { + k.CreateReservePool(ctx, msg.Deposit.Denom) + } else { + coinLiquidity = k.GetReservePool(ctx, msg.Deposit.Denom) } - totalUNI, err := k.GetTotalUNI(ctx) - if err != nil { - panic("error retrieving total UNI") - } + nativeLiquidity := k.GetReservePool(ctx, k.GetNativeDenom(ctx)) + totalUNI := k.GetTotalUNI(ctx) // calculate amount of UNI to be minted for sender // and coin amount to be deposited MintedUNI := (totalUNI.Mul(msg.DepositAmount)).Quo(nativeLiquidity) coinAmountDeposited := (totalUNI.Mul(msg.DepositAmount)).Quo(nativeLiquidity) - nativeCoinDeposited := sdk.NewCoin(NativeAsset, msg.DepositAmount) + nativeCoinDeposited := sdk.NewCoin(k.GetNativeDenom(ctx), msg.DepositAmount) coinDeposited := sdk.NewCoin(msg.Deposit.Denom, coinAmountDeposited) coins := sdk.NewCoins(nativeCoinDeposited, coinDeposited) - if !s.bk.HasCoins(ctx, msg.Sender, coins) { - return ErrInsufficientCoins(DefaultCodespace, "sender does not have sufficient funds to add liquidity").Result() + if !k.bk.HasCoins(ctx, msg.Sender, coins) { + return sdk.ErrInsufficientCoins("sender does not have sufficient funds to add liquidity").Result() } // transfer deposited liquidity into uniswaps ModuleAccount @@ -141,7 +136,7 @@ func HandleMsgAddLiquidity(ctx sdk.Context, msg MsgAddLiquidity, k Keeper) sdk.R // HandleMsgRemoveLiquidity handler for MsgRemoveLiquidity func HandleMsgRemoveLiquidity(ctx sdk.Context, msg MsgRemoveLiquidity, k Keeper) sdk.Result { // check that deadline has not passed - if msg.ctx.BlockTime.After(msg.Deadline) { + if ctx.BlockHeader().Time.After(msg.Deadline) { return ErrInvalidDeadline(DefaultCodespace, "deadline has passed for MsgRemoveLiquidity") } @@ -169,7 +164,7 @@ func HandleMsgRemoveLiquidity(ctx sdk.Context, msg MsgRemoveLiquidity, k Keeper) exchangeCoin = sdk.NewCoin(msg.Withdraw.Denom, coinWithdrawn) // transfer withdrawn liquidity from uniswaps ModuleAccount to sender's account - err := k.sk.SendCoinsFromModuleToAccount(ctx, msg.Sender, ModuleName, sdk.NewCoins(nativeCoin, coinDeposited)) + err = k.sk.SendCoinsFromModuleToAccount(ctx, msg.Sender, ModuleName, sdk.NewCoins(nativeCoin, coinDeposited)) if err != nil { return err.Result() } diff --git a/x/uniswap/internal/keeper/keeper.go b/x/uniswap/internal/keeper/keeper.go index 1bc54729f79..31c6474dab3 100644 --- a/x/uniswap/internal/keeper/keeper.go +++ b/x/uniswap/internal/keeper/keeper.go @@ -110,6 +110,13 @@ func (keeper Keeper) GetReservePool(ctx sdk.Context, denom string) (balance sdk. return } +// HasReservePool returns true if the reserve pool exists +func (keeper Keeper) HasReservePool(ctx sdk.Context, denom string) bool { + store := ctx.KVStore(keeper.storeKey) + bz := store.Get(GetReservePoolKey(denom)) + return bz != nil +} + // GetNativeDenom returns the native denomination for this module from the global param store func (keeper Keeper) GetNativeDenom(ctx sdk.Context) (nativeDenom string) { keeper.paramSpace.Get(ctx, types.KeyNativeDenom, &nativeDenom) diff --git a/x/uniswap/internal/keeper/querier_test.go b/x/uniswap/internal/keeper/querier_test.go index 971f30ce081..c29e60c23a1 100644 --- a/x/uniswap/internal/keeper/querier_test.go +++ b/x/uniswap/internal/keeper/querier_test.go @@ -1,5 +1,7 @@ package keeper +// TODO: uncomment when supply is in master +/* import ( "fmt" "testing" @@ -30,7 +32,7 @@ func TestNewQuerier(t *testing.T) { // query for non existent address should return an error req.Path = fmt.Sprintf("custom/%s/%s", types.QuerierRoute, types.QueryBalance) - req.Data = keeper.cdc.MustMarshalJSON(accs[0].Address) + req.Data = keeper.cdc.MustMarshalJSON(accs[0].GetAddress()) res, err = querier(ctx, []string{"balance"}, req) require.NotNil(t, err) require.Nil(t, res) @@ -55,9 +57,9 @@ func TestNewQuerier(t *testing.T) { var nativeDenom string req.Path = fmt.Sprintf("custom/%s/%s/%s", types.QuerierRoute, types.QueryParameters, types.ParamNativeDenom) res, err = querier(ctx, []string{types.QueryParameters, types.ParamNativeDenom}, req) - keeper.cdc.UnmsrahlJSON(res, &nativeDenom) + keeper.cdc.UnmarshalJSON(res, &nativeDenom) require.Nil(t, err) require.Equal(t, nativeDenom, types.DefaultParams().NativeDenom) } - +*/ // TODO: Add tests for valid UNI balance queries and valid liquidity queries diff --git a/x/uniswap/internal/keeper/test_common.go b/x/uniswap/internal/keeper/test_common.go index 2e62adab5b5..36f05a9c2e2 100644 --- a/x/uniswap/internal/keeper/test_common.go +++ b/x/uniswap/internal/keeper/test_common.go @@ -1,5 +1,6 @@ package keeper +/* import ( "testing" @@ -20,6 +21,8 @@ import ( //supply "github.com/cosmos/cosmos-sdk/x/supply/types" ) +// TODO: uncomment when supply is merged into master + // create a codec used only for testing func makeTestCodec() *codec.Codec { var cdc = codec.New() @@ -62,7 +65,7 @@ func createTestInput(t *testing.T, amt sdk.Int, nAccs int64) (sdk.Context, Keepe accs := createTestAccs(ctx, int(nAccs), initialCoins, &ak) sk := supply.NewKeeper(cdc, keySupply, ak, bk, suppy.DefaultCodespace) - keeper := NewKeeper(cdc, keyUniswap, bk, sk, types.DefaultCodespace) + keeper := NewKeeper(cdc, keyUniswap, bk, sk, pk.Subspace(types.DefaultParamspace)) params := types.DefaultParams() keeper.SetFeeParam(ctx, params.Fee) @@ -82,3 +85,4 @@ func createTestAccs(ctx sdk.Context, numAccs int, initialCoins sdk.Coins, ak *au } return } +*/ diff --git a/x/uniswap/internal/types/params.go b/x/uniswap/internal/types/params.go index 1137deca9ca..95bab0d2900 100644 --- a/x/uniswap/internal/types/params.go +++ b/x/uniswap/internal/types/params.go @@ -25,16 +25,19 @@ type Params struct { } func NewParams(nativeDenom string, fee sdk.Dec) Params { - return Params{ NativeDenom: nativeDenom, Fee: fee, } } -// Implements params.ParamSet. -func (p *Params) ParamSetPair() params.ParamSetPairs { +// ParamKeyTable returns the KeyTable for uniswap module +func ParamKeyTable() params.KeyTable { + return params.NewKeyTable().RegisterParamSet(&Params{}) +} +// Implements params.ParamSet. +func (p *Params) ParamSetPairs() params.ParamSetPairs { return params.ParamSetPairs{ {KeyNativeDenom, &p.NativeDenom}, {KeyFee, &p.Fee}, diff --git a/x/uniswap/module.go b/x/uniswap/module.go index b698b84feba..52e5270ba3d 100644 --- a/x/uniswap/module.go +++ b/x/uniswap/module.go @@ -9,12 +9,13 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" abci "github.com/tendermint/tendermint/abci/types" ) var ( - _ sdk.AppModule = AppModule{} - _ sdk.AppModuleBasic = AppModuleBasic{} + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} ) // AppModuleBasic app module basics object @@ -63,7 +64,7 @@ type AppModule struct { } // NewAppModule creates a new AppModule object -func NewAppModule(keeper Keeper) sdk.AppModule { +func NewAppModule(keeper Keeper) module.AppModule { return sdk.NewGenesisOnlyAppModule(AppModule{ AppModuleBasic: AppModuleBasic{}, keeper: keeper,