From ee60e0c6558f35e1ec99fece08b78bb295d7c84d Mon Sep 17 00:00:00 2001 From: Anh Minh <1phamminh0811@gmail.com> Date: Wed, 17 Jul 2024 12:22:38 +0700 Subject: [PATCH] Handle fee grant in post handle --- app/app.go | 1 + custom/auth/post/post.go | 3 +- x/tax2gas/ante/ante.go | 6 +- x/tax2gas/post/post.go | 141 +++++++++++++++++++++++++-------------- x/tax2gas/utils/utils.go | 30 +++++++-- 5 files changed, 123 insertions(+), 58 deletions(-) diff --git a/app/app.go b/app/app.go index d23a0fe6..567bf7dc 100644 --- a/app/app.go +++ b/app/app.go @@ -241,6 +241,7 @@ func NewTerraApp( custompost.HandlerOptions{ AccountKeeper: app.AccountKeeper, BankKeeper: app.BankKeeper, + FeegrantKeeper: app.FeeGrantKeeper, DyncommKeeper: app.DyncommKeeper, TreasuryKeeper: app.TreasuryKeeper, Tax2Gaskeeper: app.Tax2gasKeeper, diff --git a/custom/auth/post/post.go b/custom/auth/post/post.go index 0a5fa707..19c4eda9 100644 --- a/custom/auth/post/post.go +++ b/custom/auth/post/post.go @@ -15,6 +15,7 @@ import ( type HandlerOptions struct { AccountKeeper ante.AccountKeeper BankKeeper types.BankKeeper + FeegrantKeeper types.FeegrantKeeper DyncommKeeper dyncommkeeper.Keeper TreasuryKeeper tax2gasTypes.TreasuryKeeper Tax2Gaskeeper tax2gasKeeper.Keeper @@ -25,6 +26,6 @@ type HandlerOptions struct { func NewPostHandler(options HandlerOptions) (sdk.PostHandler, error) { return sdk.ChainPostDecorators( dyncommpost.NewDyncommPostDecorator(options.DyncommKeeper), - tax2gasPost.NewTax2GasPostDecorator(options.AccountKeeper, options.BankKeeper, options.TreasuryKeeper, options.Tax2Gaskeeper), + tax2gasPost.NewTax2GasPostDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TreasuryKeeper, options.Tax2Gaskeeper), ), nil } diff --git a/x/tax2gas/ante/ante.go b/x/tax2gas/ante/ante.go index b040cf23..d933cc5d 100644 --- a/x/tax2gas/ante/ante.go +++ b/x/tax2gas/ante/ante.go @@ -86,7 +86,7 @@ func (fd FeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, nex } // Try to deduct the gasConsumed fees - paidDenom, err := fd.checkDeductFee(ctx, feeTx, gasConsumedFees, simulate) + paidDenom, err := fd.tryDeductFee(ctx, feeTx, gasConsumedFees, simulate) if err != nil { return ctx, err } @@ -103,7 +103,7 @@ func (fd FeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, nex return next(newCtx, tx, simulate) } -func (fd FeeDecorator) checkDeductFee(ctx sdk.Context, feeTx sdk.FeeTx, taxes sdk.Coins, simulate bool) (string, error) { +func (fd FeeDecorator) tryDeductFee(ctx sdk.Context, feeTx sdk.FeeTx, taxes sdk.Coins, simulate bool) (string, error) { if addr := fd.accountKeeper.GetModuleAddress(authtypes.FeeCollectorName); addr == nil { return "", fmt.Errorf("fee collector module account (%s) has not been set", authtypes.FeeCollectorName) } @@ -145,7 +145,7 @@ func (fd FeeDecorator) checkDeductFee(ctx sdk.Context, feeTx sdk.FeeTx, taxes sd if err == nil { foundCoins = sdk.NewCoins(foundCoin) granted = true - err = fd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, sdk.NewCoins(foundCoin), feeTx.GetMsgs()) + err = fd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, foundCoins, feeTx.GetMsgs()) if err != nil { return "", errorsmod.Wrapf(err, "%s does not allow to pay fees for %s", feeGranter, feePayer) } diff --git a/x/tax2gas/post/post.go b/x/tax2gas/post/post.go index d37e5b51..acfcc3a0 100644 --- a/x/tax2gas/post/post.go +++ b/x/tax2gas/post/post.go @@ -15,19 +15,22 @@ import ( type Tax2gasPostDecorator struct { accountKeeper ante.AccountKeeper bankKeeper types.BankKeeper + feegrantKeeper types.FeegrantKeeper treasuryKeeper types.TreasuryKeeper tax2gasKeeper tax2gasKeeper.Keeper } -func NewTax2GasPostDecorator(accountKeeper ante.AccountKeeper, bankKeeper types.BankKeeper, treasuryKeeper types.TreasuryKeeper, tax2gasKeeper tax2gasKeeper.Keeper) Tax2gasPostDecorator { +func NewTax2GasPostDecorator(accountKeeper ante.AccountKeeper, bankKeeper types.BankKeeper, feegrantKeeper types.FeegrantKeeper, treasuryKeeper types.TreasuryKeeper, tax2gasKeeper tax2gasKeeper.Keeper) Tax2gasPostDecorator { return Tax2gasPostDecorator{ accountKeeper: accountKeeper, bankKeeper: bankKeeper, + feegrantKeeper: feegrantKeeper, treasuryKeeper: treasuryKeeper, tax2gasKeeper: tax2gasKeeper, } } +// TODO: handle fail tx func (dd Tax2gasPostDecorator) PostHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, success bool, next sdk.PostHandler) (sdk.Context, error) { feeTx, ok := tx.(sdk.FeeTx) if !ok { @@ -37,6 +40,10 @@ func (dd Tax2gasPostDecorator) PostHandle(ctx sdk.Context, tx sdk.Tx, simulate b if !simulate && ctx.BlockHeight() > 0 && feeTx.GetGas() == 0 { return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidGasLimit, "must provide positive gas") } + msgs := feeTx.GetMsgs() + if tax2gasutils.IsOracleTx(msgs) || simulate { + return next(ctx, tx, simulate, success) + } feeCoins := feeTx.GetFee() anteConsumedGas, ok := ctx.Value(types.AnteConsumedGas).(uint64) @@ -44,64 +51,100 @@ func (dd Tax2gasPostDecorator) PostHandle(ctx sdk.Context, tx sdk.Tx, simulate b return ctx, errorsmod.Wrap(types.ErrParsing, "Error parsing ante consumed gas") } - if !feeCoins.IsZero() { - // Get paid denom identified in ante handler - paidDenom, ok := ctx.Value(types.PaidDenom).(string) - if !ok { - return ctx, errorsmod.Wrap(types.ErrParsing, "Error parsing fee denom") - } + // Get paid denom identified in ante handler + paidDenom, ok := ctx.Value(types.PaidDenom).(string) + if !ok { + // If cannot found the paidDenom, that's mean this is the init genesis tx + // Skip this tx as it's init genesis tx + return next(ctx, tx, simulate, success) + } - gasPrices := dd.tax2gasKeeper.GetGasPrices(ctx) - paidDenomGasPrice, found := tax2gasutils.GetGasPriceByDenom(ctx, gasPrices, paidDenom) - if !found { - return ctx, types.ErrDenomNotFound - } - paidAmount := paidDenomGasPrice.Mul(sdk.NewDec(int64(anteConsumedGas))) - // Deduct feeCoins with paid amount - feeCoins = feeCoins.Sub(sdk.NewCoin(paidDenom, paidAmount.Ceil().RoundInt())) + gasPrices := dd.tax2gasKeeper.GetGasPrices(ctx) + found, paidDenomGasPrice := tax2gasutils.GetGasPriceByDenom(gasPrices, paidDenom) + if !found { + return ctx, types.ErrDenomNotFound + } + paidAmount := paidDenomGasPrice.Mul(sdk.NewDec(int64(anteConsumedGas))) + // Deduct feeCoins with paid amount + feeCoins = feeCoins.Sub(sdk.NewCoin(paidDenom, paidAmount.Ceil().RoundInt())) - taxGas, ok := ctx.Value(types.TaxGas).(uint64) - if !ok { - taxGas = 0 - } - // we consume the gas here as we need to calculate the tax for consumed gas - ctx.GasMeter().ConsumeGas(taxGas, "tax gas") - gasConsumed := ctx.GasMeter().GasConsumed() + taxGas, ok := ctx.Value(types.TaxGas).(uint64) + if !ok { + taxGas = 0 + } + // we consume the gas here as we need to calculate the tax for consumed gas + ctx.GasMeter().ConsumeGas(taxGas, "tax gas") + gasConsumed := ctx.GasMeter().GasConsumed() - // Deduct the gas consumed amount spent on ante handler - gasRemaining := gasConsumed - anteConsumedGas + // Deduct the gas consumed amount spent on ante handler + gasRemaining := gasConsumed - anteConsumedGas - if simulate { - return next(ctx, tx, simulate, success) - } + feePayer := feeTx.FeePayer() + feeGranter := feeTx.FeeGranter() - for _, feeCoin := range feeCoins { - feePayer := dd.accountKeeper.GetAccount(ctx, feeTx.FeePayer()) - gasPrice, found := tax2gasutils.GetGasPriceByDenom(ctx, gasPrices, feeCoin.Denom) - if !found { - continue + // if feegranter set deduct fee from feegranter account. + // this works with only when feegrant enabled. + if feeGranter != nil { + if dd.feegrantKeeper == nil { + return ctx, sdkerrors.ErrInvalidRequest.Wrap("fee grants are not enabled") + } else if !feeGranter.Equals(feePayer) { + allowance, err := dd.feegrantKeeper.GetAllowance(ctx, feeGranter, feePayer) + if err != nil { + return ctx, errorsmod.Wrapf(err, "fee-grant not found with granter %s and grantee %s", feeGranter, feePayer) } - feeRequired := sdk.NewCoin(feeCoin.Denom, gasPrice.MulInt64(int64(gasRemaining)).Ceil().RoundInt()) - - if feeCoin.IsGTE(feeRequired) { - err := dd.bankKeeper.SendCoinsFromAccountToModule(ctx, feePayer.GetAddress(), authtypes.FeeCollectorName, sdk.NewCoins(feeRequired)) - if err != nil { - return ctx, errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error()) - } - gasRemaining = 0 - break - } else { - err := dd.bankKeeper.SendCoinsFromAccountToModule(ctx, feePayer.GetAddress(), authtypes.FeeCollectorName, sdk.NewCoins(feeCoin)) - if err != nil { - return ctx, errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error()) + + gasRemainingFees, err := tax2gasutils.ComputeFeesOnGasConsumed(ctx, tx, gasPrices, gasRemaining) + if err != nil { + return ctx, err + } + + // For this tx, we only accept to pay by one denom + for _, feeRequired := range gasRemainingFees { + _, err := allowance.Accept(ctx, sdk.NewCoins(feeRequired), feeTx.GetMsgs()) + if err == nil { + err = dd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, sdk.NewCoins(feeRequired), feeTx.GetMsgs()) + if err != nil { + return ctx, errorsmod.Wrapf(err, "%s does not allow to pay fees for %s", feeGranter, feePayer) + } + feeGranter := dd.accountKeeper.GetAccount(ctx, feeGranter) + err = dd.bankKeeper.SendCoinsFromAccountToModule(ctx, feeGranter.GetAddress(), authtypes.FeeCollectorName, sdk.NewCoins(feeRequired)) + if err != nil { + return ctx, errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error()) + } + return next(ctx, tx, simulate, success) } - feeRemaining := sdk.NewDecCoinFromCoin(feeRequired.Sub(feeCoin)) - gasRemaining = uint64(feeRemaining.Amount.Quo(gasPrice).Ceil().RoundInt64()) } + return ctx, errorsmod.Wrapf(err, "%s does not allow to pay fees for %s", feeGranter, feePayer) + } + } + + for _, feeCoin := range feeCoins { + feePayer := dd.accountKeeper.GetAccount(ctx, feePayer) + found, gasPrice := tax2gasutils.GetGasPriceByDenom(gasPrices, feeCoin.Denom) + if !found { + continue } - if gasRemaining > 0 { - return ctx, errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "fees are not enough to pay for gas") + feeRequired := sdk.NewCoin(feeCoin.Denom, gasPrice.MulInt64(int64(gasRemaining)).Ceil().RoundInt()) + + if feeCoin.IsGTE(feeRequired) { + err := dd.bankKeeper.SendCoinsFromAccountToModule(ctx, feePayer.GetAddress(), authtypes.FeeCollectorName, sdk.NewCoins(feeRequired)) + if err != nil { + return ctx, errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error()) + } + gasRemaining = 0 + break + } else { + err := dd.bankKeeper.SendCoinsFromAccountToModule(ctx, feePayer.GetAddress(), authtypes.FeeCollectorName, sdk.NewCoins(feeCoin)) + if err != nil { + return ctx, errorsmod.Wrapf(sdkerrors.ErrInsufficientFunds, err.Error()) + } + feeRemaining := sdk.NewDecCoinFromCoin(feeRequired.Sub(feeCoin)) + gasRemaining = uint64(feeRemaining.Amount.Quo(gasPrice).Ceil().RoundInt64()) } } + if gasRemaining > 0 { + return ctx, errorsmod.Wrapf(sdkerrors.ErrInsufficientFee, "fees are not enough to pay for gas") + } + return next(ctx, tx, simulate, success) } diff --git a/x/tax2gas/utils/utils.go b/x/tax2gas/utils/utils.go index 6a96b917..65e3d39c 100644 --- a/x/tax2gas/utils/utils.go +++ b/x/tax2gas/utils/utils.go @@ -43,11 +43,31 @@ func IsOracleTx(msgs []sdk.Msg) bool { return true } -func GetGasPriceByDenom(ctx sdk.Context, gasPrices sdk.DecCoins, denom string) (sdk.Dec, bool) { - for _, price := range gasPrices { - if price.Denom == denom { - return price.Amount, true +// Find returns true and Dec amount if the denom exists in gasPrices. Otherwise it returns false +// and a zero dec. Uses binary search. +// CONTRACT: gasPrices must be valid (sorted). +func GetGasPriceByDenom(gasPrices sdk.DecCoins, denom string) (bool, sdk.Dec) { + switch len(gasPrices) { + case 0: + return false, sdk.ZeroDec() + + case 1: + gasPrice := gasPrices[0] + if gasPrice.Denom == denom { + return true, gasPrice.Amount + } + return false, sdk.ZeroDec() + + default: + midIdx := len(gasPrices) / 2 // 2:1, 3:1, 4:2 + gasPrice := gasPrices[midIdx] + switch { + case denom < gasPrice.Denom: + return GetGasPriceByDenom(gasPrices[:midIdx], denom) + case denom == gasPrice.Denom: + return true, gasPrice.Amount + default: + return GetGasPriceByDenom(gasPrices[midIdx+1:], denom) } } - return sdk.Dec{}, false }