Skip to content

Commit

Permalink
feat(ante): allow doing vesting txs based on whitelist (#216)
Browse files Browse the repository at this point in the history
Co-authored-by: bcdev.tools <153984575+0xbcdev@users.noreply.github.com>
(cherry picked from commit e1c968f)
  • Loading branch information
danwt authored and omritoptix committed May 31, 2024
1 parent 3e4a59a commit 1ddefe0
Show file tree
Hide file tree
Showing 7 changed files with 347 additions and 98 deletions.
67 changes: 0 additions & 67 deletions app/ante.go

This file was deleted.

196 changes: 196 additions & 0 deletions app/ante/ante.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package ante

import (
"fmt"
"runtime/debug"

cosmosante "github.com/evmos/evmos/v12/app/ante/cosmos"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"

"github.com/cosmos/cosmos-sdk/codec"

errorsmod "cosmossdk.io/errors"

"github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
ibckeeper "github.com/cosmos/ibc-go/v6/modules/core/keeper"
evmosante "github.com/evmos/evmos/v12/app/ante"
evmosanteevm "github.com/evmos/evmos/v12/app/ante/evm"
anteutils "github.com/evmos/evmos/v12/app/ante/utils"
evmostypes "github.com/evmos/evmos/v12/types"
evmtypes "github.com/evmos/evmos/v12/x/evm/types"
evmosvestingtypes "github.com/evmos/evmos/v12/x/vesting/types"
tmlog "github.com/tendermint/tendermint/libs/log"
)

type HasPermission = func(ctx sdk.Context, accAddr sdk.AccAddress, perm string) bool

func MustCreateHandler(codec codec.BinaryCodec,
txConfig client.TxConfig,
maxGasWanted uint64,
hasPermission HasPermission,
accountKeeper evmtypes.AccountKeeper,
stakingKeeper evmosvestingtypes.StakingKeeper,
bankKeeper evmtypes.BankKeeper,
feeMarketKeeper evmosanteevm.FeeMarketKeeper,
evmKeeper evmosanteevm.EVMKeeper,
ibcKeeper *ibckeeper.Keeper,
distrKeeper anteutils.DistributionKeeper,
) sdk.AnteHandler {
ethOpts := evmosante.HandlerOptions{
Cdc: codec,
AccountKeeper: accountKeeper,
BankKeeper: bankKeeper,
EvmKeeper: evmKeeper,
StakingKeeper: stakingKeeper,
FeegrantKeeper: nil,
DistributionKeeper: distrKeeper,
IBCKeeper: ibcKeeper,
FeeMarketKeeper: feeMarketKeeper,
SignModeHandler: txConfig.SignModeHandler(),
SigGasConsumer: evmosante.SigVerificationGasConsumer,
MaxTxGasWanted: maxGasWanted,
TxFeeChecker: evmosanteevm.NewDynamicFeeChecker(evmKeeper),
}

opts := HandlerOptions{
HandlerOptions: ethOpts,
hasPermission: hasPermission,
}

h, err := NewHandler(opts)
if err != nil {
panic(fmt.Errorf("new ante handler: %w", err))
}
return h
}

// HandlerOptions are the options required for constructing a default SDK AnteHandler.
type HandlerOptions struct {
evmosante.HandlerOptions
hasPermission HasPermission
}

func (o HandlerOptions) validate() error {
/*
First check the eth stuff - the validate method is not exported so this is copy-pasted
*/
if o.AccountKeeper == nil {
return errorsmod.Wrap(sdkerrors.ErrLogic, "account keeper missing")
}
if o.BankKeeper == nil {
return errorsmod.Wrap(sdkerrors.ErrLogic, "bank keeper missing")
}
if o.SignModeHandler == nil {
return errorsmod.Wrap(sdkerrors.ErrLogic, "sign mode handler missing")
}
if o.FeeMarketKeeper == nil {
return errorsmod.Wrap(sdkerrors.ErrLogic, "fee market keeper missing")
}
if o.EvmKeeper == nil {
return errorsmod.Wrap(sdkerrors.ErrLogic, "evm keeper missing")
}
if o.DistributionKeeper == nil {
return errorsmod.Wrap(sdkerrors.ErrLogic, "distribution keeper missing")
}
if o.StakingKeeper == nil {
return errorsmod.Wrap(sdkerrors.ErrLogic, "staking keeper missing")
}

/*
Our stuff
*/
if o.hasPermission == nil {
return errorsmod.Wrap(sdkerrors.ErrLogic, "permission checker missing")
}
if o.IBCKeeper == nil {
return errorsmod.Wrap(sdkerrors.ErrLogic, "IBC keeper missing")
}
return nil
}

func NewHandler(options HandlerOptions) (sdk.AnteHandler, error) {
if err := options.validate(); err != nil {
return nil, fmt.Errorf("options validate: %w", err)
}

return func(
ctx sdk.Context, tx sdk.Tx, sim bool,
) (newCtx sdk.Context, err error) {
var anteHandler sdk.AnteHandler

defer Recover(ctx.Logger(), &err)

txWithExtensions, ok := tx.(authante.HasExtensionOptionsTx)
if ok {
opts := txWithExtensions.GetExtensionOptions()
if len(opts) > 0 {
switch typeURL := opts[0].GetTypeUrl(); typeURL {
case "/ethermint.evm.v1.ExtensionOptionsEthereumTx":
// handle as *evmtypes.MsgEthereumTx. It will get checked by the EVM handler to make sure it is.
anteHandler = newEVMAnteHandler(options)
case "/ethermint.types.v1.ExtensionOptionsWeb3Tx":
// Deprecated: Handle as normal Cosmos SDK tx, except signature is checked for Legacy EIP712 representation
options.ExtensionOptionChecker = func(c *codectypes.Any) bool {
_, ok := c.GetCachedValue().(*evmostypes.ExtensionOptionsWeb3Tx)
return ok
}
anteHandler = cosmosHandler(
options,
cosmosante.NewLegacyEip712SigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), // Use old signature verification: uses EIP instead of the cosmos signature validator
)
case "/ethermint.types.v1.ExtensionOptionDynamicFeeTx": // TODO: can delete?
// cosmos-sdk tx with dynamic fee extension
options.ExtensionOptionChecker = evmostypes.HasDynamicFeeExtensionOption
anteHandler = cosmosHandler(
options,
authante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), // Use modern signature verification
)
default:
return ctx, errorsmod.Wrapf(
sdkerrors.ErrUnknownExtensionOptions,
"rejecting tx with unsupported extension option: %s", typeURL,
)
}

return anteHandler(ctx, tx, sim)
}
}

// handle as totally normal Cosmos SDK tx
switch tx.(type) {
case sdk.Tx:
// we reject any extension
anteHandler = cosmosHandler(
options,
authante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler), // Use modern signature verification
)
default:
return ctx, errorsmod.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx)
}

return anteHandler(ctx, tx, sim)
}, nil
}

func Recover(logger tmlog.Logger, err *error) {
if r := recover(); r != nil {
*err = errorsmod.Wrapf(sdkerrors.ErrPanic, "%v", r)

if e, ok := r.(error); ok {
logger.Error(
"ante handler panicked",
"error", e,
"stack trace", string(debug.Stack()),
)
} else {
logger.Error(
"ante handler panicked",
"recover", fmt.Sprintf("%v", r),
)
}
}
}
39 changes: 39 additions & 0 deletions app/ante/decorator_permissioned_urls.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package ante

import (
errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"golang.org/x/exp/slices"
)

// PermissionedURLsDecorator prevents invalid msg types from being executed
type PermissionedURLsDecorator struct {
hasPermission func(ctx sdk.Context, accAddr sdk.AccAddress) bool
permissionedURls []string
}

func NewPermissionedURLsDecorator(hasPermission func(ctx sdk.Context, accAddr sdk.AccAddress) bool, msgTypeURLs []string) PermissionedURLsDecorator {
return PermissionedURLsDecorator{
hasPermission: hasPermission,
permissionedURls: msgTypeURLs,
}
}

// AnteHandle rejects vesting messages that signer does not have permission
func (d PermissionedURLsDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
for _, msg := range tx.GetMsgs() {
if slices.Contains(d.permissionedURls, sdk.MsgTypeURL(msg)) {
// Check if vesting tx signer is 1
if len(msg.GetSigners()) != 1 {
return ctx, errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "expect 1 signer: signers: %v", msg.GetSigners())
}

signer := msg.GetSigners()[0]
if !d.hasPermission(ctx, signer) {
return ctx, sdkerrors.ErrUnauthorized
}
}
}
return next(ctx, tx, simulate)
}
79 changes: 79 additions & 0 deletions app/ante/handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package ante

import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/ante"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
sdkvestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
ibcante "github.com/cosmos/ibc-go/v6/modules/core/ante"
cosmosante "github.com/evmos/evmos/v12/app/ante/cosmos"
evmante "github.com/evmos/evmos/v12/app/ante/evm"
evmtypes "github.com/evmos/evmos/v12/x/evm/types"
)

// NOTE: this function is copied from evmos
func newEVMAnteHandler(options HandlerOptions) sdk.AnteHandler {
return sdk.ChainAnteDecorators(
// outermost AnteDecorator. SetUpContext must be called first
evmante.NewEthSetUpContextDecorator(options.EvmKeeper),
// Check eth effective gas price against the node's minimal-gas-prices config
evmante.NewEthMempoolFeeDecorator(options.EvmKeeper),
// Check eth effective gas price against the global MinGasPrice
evmante.NewEthMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper),
evmante.NewEthValidateBasicDecorator(options.EvmKeeper),
evmante.NewEthSigVerificationDecorator(options.EvmKeeper),
evmante.NewEthAccountVerificationDecorator(options.AccountKeeper, options.EvmKeeper),
evmante.NewCanTransferDecorator(options.EvmKeeper),
// we intentionally omit the eth vesting transaction decorator
evmante.NewEthGasConsumeDecorator(options.BankKeeper, options.DistributionKeeper, options.EvmKeeper, options.StakingKeeper, options.MaxTxGasWanted),
evmante.NewEthIncrementSenderSequenceDecorator(options.AccountKeeper),
evmante.NewGasWantedDecorator(options.EvmKeeper, options.FeeMarketKeeper),
// emit eth tx hash and index at the very last ante handler.
evmante.NewEthEmitEventDecorator(options.EvmKeeper),
)
}

func cosmosHandler(options HandlerOptions, sigChecker sdk.AnteDecorator) sdk.AnteHandler {
sigGasConsumer := options.SigGasConsumer
if sigGasConsumer == nil {
sigGasConsumer = authante.DefaultSigVerificationGasConsumer
}
return sdk.ChainAnteDecorators(
cosmosante.NewRejectMessagesDecorator(
[]string{
sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{}),
},
),
cosmosante.NewAuthzLimiterDecorator( // disable the Msg types that cannot be included on an authz.MsgExec msgs field
sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{}),
sdk.MsgTypeURL(&sdkvestingtypes.MsgCreateVestingAccount{}),
sdk.MsgTypeURL(&sdkvestingtypes.MsgCreatePermanentLockedAccount{}),
sdk.MsgTypeURL(&sdkvestingtypes.MsgCreatePeriodicVestingAccount{}),
),
ante.NewSetUpContextDecorator(),
ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
NewPermissionedURLsDecorator(
func(ctx sdk.Context, accAddr sdk.AccAddress) bool {
return options.hasPermission(ctx, accAddr, vestingtypes.ModuleName)
}, []string{
sdk.MsgTypeURL(&vestingtypes.MsgCreateVestingAccount{}),
sdk.MsgTypeURL(&vestingtypes.MsgCreatePermanentLockedAccount{}),
sdk.MsgTypeURL(&vestingtypes.MsgCreatePeriodicVestingAccount{}),
}),
ante.NewValidateMemoDecorator(options.AccountKeeper),
cosmosante.NewMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
cosmosante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.DistributionKeeper, options.FeegrantKeeper, options.StakingKeeper, options.TxFeeChecker),
// SetPubKeyDecorator must be called before all signature verification decorators
ante.NewSetPubKeyDecorator(options.AccountKeeper),
ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer),
sigChecker,
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
ibcante.NewRedundantRelayDecorator(options.IBCKeeper),
evmante.NewGasWantedDecorator(options.EvmKeeper, options.FeeMarketKeeper),
)
}
Loading

0 comments on commit 1ddefe0

Please sign in to comment.