Skip to content

Commit

Permalink
feat: init custom fee antehandler and posthandler (#27)
Browse files Browse the repository at this point in the history
* go mod

* add ante

* set antehandler

* cute

* lint fix

* setup and wire

* wire fmk

* clean

* format

* todo

* lint fix

* rename

* wip

* proto

* add param

* add get

* test

* format

* re-wire

* add ak

* fee decorator

* mock

* always check and use fee

* extend with tip

* fix lint

* refactor

* utd

* utd

* setup test

* attempt test

* fmt

* clean

* add

* fix

* rename

* set posthandler

* set posthandler

* setup post

* finalize

* add options

* clean

* comments

* clean

* rename

* use names

* Update x/feemarket/types/expected_keepers.go

Co-authored-by: David Terpay <[email protected]>

* Update x/feemarket/post/fee.go

Co-authored-by: David Terpay <[email protected]>

* Update x/feemarket/ante/fee.go

Co-authored-by: David Terpay <[email protected]>

* fix

* use feeTx

* fix

* separate

* refactor tip

* fix

* use update

---------

Co-authored-by: David Terpay <[email protected]>
  • Loading branch information
Alex Johnson and davidterpay authored Nov 28, 2023
1 parent 672c304 commit e872422
Show file tree
Hide file tree
Showing 30 changed files with 1,855 additions and 45 deletions.
36 changes: 23 additions & 13 deletions tests/app/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"

feemarketante "github.com/skip-mev/feemarket/x/feemarket/ante"
)

// HandlerOptions are the options required for constructing an SDK AnteHandler with the fee market injected.
type HandlerOptions struct {
BaseOptions authante.HandlerOptions
// AnteHandlerOptions are the options required for constructing an SDK AnteHandler with the fee market injected.
type AnteHandlerOptions struct {
BaseOptions authante.HandlerOptions
AccountKeeper feemarketante.AccountKeeper
FeeMarketKeeper feemarketante.FeeMarketKeeper
}

// NewAnteHandler returns an AnteHandler that checks and increments sequence
// numbers, checks signatures & account numbers, and deducts fees from the first
// signer.
func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
if options.BaseOptions.AccountKeeper == nil {
func NewAnteHandler(options AnteHandlerOptions) (sdk.AnteHandler, error) {
if options.AccountKeeper == nil {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "account keeper is required for ante builder")
}

Expand All @@ -29,19 +33,25 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder")
}

if options.FeeMarketKeeper == nil {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "feemarket keeper is required for ante builder")
}

anteDecorators := []sdk.AnteDecorator{
authante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
authante.NewExtensionOptionsDecorator(options.BaseOptions.ExtensionOptionChecker),
authante.NewValidateBasicDecorator(),
authante.NewTxTimeoutHeightDecorator(),
authante.NewValidateMemoDecorator(options.BaseOptions.AccountKeeper),
authante.NewConsumeGasForTxSizeDecorator(options.BaseOptions.AccountKeeper),
authante.NewDeductFeeDecorator(options.BaseOptions.AccountKeeper, options.BaseOptions.BankKeeper, options.BaseOptions.FeegrantKeeper, options.BaseOptions.TxFeeChecker),
authante.NewSetPubKeyDecorator(options.BaseOptions.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
authante.NewValidateSigCountDecorator(options.BaseOptions.AccountKeeper),
authante.NewSigGasConsumeDecorator(options.BaseOptions.AccountKeeper, options.BaseOptions.SigGasConsumer),
authante.NewSigVerificationDecorator(options.BaseOptions.AccountKeeper, options.BaseOptions.SignModeHandler),
authante.NewIncrementSequenceDecorator(options.BaseOptions.AccountKeeper),
authante.NewValidateMemoDecorator(options.AccountKeeper),
authante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
feemarketante.NewFeeMarketCheckDecorator( // fee market check replaces fee deduct decorator
options.FeeMarketKeeper,
), // fees are deducted in the fee market deduct post handler
authante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
authante.NewValidateSigCountDecorator(options.AccountKeeper),
authante.NewSigGasConsumeDecorator(options.AccountKeeper, options.BaseOptions.SigGasConsumer),
authante.NewSigVerificationDecorator(options.AccountKeeper, options.BaseOptions.SignModeHandler),
authante.NewIncrementSequenceDecorator(options.AccountKeeper),
}

return sdk.ChainAnteDecorators(anteDecorators...), nil
Expand Down
24 changes: 21 additions & 3 deletions tests/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (
authzmodule "github.com/cosmos/cosmos-sdk/x/authz/module"
"github.com/cosmos/cosmos-sdk/x/bank"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
consensus "github.com/cosmos/cosmos-sdk/x/consensus"
"github.com/cosmos/cosmos-sdk/x/consensus"
consensuskeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper"
"github.com/cosmos/cosmos-sdk/x/crisis"
crisiskeeper "github.com/cosmos/cosmos-sdk/x/crisis/keeper"
Expand Down Expand Up @@ -253,20 +253,38 @@ func New(

// Create a global ante handler that will be called on each transaction when
// proposals are being built and verified.
handlerOptions := ante.HandlerOptions{
anteHandlerOptions := ante.HandlerOptions{
AccountKeeper: app.AccountKeeper,
BankKeeper: app.BankKeeper,
FeegrantKeeper: app.FeeGrantKeeper,
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
SignModeHandler: app.txConfig.SignModeHandler(),
}

anteHandler, err := ante.NewAnteHandler(handlerOptions)
anteOptions := AnteHandlerOptions{
BaseOptions: anteHandlerOptions,
AccountKeeper: app.AccountKeeper,
FeeMarketKeeper: &app.FeeMarketKeeper,
}
anteHandler, err := NewAnteHandler(anteOptions)
if err != nil {
panic(err)
}

postHandlerOptions := PostHandlerOptions{
AccountKeeper: app.AccountKeeper,
BankKeeper: app.BankKeeper,
FeeGrantKeeper: app.FeeGrantKeeper,
FeeMarketKeeper: &app.FeeMarketKeeper,
}
postHandler, err := NewPostHandler(postHandlerOptions)
if err != nil {
panic(err)
}

// set ante and post handlers
app.App.SetAnteHandler(anteHandler)
app.App.SetPostHandler(postHandler)

// ---------------------------------------------------------------------------- //
// ------------------------- End Custom Code ---------------------------------- //
Expand Down
10 changes: 6 additions & 4 deletions tests/app/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,16 @@ var (
// so that other modules that want to create or claim capabilities afterwards in InitChain
// can do so safely.
genesisModuleOrder = []string{
capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName,
capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, feemarkettypes.ModuleName,
distrtypes.ModuleName, stakingtypes.ModuleName, slashingtypes.ModuleName, govtypes.ModuleName,
minttypes.ModuleName, crisistypes.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authz.ModuleName,
feegrant.ModuleName, group.ModuleName, paramstypes.ModuleName, upgradetypes.ModuleName,
vestingtypes.ModuleName, consensustypes.ModuleName, feemarkettypes.ModuleName,
vestingtypes.ModuleName, consensustypes.ModuleName,
}

// module account permissions
moduleAccPerms = []*authmodulev1.ModuleAccountPermission{
{Account: feemarkettypes.FeeCollectorName, Permissions: []string{authtypes.Burner}}, // allow fee market to burn
{Account: authtypes.FeeCollectorName},
{Account: distrtypes.ModuleName},
{Account: minttypes.ModuleName, Permissions: []string{authtypes.Minter}},
Expand All @@ -82,6 +83,7 @@ var (
minttypes.ModuleName,
stakingtypes.BondedPoolName,
stakingtypes.NotBondedPoolName,
feemarkettypes.FeeCollectorName,
// We allow the following module accounts to receive funds:
// govtypes.ModuleName
}
Expand All @@ -101,6 +103,7 @@ var (
BeginBlockers: []string{
upgradetypes.ModuleName,
capabilitytypes.ModuleName,
feemarkettypes.ModuleName,
minttypes.ModuleName,
distrtypes.ModuleName,
slashingtypes.ModuleName,
Expand All @@ -116,12 +119,12 @@ var (
group.ModuleName,
paramstypes.ModuleName,
vestingtypes.ModuleName,
feemarkettypes.ModuleName,
consensustypes.ModuleName,
},
EndBlockers: []string{
crisistypes.ModuleName,
govtypes.ModuleName,
feemarkettypes.ModuleName,
stakingtypes.ModuleName,
capabilitytypes.ModuleName,
authtypes.ModuleName,
Expand All @@ -138,7 +141,6 @@ var (
consensustypes.ModuleName,
upgradetypes.ModuleName,
vestingtypes.ModuleName,
feemarkettypes.ModuleName,
},
OverrideStoreKeys: []*runtimev1alpha1.StoreKeyConfig{
{
Expand Down
43 changes: 43 additions & 0 deletions tests/app/post.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package app

import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

feemarketpost "github.com/skip-mev/feemarket/x/feemarket/post"
)

// PostHandlerOptions are the options required for constructing a FeeMarket PostHandler.
type PostHandlerOptions struct {
AccountKeeper feemarketpost.AccountKeeper
BankKeeper feemarketpost.BankKeeper
FeeMarketKeeper feemarketpost.FeeMarketKeeper
FeeGrantKeeper feemarketpost.FeeGrantKeeper
}

// NewPostHandler returns a PostHandler chain with the fee deduct decorator.
func NewPostHandler(options PostHandlerOptions) (sdk.PostHandler, error) {
if options.AccountKeeper == nil {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "account keeper is required for post builder")
}

if options.BankKeeper == nil {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "bank keeper is required for post builder")
}

if options.FeeMarketKeeper == nil {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "feemarket keeper is required for post builder")
}

postDecorators := []sdk.PostDecorator{
feemarketpost.NewFeeMarketDeductDecorator(
options.AccountKeeper,
options.BankKeeper,
options.FeeGrantKeeper,
options.FeeMarketKeeper,
),
}

return sdk.ChainPostDecorators(postDecorators...), nil
}
8 changes: 4 additions & 4 deletions tests/integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import (

var (
// config params
numValidators = 1
numFullNodes = 0
numValidators = 3
numFullNodes = 1
denom = "stake"

image = ibc.DockerImage{
Expand All @@ -28,7 +28,7 @@ var (
}
encodingConfig = MakeEncodingConfig()
noHostMount = false
gasAdjustment = 2.0
gasAdjustment = 10.0

genesisKV = []cosmos.GenesisKV{
{
Expand Down Expand Up @@ -62,7 +62,7 @@ var (
Bech32Prefix: "cosmos",
CoinType: "118",
GasAdjustment: gasAdjustment,
GasPrices: fmt.Sprintf("20%s", denom),
GasPrices: fmt.Sprintf("200%s", denom),
TrustingPeriod: "48h",
NoHostMount: noHostMount,
ModifyGenesis: cosmos.ModifyGenesis(genesisKV),
Expand Down
95 changes: 93 additions & 2 deletions tests/integration/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math/rand"
"os"
"path"
"strings"
Expand Down Expand Up @@ -46,8 +48,8 @@ type KeyringOverride struct {
// ChainBuilderFromChainSpec creates an interchaintest chain builder factory given a ChainSpec
// and returns the associated chain
func ChainBuilderFromChainSpec(t *testing.T, spec *interchaintest.ChainSpec) ibc.Chain {
// require that NumFullNodes == NumValidators == 4
require.Equal(t, *spec.NumValidators, 1)
// require that NumFullNodes == NumValidators == 3
require.Equal(t, *spec.NumValidators, 3)

cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{spec})

Expand Down Expand Up @@ -447,3 +449,92 @@ func (s *TestSuite) keyringDirFromNode() string {

return localDir
}

// GetAndFundTestUserWithMnemonic restores a user using the given mnemonic
// and funds it with the native chain denom.
// The caller should wait for some blocks to complete before the funds will be accessible.
func (s *TestSuite) GetAndFundTestUserWithMnemonic(
ctx context.Context,
keyNamePrefix, mnemonic string,
amount int64,
chain *cosmos.CosmosChain,
) (ibc.Wallet, error) {
chainCfg := chain.Config()
keyName := fmt.Sprintf("%s-%s-%s", keyNamePrefix, chainCfg.ChainID, RandLowerCaseLetterString(3))
user, err := chain.BuildWallet(ctx, keyName, mnemonic)
if err != nil {
return nil, fmt.Errorf("failed to get source user wallet: %w", err)
}

err = chain.SendFunds(ctx, interchaintest.FaucetAccountKeyName, ibc.WalletAmount{
Address: user.FormattedAddress(),
Amount: math.NewInt(amount),
Denom: chainCfg.Denom,
})

_, err = s.ExecTx(
ctx,
chain,
interchaintest.FaucetAccountKeyName,
"bank",
"send",
interchaintest.FaucetAccountKeyName,
user.FormattedAddress(),
fmt.Sprintf("%d%s", amount, chainCfg.Denom),
"--fees",
fmt.Sprintf("%d%s", 200000000000, chainCfg.Denom),
)

if err != nil {
return nil, fmt.Errorf("failed to get funds from faucet: %w", err)
}
return user, nil
}

// GetAndFundTestUsers generates and funds chain users with the native chain denom.
// The caller should wait for some blocks to complete before the funds will be accessible.
func (s *TestSuite) GetAndFundTestUsers(
ctx context.Context,
keyNamePrefix string,
amount int64,
chains ...*cosmos.CosmosChain,
) []ibc.Wallet {
users := make([]ibc.Wallet, len(chains))
var eg errgroup.Group
for i, chain := range chains {
i := i
chain := chain
eg.Go(func() error {
user, err := s.GetAndFundTestUserWithMnemonic(ctx, keyNamePrefix, "", amount, chain)
if err != nil {
return err
}
users[i] = user
return nil
})
}
s.Require().NoError(eg.Wait())

// TODO(nix 05-17-2022): Map with generics once using go 1.18
chainHeights := make([]testutil.ChainHeighter, len(chains))
for i := range chains {
chainHeights[i] = chains[i]
}
return users
}

func (s *TestSuite) ExecTx(ctx context.Context, chain *cosmos.CosmosChain, keyName string, command ...string) (string, error) {
node := chain.FullNodes[0]
return node.ExecTx(ctx, keyName, command...)
}

var chars = []byte("abcdefghijklmnopqrstuvwxyz")

// RandLowerCaseLetterString returns a lowercase letter string of given length
func RandLowerCaseLetterString(length int) string {
b := make([]byte, length)
for i := range b {
b[i] = chars[rand.Intn(len(chars))]
}
return string(b)
}
11 changes: 8 additions & 3 deletions tests/integration/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,15 @@ func (s *TestSuite) SetupSuite() {
ctx := context.Background()
s.ic = BuildInterchain(s.T(), ctx, s.chain)

cc, ok := s.chain.(*cosmos.CosmosChain)
if !ok {
panic("unable to assert ibc.Chain as CosmosChain")
}

// get the users
s.user1 = interchaintest.GetAndFundTestUsers(s.T(), ctx, s.T().Name(), initBalance, s.chain)[0]
s.user2 = interchaintest.GetAndFundTestUsers(s.T(), ctx, s.T().Name(), initBalance, s.chain)[0]
s.user3 = interchaintest.GetAndFundTestUsers(s.T(), ctx, s.T().Name(), initBalance, s.chain)[0]
s.user1 = s.GetAndFundTestUsers(ctx, s.T().Name(), initBalance, cc)[0]
s.user2 = s.GetAndFundTestUsers(ctx, s.T().Name(), initBalance, cc)[0]
s.user3 = s.GetAndFundTestUsers(ctx, s.T().Name(), initBalance, cc)[0]

// create the broadcaster
s.T().Log("creating broadcaster")
Expand Down
Loading

0 comments on commit e872422

Please sign in to comment.