Skip to content

Commit

Permalink
feat: global min gas price (#53)
Browse files Browse the repository at this point in the history
* add min gas price to feemarket module at genesis

* make zetaclient respect feemarket base fee

* add min gas price to feemarket module at genesis

* add a nil check

* add a default base gas price

* rename GetGasPrice

* fix build flag in smoketest

* fix fee in smoketest broadcast

* special ante handler for tx with only 1 msg

* add changelog

* make linter happy

* allow MsgCreateValidator to bypass min gas price check only at genesis time

* add more gas limit to address out of gas in smoketest

* fix linter complaint

* uncomment multiple deposit smoketest

* increase smoketest timeout to 35min

---------

Co-authored-by: brewmaster012 <>
  • Loading branch information
brewmaster012 authored and lumtis committed Dec 19, 2023
1 parent 672f526 commit a45f519
Show file tree
Hide file tree
Showing 13 changed files with 301 additions and 21 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ jobs:

smoke-test:
runs-on: ["zeta-runners"] # 25 minutes
timeout-minutes: 30
timeout-minutes: 35
steps:
- uses: actions/checkout@v3

Expand Down
10 changes: 8 additions & 2 deletions app/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"runtime/debug"

stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
ethante "github.com/evmos/ethermint/app/ante"
cctxtypes "github.com/zeta-chain/zetacore/x/crosschain/types"

Expand Down Expand Up @@ -96,14 +97,19 @@ func NewAnteHandler(options ethante.HandlerOptions) (sdk.AnteHandler, error) {
found := false
for _, msg := range tx.GetMsgs() {
switch msg.(type) {
// treat these two msg types differently because they might call EVM which results in massive gas consumption
// treat these three msg types differently because they might call EVM which results in massive gas consumption
// For these two msg types, we don't check gas limit by using a different ante handler
case *cctxtypes.MsgGasPriceVoter, *cctxtypes.MsgVoteOnObservedInboundTx:
found = true
break
case *stakingtypes.MsgCreateValidator:
if ctx.BlockHeight() == 0 {
found = true
break
}
}
}
if found {
if len(tx.GetMsgs()) == 1 && found {
// this differs newCosmosAnteHandler only in that it doesn't check gas limit
// by using an Infinite Gas Meter.
anteHandler = newCosmosAnteHandlerNoGasLimit(options)
Expand Down
239 changes: 239 additions & 0 deletions app/ante/fees.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
// Copyright 2021 Evmos Foundation
// This file is part of Evmos' Ethermint library.
//
// The Ethermint library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The Ethermint library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the Ethermint library. If not, see https://github.com/evmos/ethermint/blob/main/LICENSE

// Copyright 2023 ZetaChain
// modified to exclude gentx transaction type from the min gas price check
package ante

import (
"math/big"

errorsmod "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
ethtypes "github.com/ethereum/go-ethereum/core/types"
evmtypes "github.com/evmos/ethermint/x/evm/types"
)

// 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 FeeMarketKeeper
evmKeeper EVMKeeper
}

// EthMinGasPriceDecorator 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 to both CheckTx and DeliverTx and regardless
// if London hard fork or fee market params (EIP-1559) are enabled.
// If fee is high enough, then call next AnteHandler
type EthMinGasPriceDecorator struct {
feesKeeper FeeMarketKeeper
evmKeeper EVMKeeper
}

// EthMempoolFeeDecorator will check if the transaction's effective fee is at least as large
// as the local validator's minimum gasFee (defined in validator config).
// If fee is too low, decorator returns error and tx is rejected from mempool.
// Note this only applies when ctx.CheckTx = true
// If fee is high enough or not CheckTx, then call next AnteHandler
// CONTRACT: Tx must implement FeeTx to use MempoolFeeDecorator
type EthMempoolFeeDecorator struct {
evmKeeper EVMKeeper
}

// NewMinGasPriceDecorator creates a new MinGasPriceDecorator instance used only for
// Cosmos transactions.
func NewMinGasPriceDecorator(fk FeeMarketKeeper, ek EVMKeeper) MinGasPriceDecorator {
return MinGasPriceDecorator{feesKeeper: fk, evmKeeper: ek}
}

// NewEthMinGasPriceDecorator creates a new MinGasPriceDecorator instance used only for
// Ethereum transactions.
func NewEthMinGasPriceDecorator(fk FeeMarketKeeper, ek EVMKeeper) EthMinGasPriceDecorator {
return EthMinGasPriceDecorator{feesKeeper: fk, evmKeeper: ek}
}

// NewEthMempoolFeeDecorator creates a new NewEthMempoolFeeDecorator instance used only for
// Ethereum transactions.
func NewEthMempoolFeeDecorator(ek EVMKeeper) EthMempoolFeeDecorator {
return EthMempoolFeeDecorator{
evmKeeper: ek,
}
}

func (mpd MinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return ctx, errorsmod.Wrapf(errortypes.ErrInvalidType, "invalid transaction type %T, expected sdk.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)
}

// Short-circuit genesis txs gentx
if len(tx.GetMsgs()) == 1 {
if _, ok := tx.GetMsgs()[0].(*stakingtypes.MsgCreateValidator); ok {
return next(ctx, tx, simulate)
}
}

evmParams := mpd.evmKeeper.GetParams(ctx)
evmDenom := evmParams.GetEvmDenom()
minGasPrices := sdk.DecCoins{
{
Denom: evmDenom,
Amount: minGasPrice,
},
}

feeCoins := feeTx.GetFee()
gas := feeTx.GetGas()

requiredFees := make(sdk.Coins, 0)

// Determine the required fees by multiplying each required minimum gas
// price by the gas limit, where fee = ceil(minGasPrice * gasLimit).
gasLimit := sdk.NewDecFromBigInt(new(big.Int).SetUint64(gas))

for _, gp := range minGasPrices {
fee := gp.Amount.Mul(gasLimit).Ceil().RoundInt()
if fee.IsPositive() {
requiredFees = requiredFees.Add(sdk.Coin{Denom: gp.Denom, Amount: fee})
}
}

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)
}

// AnteHandle ensures that the that the effective fee from the transaction is greater than the
// minimum global fee, which is defined by the MinGasPrice (parameter) * GasLimit (tx argument).
func (empd EthMinGasPriceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
minGasPrice := empd.feesKeeper.GetParams(ctx).MinGasPrice

// short-circuit if min gas price is 0
if minGasPrice.IsZero() {
return next(ctx, tx, simulate)
}

evmParams := empd.evmKeeper.GetParams(ctx)
chainCfg := evmParams.GetChainConfig()
ethCfg := chainCfg.EthereumConfig(empd.evmKeeper.ChainID())
baseFee := empd.evmKeeper.GetBaseFee(ctx, ethCfg)

for _, msg := range tx.GetMsgs() {
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
return ctx, errorsmod.Wrapf(
errortypes.ErrUnknownRequest,
"invalid message type %T, expected %T",
msg, (*evmtypes.MsgEthereumTx)(nil),
)
}

feeAmt := ethMsg.GetFee()

// For dynamic transactions, GetFee() uses the GasFeeCap value, which
// is the maximum gas price that the signer can pay. In practice, the
// signer can pay less, if the block's BaseFee is lower. So, in this case,
// we use the EffectiveFee. If the feemarket formula results in a BaseFee
// that lowers EffectivePrice until it is < MinGasPrices, the users must
// increase the GasTipCap (priority fee) until EffectivePrice > MinGasPrices.
// Transactions with MinGasPrices * gasUsed < tx fees < EffectiveFee are rejected
// by the feemarket AnteHandle

txData, err := evmtypes.UnpackTxData(ethMsg.Data)
if err != nil {
return ctx, errorsmod.Wrapf(err, "failed to unpack tx data %s", ethMsg.Hash)
}

if txData.TxType() != ethtypes.LegacyTxType {
feeAmt = ethMsg.GetEffectiveFee(baseFee)
}

gasLimit := sdk.NewDecFromBigInt(new(big.Int).SetUint64(ethMsg.GetGas()))

requiredFee := minGasPrice.Mul(gasLimit)
fee := sdk.NewDecFromBigInt(feeAmt)

if fee.LT(requiredFee) {
return ctx, errorsmod.Wrapf(
errortypes.ErrInsufficientFee,
"provided fee < minimum global fee (%d < %d). Please increase the priority tip (for EIP-1559 txs) or the gas prices (for access list or legacy txs)", //nolint:lll
fee.TruncateInt().Int64(), requiredFee.TruncateInt().Int64(),
)
}
}

return next(ctx, tx, simulate)
}

// AnteHandle ensures that the provided fees meet a minimum threshold for the validator.
// This check only for local mempool purposes, and thus it is only run on (Re)CheckTx.
// The logic is also skipped if the London hard fork and EIP-1559 are enabled.
func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
if !ctx.IsCheckTx() || simulate {
return next(ctx, tx, simulate)
}
evmParams := mfd.evmKeeper.GetParams(ctx)
chainCfg := evmParams.GetChainConfig()
ethCfg := chainCfg.EthereumConfig(mfd.evmKeeper.ChainID())

baseFee := mfd.evmKeeper.GetBaseFee(ctx, ethCfg)
// skip check as the London hard fork and EIP-1559 are enabled
if baseFee != nil {
return next(ctx, tx, simulate)
}

evmDenom := evmParams.GetEvmDenom()
minGasPrice := ctx.MinGasPrices().AmountOf(evmDenom)

for _, msg := range tx.GetMsgs() {
ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
if !ok {
return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
}

fee := sdk.NewDecFromBigInt(ethMsg.GetFee())
gasLimit := sdk.NewDecFromBigInt(new(big.Int).SetUint64(ethMsg.GetGas()))
requiredFee := minGasPrice.Mul(gasLimit)

if fee.LT(requiredFee) {
return ctx, errorsmod.Wrapf(
errortypes.ErrInsufficientFee,
"insufficient fee; got: %s required: %s",
fee, requiredFee,
)
}
}

return next(ctx, tx, simulate)
}
2 changes: 1 addition & 1 deletion app/ante/handler_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func newCosmosAnteHandlerNoGasLimit(options ethante.HandlerOptions) sdk.AnteHand
ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
ante.NewValidateBasicDecorator(),
ante.NewTxTimeoutHeightDecorator(),
ethante.NewMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper),
NewMinGasPriceDecorator(options.FeeMarketKeeper, options.EvmKeeper),
ante.NewValidateMemoDecorator(options.AccountKeeper),
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker),
Expand Down
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Features
* [1395](https://github.com/zeta-chain/node/pull/1395) - Add state variable to track aborted zeta amount
* [1387](https://github.com/zeta-chain/node/pull/1387) - Add HSM capability for zetaclient hot key
* enable zetaclients to use dynamic gas price on zetachain - enables >0 min_gas_price in feemarket module

### Fixes
* 6582f6b42f4f0eb0358e6fdefe5278295c791166 - masked zetaclient config at startup
Expand Down
18 changes: 9 additions & 9 deletions contrib/localnet/orchestrator/smoketest/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,29 +180,29 @@ func DeploySystemContractsAndZRC20(zetaTxServer ZetaTxServer) error {
// Deploy new system contracts
res, err := zetaTxServer.BroadcastTx(FungibleAdminName, fungibletypes.NewMsgDeploySystemContracts(FungibleAdminAddress))
if err != nil {
return err
return fmt.Errorf("failed to deploy system contracts: %s", err.Error())
}
fmt.Println("System contracts deployed")

address, err := fetchAttribute(res, "system_contract")
if err != nil {
return err
return fmt.Errorf("failed to fetch system contract address: %s; rawlog %s", err.Error(), res.RawLog)
}

// set system contract
_, err = zetaTxServer.BroadcastTx(FungibleAdminName, fungibletypes.NewMsgUpdateSystemContract(FungibleAdminAddress, address))
if err != nil {
return err
return fmt.Errorf("failed to set system contract: %s", err.Error())
}

// set uniswap contract addresses
UniswapV2FactoryAddr, err = fetchAttribute(res, "uniswap_v2_factory")
if err != nil {
return err
return fmt.Errorf("failed to fetch uniswap v2 factory address: %s", err.Error())
}
UniswapV2RouterAddr, err = fetchAttribute(res, "uniswap_v2_router")
if err != nil {
return err
return fmt.Errorf("failed to fetch uniswap v2 router address: %s", err.Error())
}

// deploy eth zrc20
Expand All @@ -217,7 +217,7 @@ func DeploySystemContractsAndZRC20(zetaTxServer ZetaTxServer) error {
1000000,
))
if err != nil {
return err
return fmt.Errorf("failed to deploy eth zrc20: %s", err.Error())
}

// deploy btc zrc20
Expand All @@ -232,7 +232,7 @@ func DeploySystemContractsAndZRC20(zetaTxServer ZetaTxServer) error {
1000000,
))
if err != nil {
return err
return fmt.Errorf("failed to deploy btc zrc20: %s", err.Error())
}

// deploy usdt zrc20
Expand All @@ -247,13 +247,13 @@ func DeploySystemContractsAndZRC20(zetaTxServer ZetaTxServer) error {
1000000,
))
if err != nil {
return err
return fmt.Errorf("failed to deploy usdt zrc20: %s", err.Error())
}

// fetch the usdt zrc20 contract address and remove the quotes
address, err = fetchAttribute(res, "Contract")
if err != nil {
return err
return fmt.Errorf("failed to fetch usdt zrc20 contract address: %s", err.Error())
}
if !ethcommon.IsHexAddress(address) {
return fmt.Errorf("invalid address in event: %s", address)
Expand Down
2 changes: 1 addition & 1 deletion contrib/localnet/orchestrator/smoketest/zeta_tx_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func newFactory(clientCtx client.Context) tx.Factory {
WithSignMode(signing.SignMode_SIGN_MODE_UNSPECIFIED).
WithAccountRetriever(clientCtx.AccountRetriever).
WithTxConfig(clientCtx.TxConfig).
WithFees("1000azeta")
WithFees("100000000000000000azeta")
}

type messageLog struct {
Expand Down
3 changes: 2 additions & 1 deletion contrib/localnet/scripts/genesis.sh
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,15 @@ then
cat $HOME/.zetacored/config/genesis.json | jq '.app_state["evm"]["params"]["evm_denom"]="azeta"' > $HOME/.zetacored/config/tmp_genesis.json && mv $HOME/.zetacored/config/tmp_genesis.json $HOME/.zetacored/config/genesis.json
cat $HOME/.zetacored/config/genesis.json | jq '.consensus_params["block"]["max_gas"]="500000000"' > $HOME/.zetacored/config/tmp_genesis.json && mv $HOME/.zetacored/config/tmp_genesis.json $HOME/.zetacored/config/genesis.json
cat $HOME/.zetacored/config/genesis.json | jq '.app_state["gov"]["voting_params"]["voting_period"]="100s"' > $HOME/.zetacored/config/tmp_genesis.json && mv $HOME/.zetacored/config/tmp_genesis.json $HOME/.zetacored/config/genesis.json
cat $HOME/.zetacored/config/genesis.json | jq '.app_state["feemarket"]["params"]["min_gas_price"]="10000000000.0000"' > $HOME/.zetacored/config/tmp_genesis.json && mv $HOME/.zetacored/config/tmp_genesis.json $HOME/.zetacored/config/genesis.json

# set admin account
zetacored add-genesis-account zeta1srsq755t654agc0grpxj4y3w0znktrpr9tcdgk 100000000000000000000000000azeta
cat $HOME/.zetacored/config/genesis.json | jq '.app_state["observer"]["params"]["admin_policy"][0]["address"]="zeta1srsq755t654agc0grpxj4y3w0znktrpr9tcdgk"' > $HOME/.zetacored/config/tmp_genesis.json && mv $HOME/.zetacored/config/tmp_genesis.json $HOME/.zetacored/config/genesis.json
cat $HOME/.zetacored/config/genesis.json | jq '.app_state["observer"]["params"]["admin_policy"][1]["address"]="zeta1srsq755t654agc0grpxj4y3w0znktrpr9tcdgk"' > $HOME/.zetacored/config/tmp_genesis.json && mv $HOME/.zetacored/config/tmp_genesis.json $HOME/.zetacored/config/genesis.json

# 3. Copy the genesis.json to all the nodes .And use it to create a gentx for every node
zetacored gentx operator 1000000000000000000000azeta --chain-id=$CHAINID --keyring-backend=$KEYRING
zetacored gentx operator 1000000000000000000000azeta --chain-id=$CHAINID --keyring-backend=$KEYRING --gas-prices 20000000000azeta
# Copy host gentx to other nodes
for NODE in "${NODELIST[@]}"; do
ssh $NODE mkdir -p ~/.zetacored/config/gentx/peer/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@
"elasticity_multiplier": 2,
"enable_height": "0",
"base_fee": "1000000000",
"min_gas_price": "0.000000000000000000",
"min_gas_price": "10000000000.0",
"min_gas_multiplier": "0.500000000000000000"
},
"block_gas": "0"
Expand Down
Loading

0 comments on commit a45f519

Please sign in to comment.