[ENG-599]: Ante handler testing (#33)

Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>
This commit is contained in:
David Terpay 2023-03-22 14:32:58 -04:00 committed by GitHub
parent 4a13a85ab1
commit 2db90d4eae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 553 additions and 0 deletions

262
x/auction/ante/ante_test.go Normal file
View File

@ -0,0 +1,262 @@
package ante_test
import (
"math/rand"
"testing"
"time"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/golang/mock/gomock"
"github.com/skip-mev/pob/mempool"
"github.com/skip-mev/pob/x/auction/ante"
"github.com/skip-mev/pob/x/auction/keeper"
auctiontypes "github.com/skip-mev/pob/x/auction/types"
"github.com/stretchr/testify/suite"
)
type AnteTestSuite struct {
suite.Suite
ctx sdk.Context
// mempool setup
encodingConfig encodingConfig
random *rand.Rand
// auction setup
auctionKeeper keeper.Keeper
bankKeeper *MockBankKeeper
accountKeeper *MockAccountKeeper
distrKeeper *MockDistributionKeeper
stakingKeeper *MockStakingKeeper
auctionDecorator ante.AuctionDecorator
key *storetypes.KVStoreKey
authorityAccount sdk.AccAddress
}
func TestAnteTestSuite(t *testing.T) {
suite.Run(t, new(AnteTestSuite))
}
func (suite *AnteTestSuite) SetupTest() {
// General config
suite.encodingConfig = createTestEncodingConfig()
suite.random = rand.New(rand.NewSource(time.Now().Unix()))
suite.key = sdk.NewKVStoreKey(auctiontypes.StoreKey)
testCtx := testutil.DefaultContextWithDB(suite.T(), suite.key, sdk.NewTransientStoreKey("transient_test"))
suite.ctx = testCtx.Ctx
// Keepers set up
ctrl := gomock.NewController(suite.T())
suite.accountKeeper = NewMockAccountKeeper(ctrl)
suite.accountKeeper.EXPECT().GetModuleAddress(auctiontypes.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.encodingConfig.Codec,
suite.key,
suite.accountKeeper,
suite.bankKeeper,
suite.distrKeeper,
suite.stakingKeeper,
suite.authorityAccount.String(),
)
err := suite.auctionKeeper.SetParams(suite.ctx, auctiontypes.DefaultParams())
suite.Require().NoError(err)
}
func (suite *AnteTestSuite) executeAnteHandler(tx sdk.Tx, balance sdk.Coins) (sdk.Context, error) {
signer := tx.GetMsgs()[0].GetSigners()[0]
suite.bankKeeper.EXPECT().GetAllBalances(suite.ctx, signer).AnyTimes().Return(balance)
next := func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) {
return ctx, nil
}
return suite.auctionDecorator.AnteHandle(suite.ctx, tx, false, next)
}
func (suite *AnteTestSuite) TestAnteHandler() {
var (
// Bid set up
bidder = RandomAccounts(suite.random, 1)[0]
bid = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1000)))
balance = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(10000)))
signers = []Account{bidder}
// Top bidding auction tx set up
topBidder = RandomAccounts(suite.random, 1)[0]
topBid = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(100)))
insertTopBid = true
// Auction setup
maxBundleSize uint32 = 5
reserveFee = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(100)))
minBuyInFee = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(100)))
minBidIncrement = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(100)))
frontRunningProtection = true
)
cases := []struct {
name string
malleate func()
pass bool
}{
{
"empty mempool, valid bid",
func() {
insertTopBid = false
},
true,
},
{
"smaller bid than winning bid, invalid auction tx",
func() {
insertTopBid = true
topBid = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(100000)))
},
false,
},
{
"bidder has insufficient balance, invalid auction tx",
func() {
insertTopBid = false
balance = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(10)))
},
false,
},
{
"bid is smaller than reserve fee, invalid auction tx",
func() {
balance = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(10000)))
bid = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(101)))
reserveFee = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1000)))
},
false,
},
{
"bid is greater than reserve fee but has insufficient balance to pay the buy in fee",
func() {
balance = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1000)))
bid = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(101)))
reserveFee = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(100)))
minBuyInFee = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1000)))
},
false,
},
{
"valid auction bid tx",
func() {
balance = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(10000)))
bid = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1000)))
reserveFee = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(100)))
minBuyInFee = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(100)))
},
true,
},
{
"auction tx is the top bidding tx",
func() {
balance = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(10000)))
bid = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1000)))
reserveFee = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(100)))
minBuyInFee = sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(100)))
insertTopBid = true
topBidder = bidder
topBid = bid
signers = []Account{}
},
true,
},
{
"invalid frontrunning auction bid tx",
func() {
randomAccount := RandomAccounts(suite.random, 2)
bidder := randomAccount[0]
otherUser := randomAccount[1]
insertTopBid = false
signers = []Account{bidder, otherUser}
},
false,
},
{
"valid frontrunning auction bid tx",
func() {
randomAccount := RandomAccounts(suite.random, 2)
bidder := randomAccount[0]
otherUser := randomAccount[1]
signers = []Account{bidder, otherUser}
frontRunningProtection = false
},
true,
},
{
"invalid sandwiching auction bid tx",
func() {
randomAccount := RandomAccounts(suite.random, 2)
bidder := randomAccount[0]
otherUser := randomAccount[1]
signers = []Account{bidder, otherUser, bidder}
frontRunningProtection = true
},
false,
},
{
"invalid auction bid tx with many signers",
func() {
signers = RandomAccounts(suite.random, 10)
frontRunningProtection = true
},
false,
},
}
for _, tc := range cases {
suite.Run(tc.name, func() {
suite.SetupTest()
tc.malleate()
// Set the auction params
err := suite.auctionKeeper.SetParams(suite.ctx, auctiontypes.Params{
MaxBundleSize: maxBundleSize,
ReserveFee: reserveFee,
MinBuyInFee: minBuyInFee,
MinBidIncrement: minBidIncrement,
FrontRunningProtection: frontRunningProtection,
})
suite.Require().NoError(err)
// Insert the top bid into the mempool
mempool := mempool.NewAuctionMempool(suite.encodingConfig.TxConfig.TxDecoder(), 0)
if insertTopBid {
topAuctionTx, err := createAuctionTxWithSigners(suite.encodingConfig.TxConfig, topBidder, topBid, 0, []Account{})
suite.Require().NoError(err)
suite.Require().Equal(0, mempool.CountTx())
suite.Require().Equal(0, mempool.CountAuctionTx())
suite.Require().NoError(mempool.Insert(suite.ctx, topAuctionTx))
suite.Require().Equal(1, mempool.CountTx())
suite.Require().Equal(1, mempool.CountAuctionTx())
}
// Create the actual auction tx and insert into the mempool
auctionTx, err := createAuctionTxWithSigners(suite.encodingConfig.TxConfig, bidder, bid, 0, signers)
suite.Require().NoError(err)
// Execute the ante handler
suite.auctionDecorator = ante.NewAuctionDecorator(suite.auctionKeeper, suite.encodingConfig.TxConfig.TxDecoder(), suite.encodingConfig.TxConfig.TxEncoder(), mempool)
_, err = suite.executeAnteHandler(auctionTx, balance)
if tc.pass {
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
}
})
}
}

View File

@ -0,0 +1,144 @@
package ante_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)
}

View File

@ -0,0 +1,147 @@
package ante_test
import (
"math/rand"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"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"
auctiontypes "github.com/skip-mev/pob/x/auction/types"
)
type encodingConfig struct {
InterfaceRegistry types.InterfaceRegistry
Codec codec.Codec
TxConfig client.TxConfig
Amino *codec.LegacyAmino
}
func createTestEncodingConfig() encodingConfig {
cdc := codec.NewLegacyAmino()
interfaceRegistry := types.NewInterfaceRegistry()
banktypes.RegisterInterfaces(interfaceRegistry)
cryptocodec.RegisterInterfaces(interfaceRegistry)
auctiontypes.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 createTx(txCfg client.TxConfig, account Account, nonce uint64, msgs []sdk.Msg) (authsigning.Tx, error) {
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
}
func createRandomMsgs(acc sdk.AccAddress, numberMsgs int) []sdk.Msg {
msgs := make([]sdk.Msg, numberMsgs)
for i := 0; i < numberMsgs; i++ {
msgs[i] = &banktypes.MsgSend{
FromAddress: acc.String(),
ToAddress: acc.String(),
}
}
return msgs
}
func createAuctionTxWithSigners(txCfg client.TxConfig, bidder Account, bid sdk.Coins, nonce uint64, signers []Account) (authsigning.Tx, error) {
bidMsg := &auctiontypes.MsgAuctionBid{
Bidder: bidder.Address.String(),
Bid: bid,
Transactions: make([][]byte, len(signers)),
}
for i := 0; i < len(signers); i++ {
randomMsg := createRandomMsgs(signers[i].Address, 1)
randomTx, err := createTx(txCfg, signers[i], 0, randomMsg)
if err != nil {
return nil, err
}
bz, err := txCfg.TxEncoder()(randomTx)
if err != nil {
return nil, err
}
bidMsg.Transactions[i] = bz
}
txBuilder := txCfg.NewTxBuilder()
if err := txBuilder.SetMsgs(bidMsg); err != nil {
return nil, err
}
sigV2 := signing.SignatureV2{
PubKey: bidder.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
}