-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
evm/fee: migrated fee modules & wip migrating evm module
- Loading branch information
Showing
174 changed files
with
27,252 additions
and
112 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package cosmos | ||
|
||
import ( | ||
"fmt" | ||
|
||
errorsmod "cosmossdk.io/errors" | ||
cosmos "github.com/cosmos/cosmos-sdk/types" | ||
errortypes "github.com/cosmos/cosmos-sdk/types/errors" | ||
"github.com/cosmos/cosmos-sdk/x/authz" | ||
) | ||
|
||
// maxNestedMsgs defines a cap for the number of nested messages on a MsgExec message | ||
const maxNestedMsgs = 7 | ||
|
||
// AuthzLimiterDecorator blocks certain msg types from being granted or executed | ||
// within the authorization module. | ||
type AuthzLimiterDecorator struct { | ||
// disabledMsgTypes is the type urls of the msgs to block. | ||
disabledMsgTypes []string | ||
} | ||
|
||
// NewAuthzLimiterDecorator creates a decorator to block certain msg types from being granted or executed within authz. | ||
func NewAuthzLimiterDecorator(disabledMsgTypes ...string) AuthzLimiterDecorator { | ||
return AuthzLimiterDecorator{ | ||
disabledMsgTypes: disabledMsgTypes, | ||
} | ||
} | ||
|
||
func (ald AuthzLimiterDecorator) AnteHandle(ctx cosmos.Context, tx cosmos.Tx, simulate bool, next cosmos.AnteHandler) (newCtx cosmos.Context, err error) { | ||
if err := ald.checkDisabledMsgs(tx.GetMsgs(), false, 1); err != nil { | ||
return ctx, errorsmod.Wrapf(errortypes.ErrUnauthorized, err.Error()) | ||
} | ||
return next(ctx, tx, simulate) | ||
} | ||
|
||
// checkDisabledMsgs iterates through the msgs and returns an error if it finds any unauthorized msgs. | ||
// | ||
// When searchOnlyInAuthzMsgs is enabled, only authz MsgGrant and MsgExec are blocked, if they contain unauthorized msg types. | ||
// Otherwise any msg matching the disabled types are blocked, regardless of being in an authz msg or not. | ||
// | ||
// This method is recursive as MsgExec's can wrap other MsgExecs. The check for nested messages is performed up to the | ||
// maxNestedMsgs threshold. If there are more than that limit, it returns an error | ||
func (ald AuthzLimiterDecorator) checkDisabledMsgs(msgs []cosmos.Msg, isAuthzInnerMsg bool, nestedLvl int) error { | ||
if nestedLvl >= maxNestedMsgs { | ||
return fmt.Errorf("found more nested msgs than permited. Limit is : %d", maxNestedMsgs) | ||
} | ||
for _, msg := range msgs { | ||
switch msg := msg.(type) { | ||
case *authz.MsgExec: | ||
innerMsgs, err := msg.GetMessages() | ||
if err != nil { | ||
return err | ||
} | ||
nestedLvl++ | ||
if err := ald.checkDisabledMsgs(innerMsgs, true, nestedLvl); err != nil { | ||
return err | ||
} | ||
case *authz.MsgGrant: | ||
authorization, err := msg.GetAuthorization() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
url := authorization.MsgTypeURL() | ||
if ald.isDisabledMsg(url) { | ||
return fmt.Errorf("found disabled msg type: %s", url) | ||
} | ||
default: | ||
url := cosmos.MsgTypeURL(msg) | ||
if isAuthzInnerMsg && ald.isDisabledMsg(url) { | ||
return fmt.Errorf("found disabled msg type: %s", url) | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// isDisabledMsg returns true if the given message is in the list of restricted | ||
// messages from the AnteHandler. | ||
func (ald AuthzLimiterDecorator) isDisabledMsg(msgTypeURL string) bool { | ||
for _, disabledType := range ald.disabledMsgTypes { | ||
if msgTypeURL == disabledType { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
package cosmos | ||
|
||
import ( | ||
"fmt" | ||
"math" | ||
|
||
errorsmod "cosmossdk.io/errors" | ||
cosmos "github.com/cosmos/cosmos-sdk/types" | ||
errortypes "github.com/cosmos/cosmos-sdk/types/errors" | ||
authante "github.com/cosmos/cosmos-sdk/x/auth/ante" | ||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" | ||
|
||
anteutils "github.com/artela-network/artela-rollkit/app/ante/utils" | ||
) | ||
|
||
// DeductFeeDecorator deducts fees from the first signer of the tx. | ||
// If the first signer does not have the funds to pay for the fees, | ||
// and does not have enough unclaimed staking rewards, then return | ||
// with InsufficientFunds error. | ||
// The next AnteHandler is called if fees are successfully deducted. | ||
// | ||
// CONTRACT: Tx must implement FeeTx interface to use DeductFeeDecorator | ||
type DeductFeeDecorator struct { | ||
accountKeeper authante.AccountKeeper | ||
bankKeeper BankKeeper | ||
distributionKeeper anteutils.DistributionKeeper | ||
feegrantKeeper authante.FeegrantKeeper | ||
stakingKeeper anteutils.StakingKeeper | ||
txFeeChecker anteutils.TxFeeChecker | ||
} | ||
|
||
// NewDeductFeeDecorator returns a new DeductFeeDecorator. | ||
func NewDeductFeeDecorator( | ||
ak authante.AccountKeeper, | ||
bk BankKeeper, | ||
dk anteutils.DistributionKeeper, | ||
fk authante.FeegrantKeeper, | ||
sk anteutils.StakingKeeper, | ||
tfc anteutils.TxFeeChecker, | ||
) DeductFeeDecorator { | ||
if tfc == nil { | ||
tfc = checkTxFeeWithValidatorMinGasPrices | ||
} | ||
|
||
return DeductFeeDecorator{ | ||
accountKeeper: ak, | ||
bankKeeper: bk, | ||
distributionKeeper: dk, | ||
feegrantKeeper: fk, | ||
stakingKeeper: sk, | ||
txFeeChecker: tfc, | ||
} | ||
} | ||
|
||
// AnteHandle ensures that the transaction contains valid fee requirements and tries to deduct those | ||
// from the account balance or unclaimed staking rewards, which the transaction sender might have. | ||
func (dfd DeductFeeDecorator) AnteHandle(ctx cosmos.Context, tx cosmos.Tx, simulate bool, next cosmos.AnteHandler) (cosmos.Context, error) { | ||
feeTx, ok := tx.(cosmos.FeeTx) | ||
if !ok { | ||
return ctx, errorsmod.Wrap(errortypes.ErrTxDecode, "Tx must be a FeeTx") | ||
} | ||
|
||
if !simulate && ctx.BlockHeight() > 0 && feeTx.GetGas() <= 0 { | ||
return ctx, errorsmod.Wrap(errortypes.ErrInvalidGasLimit, "must provide positive gas") | ||
} | ||
|
||
var ( | ||
priority int64 | ||
err error | ||
) | ||
|
||
fee := feeTx.GetFee() | ||
if !simulate { | ||
fee, priority, err = dfd.txFeeChecker(ctx, feeTx) | ||
if err != nil { | ||
return ctx, err | ||
} | ||
} | ||
|
||
feePayer := feeTx.FeePayer() | ||
feeGranter := feeTx.FeeGranter() | ||
|
||
if err = dfd.deductFee(ctx, tx, fee, feePayer, feeGranter); err != nil { | ||
return ctx, err | ||
} | ||
|
||
newCtx := ctx.WithPriority(priority) | ||
|
||
return next(newCtx, tx, simulate) | ||
} | ||
|
||
// deductFee checks if the fee payer has enough funds to pay for the fees and deducts them. | ||
// If the spendable balance is not enough, it tries to claim enough staking rewards to cover the fees. | ||
func (dfd DeductFeeDecorator) deductFee(ctx cosmos.Context, sdkTx cosmos.Tx, fees cosmos.Coins, feePayer, feeGranter cosmos.AccAddress) error { | ||
if fees.IsZero() { | ||
return nil | ||
} | ||
|
||
if addr := dfd.accountKeeper.GetModuleAddress(authtypes.FeeCollectorName); addr == nil { | ||
return fmt.Errorf("fee collector module account (%s) has not been set", authtypes.FeeCollectorName) | ||
} | ||
|
||
// by default, deduct fees from feePayer address | ||
deductFeesFrom := feePayer | ||
|
||
// if feegranter is set, then deduct the fee from the feegranter account. | ||
// this works only when feegrant is enabled. | ||
if feeGranter != nil { | ||
if dfd.feegrantKeeper == nil { | ||
return errortypes.ErrInvalidRequest.Wrap("fee grants are not enabled") | ||
} | ||
|
||
if !feeGranter.Equals(feePayer) { | ||
err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fees, sdkTx.GetMsgs()) | ||
if err != nil { | ||
return errorsmod.Wrapf(err, "%s does not not allow to pay fees for %s", feeGranter, feePayer) | ||
} | ||
} | ||
|
||
deductFeesFrom = feeGranter | ||
} | ||
|
||
deductFeesFromAcc := dfd.accountKeeper.GetAccount(ctx, deductFeesFrom) | ||
if deductFeesFromAcc == nil { | ||
return errortypes.ErrUnknownAddress.Wrapf("fee payer address: %s does not exist", deductFeesFrom) | ||
} | ||
|
||
// TODO mark deduct the fees | ||
// if err := deductFeesFromBalanceOrUnclaimedStakingRewards(ctx, dfd, deductFeesFromAcc, fees); err != nil { | ||
// return fmt.Errorf("insufficient funds and failed to claim sufficient staking rewards to pay for fees: %w", err) | ||
// } | ||
|
||
events := cosmos.Events{ | ||
cosmos.NewEvent( | ||
cosmos.EventTypeTx, | ||
cosmos.NewAttribute(cosmos.AttributeKeyFee, fees.String()), | ||
cosmos.NewAttribute(cosmos.AttributeKeyFeePayer, deductFeesFrom.String()), | ||
), | ||
} | ||
ctx.EventManager().EmitEvents(events) | ||
|
||
return nil | ||
} | ||
|
||
// deductFeesFromBalanceOrUnclaimedStakingRewards tries to deduct the fees from the account balance. | ||
// If the account balance is not enough, it tries to claim enough staking rewards to cover the fees. | ||
// | ||
//nolint:unused | ||
func deductFeesFromBalanceOrUnclaimedStakingRewards( | ||
ctx cosmos.Context, dfd DeductFeeDecorator, deductFeesFromAcc authtypes.AccountI, fees cosmos.Coins, | ||
) error { | ||
if err := anteutils.ClaimStakingRewardsIfNecessary( | ||
ctx, dfd.bankKeeper, dfd.distributionKeeper, dfd.stakingKeeper, deductFeesFromAcc.GetAddress(), fees, | ||
); err != nil { | ||
return err | ||
} | ||
|
||
// TODO mark return authante.DeductFees(dfd.bankKeeper, ctx, deductFeesFromAcc, fees) | ||
return nil | ||
} | ||
|
||
// checkTxFeeWithValidatorMinGasPrices implements the default fee logic, where the minimum price per | ||
// unit of gas is fixed and set by each validator, and the tx priority is computed from the gas price. | ||
func checkTxFeeWithValidatorMinGasPrices(ctx cosmos.Context, feeTx cosmos.FeeTx) (cosmos.Coins, int64, error) { | ||
feeCoins := feeTx.GetFee() | ||
gas := feeTx.GetGas() | ||
|
||
// Ensure that the provided fees meets a minimum threshold for the validator, | ||
// if this is a CheckTx. This is only for local mempool purposes, and thus | ||
// is only ran on CheckTx. | ||
if ctx.IsCheckTx() { | ||
if err := checkFeeCoinsAgainstMinGasPrices(ctx, feeCoins, gas); err != nil { | ||
return nil, 0, err | ||
} | ||
} | ||
|
||
priority := getTxPriority(feeCoins, int64(gas)) // #nosec G701 -- gosec warning about integer overflow is not relevant here | ||
return feeCoins, priority, nil | ||
} | ||
|
||
// checkFeeCoinsAgainstMinGasPrices checks if the provided fee coins are greater than or equal to the | ||
// required fees, that are based on the minimum gas prices and the gas. If not, it will return an error. | ||
func checkFeeCoinsAgainstMinGasPrices(ctx cosmos.Context, feeCoins cosmos.Coins, gas uint64) error { | ||
minGasPrices := ctx.MinGasPrices() | ||
if minGasPrices.IsZero() { | ||
return nil | ||
} | ||
|
||
requiredFees := make(cosmos.Coins, len(minGasPrices)) | ||
|
||
// Determine the required fees by multiplying each required minimum gas | ||
// price by the gas limit, where fee = ceil(minGasPrice * gasLimit). | ||
glDec := cosmos.NewDec(int64(gas)) // #nosec G701 -- gosec warning about integer overflow is not relevant here | ||
for i, gp := range minGasPrices { | ||
fee := gp.Amount.Mul(glDec) | ||
requiredFees[i] = cosmos.NewCoin(gp.Denom, fee.Ceil().RoundInt()) | ||
} | ||
|
||
if !feeCoins.IsAnyGTE(requiredFees) { | ||
return errorsmod.Wrapf(errortypes.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// getTxPriority returns a naive tx priority based on the amount of the smallest denomination of the gas price | ||
// provided in a transaction. | ||
// NOTE: This implementation should be used with a great consideration as it opens potential attack vectors | ||
// where txs with multiple coins could not be prioritized as expected. | ||
func getTxPriority(fees cosmos.Coins, gas int64) int64 { | ||
var priority int64 | ||
for _, c := range fees { | ||
p := int64(math.MaxInt64) | ||
gasPrice := c.Amount.QuoRaw(gas) | ||
if gasPrice.IsInt64() { | ||
p = gasPrice.Int64() | ||
} | ||
if priority == 0 || p < priority { | ||
priority = p | ||
} | ||
} | ||
|
||
return priority | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package cosmos | ||
|
||
import ( | ||
"math/big" | ||
|
||
errorsmod "cosmossdk.io/errors" | ||
cosmos "github.com/cosmos/cosmos-sdk/types" | ||
errortypes "github.com/cosmos/cosmos-sdk/types/errors" | ||
|
||
"github.com/artela-network/artela-rollkit/app/interfaces" | ||
) | ||
|
||
// MinGasPriceDecorator will check if the transaction's fee is at least as large | ||
// as the MinGasPrices param. If fee is too low, decorator returns error and tx | ||
// is rejected. This applies for both CheckTx and DeliverTx | ||
// If fee is high enough, then call next AnteHandler | ||
// CONTRACT: Tx must implement FeeTx to use MinGasPriceDecorator | ||
type MinGasPriceDecorator struct { | ||
feesKeeper interfaces.FeeKeeper | ||
evmKeeper interfaces.EVMKeeper | ||
} | ||
|
||
// NewMinGasPriceDecorator creates a new MinGasPriceDecorator instance used only for | ||
// Cosmos transactions. | ||
func NewMinGasPriceDecorator(fk interfaces.FeeKeeper, ek interfaces.EVMKeeper) MinGasPriceDecorator { | ||
return MinGasPriceDecorator{feesKeeper: fk, evmKeeper: ek} | ||
} | ||
|
||
func (mpd MinGasPriceDecorator) AnteHandle(ctx cosmos.Context, tx cosmos.Tx, simulate bool, next cosmos.AnteHandler) (newCtx cosmos.Context, err error) { | ||
feeTx, ok := tx.(cosmos.FeeTx) | ||
if !ok { | ||
return ctx, errorsmod.Wrapf(errortypes.ErrInvalidType, "invalid transaction type %T, expected cosmos.FeeTx", tx) | ||
} | ||
|
||
minGasPrice := mpd.feesKeeper.GetParams(ctx).MinGasPrice | ||
|
||
// Short-circuit if min gas price is 0 or if simulating | ||
if minGasPrice.IsZero() || simulate { | ||
return next(ctx, tx, simulate) | ||
} | ||
evmParams := mpd.evmKeeper.GetParams(ctx) | ||
evmDenom := evmParams.GetEvmDenom() | ||
minGasPrices := cosmos.DecCoins{ | ||
{ | ||
Denom: evmDenom, | ||
Amount: minGasPrice, | ||
}, | ||
} | ||
|
||
feeCoins := feeTx.GetFee() | ||
gas := feeTx.GetGas() | ||
|
||
requiredFees := make(cosmos.Coins, 0) | ||
|
||
// Determine the required fees by multiplying each required minimum gas | ||
// price by the gas limit, where fee = ceil(minGasPrice * gasLimit). | ||
gasLimit := cosmos.NewDecFromBigInt(new(big.Int).SetUint64(gas)) | ||
|
||
for _, gp := range minGasPrices { | ||
fee := gp.Amount.Mul(gasLimit).Ceil().RoundInt() | ||
if fee.IsPositive() { | ||
requiredFees = requiredFees.Add(cosmos.Coin{Denom: gp.Denom, Amount: fee}) | ||
} | ||
} | ||
|
||
// Fees not provided (or flag "auto"). Then use the base fee to make the check pass | ||
if feeCoins == nil { | ||
return ctx, errorsmod.Wrapf(errortypes.ErrInsufficientFee, | ||
"fee not provided. Please use the --fees flag or the --gas-price flag along with the --gas flag to estimate the fee. The minimun global fee for this tx is: %s", | ||
requiredFees) | ||
} | ||
|
||
if !feeCoins.IsAnyGTE(requiredFees) { | ||
return ctx, errorsmod.Wrapf(errortypes.ErrInsufficientFee, | ||
"provided fee < minimum global fee (%s < %s). Please increase the gas price.", | ||
feeCoins, | ||
requiredFees) | ||
} | ||
|
||
return next(ctx, tx, simulate) | ||
} |
Oops, something went wrong.