8000 fix(ante): handle zero fee case on evm transactions by GAtom22 · Pull Request #1753 · evmos/evmos · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

fix(ante): handle zero fee case on evm transactions #1753

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Sep 19, 2023
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 @@ -69,6 +69,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- (evm) [#1693](https://github.com/evmos/evmos/pull/1703) Prevent panic on uint64 conversion in EVM keeper `ApplyMessageWithConfig` function.
- (deps) [#1718](https://github.com/evmos/evmos/pull/1718) Update rosetta types import.
- (consensus) [#1740](https://github.com/evmos/evmos/pull/1740) Enable setting block gas limit to max by specifying it as -1 in the genesis file.
- (ante) [#1753](https://github.com/evmos/evmos/pull/1753) Handle zero fee case on evm transactions.

## [v13.0.2] - 2023-07-05

Expand Down
27 changes: 19 additions & 8 deletions app/ante/evm/eth.go
10000
Original file line number Diff line number Diff line change
Expand Up @@ -191,17 +191,10 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
return ctx, errorsmod.Wrapf(err, "failed to verify the fees")
}

// If the account balance is not sufficient, try to withdraw enough staking rewards
err = anteutils.ClaimStakingRewardsIfNecessary(ctx, egcd.bankKeeper, egcd.distributionKeeper, egcd.stakingKeeper, from, fees)
if err != nil {
if err = egcd.deductFee(ctx, fees, from); err != nil {
return ctx, err
}

err = egcd.evmKeeper.DeductTxCostsFromUserBalance(ctx, fees, common.HexToAddress(msgEthTx.From))
if err != nil {
return ctx, errorsmod.Wrapf(err, "failed to deduct transaction costs from user balance")
}

events = append(events,
sdk.NewEvent(
sdk.EventTypeTx,
Expand Down Expand Up @@ -248,6 +241,24 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
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 (egcd EthGasConsumeDecorator) deductFee(ctx sdk.Context, fees sdk.Coins, feePayer sdk.AccAddress) error {
if fees.IsZero() {
return nil
}

// If the account balance is not sufficient, try to withdraw enough staking rewards
if err := anteutils.ClaimStakingRewardsIfNecessary(ctx, egcd.bankKeeper, egcd.distributionKeeper, egcd.stakingKeeper, feePayer, fees); err != nil {
return err
}

if err := egcd.evmKeeper.DeductTxCostsFromUserBalance(ctx, fees, common.BytesToAddress(feePayer)); err != nil {
return errorsmod.Wrapf(err, "failed to deduct transaction costs from user balance")
}
return nil
}

// CanTransferDecorator checks if the sender is allowed to transfer funds according to the EVM block
// context rules.
type CanTransferDecorator struct {
Expand Down
35 changes: 35 additions & 0 deletions app/ante/evm/eth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,9 @@ func (suite *AnteTestSuite) TestEthGasConsumeDecorator() {
tx := evmtypes.NewTx(ethContractCreationTxParams)
tx.From = addr.Hex()

ethContractCreationTxParams.GasLimit = 55000
zeroFeeTx := makeZeroFeeTx(addr, *ethContractCreationTxParams)

ethCfg := suite.app.EvmKeeper.GetParams(suite.ctx).
ChainConfig.EthereumConfig(chainID)
baseFee := suite.app.EvmKeeper.GetBaseFee(suite.ctx, ethCfg)
Expand Down Expand Up @@ -243,6 +246,8 @@ func (suite *AnteTestSuite) TestEthGasConsumeDecorator() {

var vmdb *statedb.StateDB

initialBalance := suite.app.BankKeeper.GetBalance(suite.ctx, addr.Bytes(), utils.BaseDenom)

testCases := []struct {
name string
tx sdk.Tx
Expand Down Expand Up @@ -424,6 +429,36 @@ func (suite *AnteTestSuite) TestEthGasConsumeDecorator() {
)
},
},
{
"success - zero fees (no base fee)",
zeroFeeTx,
zeroFeeTx.GetGas(),
func(ctx sdk.Context) sdk.Context {
suite.disableBaseFee(ctx)
return ctx
},
true, false,
0,
func(ctx sdk.Context) {
finalBalance := suite.app.BankKeeper.GetBalance(ctx, addr.Bytes(), utils. 10000 BaseDenom)
suite.Require().Equal(initialBalance, finalBalance)
},
},
{
"success - zero fees (no base fee) - legacy tx",
makeZeroFeeTx(addr, *eth2TxContractParams),
tx2GasLimit,
func(ctx sdk.Context) sdk.Context {
suite.disableBaseFee(ctx)
return ctx
},
true, false,
0,
func(ctx sdk.Context) {
finalBalance := suite.app.BankKeeper.GetBalance(ctx, addr.Bytes(), utils.BaseDenom)
suite.Require().Equal(initialBalance, finalBalance)
},
},
}

for _, tc := range testCases {
Expand Down
19 changes: 19 additions & 0 deletions app/ante/evm/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -665,3 +665,22 @@ func (suite *AnteTestSuite) prepareAccount(ctx sdk.Context, addr sdk.AccAddress,
WithBlockGasMeter(sdk.NewGasMeter(1e19)).
WithBlockHeight(ctx.BlockHeight() + 1)
}

// disableBaseFee is a helper function that edits the
// feemarket parameters to not use base fee
func (suite *AnteTestSuite) disableBaseFee(ctx sdk.Context) {
params := suite.app.FeeMarketKeeper.GetParams(ctx)
params.NoBaseFee = true
params.MinGasPrice = sdk.ZeroDec()
err := suite.app.FeeMarketKeeper.SetParams(ctx, params)
suite.Require().NoError(err)
}

// makeZeroFeeTx is a helper function that sets
// the GasPrice field to zero on the provided evmTxArgs
func makeZeroFeeTx(from common.Address, args evmtypes.EvmTxArgs) *evmtypes.MsgEthereumTx {
args.GasPrice = sdk.ZeroInt().BigInt()
tx := evmtypes.NewTx(&args)
tx.From = from.Hex()
return tx
}
17 changes: 17 additions & 0 deletions tests/nix_tests/configs/zero-fee.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
local config = import 'default.jsonnet';

config {
'evmos_9000-1'+: {
genesis+: {
app_state+: {
feemarket+: {
params+: {
min_gas_price: '0',
no_base_fee: true,
base_fee: '0',
},
},
},
},
},
}
96 changes: 96 additions & 0 deletions tests/nix_tests/test_zero_fee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
from pathlib import Path

import pytest
from web3 import Web3

from .network import setup_custom_evmos
from .utils import ADDRS, eth_to_bech32, wait_for_fn


@pytest.fixture(scope="module")
def evmos(request, tmp_path_factory):
yield from setup_custom_evmos(
tmp_path_factory.mktemp("zero-fee"),
26900,
Path(__file__).parent / "configs/zero-fee.jsonnet",
)


def test_cosmos_tx(evmos):
"""
test basic cosmos transaction works with zero fees
"""
denom = "aevmos"
cli = evmos.cosmos_cli()
sender = eth_to_bech32(ADDRS["signer1"])
receiver = eth_to_bech32(ADDRS["signer2"])
amt = 1000

old_src_balance = cli.balance(sender, denom)
old_dst_balance = cli.balance(receiver, denom)

tx = cli.transfer(
sender,
receiver,
f"{amt}{denom}",
gas_prices=f"0{denom}",
generate_only=True,
)

tx = cli.sign_tx_json(tx, sender, max_priority_price=0)

rsp = cli.broadcast_tx_json(tx, broadcast_mode="sync")
assert rsp["code"] == 0, rsp["raw_log"]

new_dst_balance = 0

def check_balance_change():
nonlocal new_dst_balance
new_dst_balance = cli.balance(receiver, denom)
return old_dst_balance != new_dst_balance

wait_for_fn("balance change", check_balance_change)
assert old_dst_balance + amt == new_dst_balance
new_src_balance = cli.balance(sender, denom)
# no fees paid, so sender balance should be
# initial_balance - amount_sent
assert old_src_balance - amt == new_src_balance


def test_eth_tx(evmos):
"""
test basic Ethereum transaction works with zero fees
"""
w3: Web3 = evmos.w3

sender = ADDRS["signer1"]
receiver = ADDRS["signer2"]
amt = 1000

old_src_balance = w3.eth.get_balance(sender)
old_dst_balance = w3.eth.get_balance(receiver)

txhash = w3.eth.send_transaction(
{
"from": sender,
"to": receiver,
"value": amt,
"gasPrice": 0,
}
)
receipt = w3.eth.wait_for_transaction_receipt(txhash)
assert receipt.status == 1

new_dst_balance = 0

def check_balance_change():
nonlocal new_dst_balance
new_dst_balance = w3.eth.get_balance(receiver)
return old_dst_balance != new_dst_balance

wait_for_fn("balance change", check_balance_change)
assert old_dst_balance + amt == new_dst_balance
new_src_balance = w3.eth.get_balance(sender)
# no fees paid, so sender balance should be
# initial_balance - amount_sent
assert old_src_balance - amt == new_src_balance
0