From aff1a26da922c96c40aa09fcc1a6780176bb0e88 Mon Sep 17 00:00:00 2001 From: David Terpay <35130517+davidterpay@users.noreply.github.com> Date: Thu, 2 Mar 2023 16:02:01 -0500 Subject: [PATCH] Scaffolding basic module configurations (#5) Co-authored-by: Aleksandr Bezobchuk --- go.mod | 5 +- go.sum | 8 +- proto/pob/auction/v1/genesis.proto | 2 +- x/auction/.keep | 0 x/auction/keeper/genesis.go | 25 +++++ x/auction/keeper/grpc_query.go | 34 +++++++ x/auction/keeper/keeper.go | 141 ++++++++++++++++++++++++++++ x/auction/keeper/msg_server.go | 71 ++++++++++++++ x/auction/module.go | 137 +++++++++++++++++++++++++++ x/auction/types/expected_keepers.go | 16 ++++ x/auction/types/genesis.go | 24 +++++ x/auction/types/genesis.pb.go | 2 +- x/auction/types/keys.go | 24 +++++ x/auction/types/msgs.go | 31 +++++- x/auction/types/msgs_test.go | 131 ++++++++++++++++++++++++++ x/auction/types/params.go | 76 ++++++++++----- 16 files changed, 692 insertions(+), 35 deletions(-) delete mode 100644 x/auction/.keep create mode 100644 x/auction/keeper/genesis.go create mode 100644 x/auction/keeper/grpc_query.go create mode 100644 x/auction/keeper/keeper.go create mode 100644 x/auction/keeper/msg_server.go create mode 100644 x/auction/module.go create mode 100644 x/auction/types/expected_keepers.go create mode 100644 x/auction/types/genesis.go create mode 100644 x/auction/types/keys.go create mode 100644 x/auction/types/msgs_test.go diff --git a/go.mod b/go.mod index b7bbac1..53e4a7e 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,16 @@ module github.com/skip-mev/pob go 1.20 require ( + cosmossdk.io/api v0.3.1 cosmossdk.io/errors v1.0.0-beta.7 github.com/cosmos/cosmos-proto v1.0.0-beta.2 github.com/cosmos/cosmos-sdk v0.47.0-rc2.0.20230228000043-54240ec9ab19 github.com/cosmos/gogoproto v1.4.4 github.com/golang/protobuf v1.5.2 + github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 github.com/stretchr/testify v1.8.1 + github.com/spf13/cobra v1.6.1 google.golang.org/genproto v0.0.0-20230216225411-c8e22ba71e44 google.golang.org/grpc v1.53.0 ) @@ -86,10 +89,10 @@ require ( github.com/sasha-s/go-deadlock v0.3.1 // indirect github.com/spf13/afero v1.9.2 // indirect github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.6.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.14.0 // indirect + github.com/stretchr/testify v1.8.1 // indirect github.com/subosito/gotenv v1.4.1 // indirect github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect diff --git a/go.sum b/go.sum index 4db6ebd..2785fea 100644 --- a/go.sum +++ b/go.sum @@ -45,7 +45,6 @@ cosmossdk.io/errors v1.0.0-beta.7 h1:gypHW76pTQGVnHKo6QBkb4yFOJjC+sUGRc5Al3Odj1w cosmossdk.io/errors v1.0.0-beta.7/go.mod h1:mz6FQMJRku4bY7aqS/Gwfcmr/ue91roMEKAmDUDpBfE= cosmossdk.io/math v1.0.0-beta.6 h1:WF29SiFYNde5eYvqO2kdOM9nYbDb44j3YW5B8M1m9KE= cosmossdk.io/math v1.0.0-beta.6/go.mod h1:gUVtWwIzfSXqcOT+lBVz2jyjfua8DoBdzRsIyaUAT/8= -cosmossdk.io/tools/rosetta v0.2.1 h1:ddOMatOH+pbxWbrGJKRAawdBkPYLfKXutK9IETnjYxw= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= @@ -81,8 +80,6 @@ github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/btcutil v1.1.2 h1:XLMbX8JQEiwMcYft2EGi8zPUkoa0abKIU6/BJSRsjzQ= github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= -github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= @@ -127,7 +124,6 @@ github.com/cosmos/iavl v0.20.0-alpha4 h1:49SZoxNwah5nqbVE1da8BAhenC7HMSVOTZ0XKVh github.com/cosmos/iavl v0.20.0-alpha4/go.mod h1:WO7FyvaZJoH65+HFOsDir7xU9FWk2w9cHXNW1XHcl7A= github.com/cosmos/ledger-cosmos-go v0.12.1 h1:sMBxza5p/rNK/06nBSNmsI/WDqI0pVJFVNihy1Y984w= github.com/cosmos/ledger-cosmos-go v0.12.1/go.mod h1:dhO6kj+Y+AHIOgAe4L9HL/6NDdyyth4q238I9yFpD2g= -github.com/cosmos/rosetta-sdk-go v0.10.0 h1:E5RhTruuoA7KTIXUcMicL76cffyeoyvNybzUGSKFTcM= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM= @@ -141,7 +137,6 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= -github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E= @@ -269,6 +264,7 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 h1:+9834+KizmvFV7pXQGSXQTsaWhq2GjuNUt0aUU0YBYw= @@ -304,7 +300,6 @@ github.com/huandu/skiplist v1.2.0 h1:gox56QD77HzSC0w+Ws3MH3iie755GBJU1OER3h5VsYw github.com/huandu/skiplist v1.2.0/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/improbable-eng/grpc-web v0.15.0 h1:BN+7z6uNXZ1tQGcNAuaU1YjsLTApzkjt2tzCixLaUPQ= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -419,7 +414,6 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5mo= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= -github.com/rakyll/statik v0.1.7 h1:OF3QCZUuyPxuGEP7B4ypUa7sB/iHtqOTDYZXGM8KOdQ= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/regen-network/gocuke v0.6.2 h1:pHviZ0kKAq2U2hN2q3smKNxct6hS0mGByFMHGnWA97M= diff --git a/proto/pob/auction/v1/genesis.proto b/proto/pob/auction/v1/genesis.proto index b968efa..b8f044c 100644 --- a/proto/pob/auction/v1/genesis.proto +++ b/proto/pob/auction/v1/genesis.proto @@ -26,7 +26,7 @@ message Params { (amino.dont_omitempty) = true, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" ]; - // min_buy_in_fee speficies the bid floor for the auction. + // min_buy_in_fee specifies the bid floor for the auction. repeated cosmos.base.v1beta1.Coin min_buy_in_fee = 4 [ (gogoproto.nullable) = false, (amino.dont_omitempty) = true, diff --git a/x/auction/.keep b/x/auction/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/x/auction/keeper/genesis.go b/x/auction/keeper/genesis.go new file mode 100644 index 0000000..d0d66b6 --- /dev/null +++ b/x/auction/keeper/genesis.go @@ -0,0 +1,25 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/skip-mev/pob/x/auction/types" +) + +// InitGenesis initializes the auction module's state from a given genesis state. +func (k Keeper) InitGenesis(ctx sdk.Context, gs types.GenesisState) { + // Set the auction module's parameters. + if err := k.SetParams(ctx, gs.Params); err != nil { + panic(err) + } +} + +// ExportGenesis returns a GenesisState for a given context. +func (k Keeper) ExportGenesis(ctx sdk.Context) *types.GenesisState { + // Get the auction module's parameters. + params, err := k.GetParams(ctx) + if err != nil { + panic(err) + } + + return types.NewGenesisState(params) +} diff --git a/x/auction/keeper/grpc_query.go b/x/auction/keeper/grpc_query.go new file mode 100644 index 0000000..2d83f51 --- /dev/null +++ b/x/auction/keeper/grpc_query.go @@ -0,0 +1,34 @@ +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/skip-mev/pob/x/auction/types" +) + +var ( + _ types.QueryServer = QueryServer{} +) + +// QueryServer defines the auction module's gRPC querier service. +type QueryServer struct { + keeper Keeper +} + +// NewQueryServer creates a new gRPC query server for the auction module. +func NewQueryServer(keeper Keeper) *QueryServer { + return &QueryServer{keeper: keeper} +} + +// Params queries all parameters of the auction module. +func (q QueryServer) Params(c context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { + ctx := sdk.UnwrapSDKContext(c) + + params, err := q.keeper.GetParams(ctx) + if err != nil { + return nil, err + } + + return &types.QueryParamsResponse{Params: params}, nil +} diff --git a/x/auction/keeper/keeper.go b/x/auction/keeper/keeper.go new file mode 100644 index 0000000..83fc7f4 --- /dev/null +++ b/x/auction/keeper/keeper.go @@ -0,0 +1,141 @@ +package keeper + +import ( + "fmt" + + "github.com/cometbft/cometbft/libs/log" + + "github.com/cosmos/cosmos-sdk/codec" + storetypes "github.com/cosmos/cosmos-sdk/store/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/skip-mev/pob/x/auction/types" +) + +type Keeper struct { + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + + bankkeeper types.BankKeeper + + // The address that is capable of executing a MsgUpdateParams message. Typically this will be the + // governance module's address. + authority string +} + +// NewKeeper creates a new keeper instance. +func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, accountKeeper types.AccountKeeper, bankkeeper types.BankKeeper, authority string) Keeper { + // Ensure that the authority address is valid. + if _, err := sdk.AccAddressFromBech32(authority); err != nil { + panic(err) + } + + // Ensure that the auction module account exists. + if accountKeeper.GetModuleAddress(types.ModuleName) == nil { + panic("auction module account has not been set") + } + + return Keeper{ + cdc: cdc, + storeKey: storeKey, + bankkeeper: bankkeeper, + authority: authority, + } +} + +// Logger returns an auction module-specific logger. +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", "x/"+types.ModuleName) +} + +// GetAuthority returns the address that is capable of executing a MsgUpdateParams message. +func (k Keeper) GetAuthority() string { + return k.authority +} + +// GetParams returns the auction module's parameters. +func (k Keeper) GetParams(ctx sdk.Context) (types.Params, error) { + store := ctx.KVStore(k.storeKey) + + key := types.KeyParams + bz := store.Get(key) + + if len(bz) == 0 { + return types.Params{}, fmt.Errorf("no params found for the auction module") + } + + params := types.Params{} + if err := params.Unmarshal(bz); err != nil { + return types.Params{}, err + } + + return params, nil +} + +// SetParams sets the auction module's parameters. +func (k Keeper) SetParams(ctx sdk.Context, params types.Params) error { + store := ctx.KVStore(k.storeKey) + + bz, err := params.Marshal() + if err != nil { + return err + } + + store.Set(types.KeyParams, bz) + + return nil +} + +// GetMaxBundleSize returns the maximum number of transactions that can be included in a bundle. +func (k Keeper) GetMaxBundleSize(ctx sdk.Context) (uint32, error) { + params, err := k.GetParams(ctx) + if err != nil { + return 0, err + } + + return params.MaxBundleSize, nil +} + +// GetEscrowAccount returns the auction module's escrow account. +func (k Keeper) GetEscrowAccount(ctx sdk.Context) (sdk.AccAddress, error) { + params, err := k.GetParams(ctx) + if err != nil { + return nil, err + } + + account, err := sdk.AccAddressFromBech32(params.EscrowAccountAddress) + if err != nil { + return nil, err + } + + return account, nil +} + +// GetReserveFee returns the reserve fee of the auction module. +func (k Keeper) GetReserveFee(ctx sdk.Context) (sdk.Coins, error) { + params, err := k.GetParams(ctx) + if err != nil { + return sdk.NewCoins(), err + } + + return params.ReserveFee, nil +} + +// GetMinBuyInFee returns the bid floor for an auction. +func (k Keeper) GetMinBuyInFee(ctx sdk.Context) (sdk.Coins, error) { + params, err := k.GetParams(ctx) + if err != nil { + return sdk.NewCoins(), err + } + + return params.MinBuyInFee, nil +} + +// GetMinBidIncrement returns the minimum bid increment for the auction. +func (k Keeper) GetMinBidIncrement(ctx sdk.Context) (sdk.Coins, error) { + params, err := k.GetParams(ctx) + if err != nil { + return sdk.NewCoins(), err + } + + return params.MinBidIncrement, nil +} diff --git a/x/auction/keeper/msg_server.go b/x/auction/keeper/msg_server.go new file mode 100644 index 0000000..0ebb7cb --- /dev/null +++ b/x/auction/keeper/msg_server.go @@ -0,0 +1,71 @@ +package keeper + +import ( + "context" + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/skip-mev/pob/x/auction/types" +) + +var ( + _ types.MsgServer = MsgServer{} +) + +// MsgServer is the wrapper for the auction module's msg service. +type MsgServer struct { + Keeper +} + +// NewMsgServerImpl returns an implementation of the auction MsgServer interface. +func NewMsgServerImpl(keeper Keeper) *MsgServer { + return &MsgServer{Keeper: keeper} +} + +// AuctionBid is the server implementation for Msg/AuctionBid. +func (m MsgServer) AuctionBid(goCtx context.Context, msg *types.MsgAuctionBid) (*types.MsgAuctionBidResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // This should never return an error because the address was validated when the message was ingressed. + bidder, err := sdk.AccAddressFromBech32(msg.Bidder) + if err != nil { + return nil, err + } + + // Ensure that the number of transactions is less than or equal to the maximum allowed. + maxBundleSize, err := m.Keeper.GetMaxBundleSize(ctx) + if err != nil { + return nil, err + } + + if uint32(len(msg.Transactions)) > maxBundleSize { + return nil, fmt.Errorf("the number of transactions in the bid is greater than the maximum allowed; expected <= %d, got %d", maxBundleSize, len(msg.Transactions)) + } + + // Attempt to send the bid to the module account. + if err := m.Keeper.bankkeeper.SendCoinsFromAccountToModule(ctx, bidder, types.ModuleName, msg.Bid); err != nil { + return nil, err + } + + // TODO: figure out how to handle payments to the escrow address. + // Ref: https://github.com/skip-mev/pob/issues/11 + + return &types.MsgAuctionBidResponse{}, nil +} + +// UpdateParams is the server implementation for Msg/UpdateParams. +func (m MsgServer) UpdateParams(goCtx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Ensure that the message signer is the authority. + if msg.Authority != m.Keeper.GetAuthority() { + return nil, fmt.Errorf("this message can only be executed by the authority; expected %s, got %s", m.Keeper.GetAuthority(), msg.Authority) + } + + // Update the parameters. + if err := m.Keeper.SetParams(ctx, msg.Params); err != nil { + return nil, err + } + + return &types.MsgUpdateParamsResponse{}, nil +} diff --git a/x/auction/module.go b/x/auction/module.go new file mode 100644 index 0000000..e270aaa --- /dev/null +++ b/x/auction/module.go @@ -0,0 +1,137 @@ +package auction + +import ( + "context" + "encoding/json" + "fmt" + + "cosmossdk.io/api/tendermint/abci" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + cdctypes "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/skip-mev/pob/x/auction/keeper" + "github.com/skip-mev/pob/x/auction/types" + "github.com/spf13/cobra" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// ConsensusVersion defines the current x/auction module consensus version. +const ConsensusVersion = 1 + +// -------------------- AppModuleBasic -------------------- // +// AppModuleBasic defines the basic application module used by the auction module. +type AppModuleBasic struct { + cdc codec.Codec +} + +// Name returns the auction module's name. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterLegacyAminoCodec registers the auction module's types on the given LegacyAmino codec. +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { + types.RegisterLegacyAminoCodec(cdc) +} + +// RegisterInterfaces registers the auction module's interface types. +func (AppModuleBasic) RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + types.RegisterInterfaces(registry) +} + +// DefaultGenesis returns default genesis state as raw bytes for the auction module. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONCodec) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the auction module. +func (AppModuleBasic) ValidateGenesis(cdc codec.JSONCodec, config client.TxEncodingConfig, bz json.RawMessage) error { + var genState types.GenesisState + if err := cdc.UnmarshalJSON(bz, &genState); err != nil { + return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) + } + + return genState.Validate() +} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the auction module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { + if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)); err != nil { + panic(err) + } +} + +// GetTxCmd returns the root tx command for the auction module. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return nil +} + +// GetQueryCmd returns no root query command for the auction module. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return nil +} + +// -------------------- AppModule -------------------- // +type AppModule struct { + AppModuleBasic + + keeper keeper.Keeper + accountKeeper types.AccountKeeper + bankKeeper types.BankKeeper +} + +// NewAppModule creates a new AppModule object. +func NewAppModule(cdc codec.Codec, keeper keeper.Keeper, accountKeeper types.AccountKeeper, bankKeeper types.BankKeeper) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{cdc: cdc}, + keeper: keeper, + accountKeeper: accountKeeper, + bankKeeper: bankKeeper, + } +} + +// RegisterServices registers a the gRPC Query and Msg services for the auciton module. +func (am AppModule) RegisterServices(cfg module.Configurator) { + // TODO: Define the gRPC querier service and register it with the auction module configurator + // TODO: Define the gRPC Msg service and register it with the auction module configurator +} + +func (a AppModuleBasic) RegisterRESTRoutes(ctx client.Context, r *mux.Router) {} + +// RegisterInvariants registers the invariants of the module. If an invariant deviates from its predicted value, the InvariantRegistry triggers appropriate logic (most often the chain will be halted) +func (am AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// InitGenesis performs the module's genesis initialization for the auction module. It returns no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONCodec, gs json.RawMessage) []abci.ValidatorUpdate { + var genState types.GenesisState + cdc.MustUnmarshalJSON(gs, &genState) + + am.keeper.InitGenesis(ctx, genState) + + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the auction module's exported genesis state as raw JSON bytes. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.RawMessage { + genState := am.keeper.ExportGenesis(ctx) + return cdc.MustMarshalJSON(genState) +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } + +// BeginBlock returns the begin blocker for the auction module. +func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock returns the end blocker for the auction module. It returns no validator updates. +func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} diff --git a/x/auction/types/expected_keepers.go b/x/auction/types/expected_keepers.go new file mode 100644 index 0000000..f2b1616 --- /dev/null +++ b/x/auction/types/expected_keepers.go @@ -0,0 +1,16 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// AccountKeeper defines the contract required for account APIs. +type AccountKeeper interface { + GetModuleAddress(moduleName string) sdk.AccAddress +} + +// BankKeeper defines the contract required for bank APIs. +type BankKeeper interface { + SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error +} diff --git a/x/auction/types/genesis.go b/x/auction/types/genesis.go new file mode 100644 index 0000000..886b78b --- /dev/null +++ b/x/auction/types/genesis.go @@ -0,0 +1,24 @@ +package types + +// NewGenesisState creates a new GenesisState instance. +func NewGenesisState(params Params) *GenesisState { + return &GenesisState{ + Params: params, + } +} + +// DefaultGenesisState returns the default GenesisState instance. +func DefaultGenesisState() *GenesisState { + return &GenesisState{ + Params: DefaultParams(), + } +} + +// Validate performs basic validation of the auction module genesis state. +func (gs GenesisState) Validate() error { + if err := gs.Params.Validate(); err != nil { + return err + } + + return nil +} diff --git a/x/auction/types/genesis.pb.go b/x/auction/types/genesis.pb.go index 87d9c31..d3879de 100644 --- a/x/auction/types/genesis.pb.go +++ b/x/auction/types/genesis.pb.go @@ -81,7 +81,7 @@ type Params struct { EscrowAccountAddress string `protobuf:"bytes,2,opt,name=escrow_account_address,json=escrowAccountAddress,proto3" json:"escrow_account_address,omitempty"` // reserve_fee specifies a fee that the bidder must pay to enter the auction. ReserveFee github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,3,rep,name=reserve_fee,json=reserveFee,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"reserve_fee"` - // min_buy_in_fee speficies the bid floor for the auction. + // min_buy_in_fee specifies the bid floor for the auction. MinBuyInFee github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,4,rep,name=min_buy_in_fee,json=minBuyInFee,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"min_buy_in_fee"` // min_bid_increment specifies the minimum amount that the next bid must be // greater than the previous bid. diff --git a/x/auction/types/keys.go b/x/auction/types/keys.go new file mode 100644 index 0000000..acacbde --- /dev/null +++ b/x/auction/types/keys.go @@ -0,0 +1,24 @@ +package types + +const ( + // ModuleName is the name of the auction module + ModuleName = "auction" + + // StoreKey is the default store key for the auction module + StoreKey = ModuleName + + // RouterKey is the message route for the auction module + RouterKey = ModuleName + + // QuerierRoute is the querier route for the auction module + QuerierRoute = ModuleName +) + +const ( + prefixParams = iota + 1 +) + +var ( + // KeyPrefixParams is the store key for the auction module's parameters. + KeyParams = []byte{prefixParams} +) diff --git a/x/auction/types/msgs.go b/x/auction/types/msgs.go index b55e996..3e044e8 100644 --- a/x/auction/types/msgs.go +++ b/x/auction/types/msgs.go @@ -1,6 +1,8 @@ package types import ( + fmt "fmt" + "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -48,8 +50,29 @@ func (m MsgAuctionBid) GetSigners() []sdk.AccAddress { // ValidateBasic does a sanity check on the provided data. func (m MsgAuctionBid) ValidateBasic() error { - // TODO: Implement validation. - // - // Ref: https://github.com/skip-mev/pob/issues/8 - panic("implement me") + if _, err := sdk.AccAddressFromBech32(m.Bidder); err != nil { + return errors.Wrap(err, "invalid bidder address") + } + + // Validate the bid. + if m.Bid.IsZero() { + return fmt.Errorf("no bid included") + } + + if err := m.Bid.Validate(); err != nil { + return errors.Wrap(err, "invalid bid") + } + + // Validate the transactions. + if len(m.Transactions) == 0 { + return fmt.Errorf("no transactions included") + } + + for _, tx := range m.Transactions { + if len(tx) == 0 { + return fmt.Errorf("empty transaction included") + } + } + + return nil } diff --git a/x/auction/types/msgs_test.go b/x/auction/types/msgs_test.go new file mode 100644 index 0000000..06ae855 --- /dev/null +++ b/x/auction/types/msgs_test.go @@ -0,0 +1,131 @@ +package types_test + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/skip-mev/pob/x/auction/types" +) + +// TestMsgAuctionBid tests the ValidateBasic method of MsgAuctionBid +func TestMsgAuctionBid(t *testing.T) { + cases := []struct { + description string + msg types.MsgAuctionBid + expectPass bool + }{ + { + description: "invalid message with empty bidder", + msg: types.MsgAuctionBid{ + Bidder: "", + Bid: sdk.NewCoins(), + Transactions: [][]byte{}, + }, + expectPass: false, + }, + { + description: "invalid message with empty bid", + msg: types.MsgAuctionBid{ + Bidder: sdk.AccAddress([]byte("test")).String(), + Bid: sdk.NewCoins(), + Transactions: [][]byte{}, + }, + expectPass: false, + }, + { + description: "invalid message with empty transactions", + msg: types.MsgAuctionBid{ + Bidder: sdk.AccAddress([]byte("test")).String(), + Bid: sdk.NewCoins(sdk.NewCoin("test", sdk.NewInt(100))), + Transactions: [][]byte{}, + }, + expectPass: false, + }, + { + description: "valid message", + msg: types.MsgAuctionBid{ + Bidder: sdk.AccAddress([]byte("test")).String(), + Bid: sdk.NewCoins(sdk.NewCoin("test", sdk.NewInt(100))), + Transactions: [][]byte{[]byte("test")}, + }, + expectPass: true, + }, + { + description: "valid message with multiple transactions", + msg: types.MsgAuctionBid{ + Bidder: sdk.AccAddress([]byte("test")).String(), + Bid: sdk.NewCoins(sdk.NewCoin("test", sdk.NewInt(100))), + Transactions: [][]byte{[]byte("test"), []byte("test2")}, + }, + expectPass: true, + }, + { + description: "invalid message with empty transaction in transactions", + msg: types.MsgAuctionBid{ + Bidder: sdk.AccAddress([]byte("test")).String(), + Bid: sdk.NewCoins(sdk.NewCoin("test", sdk.NewInt(100))), + Transactions: [][]byte{[]byte("test"), []byte("")}, + }, + expectPass: false, + }, + } + + for _, tc := range cases { + t.Run(tc.description, func(t *testing.T) { + + err := tc.msg.ValidateBasic() + if tc.expectPass { + if err != nil { + t.Errorf("expected no error on %s, got %s", tc.description, err) + } + } else { + if err == nil { + t.Errorf("expected error on %s, got none", tc.description) + } + } + }) + } +} + +// TestMsgUpdateParams tests the ValidateBasic method of MsgUpdateParams +func TestMsgUpdateParams(t *testing.T) { + cases := []struct { + description string + msg types.MsgUpdateParams + expectPass bool + }{ + { + description: "invalid message with empty authority address", + msg: types.MsgUpdateParams{ + Authority: "", + Params: types.Params{}, + }, + }, + { + description: "invalid message with invalid params (invalid escrow address)", + msg: types.MsgUpdateParams{ + Authority: sdk.AccAddress([]byte("test")).String(), + Params: types.Params{ + EscrowAccountAddress: "test", + }, + }, + expectPass: false, + }, + } + + for _, tc := range cases { + t.Run(tc.description, func(t *testing.T) { + + err := tc.msg.ValidateBasic() + if tc.expectPass { + if err != nil { + t.Errorf("expected no error on %s, got %s", tc.description, err) + } + } else { + if err == nil { + t.Errorf("expected error on %s, got none", tc.description) + } + } + }) + } +} diff --git a/x/auction/types/params.go b/x/auction/types/params.go index 4a85042..a9b5b0f 100644 --- a/x/auction/types/params.go +++ b/x/auction/types/params.go @@ -1,37 +1,71 @@ package types import ( + fmt "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" ) -func NewParams(maxBundleSize uint32, escrowAddr string, reserveFee, minBuyInFee, minBidIncr sdk.Coins) Params { - return Params{ - MaxBundleSize: maxBundleSize, - EscrowAccountAddress: escrowAddr, - ReserveFee: reserveFee, - MinBuyInFee: minBuyInFee, - MinBidIncrement: minBidIncr, - } -} - -// DefaultParams returns default x/auction module parameters. -func DefaultParams() Params { +var ( // TODO: Choose reasonable default values. // // Ref: https://github.com/skip-mev/pob/issues/7 + DefaultMaxBundleSize uint32 = 0 + DefaultEscrowAccountAddress = "" + DefaultReserveFee = sdk.Coins{} + DefaultMinBuyInFee = sdk.Coins{} + DefaultMinBidIncrement = sdk.Coins{} +) + +// NewParams returns a new Params instance with the provided values. +func NewParams(maxBundleSize uint32, escrowAccountAddress string, reserveFee, minBuyInFee, minBidIncrement sdk.Coins) Params { return Params{ - MaxBundleSize: 0, - EscrowAccountAddress: "", - ReserveFee: sdk.NewCoins(), - MinBuyInFee: sdk.NewCoins(), - MinBidIncrement: sdk.NewCoins(), + MaxBundleSize: maxBundleSize, + EscrowAccountAddress: escrowAccountAddress, + ReserveFee: reserveFee, + MinBuyInFee: minBuyInFee, + MinBidIncrement: minBidIncrement, } } +// DefaultParams returns the default x/auction parameters. +func DefaultParams() Params { + return NewParams( + DefaultMaxBundleSize, + DefaultEscrowAccountAddress, + DefaultReserveFee, + DefaultMinBuyInFee, + DefaultMinBidIncrement, + ) +} + // Validate performs basic validation on the parameters. func (p Params) Validate() error { - // TODO: Implement validation. - // - // Ref: https://github.com/skip-mev/pob/issues/6 - panic("not implemented") + if err := validateEscrowAccountAddress(p.EscrowAccountAddress); err != nil { + return err + } + + if err := p.ReserveFee.Validate(); err != nil { + return fmt.Errorf("invalid reserve fee (%s)", err) + } + + if err := p.MinBuyInFee.Validate(); err != nil { + return fmt.Errorf("invalid minimum buy-in fee (%s)", err) + } + + if err := p.MinBidIncrement.Validate(); err != nil { + return fmt.Errorf("invalid minimum bid increment (%s)", err) + } + + return nil +} + +// validateEscrowAccountAddress ensures the escrow account address is a valid address (if set). +func validateEscrowAccountAddress(account string) error { + // If the escrow account address is set, ensure it is a valid address. + if _, err := sdk.AccAddressFromBech32(account); err != nil { + return fmt.Errorf("invalid escrow account address (%s)", err) + } + + return nil }