diff --git a/proto/pob/auction/v1/genesis.proto b/proto/pob/auction/v1/genesis.proto index 8098a88..26ddfab 100644 --- a/proto/pob/auction/v1/genesis.proto +++ b/proto/pob/auction/v1/genesis.proto @@ -8,7 +8,9 @@ import "amino/amino.proto"; option go_package = "github.com/skip-mev/pob/x/auction/types"; // GenesisState defines the genesis state of the x/auction module. -message GenesisState { Params params = 1 [ (gogoproto.nullable) = false ]; } +message GenesisState { + Params params = 1 [(gogoproto.nullable) = false]; +} // Params defines the parameters of the x/auction module. message Params { @@ -17,29 +19,42 @@ message Params { // max_bundle_size is the maximum number of transactions that can be bundled // in a single bundle. uint32 max_bundle_size = 1; - // escrow_account_address is the address of the account that will hold the - // funds for the auctions. + + // escrow_account_address is the address of the account that will receive a + // portion of the bid proceeds. string escrow_account_address = 2; + // reserve_fee specifies the bid floor for the auction. repeated cosmos.base.v1beta1.Coin reserve_fee = 3 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, + (gogoproto.nullable) = false, + (amino.dont_omitempty) = true, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" ]; - // min_buy_in_fee specifies the fee that the bidder must pay to enter the auction. + + // min_buy_in_fee specifies the fee that the bidder must pay to enter the + // auction. repeated cosmos.base.v1beta1.Coin min_buy_in_fee = 4 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, + (gogoproto.nullable) = false, + (amino.dont_omitempty) = true, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" ]; + // min_bid_increment specifies the minimum amount that the next bid must be // greater than the previous bid. repeated cosmos.base.v1beta1.Coin min_bid_increment = 5 [ - (gogoproto.nullable) = false, - (amino.dont_omitempty) = true, + (gogoproto.nullable) = false, + (amino.dont_omitempty) = true, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins" ]; - // front_running_protection specifies whether front running and sandwich attack protection - // is enabled. + + // front_running_protection specifies whether front running and sandwich + // attack protection is enabled. bool front_running_protection = 6; + + // proposer_fee defines the portion of the winning bid that goes to the block + // proposer that proposed the block. + string proposer_fee = 7 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec" + ]; } diff --git a/x/auction/keeper/auction.go b/x/auction/keeper/auction.go index ee8fcd4..6a01563 100644 --- a/x/auction/keeper/auction.go +++ b/x/auction/keeper/auction.go @@ -70,7 +70,7 @@ func (k Keeper) ValidateAuctionBid(ctx sdk.Context, bidder sdk.AccAddress, bid, // Ensure the bidder has enough funds to cover all the inclusion fees. minBalance := bid.Add(minBuyInFee...) - balances := k.bankkeeper.GetAllBalances(ctx, bidder) + balances := k.bankKeeper.GetAllBalances(ctx, bidder) if !balances.IsAllGTE(minBalance) { return fmt.Errorf("insufficient funds to bid %s (reserve fee + bid) with balance %s", minBalance, balances) } @@ -103,8 +103,8 @@ func (k Keeper) ValidateAuctionBundle(ctx sdk.Context, bidder sdk.AccAddress, tr // Check that all subsequent transactions are signed by either // 1. the same party as the first transaction // 2. the same party for some arbitrary number of txs and then are all remaining txs are signed by the bidder. - for _, txbytes := range transactions[1:] { - txSigners, err := k.getTxSigners(txbytes) + for _, refTx := range transactions[1:] { + txSigners, err := k.getTxSigners(refTx) if err != nil { return err } diff --git a/x/auction/keeper/auction_test.go b/x/auction/keeper/auction_test.go index 70d1d76..ec73b37 100644 --- a/x/auction/keeper/auction_test.go +++ b/x/auction/keeper/auction_test.go @@ -4,16 +4,12 @@ import ( "math/rand" "time" - "github.com/cosmos/cosmos-sdk/client" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/skip-mev/pob/x/auction/keeper" auctiontypes "github.com/skip-mev/pob/x/auction/types" ) -func (suite *IntegrationTestSuite) TestValidateAuctionMsg() { +func (suite *KeeperTestSuite) TestValidateAuctionMsg() { var ( // Tx building variables accounts = []Account{} // tracks the order of signers in the bundle @@ -161,6 +157,8 @@ func (suite *IntegrationTestSuite) TestValidateAuctionMsg() { suite.key, suite.accountKeeper, suite.bankKeeper, + suite.distrKeeper, + suite.stakingKeeper, suite.authorityAccount.String(), ) params := auctiontypes.Params{ @@ -191,7 +189,7 @@ func (suite *IntegrationTestSuite) TestValidateAuctionMsg() { } } -func (suite *IntegrationTestSuite) TestValidateBundle() { +func (suite *KeeperTestSuite) TestValidateBundle() { // TODO: Update this to be multi-dimensional to test multi-sig // https://github.com/skip-mev/pob/issues/14 var accounts []Account // tracks the order of signers in the bundle @@ -301,33 +299,3 @@ func (suite *IntegrationTestSuite) TestValidateBundle() { }) } } - -// createRandomTx creates a random transaction with a given account, nonce, and number of messages. -func createRandomTx(txCfg client.TxConfig, account Account, nonce, numberMsgs uint64) (authsigning.Tx, error) { - msgs := make([]sdk.Msg, numberMsgs) - for i := 0; i < int(numberMsgs); i++ { - msgs[i] = &banktypes.MsgSend{ - FromAddress: account.Address.String(), - ToAddress: account.Address.String(), - } - } - - txBuilder := txCfg.NewTxBuilder() - if err := txBuilder.SetMsgs(msgs...); err != nil { - return nil, err - } - - sigV2 := signing.SignatureV2{ - PubKey: account.PrivKey.PubKey(), - Data: &signing.SingleSignatureData{ - SignMode: txCfg.SignModeHandler().DefaultMode(), - Signature: nil, - }, - Sequence: nonce, - } - if err := txBuilder.SetSignatures(sigV2); err != nil { - return nil, err - } - - return txBuilder.GetTx(), nil -} diff --git a/x/auction/keeper/keeper.go b/x/auction/keeper/keeper.go index 09ae15d..9cd5fca 100644 --- a/x/auction/keeper/keeper.go +++ b/x/auction/keeper/keeper.go @@ -15,15 +15,24 @@ type Keeper struct { cdc codec.BinaryCodec storeKey storetypes.StoreKey - bankkeeper types.BankKeeper + bankKeeper types.BankKeeper + distrKeeper types.DistributionKeeper + stakingKeeper types.StakingKeeper - // The address that is capable of executing a MsgUpdateParams message. Typically this will be the - // governance module's address. + // 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 { +func NewKeeper( + cdc codec.BinaryCodec, + storeKey storetypes.StoreKey, + accountKeeper types.AccountKeeper, + bankKeeper types.BankKeeper, + distrKeeper types.DistributionKeeper, + stakingKeeper types.StakingKeeper, + authority string, +) Keeper { // Ensure that the authority address is valid. if _, err := sdk.AccAddressFromBech32(authority); err != nil { panic(err) @@ -35,10 +44,12 @@ func NewKeeper(cdc codec.BinaryCodec, storeKey storetypes.StoreKey, accountKeepe } return Keeper{ - cdc: cdc, - storeKey: storeKey, - bankkeeper: bankkeeper, - authority: authority, + cdc: cdc, + storeKey: storeKey, + bankKeeper: bankKeeper, + distrKeeper: distrKeeper, + stakingKeeper: stakingKeeper, + authority: authority, } } @@ -140,6 +151,16 @@ func (k Keeper) GetMinBidIncrement(ctx sdk.Context) (sdk.Coins, error) { return params.MinBidIncrement, nil } +// GetProposerFee returns the proposer fee for the auction module. +func (k Keeper) GetProposerFee(ctx sdk.Context) (sdk.Dec, error) { + params, err := k.GetParams(ctx) + if err != nil { + return sdk.ZeroDec(), err + } + + return params.ProposerFee, nil +} + // FrontRunningProtectionEnabled returns true if front-running protection is enabled. func (k Keeper) FrontRunningProtectionEnabled(ctx sdk.Context) (bool, error) { params, err := k.GetParams(ctx) diff --git a/x/auction/keeper/keeper_test.go b/x/auction/keeper/keeper_test.go index af76edf..3508f4a 100644 --- a/x/auction/keeper/keeper_test.go +++ b/x/auction/keeper/keeper_test.go @@ -1,22 +1,11 @@ package keeper_test import ( - "math/rand" - "reflect" "testing" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/codec" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" storetypes "github.com/cosmos/cosmos-sdk/store/types" "github.com/cosmos/cosmos-sdk/testutil" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/tx" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/golang/mock/gomock" "github.com/skip-mev/pob/mempool" "github.com/skip-mev/pob/x/auction/ante" @@ -26,12 +15,14 @@ import ( "github.com/stretchr/testify/suite" ) -type IntegrationTestSuite struct { +type KeeperTestSuite struct { suite.Suite auctionKeeper keeper.Keeper bankKeeper *MockBankKeeper accountKeeper *MockAccountKeeper + distrKeeper *MockDistributionKeeper + stakingKeeper *MockStakingKeeper encCfg encodingConfig AuctionDecorator sdk.AnteDecorator ctx sdk.Context @@ -43,22 +34,33 @@ type IntegrationTestSuite struct { } func TestKeeperTestSuite(t *testing.T) { - suite.Run(t, new(IntegrationTestSuite)) + suite.Run(t, new(KeeperTestSuite)) } -func (suite *IntegrationTestSuite) SetupTest() { +func (suite *KeeperTestSuite) SetupTest() { suite.encCfg = createTestEncodingConfig() suite.key = sdk.NewKVStoreKey(types.StoreKey) testCtx := testutil.DefaultContextWithDB(suite.T(), suite.key, sdk.NewTransientStoreKey("transient_test")) suite.ctx = testCtx.Ctx - // Auction Keeper setup ctrl := gomock.NewController(suite.T()) + suite.accountKeeper = NewMockAccountKeeper(ctrl) suite.accountKeeper.EXPECT().GetModuleAddress(types.ModuleName).Return(sdk.AccAddress{}).AnyTimes() + suite.bankKeeper = NewMockBankKeeper(ctrl) + suite.distrKeeper = NewMockDistributionKeeper(ctrl) + suite.stakingKeeper = NewMockStakingKeeper(ctrl) suite.authorityAccount = sdk.AccAddress([]byte("authority")) - suite.auctionKeeper = keeper.NewKeeper(suite.encCfg.Codec, suite.key, suite.accountKeeper, suite.bankKeeper, suite.authorityAccount.String()) + suite.auctionKeeper = keeper.NewKeeper( + suite.encCfg.Codec, + suite.key, + suite.accountKeeper, + suite.bankKeeper, + suite.distrKeeper, + suite.stakingKeeper, + suite.authorityAccount.String(), + ) err := suite.auctionKeeper.SetParams(suite.ctx, types.DefaultParams()) suite.Require().NoError(err) @@ -67,157 +69,3 @@ func (suite *IntegrationTestSuite) SetupTest() { suite.AuctionDecorator = ante.NewAuctionDecorator(suite.auctionKeeper, suite.encCfg.TxConfig.TxDecoder(), suite.mempool) suite.msgServer = keeper.NewMsgServerImpl(suite.auctionKeeper) } - -type encodingConfig struct { - InterfaceRegistry codectypes.InterfaceRegistry - Codec codec.Codec - TxConfig client.TxConfig - Amino *codec.LegacyAmino -} - -func createTestEncodingConfig() encodingConfig { - cdc := codec.NewLegacyAmino() - interfaceRegistry := codectypes.NewInterfaceRegistry() - - banktypes.RegisterInterfaces(interfaceRegistry) - cryptocodec.RegisterInterfaces(interfaceRegistry) - - codec := codec.NewProtoCodec(interfaceRegistry) - - return encodingConfig{ - InterfaceRegistry: interfaceRegistry, - Codec: codec, - TxConfig: tx.NewTxConfig(codec, tx.DefaultSignModes), - Amino: cdc, - } -} - -type Account struct { - PrivKey cryptotypes.PrivKey - PubKey cryptotypes.PubKey - Address sdk.AccAddress - ConsKey cryptotypes.PrivKey -} - -func (acc Account) Equals(acc2 Account) bool { - return acc.Address.Equals(acc2.Address) -} - -// RandomAccounts returns a slice of n random accounts. -func RandomAccounts(r *rand.Rand, n int) []Account { - accs := make([]Account, n) - - for i := 0; i < n; i++ { - pkSeed := make([]byte, 15) - r.Read(pkSeed) - - accs[i].PrivKey = secp256k1.GenPrivKeyFromSecret(pkSeed) - accs[i].PubKey = accs[i].PrivKey.PubKey() - accs[i].Address = sdk.AccAddress(accs[i].PubKey.Address()) - - accs[i].ConsKey = ed25519.GenPrivKeyFromSecret(pkSeed) - } - - return accs -} - -// MockAccountKeeper is a mock of AccountKeeper interface. -type MockAccountKeeper struct { - ctrl *gomock.Controller - recorder *MockAccountKeeperMockRecorder -} - -// MockAccountKeeperMockRecorder is the mock recorder for MockAccountKeeper. -type MockAccountKeeperMockRecorder struct { - mock *MockAccountKeeper -} - -// NewMockAccountKeeper creates a new mock instance. -func NewMockAccountKeeper(ctrl *gomock.Controller) *MockAccountKeeper { - mock := &MockAccountKeeper{ctrl: ctrl} - mock.recorder = &MockAccountKeeperMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { - return m.recorder -} - -// GetModuleAddress mocks base method. -func (m *MockAccountKeeper) GetModuleAddress(name string) sdk.AccAddress { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetModuleAddress", name) - ret0, _ := ret[0].(sdk.AccAddress) - return ret0 -} - -// GetModuleAddress indicates an expected call of GetModuleAddress. -func (mr *MockAccountKeeperMockRecorder) GetModuleAddress(name interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetModuleAddress", reflect.TypeOf((*MockAccountKeeper)(nil).GetModuleAddress), name) -} - -// MockBankKeeper is a mock of BankKeeper interface. -type MockBankKeeper struct { - ctrl *gomock.Controller - recorder *MockBankKeeperMockRecorder -} - -// MockBankKeeperMockRecorder is the mock recorder for MockBankKeeper. -type MockBankKeeperMockRecorder struct { - mock *MockBankKeeper -} - -// NewMockBankKeeper creates a new mock instance. -func NewMockBankKeeper(ctrl *gomock.Controller) *MockBankKeeper { - mock := &MockBankKeeper{ctrl: ctrl} - mock.recorder = &MockBankKeeperMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { - return m.recorder -} - -// GetAllBalances mocks base method. -func (m *MockBankKeeper) GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetAllBalances", ctx, addr) - ret0 := ret[0].(sdk.Coins) - return ret0 -} - -// GetAllBalances indicates an expected call of GetAllBalances. -func (mr *MockBankKeeperMockRecorder) GetAllBalances(ctx, addr interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllBalances", reflect.TypeOf((*MockBankKeeper)(nil).GetAllBalances), ctx, addr) -} - -// SendCoins mocks base method. -func (m *MockBankKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error { - m.ctrl.T.Helper() - m.ctrl.Call(m, "SendCoins", ctx, fromAddr, toAddr, amt) - return nil -} - -// SendCoins indicates an expected call of SendCoins. -func (mr *MockBankKeeperMockRecorder) SendCoins(ctx, fromAddr, toAddr, amt interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoins", reflect.TypeOf((*MockBankKeeper)(nil).SendCoins), ctx, fromAddr, toAddr, amt) -} - -// SendCoinsFromAccountToModule mocks base method. -func (m *MockBankKeeper) SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SendCoinsFromAccountToModule", ctx, senderAddr, recipientModule, amt) - ret0, _ := ret[0].(error) - return ret0 -} - -// SendCoinsFromAccountToModule indicates an expected call of SendCoinsFromAccountToModule. -func (mr *MockBankKeeperMockRecorder) SendCoinsFromAccountToModule(ctx, senderAddr, recipientModule, amt interface{}) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoinsFromAccountToModule", reflect.TypeOf((*MockBankKeeper)(nil).SendCoinsFromAccountToModule), ctx, senderAddr, recipientModule, amt) -} diff --git a/x/auction/keeper/mocks_test.go b/x/auction/keeper/mocks_test.go new file mode 100644 index 0000000..108aff8 --- /dev/null +++ b/x/auction/keeper/mocks_test.go @@ -0,0 +1,144 @@ +package keeper_test + +import ( + "reflect" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/golang/mock/gomock" +) + +type MockAccountKeeper struct { + ctrl *gomock.Controller + recorder *MockAccountKeeperMockRecorder +} + +type MockAccountKeeperMockRecorder struct { + mock *MockAccountKeeper +} + +func NewMockAccountKeeper(ctrl *gomock.Controller) *MockAccountKeeper { + mock := &MockAccountKeeper{ctrl: ctrl} + mock.recorder = &MockAccountKeeperMockRecorder{mock} + return mock +} + +func (m *MockAccountKeeper) EXPECT() *MockAccountKeeperMockRecorder { + return m.recorder +} + +func (m *MockAccountKeeper) GetModuleAddress(name string) sdk.AccAddress { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetModuleAddress", name) + ret0, _ := ret[0].(sdk.AccAddress) + return ret0 +} + +func (mr *MockAccountKeeperMockRecorder) GetModuleAddress(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetModuleAddress", reflect.TypeOf((*MockAccountKeeper)(nil).GetModuleAddress), name) +} + +type MockBankKeeper struct { + ctrl *gomock.Controller + recorder *MockBankKeeperMockRecorder +} + +type MockBankKeeperMockRecorder struct { + mock *MockBankKeeper +} + +func NewMockBankKeeper(ctrl *gomock.Controller) *MockBankKeeper { + mock := &MockBankKeeper{ctrl: ctrl} + mock.recorder = &MockBankKeeperMockRecorder{mock} + return mock +} + +func (m *MockBankKeeper) EXPECT() *MockBankKeeperMockRecorder { + return m.recorder +} + +func (m *MockBankKeeper) GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetAllBalances", ctx, addr) + ret0 := ret[0].(sdk.Coins) + return ret0 +} + +func (mr *MockBankKeeperMockRecorder) GetAllBalances(ctx, addr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllBalances", reflect.TypeOf((*MockBankKeeper)(nil).GetAllBalances), ctx, addr) +} + +func (m *MockBankKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SendCoins", ctx, fromAddr, toAddr, amt) + return nil +} + +func (mr *MockBankKeeperMockRecorder) SendCoins(ctx, fromAddr, toAddr, amt interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SendCoins", reflect.TypeOf((*MockBankKeeper)(nil).SendCoins), ctx, fromAddr, toAddr, amt) +} + +type MockDistributionKeeperRecorder struct { + mock *MockDistributionKeeper +} + +type MockDistributionKeeper struct { + ctrl *gomock.Controller + recorder *MockDistributionKeeperRecorder +} + +func NewMockDistributionKeeper(ctrl *gomock.Controller) *MockDistributionKeeper { + mock := &MockDistributionKeeper{ctrl: ctrl} + mock.recorder = &MockDistributionKeeperRecorder{mock} + return mock +} + +func (m *MockDistributionKeeper) EXPECT() *MockDistributionKeeperRecorder { + return m.recorder +} + +func (m *MockDistributionKeeper) GetPreviousProposerConsAddr(ctx sdk.Context) sdk.ConsAddress { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPreviousProposerConsAddr", ctx) + ret0 := ret[0].(sdk.ConsAddress) + return ret0 +} + +func (mr *MockDistributionKeeperRecorder) GetPreviousProposerConsAddr(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPreviousProposerConsAddr", reflect.TypeOf((*MockDistributionKeeper)(nil).GetPreviousProposerConsAddr), ctx) +} + +type MockStakingKeeperRecorder struct { + mock *MockStakingKeeper +} + +type MockStakingKeeper struct { + ctrl *gomock.Controller + recorder *MockStakingKeeperRecorder +} + +func NewMockStakingKeeper(ctrl *gomock.Controller) *MockStakingKeeper { + mock := &MockStakingKeeper{ctrl: ctrl} + mock.recorder = &MockStakingKeeperRecorder{mock} + return mock +} + +func (m *MockStakingKeeper) EXPECT() *MockStakingKeeperRecorder { + return m.recorder +} + +func (m *MockStakingKeeper) ValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) stakingtypes.ValidatorI { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidatorByConsAddr", ctx, consAddr) + ret0 := ret[0].(stakingtypes.ValidatorI) + return ret0 +} + +func (mr *MockStakingKeeperRecorder) ValidatorByConsAddr(ctx, consAddr any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidatorByConsAddr", reflect.TypeOf((*MockStakingKeeper)(nil).ValidatorByConsAddr), ctx, consAddr) +} diff --git a/x/auction/keeper/msg_server.go b/x/auction/keeper/msg_server.go index ba42fe1..ac8b9da 100644 --- a/x/auction/keeper/msg_server.go +++ b/x/auction/keeper/msg_server.go @@ -20,18 +20,19 @@ 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. + // 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) + // Ensure that the number of transactions is less than or equal to the maximum + // allowed. + maxBundleSize, err := m.GetMaxBundleSize(ctx) if err != nil { return nil, err } @@ -40,27 +41,54 @@ func (m MsgServer) AuctionBid(goCtx context.Context, msg *types.MsgAuctionBid) ( 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 { + proposerFee, err := m.GetProposerFee(ctx) + if 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 + escrow, err := m.Keeper.GetEscrowAccount(ctx) + if err != nil { + return nil, err + } + + if proposerFee.IsZero() { + // send the entire bid to the escrow account when no proposer fee is set + if err := m.bankKeeper.SendCoins(ctx, bidder, escrow, msg.Bid); err != nil { + return nil, err + } + } else { + prevPropConsAddr := m.distrKeeper.GetPreviousProposerConsAddr(ctx) + prevProposer := m.stakingKeeper.ValidatorByConsAddr(ctx, prevPropConsAddr) + + // determine the amount of the bid that goes to the (previous) proposer + bid := sdk.NewDecCoinsFromCoins(msg.Bid...) + proposerReward, _ := bid.MulDecTruncate(proposerFee).TruncateDecimal() + + if err := m.bankKeeper.SendCoins(ctx, bidder, sdk.AccAddress(prevProposer.GetOperator()), proposerReward); err != nil { + return nil, err + } + + // Determine the amount of the remaining bid that goes to the escrow account. + // If a decimal remainder exists, it'll stay with the bidding account. + escrowTotal := bid.Sub(sdk.NewDecCoinsFromCoins(proposerReward...)) + escrowReward, _ := escrowTotal.TruncateDecimal() + + if err := m.bankKeeper.SendCoins(ctx, bidder, escrow, escrowReward); err != nil { + return nil, err + } + } 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. + // 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 } diff --git a/x/auction/keeper/msg_server_test.go b/x/auction/keeper/msg_server_test.go new file mode 100644 index 0000000..8aac072 --- /dev/null +++ b/x/auction/keeper/msg_server_test.go @@ -0,0 +1,127 @@ +package keeper_test + +import ( + "math/rand" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/skip-mev/pob/x/auction/types" +) + +func (suite *KeeperTestSuite) TestMsgAuctionBid() { + rng := rand.New(rand.NewSource(time.Now().Unix())) + accounts := RandomAccounts(rng, 4) + + bidder := accounts[0] + escrow := accounts[1] + + proposerCons := accounts[2] + proposerOperator := accounts[3] + proposer := stakingtypes.Validator{ + OperatorAddress: sdk.ValAddress(proposerOperator.Address).String(), + } + + testCases := []struct { + name string + msg *types.MsgAuctionBid + malleate func() + expectErr bool + }{ + { + name: "invalid bidder address", + msg: &types.MsgAuctionBid{ + Bidder: "foo", + }, + malleate: func() {}, + expectErr: true, + }, + { + name: "too many bundled transactions", + msg: &types.MsgAuctionBid{ + Bidder: bidder.Address.String(), + Transactions: [][]byte{{0xFF}, {0xFF}, {0xFF}}, + }, + malleate: func() { + params := types.DefaultParams() + params.MaxBundleSize = 2 + suite.auctionKeeper.SetParams(suite.ctx, params) + }, + expectErr: true, + }, + { + name: "valid bundle with no proposer fee", + msg: &types.MsgAuctionBid{ + Bidder: bidder.Address.String(), + Bid: sdk.NewCoins(sdk.NewInt64Coin("foo", 1024)), + Transactions: [][]byte{{0xFF}, {0xFF}}, + }, + malleate: func() { + params := types.DefaultParams() + params.ProposerFee = sdk.ZeroDec() + params.EscrowAccountAddress = escrow.Address.String() + suite.auctionKeeper.SetParams(suite.ctx, params) + + suite.bankKeeper.EXPECT(). + SendCoins( + suite.ctx, + bidder.Address, + escrow.Address, + sdk.NewCoins(sdk.NewInt64Coin("foo", 1024)), + ). + Return(nil). + AnyTimes() + }, + expectErr: false, + }, + { + name: "valid bundle with proposer fee", + msg: &types.MsgAuctionBid{ + Bidder: bidder.Address.String(), + Bid: sdk.NewCoins(sdk.NewInt64Coin("foo", 3416)), + Transactions: [][]byte{{0xFF}, {0xFF}}, + }, + malleate: func() { + params := types.DefaultParams() + params.ProposerFee = sdk.MustNewDecFromStr("0.30") + params.EscrowAccountAddress = escrow.Address.String() + suite.auctionKeeper.SetParams(suite.ctx, params) + + suite.distrKeeper.EXPECT(). + GetPreviousProposerConsAddr(suite.ctx). + Return(proposerCons.ConsKey.PubKey().Address().Bytes()) + + suite.stakingKeeper.EXPECT(). + ValidatorByConsAddr(suite.ctx, sdk.ConsAddress(proposerCons.ConsKey.PubKey().Address().Bytes())). + Return(proposer). + AnyTimes() + + suite.bankKeeper.EXPECT(). + SendCoins(suite.ctx, bidder.Address, proposerOperator.Address, sdk.NewCoins(sdk.NewInt64Coin("foo", 1024))). + Return(nil) + + suite.bankKeeper.EXPECT(). + SendCoins(suite.ctx, bidder.Address, escrow.Address, sdk.NewCoins(sdk.NewInt64Coin("foo", 2392))). + Return(nil) + }, + expectErr: false, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.malleate() + + _, err := suite.msgServer.AuctionBid(suite.ctx, tc.msg) + if tc.expectErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + } + }) + } +} + +func (suite *KeeperTestSuite) TestMsgUpdateParams() { + suite.T().SkipNow() +} diff --git a/x/auction/keeper/util_test.go b/x/auction/keeper/util_test.go new file mode 100644 index 0000000..c9ddb2c --- /dev/null +++ b/x/auction/keeper/util_test.go @@ -0,0 +1,99 @@ +package keeper_test + +import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + "github.com/cosmos/cosmos-sdk/x/auth/tx" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +type encodingConfig struct { + InterfaceRegistry codectypes.InterfaceRegistry + Codec codec.Codec + TxConfig client.TxConfig + Amino *codec.LegacyAmino +} + +func createTestEncodingConfig() encodingConfig { + cdc := codec.NewLegacyAmino() + interfaceRegistry := codectypes.NewInterfaceRegistry() + + banktypes.RegisterInterfaces(interfaceRegistry) + cryptocodec.RegisterInterfaces(interfaceRegistry) + + codec := codec.NewProtoCodec(interfaceRegistry) + + return encodingConfig{ + InterfaceRegistry: interfaceRegistry, + Codec: codec, + TxConfig: tx.NewTxConfig(codec, tx.DefaultSignModes), + Amino: cdc, + } +} + +type Account struct { + PrivKey cryptotypes.PrivKey + PubKey cryptotypes.PubKey + Address sdk.AccAddress + ConsKey cryptotypes.PrivKey +} + +func (acc Account) Equals(acc2 Account) bool { + return acc.Address.Equals(acc2.Address) +} + +func RandomAccounts(r *rand.Rand, n int) []Account { + accs := make([]Account, n) + + for i := 0; i < n; i++ { + pkSeed := make([]byte, 15) + r.Read(pkSeed) + + accs[i].PrivKey = secp256k1.GenPrivKeyFromSecret(pkSeed) + accs[i].PubKey = accs[i].PrivKey.PubKey() + accs[i].Address = sdk.AccAddress(accs[i].PubKey.Address()) + + accs[i].ConsKey = ed25519.GenPrivKeyFromSecret(pkSeed) + } + + return accs +} + +func createRandomTx(txCfg client.TxConfig, account Account, nonce, numberMsgs uint64) (authsigning.Tx, error) { + msgs := make([]sdk.Msg, numberMsgs) + for i := 0; i < int(numberMsgs); i++ { + msgs[i] = &banktypes.MsgSend{ + FromAddress: account.Address.String(), + ToAddress: account.Address.String(), + } + } + + txBuilder := txCfg.NewTxBuilder() + if err := txBuilder.SetMsgs(msgs...); err != nil { + return nil, err + } + + sigV2 := signing.SignatureV2{ + PubKey: account.PrivKey.PubKey(), + Data: &signing.SingleSignatureData{ + SignMode: txCfg.SignModeHandler().DefaultMode(), + Signature: nil, + }, + Sequence: nonce, + } + if err := txBuilder.SetSignatures(sigV2); err != nil { + return nil, err + } + + return txBuilder.GetTx(), nil +} diff --git a/x/auction/module.go b/x/auction/module.go index 04a3545..68922a5 100644 --- a/x/auction/module.go +++ b/x/auction/module.go @@ -26,7 +26,6 @@ var ( // 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 @@ -70,16 +69,11 @@ func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *r } // GetTxCmd returns the root tx command for the auction module. -func (AppModuleBasic) GetTxCmd() *cobra.Command { - return nil -} +func (AppModuleBasic) GetTxCmd() *cobra.Command { return nil } // GetQueryCmd returns no root query command for the auction module. -func (AppModuleBasic) GetQueryCmd() *cobra.Command { - return nil -} +func (AppModuleBasic) GetQueryCmd() *cobra.Command { return nil } -// -------------------- AppModule -------------------- // type AppModule struct { AppModuleBasic @@ -98,7 +92,11 @@ func NewAppModule(cdc codec.Codec, keeper keeper.Keeper, accountKeeper types.Acc } } -// RegisterServices registers a the gRPC Query and Msg services for the auciton module. +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return ConsensusVersion } + +// RegisterServices registers a the gRPC Query and Msg services for the x/auction +// 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 @@ -106,33 +104,24 @@ func (am AppModule) RegisterServices(cfg 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) +// 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. +// 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. +// 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) {} //nolint - -// EndBlock returns the end blocker for the auction module. It returns no validator updates. - -func (am AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { //nolint - return []abci.ValidatorUpdate{} -} diff --git a/x/auction/types/expected_keepers.go b/x/auction/types/expected_keepers.go index 6522c64..90c48a2 100644 --- a/x/auction/types/expected_keepers.go +++ b/x/auction/types/expected_keepers.go @@ -2,16 +2,27 @@ package types import ( sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) -// AccountKeeper defines the contract required for account APIs. +// BankKeeper defines the expected API contract for the x/auth module. type AccountKeeper interface { GetModuleAddress(moduleName string) sdk.AccAddress } -// BankKeeper defines the contract required for bank APIs. +// BankKeeper defines the expected API contract for the x/bank module. 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 GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins } + +// DistributionKeeper defines the expected API contract for the x/distribution +// module. +type DistributionKeeper interface { + GetPreviousProposerConsAddr(ctx sdk.Context) sdk.ConsAddress +} + +// StakingKeeper defines the expected API contract for the x/staking module. +type StakingKeeper interface { + ValidatorByConsAddr(sdk.Context, sdk.ConsAddress) stakingtypes.ValidatorI +} diff --git a/x/auction/types/genesis.pb.go b/x/auction/types/genesis.pb.go index 881ec9f..ac92b56 100644 --- a/x/auction/types/genesis.pb.go +++ b/x/auction/types/genesis.pb.go @@ -76,19 +76,23 @@ type Params struct { // max_bundle_size is the maximum number of transactions that can be bundled // in a single bundle. MaxBundleSize uint32 `protobuf:"varint,1,opt,name=max_bundle_size,json=maxBundleSize,proto3" json:"max_bundle_size,omitempty"` - // escrow_account_address is the address of the account that will hold the - // funds for the auctions. + // escrow_account_address is the address of the account that will receive a + // portion of the bid proceeds. EscrowAccountAddress string `protobuf:"bytes,2,opt,name=escrow_account_address,json=escrowAccountAddress,proto3" json:"escrow_account_address,omitempty"` // reserve_fee specifies the bid floor for 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 specifies the fee that the bidder must pay to enter the auction. + // min_buy_in_fee specifies the fee that the bidder must pay to enter 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. MinBidIncrement github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,5,rep,name=min_bid_increment,json=minBidIncrement,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"min_bid_increment"` - // front_running_protection specifies whether front running and sandwich attack protection - // is enabled. + // front_running_protection specifies whether front running and sandwich + // attack protection is enabled. FrontRunningProtection bool `protobuf:"varint,6,opt,name=front_running_protection,json=frontRunningProtection,proto3" json:"front_running_protection,omitempty"` + // proposer_fee defines the portion of the winning bid that goes to the block + // proposer that proposed the block. + ProposerFee github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,7,opt,name=proposer_fee,json=proposerFee,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"proposer_fee"` } func (m *Params) Reset() { *m = Params{} } @@ -174,37 +178,40 @@ func init() { func init() { proto.RegisterFile("pob/auction/v1/genesis.proto", fileDescriptor_9ed8651e43f855a1) } var fileDescriptor_9ed8651e43f855a1 = []byte{ - // 480 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x93, 0xb1, 0x6e, 0xd4, 0x30, - 0x18, 0xc7, 0x2f, 0xf4, 0x38, 0x81, 0x8f, 0x52, 0x35, 0xaa, 0x4e, 0xa1, 0xa0, 0x34, 0xea, 0x00, - 0x51, 0xa5, 0xda, 0xba, 0x02, 0x12, 0x42, 0x2c, 0x77, 0x48, 0xa0, 0x4a, 0x0c, 0x55, 0xba, 0xb1, - 0x58, 0x4e, 0xf2, 0x35, 0x58, 0xc5, 0x76, 0x88, 0x9d, 0x70, 0x57, 0xf1, 0x04, 0x4c, 0x3c, 0x06, - 0xea, 0xd4, 0xc7, 0xe8, 0xd8, 0x91, 0x09, 0xd0, 0xdd, 0xd0, 0xd7, 0x40, 0xb1, 0x43, 0xe9, 0xc0, - 0xda, 0x25, 0x89, 0xfc, 0xfb, 0xec, 0xdf, 0xf7, 0x45, 0x7f, 0xa3, 0x47, 0xa5, 0x4a, 0x09, 0xab, - 0x33, 0xc3, 0x95, 0x24, 0xcd, 0x98, 0x14, 0x20, 0x41, 0x73, 0x8d, 0xcb, 0x4a, 0x19, 0xe5, 0x8f, - 0xf4, 0x31, 0x2f, 0x05, 0x34, 0xb8, 0x54, 0x29, 0xee, 0xaa, 0x70, 0x33, 0xde, 0xdc, 0x28, 0x54, - 0xa1, 0x6c, 0x09, 0x69, 0xbf, 0x5c, 0xf5, 0x66, 0x98, 0x29, 0x2d, 0x94, 0x26, 0x29, 0xd3, 0x40, - 0x9a, 0x71, 0x0a, 0x86, 0x8d, 0x49, 0xa6, 0xb8, 0xec, 0xf8, 0x3a, 0x13, 0x5c, 0x2a, 0x62, 0x9f, - 0x6e, 0x69, 0xfb, 0x1d, 0xba, 0xf7, 0xd6, 0x19, 0x0f, 0x0d, 0x33, 0xe0, 0xbf, 0x42, 0x83, 0x92, - 0x55, 0x4c, 0xe8, 0xc0, 0x8b, 0xbc, 0x78, 0xb8, 0x17, 0xe2, 0xff, 0x77, 0x80, 0x0f, 0x6c, 0xd5, - 0xb4, 0x7f, 0xfe, 0x73, 0xab, 0x97, 0x74, 0x7b, 0xb6, 0x4f, 0xfb, 0x68, 0xe0, 0x80, 0xff, 0x18, - 0xad, 0x09, 0x36, 0xa3, 0x69, 0x2d, 0xf3, 0x8f, 0x40, 0x35, 0x3f, 0x01, 0x7b, 0xe2, 0x6a, 0xb2, - 0x2a, 0xd8, 0x6c, 0x6a, 0x57, 0x0f, 0xf9, 0x09, 0xf8, 0xcf, 0xd0, 0x08, 0x74, 0x56, 0xa9, 0xcf, - 0x94, 0x65, 0x99, 0xaa, 0xa5, 0xa1, 0x2c, 0xcf, 0x2b, 0xd0, 0x3a, 0xb8, 0x15, 0x79, 0xf1, 0xdd, - 0x64, 0xc3, 0xd1, 0x89, 0x83, 0x13, 0xc7, 0xfc, 0x4f, 0x68, 0x58, 0x81, 0x86, 0xaa, 0x01, 0x7a, - 0x04, 0x10, 0xac, 0x44, 0x2b, 0xf1, 0x70, 0xef, 0x01, 0x76, 0xf3, 0xe3, 0x76, 0x7e, 0xdc, 0xcd, - 0x8f, 0x5f, 0x2b, 0x2e, 0xa7, 0xcf, 0xdb, 0x36, 0x4f, 0x7f, 0x6d, 0xc5, 0x05, 0x37, 0x1f, 0xea, - 0x14, 0x67, 0x4a, 0x90, 0xee, 0x67, 0xb9, 0xd7, 0xae, 0xce, 0x8f, 0x89, 0x99, 0x97, 0xa0, 0xed, - 0x06, 0xfd, 0xfd, 0xf2, 0x6c, 0xc7, 0x4b, 0x50, 0x27, 0x79, 0x03, 0xe0, 0xd7, 0xe8, 0xbe, 0xe0, - 0x92, 0xa6, 0xf5, 0x9c, 0x72, 0x69, 0xad, 0xfd, 0x1b, 0xb2, 0x0e, 0x05, 0x97, 0xd3, 0x7a, 0xbe, - 0x2f, 0x5b, 0xed, 0x17, 0xb4, 0x6e, 0xb5, 0x3c, 0xa7, 0x5c, 0x66, 0x15, 0x08, 0x90, 0x26, 0xb8, - 0x7d, 0x43, 0xe6, 0xb5, 0xd6, 0xcc, 0xf3, 0xfd, 0xbf, 0x22, 0xff, 0x05, 0x0a, 0x8e, 0x2a, 0x25, - 0x0d, 0xad, 0x6a, 0x29, 0xb9, 0x2c, 0x68, 0x9b, 0x1a, 0xb0, 0x21, 0x08, 0x06, 0x91, 0x17, 0xdf, - 0x49, 0x46, 0x96, 0x27, 0x0e, 0x1f, 0x5c, 0xd1, 0x97, 0xd1, 0xd7, 0xcb, 0xb3, 0x9d, 0x87, 0xd7, - 0x64, 0xb3, 0xab, 0x94, 0x77, 0xd1, 0x99, 0x9c, 0x2f, 0x42, 0xef, 0x62, 0x11, 0x7a, 0xbf, 0x17, - 0xa1, 0xf7, 0x6d, 0x19, 0xf6, 0x2e, 0x96, 0x61, 0xef, 0xc7, 0x32, 0xec, 0xbd, 0x7f, 0x72, 0xad, - 0xeb, 0x36, 0x7e, 0xbb, 0x02, 0x1a, 0xd2, 0xde, 0x93, 0x7f, 0x67, 0xd8, 0xd6, 0xd3, 0x81, 0x0d, - 0xf1, 0xd3, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x96, 0x16, 0x18, 0xc6, 0x45, 0x03, 0x00, 0x00, + // 517 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x93, 0x31, 0x6f, 0xd3, 0x40, + 0x14, 0xc7, 0x63, 0x1a, 0x02, 0x5c, 0x5a, 0xaa, 0x5a, 0x55, 0x64, 0x0a, 0x72, 0xa2, 0x0e, 0x25, + 0xaa, 0xd4, 0x3b, 0xa5, 0x80, 0x84, 0x10, 0x4b, 0x02, 0x2a, 0xaa, 0xc4, 0x50, 0xdc, 0x8d, 0xc5, + 0x3a, 0xdb, 0xaf, 0xe1, 0x54, 0xee, 0xce, 0xdc, 0xd9, 0x26, 0xa9, 0xf8, 0x04, 0x4c, 0x7c, 0x0c, + 0xc4, 0xd4, 0x8f, 0xd1, 0xb1, 0x23, 0x62, 0x28, 0x28, 0x19, 0xfa, 0x29, 0x90, 0xd0, 0xdd, 0xb9, + 0xa5, 0x03, 0x03, 0x4b, 0x17, 0xdb, 0xba, 0xff, 0xff, 0xde, 0xef, 0xbd, 0xe7, 0xf7, 0xd0, 0x83, + 0x5c, 0x26, 0x84, 0x96, 0x69, 0xc1, 0xa4, 0x20, 0xd5, 0x80, 0x8c, 0x41, 0x80, 0x66, 0x1a, 0xe7, + 0x4a, 0x16, 0xd2, 0xef, 0xe8, 0x43, 0x96, 0x73, 0xa8, 0x70, 0x2e, 0x13, 0x5c, 0xbb, 0x70, 0x35, + 0x58, 0x5b, 0x1d, 0xcb, 0xb1, 0xb4, 0x16, 0x62, 0xbe, 0x9c, 0x7b, 0x2d, 0x4c, 0xa5, 0xe6, 0x52, + 0x93, 0x84, 0x6a, 0x20, 0xd5, 0x20, 0x81, 0x82, 0x0e, 0x48, 0x2a, 0x99, 0xa8, 0xf5, 0x15, 0xca, + 0x99, 0x90, 0xc4, 0x3e, 0xdd, 0xd1, 0xfa, 0x6b, 0xb4, 0xf8, 0xca, 0x11, 0xf7, 0x0b, 0x5a, 0x80, + 0xff, 0x1c, 0xb5, 0x72, 0xaa, 0x28, 0xd7, 0x81, 0xd7, 0xf3, 0xfa, 0xed, 0xed, 0x10, 0xff, 0x3b, + 0x03, 0xbc, 0x67, 0x5d, 0xa3, 0xe6, 0xc9, 0x59, 0xb7, 0x11, 0xd5, 0x77, 0xd6, 0x7f, 0x37, 0x51, + 0xcb, 0x09, 0xfe, 0x06, 0x5a, 0xe6, 0x74, 0x12, 0x27, 0xa5, 0xc8, 0xde, 0x43, 0xac, 0xd9, 0x11, + 0xd8, 0x88, 0x4b, 0xd1, 0x12, 0xa7, 0x93, 0x91, 0x3d, 0xdd, 0x67, 0x47, 0xe0, 0x3f, 0x46, 0x1d, + 0xd0, 0xa9, 0x92, 0x1f, 0x63, 0x9a, 0xa6, 0xb2, 0x14, 0x45, 0x4c, 0xb3, 0x4c, 0x81, 0xd6, 0xc1, + 0x8d, 0x9e, 0xd7, 0xbf, 0x13, 0xad, 0x3a, 0x75, 0xe8, 0xc4, 0xa1, 0xd3, 0xfc, 0x0f, 0xa8, 0xad, + 0x40, 0x83, 0xaa, 0x20, 0x3e, 0x00, 0x08, 0x16, 0x7a, 0x0b, 0xfd, 0xf6, 0xf6, 0x3d, 0xec, 0xea, + 0xc7, 0xa6, 0x7e, 0x5c, 0xd7, 0x8f, 0x5f, 0x48, 0x26, 0x46, 0x4f, 0x4c, 0x9a, 0xdf, 0x7e, 0x76, + 0xfb, 0x63, 0x56, 0xbc, 0x2b, 0x13, 0x9c, 0x4a, 0x4e, 0xea, 0x66, 0xb9, 0xd7, 0x96, 0xce, 0x0e, + 0x49, 0x31, 0xcd, 0x41, 0xdb, 0x0b, 0xfa, 0xeb, 0xf9, 0xf1, 0xa6, 0x17, 0xa1, 0x1a, 0xb2, 0x03, + 0xe0, 0x97, 0xe8, 0x2e, 0x67, 0x22, 0x4e, 0xca, 0x69, 0xcc, 0x84, 0xa5, 0x36, 0xaf, 0x89, 0xda, + 0xe6, 0x4c, 0x8c, 0xca, 0xe9, 0xae, 0x30, 0xd8, 0x4f, 0x68, 0xc5, 0x62, 0x59, 0x16, 0x33, 0x91, + 0x2a, 0xe0, 0x20, 0x8a, 0xe0, 0xe6, 0x35, 0x91, 0x97, 0x0d, 0x99, 0x65, 0xbb, 0x17, 0x20, 0xff, + 0x29, 0x0a, 0x0e, 0x94, 0x14, 0x45, 0xac, 0x4a, 0x21, 0x98, 0x18, 0xc7, 0x66, 0x6a, 0xc0, 0x0e, + 0x41, 0xd0, 0xea, 0x79, 0xfd, 0xdb, 0x51, 0xc7, 0xea, 0x91, 0x93, 0xf7, 0x2e, 0x55, 0xff, 0x0d, + 0x5a, 0xcc, 0x95, 0xcc, 0xa5, 0x06, 0x65, 0x9b, 0x75, 0xcb, 0xfc, 0xcd, 0x11, 0x36, 0x79, 0xfd, + 0x38, 0xeb, 0x6e, 0xfc, 0x47, 0x5e, 0x2f, 0x21, 0x8d, 0xda, 0x17, 0x31, 0x76, 0x00, 0x9e, 0xf5, + 0x3e, 0x9f, 0x1f, 0x6f, 0xde, 0xbf, 0xe2, 0x9b, 0x5c, 0x2e, 0x4e, 0x3d, 0x8d, 0xc3, 0x93, 0x59, + 0xe8, 0x9d, 0xce, 0x42, 0xef, 0xd7, 0x2c, 0xf4, 0xbe, 0xcc, 0xc3, 0xc6, 0xe9, 0x3c, 0x6c, 0x7c, + 0x9f, 0x87, 0x8d, 0xb7, 0x0f, 0xaf, 0x00, 0xcd, 0x44, 0x6f, 0x71, 0xa8, 0x88, 0x59, 0xbd, 0xbf, + 0x31, 0x2c, 0x35, 0x69, 0xd9, 0xbd, 0x78, 0xf4, 0x27, 0x00, 0x00, 0xff, 0xff, 0x32, 0x3f, 0xb6, + 0x9a, 0x98, 0x03, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -260,6 +267,16 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size := m.ProposerFee.Size() + i -= size + if _, err := m.ProposerFee.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x3a if m.FrontRunningProtection { i-- if m.FrontRunningProtection { @@ -383,6 +400,8 @@ func (m *Params) Size() (n int) { if m.FrontRunningProtection { n += 2 } + l = m.ProposerFee.Size() + n += 1 + l + sovGenesis(uint64(l)) return n } @@ -677,6 +696,40 @@ func (m *Params) Unmarshal(dAtA []byte) error { } } m.FrontRunningProtection = bool(v != 0) + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProposerFee", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ProposerFee.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/auction/types/msgs_test.go b/x/auction/types/msgs_test.go index 80d599b..3c1dd2e 100644 --- a/x/auction/types/msgs_test.go +++ b/x/auction/types/msgs_test.go @@ -116,6 +116,7 @@ func TestMsgUpdateParams(t *testing.T) { msg: types.MsgUpdateParams{ Authority: sdk.AccAddress([]byte("test")).String(), Params: types.Params{ + ProposerFee: sdk.NewDec(1), EscrowAccountAddress: sdk.AccAddress([]byte("test")).String(), }, }, diff --git a/x/auction/types/params.go b/x/auction/types/params.go index 221bbc3..4cc5157 100644 --- a/x/auction/types/params.go +++ b/x/auction/types/params.go @@ -3,6 +3,7 @@ package types import ( fmt "fmt" + "cosmossdk.io/math" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -13,10 +14,17 @@ var ( DefaultMinBuyInFee = sdk.Coins{} DefaultMinBidIncrement = sdk.Coins{} DefaultFrontRunningProtection = true + DefaultProposerFee = sdk.ZeroDec() ) // NewParams returns a new Params instance with the provided values. -func NewParams(maxBundleSize uint32, escrowAccountAddress string, reserveFee, minBuyInFee, minBidIncrement sdk.Coins, frontRunningProtection bool) Params { +func NewParams( + maxBundleSize uint32, + escrowAccountAddress string, + reserveFee, minBuyInFee, minBidIncrement sdk.Coins, + frontRunningProtection bool, + proposerFee sdk.Dec, +) Params { return Params{ MaxBundleSize: maxBundleSize, EscrowAccountAddress: escrowAccountAddress, @@ -24,6 +32,7 @@ func NewParams(maxBundleSize uint32, escrowAccountAddress string, reserveFee, mi MinBuyInFee: minBuyInFee, MinBidIncrement: minBidIncrement, FrontRunningProtection: frontRunningProtection, + ProposerFee: proposerFee, } } @@ -36,6 +45,7 @@ func DefaultParams() Params { DefaultMinBuyInFee, DefaultMinBidIncrement, DefaultFrontRunningProtection, + DefaultProposerFee, ) } @@ -57,10 +67,29 @@ func (p Params) Validate() error { return fmt.Errorf("invalid minimum bid increment (%s)", err) } + if err := validateProposerFee(p.ProposerFee); err != nil { + return err + } + return nil } -// validateEscrowAccountAddress ensures the escrow account address is a valid address (if set). +func validateProposerFee(v sdk.Dec) error { + if v.IsNil() { + return fmt.Errorf("proposer fee cannot be nil: %s", v) + } + if v.IsNegative() { + return fmt.Errorf("proposer fee cannot be negative: %s", v) + } + if v.GT(math.LegacyOneDec()) { + return fmt.Errorf("proposer fee too large: %s", v) + } + + return nil +} + +// validateEscrowAccountAddress ensures the escrow account address is a valid +// address. 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 {