149 lines
4.5 KiB
Go
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
|
|
}
|