Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(vpool): create pools gov handler #725

Merged
merged 17 commits into from
Jul 26, 2022
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#712](https://github.com/NibiruChain/nibiru/pull/712) Add funding rate calculation and `FundingRateChangedEvent`.

### Upgrades

* [#725](https://github.com/NibiruChain/nibiru/pull/725) Add governance handler for creating new virtual pools.
* [#702](https://github.com/NibiruChain/nibiru/pull/702) Add upgrade handler for v0.10.0.

## [v0.9.2](https://github.com/NibiruChain/nibiru/releases/tag/v0.9.2) - 2022-07-11
Expand Down
6 changes: 5 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"os"
"path/filepath"

vpoolcli "github.com/NibiruChain/nibiru/x/vpool/client/cli"

pricefeedcli "github.com/NibiruChain/nibiru/x/pricefeed/client/cli"

"github.com/cosmos/cosmos-sdk/baseapp"
Expand Down Expand Up @@ -154,6 +156,7 @@ var (
upgradeclient.ProposalHandler,
upgradeclient.CancelProposalHandler,
pricefeedcli.AddOracleProposalHandler,
vpoolcli.CreatePoolProposalHandler,
// pricefeedcli.RemoveOracleProposalHandler, // TODO
ibcclientclient.UpdateClientProposalHandler,
ibcclientclient.UpgradeProposalHandler,
Expand Down Expand Up @@ -481,7 +484,8 @@ func NewNibiruApp(
AddRoute(distrtypes.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.DistrKeeper)).
AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.UpgradeKeeper)).
AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)).
AddRoute(pricefeedtypes.RouterKey, pricefeed.NewPricefeedProposalHandler(app.PricefeedKeeper))
AddRoute(pricefeedtypes.RouterKey, pricefeed.NewPricefeedProposalHandler(app.PricefeedKeeper)).
AddRoute(vpooltypes.RouterKey, vpool.NewCreatePoolProposalHandler(app.VpoolKeeper))

app.TransferKeeper = ibctransferkeeper.NewKeeper(
appCodec,
Expand Down
43 changes: 43 additions & 0 deletions proto/vpool/v1/gov.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
syntax = "proto3";

package nibiru.vpool.v1;

import "gogoproto/gogo.proto";

option go_package = "github.com/NibiruChain/nibiru/x/vpool/types";

message CreatePoolProposal {
string title = 1;
string description = 2;
// pair represents the pair of the vpool.
string pair = 3;
// trade_limit_ratio represents the limit on trading amounts.
string trade_limit_ratio = 4 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// quote_asset_reserve is the amount of quote asset the pool will be initialized with.
string quote_asset_reserve = 5 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// base_asset_reserve is the amount of base asset the pool will be initialized with.
string base_asset_reserve = 6 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];

// fluctuation_limit_ratio represents the maximum price
// percentage difference a trade can create on the pool.
string fluctuation_limit_ratio = 7 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];

// max_oracle_spread_ratio represents the maximum price percentage
// difference that can exist between oracle price and vpool prices after a trade.
string max_oracle_spread_ratio = 8 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
}
110 changes: 110 additions & 0 deletions x/vpool/client/cli/gov_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package cli

import (
"fmt"
"io/ioutil"
"net/http"
"strings"

govclientrest "github.com/cosmos/cosmos-sdk/x/gov/client/rest"

"github.com/NibiruChain/nibiru/x/vpool/types"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/version"
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
govcli "github.com/cosmos/cosmos-sdk/x/gov/client/cli"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"

"github.com/spf13/cobra"
)

var (
CreatePoolProposalHandler = govclient.NewProposalHandler(
/* govclient.CLIHandlerFn */ CmdCreatePoolProposal,
/* govclient.RESTHandlerFn */ func(context client.Context) govclientrest.ProposalRESTHandler {
return govclientrest.ProposalRESTHandler{
SubRoute: "create_pool",
Handler: func(writer http.ResponseWriter, request *http.Request) {
_, _ = writer.Write([]byte("deprecated"))
writer.WriteHeader(http.StatusMethodNotAllowed)
},
}
})
)

// CmdCreatePoolProposal implements the client command to submit a governance
// proposal to whitelist an oracle for specified asset pairs.
func CmdCreatePoolProposal() *cobra.Command {
cmd := &cobra.Command{
Use: "create-pool [proposal-json] --deposit=[deposit]",
Args: cobra.ExactArgs(1),
Short: "Submit a proposal to create a new vpool",
Example: strings.TrimSpace(fmt.Sprintf(`
Example:
$ %s tx gov submit-proposal create-pool <path/to/proposal.json> --deposit="1000unibi" --from=<key_or_address>
`, version.AppName)),
Long: strings.TrimSpace(
`Submits a proposal to create a new vpool, which in turn create a new x/perp market

A proposal.json for 'CreatePoolProposal' contains:
{
"title": "Create vpool for ETH:USDT",
"description": "I wanna get liquidated on ETH:USDT",
"pair": "ETH:USDT",
"trade_limit_ratio": "0.2",
...
}
`),
RunE: func(cmd *cobra.Command, args []string) (err error) {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

from := clientCtx.GetFromAddress()

proposal := &types.CreatePoolProposal{}
contents, err := ioutil.ReadFile(args[0])
if err != nil {
return err
}

// marshals the contents into the proto.Message to which 'proposal' points.
if err = clientCtx.Codec.UnmarshalJSON(contents, proposal); err != nil {
return err
}

depositStr, err := cmd.Flags().GetString(govcli.FlagDeposit)
if err != nil {
return err
}
deposit, err := sdk.ParseCoinsNormalized(depositStr)
if err != nil {
return err
}

msg, err := govtypes.NewMsgSubmitProposal(proposal, deposit, from)
if err != nil {
return err
}
if err = msg.ValidateBasic(); err != nil {
return err
}

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
},
}

cmd.Flags().String(
/*name=*/ govcli.FlagDeposit,
/*defaultValue=*/ "",
/*usage=*/ "governance deposit for proposal")
if err := cmd.MarkFlagRequired(govcli.FlagDeposit); err != nil {
panic(err)
}

return cmd
}
31 changes: 31 additions & 0 deletions x/vpool/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ package vpool
import (
"fmt"

govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"

"github.com/NibiruChain/nibiru/x/common"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

Expand All @@ -19,3 +23,30 @@ func NewHandler(k keeper.Keeper) sdk.Handler {
return nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, errMsg)
}
}

func NewCreatePoolProposalHandler(k keeper.Keeper) govtypes.Handler {
return func(ctx sdk.Context, content govtypes.Content) error {
switch m := content.(type) {
case *types.CreatePoolProposal:
if err := m.ValidateBasic(); err != nil {
testinginprod marked this conversation as resolved.
Show resolved Hide resolved
return err
}
pair := common.MustNewAssetPair(m.Pair)
k.CreatePool(
ctx,
pair,
m.TradeLimitRatio,
m.QuoteAssetReserve,
m.BaseAssetReserve,
m.FluctuationLimitRatio,
m.MaxOracleSpreadRatio,
)
// TODO(mercilex): oracle missing, pool should not be tradeable
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add the oracle part too? Otherwise, it's an incomplete feature.

Also if you're going to check in TODOs, please create and attach a GH issue instead of a name so that we can attach context to the TODO.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've opened a separate issue for this, plus this problem does not relate to gov proposals specifically, it's about vpool itself, if a pricefeed stops posting prices then vpool would still allow trades, which from my understanding is incorrect.

return nil
default:
return sdkerrors.Wrapf(
sdkerrors.ErrUnknownRequest,
"unrecognized %s proposal content type: %T", types.ModuleName, m)
}
}
}
3 changes: 3 additions & 0 deletions x/vpool/types/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
)

func RegisterCodec(cdc *codec.LegacyAmino) {
Expand All @@ -15,6 +16,8 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) {
/* implementations */
)

registry.RegisterImplementations((*govtypes.Content)(nil), &CreatePoolProposal{})

// msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
}

Expand Down
77 changes: 77 additions & 0 deletions x/vpool/types/gov.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package types

import (
"fmt"

sdk "github.com/cosmos/cosmos-sdk/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"

"github.com/NibiruChain/nibiru/x/common"
)

const (
ProposalTypeCreatePool = "CreatePool"
)

var _ govtypes.Content = &CreatePoolProposal{}

// NEVER MUTATE THESE!
// they exist for comparisons only and to avoid constant allocations of a new big int
// which is used only for reading.
var (
testinginprod marked this conversation as resolved.
Show resolved Hide resolved
oneDec = sdk.OneDec()
zeroDec = sdk.ZeroDec()
)

func init() {
govtypes.RegisterProposalType(ProposalTypeCreatePool)
govtypes.RegisterProposalTypeCodec(&CreatePoolProposal{}, "nibiru/CreatePoolProposal")
}

func (m *CreatePoolProposal) ProposalRoute() string {
return RouterKey
}

func (m *CreatePoolProposal) ProposalType() string {
return ProposalTypeCreatePool
}

func (m *CreatePoolProposal) ValidateBasic() error {
if err := govtypes.ValidateAbstract(m); err != nil {
return err
}

if _, err := common.NewAssetPair(m.Pair); err != nil {
return err
}

// trade limit ratio always between 0 and 1
// TODO(mercilex): does it really make sense for this to be equal to zero?
if m.TradeLimitRatio.LT(zeroDec) || m.TradeLimitRatio.GT(oneDec) {
testinginprod marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("trade limit ratio must be 0 <= ratio <= 1")
}

// quote asset reserve always > 0
if m.QuoteAssetReserve.LTE(zeroDec) {
testinginprod marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("quote asset reserve must be > 0")
}

// base asset reserve always > 0
if m.BaseAssetReserve.LTE(zeroDec) {
testinginprod marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("base asset reserve must be > 0")
}

// fluctuation limit ratio between 0 and 1
// TODO(mercilex): does it really make sense for this to be equal to zero?
if m.FluctuationLimitRatio.LT(zeroDec) || m.FluctuationLimitRatio.GT(oneDec) {
testinginprod marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("fluctuation limit ratio must be 0 <= ratio <= 1")
}

// max oracle spread ratio between 0 and 1
// TODO(mercilex): does it really make sense for this to be equal to zero?
if m.MaxOracleSpreadRatio.LT(zeroDec) || m.MaxOracleSpreadRatio.GT(oneDec) {
testinginprod marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("max oracle spread ratio must be 0 <= ratio <= 1")
}

return nil
}
Loading