package ante import ( "bytes" "fmt" 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 check 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 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 successfully deducted. // CONTRACT: Tx must implement 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, simulate bool, next sdk.AnteHandler) (sdk.Context, error) { feeTx, ok := tx.(sdk.FeeTx) if !ok { return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "Tx must be a FeeTx") } if !simulate && 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 !simulate { 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, simulate) } 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 be a FeeTx") } if addr := dfd.accountKeeper.GetModuleAddress(types.FeeCollectorName); addr == nil { 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 with only when feegrant 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 { return errorsmod.Wrapf(err, "%s does not allow to pay fees for %s", feeGranter, feePayer) } } deductFeesFrom = feeGranterAddr } deductFeesFromAcc := dfd.accountKeeper.GetAccount(ctx, deductFeesFrom) if deductFeesFromAcc == nil { return sdkerrors.ErrUnknownAddress.Wrapf("fee payer address: %s does not exist", deductFeesFrom) } // deduct the fees if !fee.IsZero() { err := DeductFees(dfd.bankKeeper, ctx, deductFeesFromAcc, 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 sdk.AccountI, fees sdk.Coins) error { if !fees.IsValid() { return errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees) } err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.FeeCollectorName, fees) if err != nil { return errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error()) } return nil }