163 lines
5.4 KiB
Go
163 lines
5.4 KiB
Go
package ante
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
|
|
"cosmossdk.io/core/event"
|
|
"cosmossdk.io/core/transaction"
|
|
errorsmod "cosmossdk.io/errors"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
"github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
)
|
|
|
|
// TxFeeChecker checks if the provided fee is enough and returns the effective fee and tx priority.
|
|
// The effective fee should be deducted later, and the priority should be returned in the ABCI response.
|
|
type TxFeeChecker func(ctx context.Context, tx transaction.Tx) (sdk.Coins, int64, error)
|
|
|
|
// DeductFeeDecorator deducts fees from the fee payer. The fee payer is the fee granter (if specified) or first signer of the tx.
|
|
// If the fee payer does not have the funds to pay for the fees, return an InsufficientFunds error.
|
|
// Call next AnteHandler if fees are successfully deducted.
|
|
// CONTRACT: The Tx must implement the FeeTx interface to use DeductFeeDecorator.
|
|
type DeductFeeDecorator struct {
|
|
accountKeeper AccountKeeper
|
|
bankKeeper types.BankKeeper
|
|
feegrantKeeper FeegrantKeeper
|
|
txFeeChecker TxFeeChecker
|
|
minGasPrices sdk.DecCoins
|
|
}
|
|
|
|
func NewDeductFeeDecorator(ak AccountKeeper, bk types.BankKeeper, fk FeegrantKeeper, tfc TxFeeChecker) *DeductFeeDecorator {
|
|
dfd := &DeductFeeDecorator{
|
|
accountKeeper: ak,
|
|
bankKeeper: bk,
|
|
feegrantKeeper: fk,
|
|
txFeeChecker: tfc,
|
|
minGasPrices: sdk.NewDecCoins(),
|
|
}
|
|
|
|
if tfc == nil {
|
|
dfd.txFeeChecker = dfd.checkTxFeeWithValidatorMinGasPrices
|
|
}
|
|
|
|
return dfd
|
|
}
|
|
|
|
// SetMinGasPrices sets the minimum-gas-prices value in the state of DeductFeeDecorator
|
|
func (dfd *DeductFeeDecorator) SetMinGasPrices(minGasPrices sdk.DecCoins) {
|
|
dfd.minGasPrices = minGasPrices
|
|
}
|
|
|
|
// AnteHandle implements an AnteHandler decorator for the DeductFeeDecorator
|
|
func (dfd *DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (sdk.Context, error) {
|
|
dfd.minGasPrices = ctx.MinGasPrices()
|
|
txPriority, err := dfd.innerValidateTx(ctx, tx)
|
|
if err != nil {
|
|
return ctx, err
|
|
}
|
|
|
|
newCtx := ctx.WithPriority(txPriority)
|
|
return next(newCtx, tx, false)
|
|
}
|
|
|
|
func (dfd *DeductFeeDecorator) innerValidateTx(ctx context.Context, tx transaction.Tx) (priority int64, err error) {
|
|
feeTx, ok := tx.(sdk.FeeTx)
|
|
if !ok {
|
|
return 0, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must implement the FeeTx interface")
|
|
}
|
|
|
|
execMode := dfd.accountKeeper.GetEnvironment().TransactionService.ExecMode(ctx)
|
|
headerInfo := dfd.accountKeeper.GetEnvironment().HeaderService.HeaderInfo(ctx)
|
|
|
|
if execMode != transaction.ExecModeSimulate && headerInfo.Height > 0 && feeTx.GetGas() == 0 {
|
|
return 0, errorsmod.Wrap(sdkerrors.ErrInvalidGasLimit, "must provide positive gas")
|
|
}
|
|
|
|
fee := feeTx.GetFee()
|
|
if execMode != transaction.ExecModeSimulate {
|
|
fee, priority, err = dfd.txFeeChecker(ctx, tx)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
|
|
if err := dfd.checkDeductFee(ctx, feeTx, fee); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return priority, nil
|
|
}
|
|
|
|
// ValidateTx implements an TxValidator for DeductFeeDecorator
|
|
// Note: This method is applicable only for transactions that implement the sdk.FeeTx interface.
|
|
func (dfd *DeductFeeDecorator) ValidateTx(ctx context.Context, tx transaction.Tx) error {
|
|
_, err := dfd.innerValidateTx(ctx, tx)
|
|
return err
|
|
}
|
|
|
|
func (dfd *DeductFeeDecorator) checkDeductFee(ctx context.Context, feeTx sdk.FeeTx, fee sdk.Coins) error {
|
|
addr := dfd.accountKeeper.GetModuleAddress(types.FeeCollectorName)
|
|
if len(addr) == 0 {
|
|
return fmt.Errorf("fee collector module account (%s) has not been set", types.FeeCollectorName)
|
|
}
|
|
|
|
feePayer := feeTx.FeePayer()
|
|
feeGranter := feeTx.FeeGranter()
|
|
deductFeesFrom := feePayer
|
|
|
|
// if feegranter set, deduct fee from feegranter account.
|
|
// this works only when feegrant is enabled.
|
|
if feeGranter != nil {
|
|
if dfd.feegrantKeeper == nil {
|
|
return sdkerrors.ErrInvalidRequest.Wrap("fee grants are not enabled")
|
|
} else if !bytes.Equal(feeGranter, feePayer) {
|
|
err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee, feeTx.GetMsgs())
|
|
if err != nil {
|
|
granterAddr, acErr := dfd.accountKeeper.AddressCodec().BytesToString(feeGranter)
|
|
if acErr != nil {
|
|
return errorsmod.Wrapf(err, "%s, feeGranter does not allow to pay fees", acErr.Error())
|
|
}
|
|
payerAddr, acErr := dfd.accountKeeper.AddressCodec().BytesToString(feePayer)
|
|
if acErr != nil {
|
|
return errorsmod.Wrapf(err, "%s, feeGranter does not allow to pay fees", acErr.Error())
|
|
}
|
|
return errorsmod.Wrapf(err, "%s does not allow to pay fees for %s", granterAddr, payerAddr)
|
|
}
|
|
}
|
|
deductFeesFrom = feeGranter
|
|
}
|
|
|
|
// deduct the fees
|
|
if !fee.IsZero() {
|
|
if err := DeductFees(dfd.bankKeeper, ctx, deductFeesFrom, fee); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := dfd.accountKeeper.GetEnvironment().EventService.EventManager(ctx).EmitKV(
|
|
sdk.EventTypeTx,
|
|
event.NewAttribute(sdk.AttributeKeyFee, fee.String()),
|
|
event.NewAttribute(sdk.AttributeKeyFeePayer, sdk.AccAddress(deductFeesFrom).String()),
|
|
); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// DeductFees deducts fees from the given account.
|
|
func DeductFees(bankKeeper types.BankKeeper, ctx context.Context, acc []byte, fees sdk.Coins) error {
|
|
if !fees.IsValid() {
|
|
return errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
|
|
}
|
|
|
|
if err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc, types.FeeCollectorName, fees); err != nil {
|
|
return fmt.Errorf("failed to deduct fees: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|