Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

Use effectiveGasPrice in ante handler for dynamic fee tx #817

Merged
merged 10 commits into from
Dec 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

- (evm) [tharsis#840](https://github.com/tharsis/ethermint/pull/840) Store empty topics as empty array rather than nil.
- (feemarket) [tharsis#822](https://github.com/tharsis/ethermint/pull/822) Update EIP1559 base fee in `BeginBlock`.
- (evm) [tharsis#817](https://github.com/tharsis/ethermint/pull/817) Use `effectiveGasPrice` in ante handler, add `effectiveGasPrice` to tx receipt.

### Improvements

Expand Down
4 changes: 2 additions & 2 deletions app/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ func NewAnteHandler(
// handle as *evmtypes.MsgEthereumTx

anteHandler = sdk.ChainAnteDecorators(
NewEthSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
authante.NewMempoolFeeDecorator(),
NewEthSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
NewEthMempoolFeeDecorator(evmKeeper, feeMarketKeeper), // Check eth effective gas price against minimal-gas-prices
authante.NewTxTimeoutHeightDecorator(),
authante.NewValidateMemoDecorator(ak),
NewEthValidateBasicDecorator(evmKeeper),
Expand Down
11 changes: 5 additions & 6 deletions app/ante/ante_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ func (suite AnteTestSuite) TestAnteHandler() {
// bigger than MaxGasWanted
txBuilder.SetGasLimit(uint64(1 << 63))
return txBuilder.GetTx()
}, false, true, false,
}, true, false, false,
},
{
"fail - CheckTx (memo too long)",
Expand Down Expand Up @@ -286,7 +286,7 @@ func (suite AnteTestSuite) TestAnteHandler() {

for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.ctx = suite.ctx.WithIsCheckTx(tc.reCheckTx).WithIsReCheckTx(tc.reCheckTx)
suite.ctx = suite.ctx.WithIsCheckTx(tc.checkTx).WithIsReCheckTx(tc.reCheckTx)

// expConsumed := params.TxGasContractCreation + params.TxGas
_, err := suite.anteHandler(suite.ctx, tc.txFn(), false)
Expand Down Expand Up @@ -314,8 +314,6 @@ func (suite AnteTestSuite) TestAnteHandlerWithDynamicTxFee() {
suite.Require().NoError(acc.SetSequence(1))
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)

suite.app.EvmKeeper.AddBalance(addr, big.NewInt((ethparams.InitialBaseFee+10)*100000))

testCases := []struct {
name string
txFn func() sdk.Tx
Expand Down Expand Up @@ -501,7 +499,7 @@ func (suite AnteTestSuite) TestAnteHandlerWithDynamicTxFee() {
// bigger than MaxGasWanted
txBuilder.SetGasLimit(uint64(1 << 63))
return txBuilder.GetTx()
}, false, true, false,
}, true, false, false,
},
{
"fail - CheckTx (memo too long)",
Expand Down Expand Up @@ -530,7 +528,8 @@ func (suite AnteTestSuite) TestAnteHandlerWithDynamicTxFee() {

for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.ctx = suite.ctx.WithIsCheckTx(tc.reCheckTx).WithIsReCheckTx(tc.reCheckTx)
suite.ctx = suite.ctx.WithIsCheckTx(tc.checkTx).WithIsReCheckTx(tc.reCheckTx)
suite.app.EvmKeeper.AddBalance(addr, big.NewInt((ethparams.InitialBaseFee+10)*100000))
_, err := suite.anteHandler(suite.ctx, tc.txFn(), false)
if tc.expPass {
suite.Require().NoError(err)
Expand Down
55 changes: 55 additions & 0 deletions app/ante/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -543,3 +543,58 @@ func (esc EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simul
newCtx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
return next(newCtx, tx, simulate)
}

// 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 {
feemarketKeeper evmtypes.FeeMarketKeeper
evmKeeper EVMKeeper
}

func NewEthMempoolFeeDecorator(ek EVMKeeper, fmk evmtypes.FeeMarketKeeper) EthMempoolFeeDecorator {
return EthMempoolFeeDecorator{
feemarketKeeper: fmk,
evmKeeper: ek,
}
}

// AnteHandle ensures that the provided fees meet a minimum threshold for the validator,
// if this is a CheckTx. This is only for local mempool purposes, and thus
// is only ran on check tx.
func (mfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
if ctx.IsCheckTx() && !simulate {
yihuang marked this conversation as resolved.
Show resolved Hide resolved
if len(tx.GetMsgs()) != 1 {
return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "only 1 ethereum msg supported per tx")
}
msg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type %T, expected %T", tx, (*evmtypes.MsgEthereumTx)(nil))
}

var feeAmt *big.Int

feeMktParams := mfd.feemarketKeeper.GetParams(ctx)
params := mfd.evmKeeper.GetParams(ctx)
chainID := mfd.evmKeeper.ChainID()
ethCfg := params.ChainConfig.EthereumConfig(chainID)
evmDenom := params.EvmDenom
if evmtypes.IsLondon(ethCfg, ctx.BlockHeight()) && !feeMktParams.NoBaseFee {
baseFee := mfd.feemarketKeeper.GetBaseFee(ctx)
feeAmt = msg.GetEffectiveFee(baseFee)
} else {
feeAmt = msg.GetFee()
}

glDec := sdk.NewDec(int64(msg.GetGas()))
requiredFee := ctx.MinGasPrices().AmountOf(evmDenom).Mul(glDec)
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
if sdk.NewDecFromBigInt(feeAmt).LT(requiredFee) {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeAmt, requiredFee)
}
}

return next(ctx, tx, simulate)
}
8 changes: 8 additions & 0 deletions rpc/ethereum/namespaces/eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,14 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac
receipt["contractAddress"] = crypto.CreateAddress(from, txData.GetNonce())
}

if dynamicTx, ok := txData.(*evmtypes.DynamicFeeTx); ok {
baseFee, err := e.backend.BaseFee(res.Height)
if err != nil {
return nil, err
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
}
receipt["effectiveGasPrice"] = hexutil.Big(*dynamicTx.GetEffectiveGasPrice(baseFee))
}

return receipt, nil
}

Expand Down
16 changes: 5 additions & 11 deletions x/evm/keeper/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (

evmtypes "github.com/tharsis/ethermint/x/evm/types"

cmath "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
)
Expand Down Expand Up @@ -54,24 +53,19 @@ func (k Keeper) DeductTxCostsFromUserBalance(
)
}

// calculate the fees paid to validators based on the effective tip and price
effectiveTip := txData.GetGasPrice()
var feeAmt *big.Int

feeMktParams := k.feeMarketKeeper.GetParams(ctx)

if london && !feeMktParams.NoBaseFee && txData.TxType() == ethtypes.DynamicFeeTxType {
baseFee := k.feeMarketKeeper.GetBaseFee(ctx)
gasFeeGap := new(big.Int).Sub(txData.GetGasFeeCap(), baseFee)
if gasFeeGap.Sign() == -1 {
if txData.GetGasFeeCap().Cmp(baseFee) < 0 {
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
return nil, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "the tx gasfeecap is lower than the tx baseFee: %s (gasfeecap), %s (basefee) ", txData.GetGasFeeCap(), baseFee)
}

effectiveTip = cmath.BigMin(txData.GetGasTipCap(), gasFeeGap)
feeAmt = txData.EffectiveFee(baseFee)
} else {
feeAmt = txData.Fee()
}

gasUsed := new(big.Int).SetUint64(txData.GetGas())
feeAmt := new(big.Int).Mul(gasUsed, effectiveTip)

fees := sdk.Coins{sdk.NewCoin(denom, sdk.NewIntFromBigInt(feeAmt))}

// deduct the full gas cost from the user balance
Expand Down
19 changes: 8 additions & 11 deletions x/evm/keeper/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
cmath "github.com/ethereum/go-ethereum/common/math"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethparams "github.com/ethereum/go-ethereum/params"
evmkeeper "github.com/tharsis/ethermint/x/evm/keeper"
Expand Down Expand Up @@ -257,7 +256,9 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() {
oneInt := sdk.NewInt(1)
fiveInt := sdk.NewInt(5)
fiftyInt := sdk.NewInt(50)
hundredBaseFeeInt := sdk.NewInt(ethparams.InitialBaseFee * 100)

// should be enough to cover all test cases
initBalance := sdk.NewInt((ethparams.InitialBaseFee + 10) * 105)

testCases := []struct {
name string
Expand Down Expand Up @@ -331,15 +332,14 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() {
expectPass: false,
dynamicTxFee: true,
},
// TODO: is this case valid?
{
name: "empty fee failed to deduct",
name: "empty tip fee is valid to deduct",
gasLimit: 10,
gasFeeCap: big.NewInt(ethparams.InitialBaseFee),
gasTipCap: big.NewInt(1),
cost: &oneInt,
accessList: &ethtypes.AccessList{},
expectPass: false,
expectPass: true,
dynamicTxFee: true,
},
{
Expand Down Expand Up @@ -382,9 +382,9 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() {
} else {
gasTipCap = tc.gasTipCap
}
suite.app.EvmKeeper.AddBalance(suite.address, hundredBaseFeeInt.BigInt())
suite.app.EvmKeeper.AddBalance(suite.address, initBalance.BigInt())
balance := suite.app.EvmKeeper.GetBalance(suite.address)
suite.Require().Equal(balance, hundredBaseFeeInt.BigInt())
suite.Require().Equal(balance, initBalance.BigInt())
} else {
if tc.gasPrice != nil {
gasPrice = tc.gasPrice.BigInt()
Expand Down Expand Up @@ -414,13 +414,10 @@ func (suite *KeeperTestSuite) TestDeductTxCostsFromUserBalance() {
suite.Require().NoError(err, "valid test %d failed", i)
if tc.dynamicTxFee {
baseFee := suite.app.FeeMarketKeeper.GetBaseFee(suite.ctx)
gasFeeGap := new(big.Int).Sub(txData.GetGasFeeCap(), baseFee)
effectiveTip := cmath.BigMin(txData.GetGasTipCap(), gasFeeGap)

suite.Require().Equal(
fees,
sdk.NewCoins(
sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewIntFromBigInt(effectiveTip).Mul(sdk.NewIntFromUint64(tc.gasLimit))),
sdk.NewCoin(evmtypes.DefaultEVMDenom, sdk.NewIntFromBigInt(txData.EffectiveFee(baseFee))),
),
"valid test %d failed, fee value is wrong ", i,
)
Expand Down
10 changes: 10 additions & 0 deletions x/evm/types/access_list_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,13 @@ func (tx AccessListTx) Fee() *big.Int {
func (tx AccessListTx) Cost() *big.Int {
return cost(tx.Fee(), tx.GetValue())
}

// EffectiveFee is the same as Fee for AccessListTx
func (tx AccessListTx) EffectiveFee(baseFee *big.Int) *big.Int {
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
return tx.Fee()
}

// EffectiveCost is the same as Cost for AccessListTx
func (tx AccessListTx) EffectiveCost(baseFee *big.Int) *big.Int {
return tx.Cost()
}
18 changes: 17 additions & 1 deletion x/evm/types/dynamic_fee_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
ethtypes "github.com/ethereum/go-ethereum/core/types"

"github.com/tharsis/ethermint/types"
Expand Down Expand Up @@ -256,10 +257,25 @@ func (tx DynamicFeeTx) Validate() error {

// Fee returns gasprice * gaslimit.
func (tx DynamicFeeTx) Fee() *big.Int {
return fee(tx.GetGasPrice(), tx.GasLimit)
return fee(tx.GetGasFeeCap(), tx.GasLimit)
}

// Cost returns amount + gasprice * gaslimit.
func (tx DynamicFeeTx) Cost() *big.Int {
return cost(tx.Fee(), tx.GetValue())
}

// GetEffectiveGasPrice returns the effective gas price
func (tx *DynamicFeeTx) GetEffectiveGasPrice(baseFee *big.Int) *big.Int {
return math.BigMin(new(big.Int).Add(tx.GasTipCap.BigInt(), baseFee), tx.GasFeeCap.BigInt())
}

// EffectiveFee returns effective_gasprice * gaslimit.
func (tx DynamicFeeTx) EffectiveFee(baseFee *big.Int) *big.Int {
return fee(tx.GetEffectiveGasPrice(baseFee), tx.GasLimit)
}

// EffectiveCost returns amount + effective_gasprice * gaslimit.
func (tx DynamicFeeTx) EffectiveCost(baseFee *big.Int) *big.Int {
return cost(tx.EffectiveFee(baseFee), tx.GetValue())
}
10 changes: 10 additions & 0 deletions x/evm/types/legacy_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,13 @@ func (tx LegacyTx) Fee() *big.Int {
func (tx LegacyTx) Cost() *big.Int {
return cost(tx.Fee(), tx.GetValue())
}

// EffectiveFee is the same as Fee for LegacyTx
func (tx LegacyTx) EffectiveFee(baseFee *big.Int) *big.Int {
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
return tx.Fee()
}

// EffectiveCost is the same as Cost for LegacyTx
func (tx LegacyTx) EffectiveCost(baseFee *big.Int) *big.Int {
return tx.Cost()
}
18 changes: 18 additions & 0 deletions x/evm/types/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,24 @@ func (msg MsgEthereumTx) GetGas() uint64 {
return txData.GetGas()
}

// GetFee returns the fee for non dynamic fee tx
func (msg MsgEthereumTx) GetFee() *big.Int {
txData, err := UnpackTxData(msg.Data)
if err != nil {
return nil
}
return txData.Fee()
}

// GetEffectiveFee returns the fee for dynamic fee tx
func (msg MsgEthereumTx) GetEffectiveFee(baseFee *big.Int) *big.Int {
txData, err := UnpackTxData(msg.Data)
if err != nil {
return nil
}
return txData.EffectiveFee(baseFee)
}

// GetFrom loads the ethereum sender address from the sigcache and returns an
// sdk.AccAddress from its bytes
func (msg *MsgEthereumTx) GetFrom() sdk.AccAddress {
Expand Down
6 changes: 6 additions & 0 deletions x/evm/types/tx_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,14 @@ type TxData interface {

AsEthereumData() ethtypes.TxData
Validate() error

// static fee
Fee() *big.Int
Cost() *big.Int

// effective fee according to current base fee
EffectiveFee(baseFee *big.Int) *big.Int
EffectiveCost(baseFee *big.Int) *big.Int
}

func NewTxDataFromTx(tx *ethtypes.Transaction) (TxData, error) {
Expand Down