cosmos-sdk/x/auth/ante/fee.go
2024-07-16 09:17:34 +00:00

149 lines
4.5 KiB
Go

package ante
import (
"bytes"
"fmt"
"cosmossdk.io/core/transaction"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/x/auth/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// 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 sdk.Context, tx sdk.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
}
func NewDeductFeeDecorator(ak AccountKeeper, bk types.BankKeeper, fk FeegrantKeeper, tfc TxFeeChecker) DeductFeeDecorator {
if tfc == nil {
tfc = checkTxFeeWithValidatorMinGasPrices
}
return DeductFeeDecorator{
accountKeeper: ak,
bankKeeper: bk,
feegrantKeeper: fk,
txFeeChecker: tfc,
}
}
func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, _ bool, next sdk.AnteHandler) (sdk.Context, error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must implement the FeeTx interface")
}
txService := dfd.accountKeeper.GetEnvironment().TransactionService
execMode := txService.ExecMode(ctx)
if execMode != transaction.ExecModeSimulate && ctx.BlockHeight() > 0 && feeTx.GetGas() == 0 {
return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidGasLimit, "must provide positive gas")
}
var (
priority int64
err error
)
fee := feeTx.GetFee()
if execMode != transaction.ExecModeSimulate {
fee, priority, err = dfd.txFeeChecker(ctx, tx)
if err != nil {
return ctx, err
}
}
if err := dfd.checkDeductFee(ctx, tx, fee); err != nil {
return ctx, err
}
newCtx := ctx.WithPriority(priority)
return next(newCtx, tx, false)
}
func (dfd DeductFeeDecorator) checkDeductFee(ctx sdk.Context, sdkTx sdk.Tx, fee sdk.Coins) error {
feeTx, ok := sdkTx.(sdk.FeeTx)
if !ok {
return errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must implement the FeeTx interface")
}
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 {
feeGranterAddr := sdk.AccAddress(feeGranter)
if dfd.feegrantKeeper == nil {
return sdkerrors.ErrInvalidRequest.Wrap("fee grants are not enabled")
} else if !bytes.Equal(feeGranterAddr, feePayer) {
err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranterAddr, feePayer, fee, sdkTx.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 = feeGranterAddr
}
// deduct the fees
if !fee.IsZero() {
err := DeductFees(dfd.bankKeeper, ctx, deductFeesFrom, fee)
if err != nil {
return err
}
}
events := sdk.Events{
sdk.NewEvent(
sdk.EventTypeTx,
sdk.NewAttribute(sdk.AttributeKeyFee, fee.String()),
sdk.NewAttribute(sdk.AttributeKeyFeePayer, sdk.AccAddress(deductFeesFrom).String()),
),
}
ctx.EventManager().EmitEvents(events)
return nil
}
// DeductFees deducts fees from the given account.
func DeductFees(bankKeeper types.BankKeeper, ctx sdk.Context, acc []byte, fees sdk.Coins) error {
if !fees.IsValid() {
return errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
}
err := bankKeeper.SendCoinsFromAccountToModule(ctx, sdk.AccAddress(acc), types.FeeCollectorName, fees)
if err != nil {
return fmt.Errorf("failed to deduct fees: %w", err)
}
return nil
}