Skip to content

Commit

Permalink
Handle fee grant in post handle
Browse files Browse the repository at this point in the history
  • Loading branch information
phamminh0811 committed Jul 17, 2024
1 parent 3c9b03a commit 0b55dd8
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 61 deletions.
1 change: 1 addition & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion custom/auth/post/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Binary file added forwarder.wasm
Binary file not shown.
6 changes: 3 additions & 3 deletions scripts/run-node.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ $BINARY keys add $KEY1 --keyring-backend $KEYRING --home $HOME_DIR
$BINARY keys add $KEY2 --keyring-backend $KEYRING --home $HOME_DIR

# Allocate genesis accounts (cosmos formatted addresses)
$BINARY add-genesis-account $KEY "1000000000000${DENOM}" --keyring-backend $KEYRING --home $HOME_DIR
$BINARY add-genesis-account $KEY1 "1000000000000${DENOM}" --keyring-backend $KEYRING --home $HOME_DIR
$BINARY add-genesis-account $KEY2 "1000000000000${DENOM}" --keyring-backend $KEYRING --home $HOME_DIR
$BINARY add-genesis-account $KEY "1000000000000${DENOM},1000000000000uusd" --keyring-backend $KEYRING --home $HOME_DIR
$BINARY add-genesis-account $KEY1 "1000000000000${DENOM},1000000000000uusd" --keyring-backend $KEYRING --home $HOME_DIR
$BINARY add-genesis-account $KEY2 "1000000000000${DENOM},1000000000000uusd" --keyring-backend $KEYRING --home $HOME_DIR

update_test_genesis '.app_state["mint"]["params"]["mint_denom"]="'$DENOM'"'
update_test_genesis '.app_state["gov"]["deposit_params"]["min_deposit"]=[{"denom":"'$DENOM'","amount": "1000000"}]'
Expand Down
21 changes: 21 additions & 0 deletions x/tax2gas/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Tax2gas

## Testcases

- Normal tx success
- Forward tx should minus the amount to tx origin
- Multiple forward works
- Out of gas should return the tax and not consumed gas
- Error forward tx should return the fund
- Multiple msg types should works
- Grant msg should work for multiple contracts crosscall
- Allow pay with multiple fees should work

| No | Name | Scenario | Expect Result | Covered by |
|----|----------|-------------------|---------------|------------|
| 1 | Normal transaction should success | User transfer or make some special transactions which send coins to different address | Tax should be deducted with correct amount| |
| 2 | Forward transaction should deduct the amount to tx origin | User execute contract that will trigger an execute msg to another contract | - User should be the tx origin of the execute msg<br>- Tax should be deducted with correct amount | |
| 3 | Multiple forward works | Contracts will trigger another contracts multiple times | - User should be the tx origin of the execute msg<br>- Tax should be deducted with correct amount | |
| 4 | Out of gas should return the tax and not consumed gas | User make some transactions with limited gas amount that will lead to cause `out of gas` error | Tax and not consumed gas should be revert to user | |
| 5 | Error forward tx should return the tax and not consumed gas | User execute contract that will trigger an execute msg to another contract. The execute msg to another contract will be failed | Tax and not consumed gas should be revert to user | |
| 6 | Multiple msg types should works |
6 changes: 3 additions & 3 deletions x/tax2gas/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down
141 changes: 92 additions & 49 deletions x/tax2gas/post/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -37,71 +40,111 @@ 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)
if !ok {
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)
}
30 changes: 25 additions & 5 deletions x/tax2gas/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit 0b55dd8

Please sign in to comment.