feat!: Apply feemarket to native cosmos tx (#1194)

* Problem: feemarket's query cli has redundant height parameter

Soluton:
- remove the positional height parameter, since there's a flag already.

Update CHANGELOG.md

* Apply feemarket to native cosmos tx

- add tx extension option for user to input tip price
- apply feemarket's base fee to native tx

comments and cleanup

fallback to default sdk logic when london hardfork not enabled

integration test

cleanup feemarket query cli commands

Update CHANGELOG.md

update unit tests

disable feemarket in simulation tests for now

fix lint

Update app/simulation_test.go

fix python lint

fix lint

Update x/evm/types/extension_option.go

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>

address review suggestions

* fix unit tests

* fix integration test

* improve unit test coverage

* fix go lint

* refactor

* fix integration test

* fix simulation tests

* fix go linter

Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
yihuang 2022-08-11 06:33:38 +08:00 committed by GitHub
parent 42abb259cb
commit b1cd16e5bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1785 additions and 59 deletions

View File

@ -50,6 +50,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (ante) [#1176](https://github.com/evmos/ethermint/pull/1176) Fix invalid tx hashes; Remove `Size_` field and validate `Hash`/`From` fields in ante handler, * (ante) [#1176](https://github.com/evmos/ethermint/pull/1176) Fix invalid tx hashes; Remove `Size_` field and validate `Hash`/`From` fields in ante handler,
recompute eth tx hashes in JSON-RPC APIs to fix old blocks. recompute eth tx hashes in JSON-RPC APIs to fix old blocks.
* (deps) [#1168](https://github.com/evmos/ethermint/pull/1168) Upgrade cosmos-sdk to v0.46. * (deps) [#1168](https://github.com/evmos/ethermint/pull/1168) Upgrade cosmos-sdk to v0.46.
* (feemarket) [#1194](https://github.com/evmos/ethermint/pull/1194) Apply feemarket to native cosmos tx.
### API Breaking ### API Breaking

View File

@ -27,6 +27,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
if err := options.validate(); err != nil { if err := options.validate(); err != nil {
return nil, err return nil, err
} }
return func( return func(
ctx sdk.Context, tx sdk.Tx, sim bool, ctx sdk.Context, tx sdk.Tx, sim bool,
) (newCtx sdk.Context, err error) { ) (newCtx sdk.Context, err error) {
@ -45,6 +46,9 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
case "/ethermint.types.v1.ExtensionOptionsWeb3Tx": case "/ethermint.types.v1.ExtensionOptionsWeb3Tx":
// handle as normal Cosmos SDK tx, except signature is checked for EIP712 representation // handle as normal Cosmos SDK tx, except signature is checked for EIP712 representation
anteHandler = newCosmosAnteHandlerEip712(options) anteHandler = newCosmosAnteHandlerEip712(options)
case "/ethermint.types.v1.ExtensionOptionDynamicFeeTx":
// cosmos-sdk tx with dynamic fee extension
anteHandler = newCosmosAnteHandler(options)
default: default:
return ctx, sdkerrors.Wrapf( return ctx, sdkerrors.Wrapf(
sdkerrors.ErrUnknownExtensionOptions, sdkerrors.ErrUnknownExtensionOptions,

View File

@ -305,8 +305,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
"success - DeliverTx EIP712 signed Cosmos Tx with MsgSend", "success - DeliverTx EIP712 signed Cosmos Tx with MsgSend",
func() sdk.Tx { func() sdk.Tx {
from := acc.GetAddress() from := acc.GetAddress()
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
gas := uint64(200000) gas := uint64(200000)
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas))))
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9000-1", gas, amount) txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9000-1", gas, amount)
return txBuilder.GetTx() return txBuilder.GetTx()
}, false, false, true, }, false, false, true,
@ -315,9 +315,9 @@ func (suite AnteTestSuite) TestAnteHandler() {
"success - DeliverTx EIP712 signed Cosmos Tx with DelegateMsg", "success - DeliverTx EIP712 signed Cosmos Tx with DelegateMsg",
func() sdk.Tx { func() sdk.Tx {
from := acc.GetAddress() from := acc.GetAddress()
coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20))
amount := sdk.NewCoins(coinAmount)
gas := uint64(200000) gas := uint64(200000)
coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas)))
amount := sdk.NewCoins(coinAmount)
txBuilder := suite.CreateTestEIP712TxBuilderMsgDelegate(from, privKey, "ethermint_9000-1", gas, amount) txBuilder := suite.CreateTestEIP712TxBuilderMsgDelegate(from, privKey, "ethermint_9000-1", gas, amount)
return txBuilder.GetTx() return txBuilder.GetTx()
}, false, false, true, }, false, false, true,
@ -326,8 +326,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
"fails - DeliverTx EIP712 signed Cosmos Tx with wrong Chain ID", "fails - DeliverTx EIP712 signed Cosmos Tx with wrong Chain ID",
func() sdk.Tx { func() sdk.Tx {
from := acc.GetAddress() from := acc.GetAddress()
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
gas := uint64(200000) gas := uint64(200000)
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas))))
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9002-1", gas, amount) txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9002-1", gas, amount)
return txBuilder.GetTx() return txBuilder.GetTx()
}, false, false, false, }, false, false, false,
@ -336,8 +336,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
"fails - DeliverTx EIP712 signed Cosmos Tx with different gas fees", "fails - DeliverTx EIP712 signed Cosmos Tx with different gas fees",
func() sdk.Tx { func() sdk.Tx {
from := acc.GetAddress() from := acc.GetAddress()
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
gas := uint64(200000) gas := uint64(200000)
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas))))
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount) txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount)
txBuilder.SetGasLimit(uint64(300000)) txBuilder.SetGasLimit(uint64(300000))
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(30)))) txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(30))))
@ -348,8 +348,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
"fails - DeliverTx EIP712 signed Cosmos Tx with empty signature", "fails - DeliverTx EIP712 signed Cosmos Tx with empty signature",
func() sdk.Tx { func() sdk.Tx {
from := acc.GetAddress() from := acc.GetAddress()
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
gas := uint64(200000) gas := uint64(200000)
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas))))
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount) txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount)
sigsV2 := signing.SignatureV2{} sigsV2 := signing.SignatureV2{}
txBuilder.SetSignatures(sigsV2) txBuilder.SetSignatures(sigsV2)
@ -360,8 +360,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
"fails - DeliverTx EIP712 signed Cosmos Tx with invalid sequence", "fails - DeliverTx EIP712 signed Cosmos Tx with invalid sequence",
func() sdk.Tx { func() sdk.Tx {
from := acc.GetAddress() from := acc.GetAddress()
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
gas := uint64(200000) gas := uint64(200000)
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas))))
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount) txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount)
nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress())
suite.Require().NoError(err) suite.Require().NoError(err)
@ -380,8 +380,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
"fails - DeliverTx EIP712 signed Cosmos Tx with invalid signMode", "fails - DeliverTx EIP712 signed Cosmos Tx with invalid signMode",
func() sdk.Tx { func() sdk.Tx {
from := acc.GetAddress() from := acc.GetAddress()
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
gas := uint64(200000) gas := uint64(200000)
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100*int64(gas))))
txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount) txBuilder := suite.CreateTestEIP712TxBuilderMsgSend(from, privKey, "ethermint_9001-1", gas, amount)
nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress()) nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress())
suite.Require().NoError(err) suite.Require().NoError(err)

View File

@ -9,7 +9,6 @@ import (
"github.com/evmos/ethermint/app/ante" "github.com/evmos/ethermint/app/ante"
"github.com/evmos/ethermint/server/config" "github.com/evmos/ethermint/server/config"
"github.com/evmos/ethermint/tests" "github.com/evmos/ethermint/tests"
evmkeeper "github.com/evmos/ethermint/x/evm/keeper"
"github.com/evmos/ethermint/x/evm/statedb" "github.com/evmos/ethermint/x/evm/statedb"
evmtypes "github.com/evmos/ethermint/x/evm/types" evmtypes "github.com/evmos/ethermint/x/evm/types"
@ -227,7 +226,7 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
baseFee := suite.app.EvmKeeper.GetBaseFee(suite.ctx, ethCfg) baseFee := suite.app.EvmKeeper.GetBaseFee(suite.ctx, ethCfg)
suite.Require().Equal(int64(1000000000), baseFee.Int64()) suite.Require().Equal(int64(1000000000), baseFee.Int64())
gasPrice := new(big.Int).Add(baseFee, evmkeeper.DefaultPriorityReduction.BigInt()) gasPrice := new(big.Int).Add(baseFee, evmtypes.DefaultPriorityReduction.BigInt())
tx2GasLimit := uint64(1000000) tx2GasLimit := uint64(1000000)
tx2 := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), tx2GasLimit, gasPrice, nil, nil, nil, &ethtypes.AccessList{{Address: addr, StorageKeys: nil}}) tx2 := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), tx2GasLimit, gasPrice, nil, nil, nil, &ethtypes.AccessList{{Address: addr, StorageKeys: nil}})
@ -236,8 +235,8 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
dynamicFeeTx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), tx2GasLimit, dynamicFeeTx := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), tx2GasLimit,
nil, // gasPrice nil, // gasPrice
new(big.Int).Add(baseFee, big.NewInt(evmkeeper.DefaultPriorityReduction.Int64()*2)), // gasFeeCap new(big.Int).Add(baseFee, big.NewInt(evmtypes.DefaultPriorityReduction.Int64()*2)), // gasFeeCap
evmkeeper.DefaultPriorityReduction.BigInt(), // gasTipCap evmtypes.DefaultPriorityReduction.BigInt(), // gasTipCap
nil, &ethtypes.AccessList{{Address: addr, StorageKeys: nil}}) nil, &ethtypes.AccessList{{Address: addr, StorageKeys: nil}})
dynamicFeeTx.From = addr.Hex() dynamicFeeTx.From = addr.Hex()
dynamicFeeTxPriority := int64(1) dynamicFeeTxPriority := int64(1)

142
app/ante/fee_checker.go Normal file
View File

@ -0,0 +1,142 @@
package ante
import (
"fmt"
"math"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
ethermint "github.com/evmos/ethermint/types"
"github.com/evmos/ethermint/x/evm/types"
)
// NewDynamicFeeChecker returns a `TxFeeChecker` that applies a dynamic fee to
// Cosmos txs using the EIP-1559 fee market logic.
// This can be called in both CheckTx and deliverTx modes.
// a) feeCap = tx.fees / tx.gas
// b) tipFeeCap = tx.MaxPriorityPrice (default) or MaxInt64
// - when `ExtensionOptionDynamicFeeTx` is omitted, `tipFeeCap` defaults to `MaxInt64`.
// - when london hardfork is not enabled, it fallbacks to SDK default behavior (validator min-gas-prices).
// - Tx priority is set to `effectiveGasPrice / DefaultPriorityReduction`.
func NewDynamicFeeChecker(k DynamicFeeEVMKeeper) authante.TxFeeChecker {
return func(ctx sdk.Context, tx sdk.Tx) (sdk.Coins, int64, error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return nil, 0, fmt.Errorf("tx must be a FeeTx")
}
if ctx.BlockHeight() == 0 {
// genesis transactions: fallback to min-gas-price logic
return checkTxFeeWithValidatorMinGasPrices(ctx, feeTx)
}
params := k.GetParams(ctx)
denom := params.EvmDenom
ethCfg := params.ChainConfig.EthereumConfig(k.ChainID())
baseFee := k.GetBaseFee(ctx, ethCfg)
if baseFee == nil {
// london hardfork is not enabled: fallback to min-gas-prices logic
return checkTxFeeWithValidatorMinGasPrices(ctx, feeTx)
}
// default to `MaxInt64` when there's no extension option.
maxPriorityPrice := sdkmath.NewInt(math.MaxInt64)
// get the priority tip cap from the extension option.
if hasExtOptsTx, ok := tx.(authante.HasExtensionOptionsTx); ok {
for _, opt := range hasExtOptsTx.GetExtensionOptions() {
if extOpt, ok := opt.GetCachedValue().(*ethermint.ExtensionOptionDynamicFeeTx); ok {
maxPriorityPrice = extOpt.MaxPriorityPrice
break
}
}
}
gas := feeTx.GetGas()
feeCoins := feeTx.GetFee()
fee := feeCoins.AmountOfNoDenomValidation(denom)
feeCap := fee.Quo(sdkmath.NewIntFromUint64(gas))
baseFeeInt := sdkmath.NewIntFromBigInt(baseFee)
if feeCap.LT(baseFeeInt) {
return nil, 0, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient gas prices; got: %s required: %s", feeCap, baseFeeInt)
}
// calculate the effective gas price using the EIP-1559 logic.
effectivePrice := sdkmath.NewIntFromBigInt(types.EffectiveGasPrice(baseFeeInt.BigInt(), feeCap.BigInt(), maxPriorityPrice.BigInt()))
// NOTE: create a new coins slice without having to validate the denom
effectiveFee := sdk.Coins{
{
Denom: denom,
Amount: effectivePrice.Mul(sdkmath.NewIntFromUint64(gas)),
},
}
bigPriority := effectivePrice.Sub(baseFeeInt).Quo(types.DefaultPriorityReduction)
priority := int64(math.MaxInt64)
if bigPriority.IsInt64() {
priority = bigPriority.Int64()
}
return effectiveFee, priority, 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 sdk.Context, tx sdk.FeeTx) (sdk.Coins, int64, error) {
feeCoins := tx.GetFee()
gas := tx.GetGas()
minGasPrices := ctx.MinGasPrices()
// Ensure 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.
if ctx.IsCheckTx() && !minGasPrices.IsZero() {
requiredFees := make(sdk.Coins, len(minGasPrices))
// Determine the required fees by multiplying each required minimum gas
// price by the gas limit, where fee = ceil(minGasPrice * gasLimit).
glDec := sdk.NewDec(int64(gas))
for i, gp := range minGasPrices {
fee := gp.Amount.Mul(glDec)
requiredFees[i] = sdk.NewCoin(gp.Denom, fee.Ceil().RoundInt())
}
if !feeCoins.IsAnyGTE(requiredFees) {
return nil, 0, sdkerrors.Wrapf(sdkerrors.ErrInsufficientFee, "insufficient fees; got: %s required: %s", feeCoins, requiredFees)
}
}
priority := getTxPriority(feeCoins)
return feeCoins, priority, nil
}
// getTxPriority returns a naive tx priority based on the amount of the smallest denomination of the fee
// provided in a transaction.
func getTxPriority(fees sdk.Coins) int64 {
var priority int64
for _, fee := range fees {
amt := fee.Amount.Quo(types.DefaultPriorityReduction)
p := int64(math.MaxInt64)
if amt.IsInt64() {
p = amt.Int64()
}
if priority == 0 || p < priority {
priority = p
}
}
return priority
}

View File

@ -0,0 +1,219 @@
package ante
import (
"math/big"
"testing"
"github.com/stretchr/testify/require"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
"github.com/ethereum/go-ethereum/params"
"github.com/evmos/ethermint/encoding"
ethermint "github.com/evmos/ethermint/types"
"github.com/evmos/ethermint/x/evm/types"
evmtypes "github.com/evmos/ethermint/x/evm/types"
"github.com/tendermint/tendermint/libs/log"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
var _ DynamicFeeEVMKeeper = MockEVMKeeper{}
type MockEVMKeeper struct {
BaseFee *big.Int
EnableLondonHF bool
}
func (m MockEVMKeeper) GetBaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int {
if m.EnableLondonHF {
return m.BaseFee
}
return nil
}
func (m MockEVMKeeper) GetParams(ctx sdk.Context) evmtypes.Params {
return evmtypes.DefaultParams()
}
func (m MockEVMKeeper) ChainID() *big.Int {
return big.NewInt(9000)
}
func TestSDKTxFeeChecker(t *testing.T) {
// testCases:
// fallback
// genesis tx
// checkTx, validate with min-gas-prices
// deliverTx, no validation
// dynamic fee
// with extension option
// without extension option
// london hardfork enableness
encodingConfig := encoding.MakeConfig(module.NewBasicManager())
minGasPrices := sdk.NewDecCoins(sdk.NewDecCoin("aphoton", sdk.NewInt(10)))
genesisCtx := sdk.NewContext(nil, tmproto.Header{}, false, log.NewNopLogger())
checkTxCtx := sdk.NewContext(nil, tmproto.Header{Height: 1}, true, log.NewNopLogger()).WithMinGasPrices(minGasPrices)
deliverTxCtx := sdk.NewContext(nil, tmproto.Header{Height: 1}, false, log.NewNopLogger())
testCases := []struct {
name string
ctx sdk.Context
keeper DynamicFeeEVMKeeper
buildTx func() sdk.Tx
expFees string
expPriority int64
expSuccess bool
}{
{
"success, genesis tx",
genesisCtx,
MockEVMKeeper{},
func() sdk.Tx {
return encodingConfig.TxConfig.NewTxBuilder().GetTx()
},
"",
0,
true,
},
{
"fail, min-gas-prices",
checkTxCtx,
MockEVMKeeper{},
func() sdk.Tx {
return encodingConfig.TxConfig.NewTxBuilder().GetTx()
},
"",
0,
false,
},
{
"success, min-gas-prices",
checkTxCtx,
MockEVMKeeper{},
func() sdk.Tx {
txBuilder := encodingConfig.TxConfig.NewTxBuilder()
txBuilder.SetGasLimit(1)
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("aphoton", sdk.NewInt(10))))
return txBuilder.GetTx()
},
"10aphoton",
0,
true,
},
{
"success, min-gas-prices deliverTx",
deliverTxCtx,
MockEVMKeeper{},
func() sdk.Tx {
return encodingConfig.TxConfig.NewTxBuilder().GetTx()
},
"",
0,
true,
},
{
"fail, dynamic fee",
deliverTxCtx,
MockEVMKeeper{
EnableLondonHF: true, BaseFee: big.NewInt(1),
},
func() sdk.Tx {
txBuilder := encodingConfig.TxConfig.NewTxBuilder()
txBuilder.SetGasLimit(1)
return txBuilder.GetTx()
},
"",
0,
false,
},
{
"success, dynamic fee",
deliverTxCtx,
MockEVMKeeper{
EnableLondonHF: true, BaseFee: big.NewInt(10),
},
func() sdk.Tx {
txBuilder := encodingConfig.TxConfig.NewTxBuilder()
txBuilder.SetGasLimit(1)
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("aphoton", sdk.NewInt(10))))
return txBuilder.GetTx()
},
"10aphoton",
0,
true,
},
{
"success, dynamic fee priority",
deliverTxCtx,
MockEVMKeeper{
EnableLondonHF: true, BaseFee: big.NewInt(10),
},
func() sdk.Tx {
txBuilder := encodingConfig.TxConfig.NewTxBuilder()
txBuilder.SetGasLimit(1)
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("aphoton", sdk.NewInt(10).Mul(types.DefaultPriorityReduction).Add(sdk.NewInt(10)))))
return txBuilder.GetTx()
},
"10000010aphoton",
10,
true,
},
{
"success, dynamic fee empty tipFeeCap",
deliverTxCtx,
MockEVMKeeper{
EnableLondonHF: true, BaseFee: big.NewInt(10),
},
func() sdk.Tx {
txBuilder := encodingConfig.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
txBuilder.SetGasLimit(1)
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("aphoton", sdk.NewInt(10).Mul(types.DefaultPriorityReduction))))
option, err := codectypes.NewAnyWithValue(&ethermint.ExtensionOptionDynamicFeeTx{})
require.NoError(t, err)
txBuilder.SetExtensionOptions(option)
return txBuilder.GetTx()
},
"10aphoton",
0,
true,
},
{
"success, dynamic fee tipFeeCap",
deliverTxCtx,
MockEVMKeeper{
EnableLondonHF: true, BaseFee: big.NewInt(10),
},
func() sdk.Tx {
txBuilder := encodingConfig.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
txBuilder.SetGasLimit(1)
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin("aphoton", sdk.NewInt(10).Mul(types.DefaultPriorityReduction).Add(sdk.NewInt(10)))))
option, err := codectypes.NewAnyWithValue(&ethermint.ExtensionOptionDynamicFeeTx{
MaxPriorityPrice: sdk.NewInt(5).Mul(types.DefaultPriorityReduction),
})
require.NoError(t, err)
txBuilder.SetExtensionOptions(option)
return txBuilder.GetTx()
},
"5000010aphoton",
5,
true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
fees, priority, err := NewDynamicFeeChecker(tc.keeper)(tc.ctx, tc.buildTx())
if tc.expSuccess {
require.Equal(t, tc.expFees, fees.String())
require.Equal(t, tc.expPriority, priority)
} else {
require.Error(t, err)
}
})
}
}

View File

@ -14,17 +14,22 @@ import (
feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" feemarkettypes "github.com/evmos/ethermint/x/feemarket/types"
) )
// DynamicFeeEVMKeeper is a subset of EVMKeeper interface that supports dynamic fee checker
type DynamicFeeEVMKeeper interface {
ChainID() *big.Int
GetParams(ctx sdk.Context) evmtypes.Params
GetBaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int
}
// EVMKeeper defines the expected keeper interface used on the Eth AnteHandler // EVMKeeper defines the expected keeper interface used on the Eth AnteHandler
type EVMKeeper interface { type EVMKeeper interface {
statedb.Keeper statedb.Keeper
DynamicFeeEVMKeeper
ChainID() *big.Int
GetParams(ctx sdk.Context) evmtypes.Params
NewEVM(ctx sdk.Context, msg core.Message, cfg *evmtypes.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB) *vm.EVM NewEVM(ctx sdk.Context, msg core.Message, cfg *evmtypes.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB) *vm.EVM
DeductTxCostsFromUserBalance( DeductTxCostsFromUserBalance(
ctx sdk.Context, msgEthTx evmtypes.MsgEthereumTx, txData evmtypes.TxData, denom string, homestead, istanbul, london bool, ctx sdk.Context, msgEthTx evmtypes.MsgEthereumTx, txData evmtypes.TxData, denom string, homestead, istanbul, london bool,
) (fees sdk.Coins, priority int64, err error) ) (fees sdk.Coins, priority int64, err error)
GetBaseFee(ctx sdk.Context, ethCfg *params.ChainConfig) *big.Int
GetBalance(ctx sdk.Context, addr common.Address) *big.Int GetBalance(ctx sdk.Context, addr common.Address) *big.Int
ResetTransientGasUsed(ctx sdk.Context) ResetTransientGasUsed(ctx sdk.Context)
GetTxIndexTransient(ctx sdk.Context) uint64 GetTxIndexTransient(ctx sdk.Context) uint64

View File

@ -609,6 +609,8 @@ func (app *EthermintApp) setAnteHandler(txConfig client.TxConfig, maxGasWanted u
EvmKeeper: app.EvmKeeper, EvmKeeper: app.EvmKeeper,
FeeMarketKeeper: app.FeeMarketKeeper, FeeMarketKeeper: app.FeeMarketKeeper,
MaxTxGasWanted: maxGasWanted, MaxTxGasWanted: maxGasWanted,
ExtensionOptionChecker: ethermint.HasDynamicFeeExtensionOption,
TxFeeChecker: ante.NewDynamicFeeChecker(app.EvmKeeper),
}) })
if err != nil { if err != nil {
panic(err) panic(err)

View File

@ -32,6 +32,7 @@ import (
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
ibctransfertypes "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types" ibctransfertypes "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types"
ibchost "github.com/cosmos/ibc-go/v5/modules/core/24-host" ibchost "github.com/cosmos/ibc-go/v5/modules/core/24-host"
"github.com/evmos/ethermint/app/ante"
evmenc "github.com/evmos/ethermint/encoding" evmenc "github.com/evmos/ethermint/encoding"
abci "github.com/tendermint/tendermint/abci/types" abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/libs/log"
@ -62,6 +63,32 @@ func fauxMerkleModeOpt(bapp *baseapp.BaseApp) {
bapp.SetFauxMerkleMode() bapp.SetFauxMerkleMode()
} }
// NewSimApp disable feemarket on native tx, otherwise the cosmos-sdk simulation tests will fail.
func NewSimApp(logger log.Logger, db dbm.DB) (*EthermintApp, error) {
encodingConfig := MakeEncodingConfig()
app := NewEthermintApp(logger, db, nil, false, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, encodingConfig, simapp.EmptyAppOptions{}, fauxMerkleModeOpt)
// disable feemarket on native tx
anteHandler, err := ante.NewAnteHandler(ante.HandlerOptions{
AccountKeeper: app.AccountKeeper,
BankKeeper: app.BankKeeper,
SignModeHandler: encodingConfig.TxConfig.SignModeHandler(),
FeegrantKeeper: app.FeeGrantKeeper,
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
IBCKeeper: app.IBCKeeper,
EvmKeeper: app.EvmKeeper,
FeeMarketKeeper: app.FeeMarketKeeper,
MaxTxGasWanted: 0,
})
if err != nil {
return nil, err
}
app.SetAnteHandler(anteHandler)
if err := app.LoadLatestVersion(); err != nil {
return nil, err
}
return app, nil
}
// interBlockCacheOpt returns a BaseApp option function that sets the persistent // interBlockCacheOpt returns a BaseApp option function that sets the persistent
// inter-block write-through cache. // inter-block write-through cache.
func interBlockCacheOpt() func(*baseapp.BaseApp) { func interBlockCacheOpt() func(*baseapp.BaseApp) {
@ -82,8 +109,9 @@ func TestFullAppSimulation(t *testing.T) {
require.NoError(t, os.RemoveAll(dir)) require.NoError(t, os.RemoveAll(dir))
}() }()
app := NewEthermintApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), simapp.EmptyAppOptions{}, fauxMerkleModeOpt) app, err := NewSimApp(logger, db)
require.Equal(t, appName, app.Name()) require.Equal(t, appName, app.Name())
require.NoError(t, err)
// run randomized simulation // run randomized simulation
_, simParams, simErr := simulation.SimulateFromSeed( _, simParams, simErr := simulation.SimulateFromSeed(
@ -121,8 +149,8 @@ func TestAppImportExport(t *testing.T) {
require.NoError(t, db.Close()) require.NoError(t, db.Close())
require.NoError(t, os.RemoveAll(dir)) require.NoError(t, os.RemoveAll(dir))
}() }()
app, err := NewSimApp(logger, db)
app := NewEthermintApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), simapp.EmptyAppOptions{}, fauxMerkleModeOpt) require.NoError(t, err)
require.Equal(t, appName, app.Name()) require.Equal(t, appName, app.Name())
// Run randomized simulation // Run randomized simulation
@ -163,8 +191,9 @@ func TestAppImportExport(t *testing.T) {
require.NoError(t, os.RemoveAll(newDir)) require.NoError(t, os.RemoveAll(newDir))
}() }()
newApp := NewEthermintApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), simapp.EmptyAppOptions{}, fauxMerkleModeOpt) newApp, err := NewSimApp(log.NewNopLogger(), newDB)
require.Equal(t, appName, newApp.Name()) require.Equal(t, appName, newApp.Name())
require.NoError(t, err)
var genesisState simapp.GenesisState var genesisState simapp.GenesisState
err = json.Unmarshal(exported.AppState, &genesisState) err = json.Unmarshal(exported.AppState, &genesisState)
@ -236,8 +265,9 @@ func TestAppSimulationAfterImport(t *testing.T) {
require.NoError(t, os.RemoveAll(dir)) require.NoError(t, os.RemoveAll(dir))
}() }()
app := NewEthermintApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), simapp.EmptyAppOptions{}, fauxMerkleModeOpt) app, err := NewSimApp(logger, db)
require.Equal(t, appName, app.Name()) require.Equal(t, appName, app.Name())
require.NoError(t, err)
// Run randomized simulation // Run randomized simulation
stopEarly, simParams, simErr := simulation.SimulateFromSeed( stopEarly, simParams, simErr := simulation.SimulateFromSeed(
@ -281,8 +311,9 @@ func TestAppSimulationAfterImport(t *testing.T) {
require.NoError(t, os.RemoveAll(newDir)) require.NoError(t, os.RemoveAll(newDir))
}() }()
newApp := NewEthermintApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), simapp.EmptyAppOptions{}, fauxMerkleModeOpt) newApp, err := NewSimApp(log.NewNopLogger(), newDB)
require.Equal(t, appName, newApp.Name()) require.Equal(t, appName, newApp.Name())
require.NoError(t, err)
newApp.InitChain(abci.RequestInitChain{ newApp.InitChain(abci.RequestInitChain{
ChainId: SimAppChainID, ChainId: SimAppChainID,
@ -333,14 +364,15 @@ func TestAppStateDeterminism(t *testing.T) {
} }
db := dbm.NewMemDB() db := dbm.NewMemDB()
app := NewEthermintApp(logger, db, nil, true, map[int64]bool{}, DefaultNodeHome, simapp.FlagPeriodValue, MakeEncodingConfig(), simapp.EmptyAppOptions{}, interBlockCacheOpt()) app, err := NewSimApp(logger, db)
require.NoError(t, err)
fmt.Printf( fmt.Printf(
"running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n", "running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n",
config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed, config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed,
) )
_, _, err := simulation.SimulateFromSeed( _, _, err = simulation.SimulateFromSeed(
t, t,
os.Stdout, os.Stdout,
app.BaseApp, app.BaseApp,

View File

@ -79,6 +79,9 @@
- [ethermint/types/v1/account.proto](#ethermint/types/v1/account.proto) - [ethermint/types/v1/account.proto](#ethermint/types/v1/account.proto)
- [EthAccount](#ethermint.types.v1.EthAccount) - [EthAccount](#ethermint.types.v1.EthAccount)
- [ethermint/types/v1/dynamic_fee.proto](#ethermint/types/v1/dynamic_fee.proto)
- [ExtensionOptionDynamicFeeTx](#ethermint.types.v1.ExtensionOptionDynamicFeeTx)
- [ethermint/types/v1/web3.proto](#ethermint/types/v1/web3.proto) - [ethermint/types/v1/web3.proto](#ethermint/types/v1/web3.proto)
- [ExtensionOptionsWeb3Tx](#ethermint.types.v1.ExtensionOptionsWeb3Tx) - [ExtensionOptionsWeb3Tx](#ethermint.types.v1.ExtensionOptionsWeb3Tx)
@ -1134,6 +1137,37 @@ authtypes.BaseAccount type. It is compatible with the auth AccountKeeper.
<!-- end messages -->
<!-- end enums -->
<!-- end HasExtensions -->
<!-- end services -->
<a name="ethermint/types/v1/dynamic_fee.proto"></a>
<p align="right"><a href="#top">Top</a></p>
## ethermint/types/v1/dynamic_fee.proto
<a name="ethermint.types.v1.ExtensionOptionDynamicFeeTx"></a>
### ExtensionOptionDynamicFeeTx
ExtensionOptionDynamicFeeTx is an extension option that specify the maxPrioPrice for cosmos tx
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `max_priority_price` | [string](#string) | | the same as `max_priority_fee_per_gas` in eip-1559 spec |
<!-- end messages --> <!-- end messages -->
<!-- end enums --> <!-- end enums -->

View File

@ -0,0 +1,15 @@
syntax = "proto3";
package ethermint.types.v1;
import "gogoproto/gogo.proto";
option go_package = "github.com/evmos/ethermint/types";
// ExtensionOptionDynamicFeeTx is an extension option that specify the maxPrioPrice for cosmos tx
message ExtensionOptionDynamicFeeTx {
// the same as `max_priority_fee_per_gas` in eip-1559 spec
string max_priority_price = 1 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false
];
}

View File

@ -973,7 +973,6 @@ func (suite *BackendTestSuite) TestGetEthereumMsgsFromTendermintBlock() {
} }
for _, tc := range testCases { for _, tc := range testCases {
suite.Run(fmt.Sprintf("Case %s", tc.name), func() { suite.Run(fmt.Sprintf("Case %s", tc.name), func() {
suite.SetupTest() // reset test and queries suite.SetupTest() // reset test and queries
msgs := suite.backend.GetEthereumMsgsFromTendermintBlock(tc.resBlock, tc.blockRes) msgs := suite.backend.GetEthereumMsgsFromTendermintBlock(tc.resBlock, tc.blockRes)

View File

@ -0,0 +1,828 @@
import json
import tempfile
import requests
from dateutil.parser import isoparse
from pystarport.utils import build_cli_args_safe, interact
DEFAULT_GAS_PRICE = "5000000000000aphoton"
class ChainCommand:
def __init__(self, cmd):
self.cmd = cmd
def __call__(self, cmd, *args, stdin=None, **kwargs):
"execute chain-maind"
args = " ".join(build_cli_args_safe(cmd, *args, **kwargs))
return interact(f"{self.cmd} {args}", input=stdin)
class CosmosCLI:
"the apis to interact with wallet and blockchain"
def __init__(
self,
data_dir,
node_rpc,
cmd,
):
self.data_dir = data_dir
self._genesis = json.loads(
(self.data_dir / "config" / "genesis.json").read_text()
)
self.chain_id = self._genesis["chain_id"]
self.node_rpc = node_rpc
self.raw = ChainCommand(cmd)
self.output = None
self.error = None
@property
def node_rpc_http(self):
return "http" + self.node_rpc.removeprefix("tcp")
def node_id(self):
"get tendermint node id"
output = self.raw("tendermint", "show-node-id", home=self.data_dir)
return output.decode().strip()
def delete_account(self, name):
"delete wallet account in node's keyring"
return self.raw(
"keys",
"delete",
name,
"-y",
"--force",
home=self.data_dir,
output="json",
keyring_backend="test",
)
def create_account(self, name, mnemonic=None):
"create new keypair in node's keyring"
if mnemonic is None:
output = self.raw(
"keys",
"add",
name,
home=self.data_dir,
output="json",
keyring_backend="test",
)
else:
output = self.raw(
"keys",
"add",
name,
"--recover",
home=self.data_dir,
output="json",
keyring_backend="test",
stdin=mnemonic.encode() + b"\n",
)
return json.loads(output)
def init(self, moniker):
"the node's config is already added"
return self.raw(
"init",
moniker,
chain_id=self.chain_id,
home=self.data_dir,
)
def validate_genesis(self):
return self.raw("validate-genesis", home=self.data_dir)
def add_genesis_account(self, addr, coins, **kwargs):
return self.raw(
"add-genesis-account",
addr,
coins,
home=self.data_dir,
output="json",
**kwargs,
)
def gentx(self, name, coins, min_self_delegation=1, pubkey=None):
return self.raw(
"gentx",
name,
coins,
min_self_delegation=str(min_self_delegation),
home=self.data_dir,
chain_id=self.chain_id,
keyring_backend="test",
pubkey=pubkey,
)
def collect_gentxs(self, gentx_dir):
return self.raw("collect-gentxs", gentx_dir, home=self.data_dir)
def status(self):
return json.loads(self.raw("status", node=self.node_rpc))
def block_height(self):
return int(self.status()["SyncInfo"]["latest_block_height"])
def block_time(self):
return isoparse(self.status()["SyncInfo"]["latest_block_time"])
def balances(self, addr):
return json.loads(
self.raw("query", "bank", "balances", addr, home=self.data_dir)
)["balances"]
def balance(self, addr, denom="aphoton"):
denoms = {coin["denom"]: int(coin["amount"]) for coin in self.balances(addr)}
return denoms.get(denom, 0)
def query_tx(self, tx_type, tx_value):
tx = self.raw(
"query",
"tx",
"--type",
tx_type,
tx_value,
home=self.data_dir,
chain_id=self.chain_id,
node=self.node_rpc,
)
return json.loads(tx)
def query_all_txs(self, addr):
txs = self.raw(
"query",
"txs-all",
addr,
home=self.data_dir,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
return json.loads(txs)
def distribution_commission(self, addr):
coin = json.loads(
self.raw(
"query",
"distribution",
"commission",
addr,
output="json",
node=self.node_rpc,
)
)["commission"][0]
return float(coin["amount"])
def distribution_community(self):
coin = json.loads(
self.raw(
"query",
"distribution",
"community-pool",
output="json",
node=self.node_rpc,
)
)["pool"][0]
return float(coin["amount"])
def distribution_reward(self, delegator_addr):
coin = json.loads(
self.raw(
"query",
"distribution",
"rewards",
delegator_addr,
output="json",
node=self.node_rpc,
)
)["total"][0]
return float(coin["amount"])
def address(self, name, bech="acc"):
output = self.raw(
"keys",
"show",
name,
"-a",
home=self.data_dir,
keyring_backend="test",
bech=bech,
)
return output.strip().decode()
def account(self, addr):
return json.loads(
self.raw(
"query", "auth", "account", addr, output="json", node=self.node_rpc
)
)
def tx_search(self, events: str):
"/tx_search"
return json.loads(
self.raw("query", "txs", events=events, output="json", node=self.node_rpc)
)
def tx_search_rpc(self, events: str):
rsp = requests.get(
f"{self.node_rpc_http}/tx_search",
params={
"query": f'"{events}"',
},
).json()
assert "error" not in rsp, rsp["error"]
return rsp["result"]["txs"]
def tx(self, value, **kwargs):
"/tx"
default_kwargs = {
"home": self.data_dir,
}
return json.loads(self.raw("query", "tx", value, **(default_kwargs | kwargs)))
def total_supply(self):
return json.loads(
self.raw("query", "bank", "total", output="json", node=self.node_rpc)
)
def validator(self, addr):
return json.loads(
self.raw(
"query",
"staking",
"validator",
addr,
output="json",
node=self.node_rpc,
)
)
def validators(self):
return json.loads(
self.raw(
"query", "staking", "validators", output="json", node=self.node_rpc
)
)["validators"]
def staking_params(self):
return json.loads(
self.raw("query", "staking", "params", output="json", node=self.node_rpc)
)
def staking_pool(self, bonded=True):
return int(
json.loads(
self.raw("query", "staking", "pool", output="json", node=self.node_rpc)
)["bonded_tokens" if bonded else "not_bonded_tokens"]
)
def transfer(self, from_, to, coins, generate_only=False, **kwargs):
kwargs.setdefault("gas_prices", DEFAULT_GAS_PRICE)
return json.loads(
self.raw(
"tx",
"bank",
"send",
from_,
to,
coins,
"-y",
"--generate-only" if generate_only else None,
home=self.data_dir,
**kwargs,
)
)
def get_delegated_amount(self, which_addr):
return json.loads(
self.raw(
"query",
"staking",
"delegations",
which_addr,
home=self.data_dir,
chain_id=self.chain_id,
node=self.node_rpc,
output="json",
)
)
def delegate_amount(self, to_addr, amount, from_addr, gas_price=None):
if gas_price is None:
return json.loads(
self.raw(
"tx",
"staking",
"delegate",
to_addr,
amount,
"-y",
home=self.data_dir,
from_=from_addr,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
)
else:
return json.loads(
self.raw(
"tx",
"staking",
"delegate",
to_addr,
amount,
"-y",
home=self.data_dir,
from_=from_addr,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
gas_prices=gas_price,
)
)
# to_addr: croclcl1... , from_addr: cro1...
def unbond_amount(self, to_addr, amount, from_addr):
return json.loads(
self.raw(
"tx",
"staking",
"unbond",
to_addr,
amount,
"-y",
home=self.data_dir,
from_=from_addr,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
)
# to_validator_addr: crocncl1... , from_from_validator_addraddr: crocl1...
def redelegate_amount(
self, to_validator_addr, from_validator_addr, amount, from_addr
):
return json.loads(
self.raw(
"tx",
"staking",
"redelegate",
from_validator_addr,
to_validator_addr,
amount,
"-y",
home=self.data_dir,
from_=from_addr,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
)
# from_delegator can be account name or address
def withdraw_all_rewards(self, from_delegator):
return json.loads(
self.raw(
"tx",
"distribution",
"withdraw-all-rewards",
"-y",
from_=from_delegator,
home=self.data_dir,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
)
def make_multisig(self, name, signer1, signer2):
self.raw(
"keys",
"add",
name,
multisig=f"{signer1},{signer2}",
multisig_threshold="2",
home=self.data_dir,
keyring_backend="test",
)
def sign_multisig_tx(self, tx_file, multi_addr, signer_name):
return json.loads(
self.raw(
"tx",
"sign",
tx_file,
from_=signer_name,
multisig=multi_addr,
home=self.data_dir,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
)
def sign_batch_multisig_tx(
self, tx_file, multi_addr, signer_name, account_number, sequence_number
):
r = self.raw(
"tx",
"sign-batch",
"--offline",
tx_file,
account_number=account_number,
sequence=sequence_number,
from_=signer_name,
multisig=multi_addr,
home=self.data_dir,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
return r.decode("utf-8")
def encode_signed_tx(self, signed_tx):
return self.raw(
"tx",
"encode",
signed_tx,
)
def sign_tx(self, tx_file, signer):
return json.loads(
self.raw(
"tx",
"sign",
tx_file,
from_=signer,
home=self.data_dir,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
)
def sign_tx_json(self, tx, signer, max_priority_price=None):
if max_priority_price is not None:
tx["body"]["extension_options"].append(
{
"@type": "/ethermint.types.v1.ExtensionOptionDynamicFeeTx",
"max_priority_price": str(max_priority_price),
}
)
with tempfile.NamedTemporaryFile("w") as fp:
json.dump(tx, fp)
fp.flush()
return self.sign_tx(fp.name, signer)
def combine_multisig_tx(self, tx_file, multi_name, signer1_file, signer2_file):
return json.loads(
self.raw(
"tx",
"multisign",
tx_file,
multi_name,
signer1_file,
signer2_file,
home=self.data_dir,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
)
def combine_batch_multisig_tx(
self, tx_file, multi_name, signer1_file, signer2_file
):
r = self.raw(
"tx",
"multisign-batch",
tx_file,
multi_name,
signer1_file,
signer2_file,
home=self.data_dir,
keyring_backend="test",
chain_id=self.chain_id,
node=self.node_rpc,
)
return r.decode("utf-8")
def broadcast_tx(self, tx_file, **kwargs):
kwargs.setdefault("broadcast_mode", "block")
kwargs.setdefault("output", "json")
return json.loads(
self.raw("tx", "broadcast", tx_file, node=self.node_rpc, **kwargs)
)
def broadcast_tx_json(self, tx, **kwargs):
with tempfile.NamedTemporaryFile("w") as fp:
json.dump(tx, fp)
fp.flush()
return self.broadcast_tx(fp.name, **kwargs)
def unjail(self, addr):
return json.loads(
self.raw(
"tx",
"slashing",
"unjail",
"-y",
from_=addr,
home=self.data_dir,
node=self.node_rpc,
keyring_backend="test",
chain_id=self.chain_id,
)
)
def create_validator(
self,
amount,
moniker=None,
commission_max_change_rate="0.01",
commission_rate="0.1",
commission_max_rate="0.2",
min_self_delegation="1",
identity="",
website="",
security_contact="",
details="",
):
"""MsgCreateValidator
create the node with create_node before call this"""
pubkey = (
"'"
+ (
self.raw(
"tendermint",
"show-validator",
home=self.data_dir,
)
.strip()
.decode()
)
+ "'"
)
return json.loads(
self.raw(
"tx",
"staking",
"create-validator",
"-y",
from_=self.address("validator"),
amount=amount,
pubkey=pubkey,
min_self_delegation=min_self_delegation,
# commision
commission_rate=commission_rate,
commission_max_rate=commission_max_rate,
commission_max_change_rate=commission_max_change_rate,
# description
moniker=moniker,
identity=identity,
website=website,
security_contact=security_contact,
details=details,
# basic
home=self.data_dir,
node=self.node_rpc,
keyring_backend="test",
chain_id=self.chain_id,
)
)
def edit_validator(
self,
commission_rate=None,
moniker=None,
identity=None,
website=None,
security_contact=None,
details=None,
):
"""MsgEditValidator"""
options = dict(
commission_rate=commission_rate,
# description
moniker=moniker,
identity=identity,
website=website,
security_contact=security_contact,
details=details,
)
return json.loads(
self.raw(
"tx",
"staking",
"edit-validator",
"-y",
from_=self.address("validator"),
home=self.data_dir,
node=self.node_rpc,
keyring_backend="test",
chain_id=self.chain_id,
**{k: v for k, v in options.items() if v is not None},
)
)
def gov_propose(self, proposer, kind, proposal, **kwargs):
kwargs.setdefault("gas_prices", DEFAULT_GAS_PRICE)
if kind == "software-upgrade":
return json.loads(
self.raw(
"tx",
"gov",
"submit-proposal",
kind,
proposal["name"],
"-y",
from_=proposer,
# content
title=proposal.get("title"),
description=proposal.get("description"),
upgrade_height=proposal.get("upgrade-height"),
upgrade_time=proposal.get("upgrade-time"),
upgrade_info=proposal.get("upgrade-info"),
deposit=proposal.get("deposit"),
# basic
home=self.data_dir,
**kwargs,
)
)
elif kind == "cancel-software-upgrade":
return json.loads(
self.raw(
"tx",
"gov",
"submit-proposal",
kind,
"-y",
from_=proposer,
# content
title=proposal.get("title"),
description=proposal.get("description"),
deposit=proposal.get("deposit"),
# basic
home=self.data_dir,
**kwargs,
)
)
else:
with tempfile.NamedTemporaryFile("w") as fp:
json.dump(proposal, fp)
fp.flush()
return json.loads(
self.raw(
"tx",
"gov",
"submit-proposal",
kind,
fp.name,
"-y",
from_=proposer,
# basic
home=self.data_dir,
**kwargs,
)
)
def gov_vote(self, voter, proposal_id, option, **kwargs):
kwargs.setdefault("gas_prices", DEFAULT_GAS_PRICE)
return json.loads(
self.raw(
"tx",
"gov",
"vote",
proposal_id,
option,
"-y",
from_=voter,
home=self.data_dir,
**kwargs,
)
)
def gov_deposit(self, depositor, proposal_id, amount):
return json.loads(
self.raw(
"tx",
"gov",
"deposit",
proposal_id,
amount,
"-y",
from_=depositor,
home=self.data_dir,
node=self.node_rpc,
keyring_backend="test",
chain_id=self.chain_id,
)
)
def query_proposals(self, depositor=None, limit=None, status=None, voter=None):
return json.loads(
self.raw(
"query",
"gov",
"proposals",
depositor=depositor,
count_total=limit,
status=status,
voter=voter,
output="json",
node=self.node_rpc,
)
)
def query_proposal(self, proposal_id):
return json.loads(
self.raw(
"query",
"gov",
"proposal",
proposal_id,
output="json",
node=self.node_rpc,
)
)
def query_tally(self, proposal_id):
return json.loads(
self.raw(
"query",
"gov",
"tally",
proposal_id,
output="json",
node=self.node_rpc,
)
)
def ibc_transfer(
self,
from_,
to,
amount,
channel, # src channel
target_version, # chain version number of target chain
i=0,
):
return json.loads(
self.raw(
"tx",
"ibc-transfer",
"transfer",
"transfer", # src port
channel,
to,
amount,
"-y",
# FIXME https://github.com/cosmos/cosmos-sdk/issues/8059
"--absolute-timeouts",
from_=from_,
home=self.data_dir,
node=self.node_rpc,
keyring_backend="test",
chain_id=self.chain_id,
packet_timeout_height=f"{target_version}-10000000000",
packet_timeout_timestamp=0,
)
)
def export(self):
return self.raw("export", home=self.data_dir)
def unsaferesetall(self):
return self.raw("unsafe-reset-all")
def build_evm_tx(self, raw_tx: str, **kwargs):
return json.loads(
self.raw(
"tx",
"evm",
"raw",
raw_tx,
"-y",
"--generate-only",
home=self.data_dir,
**kwargs,
)
)
def query_base_fee(self, **kwargs):
default_kwargs = {"home": self.data_dir}
return int(
json.loads(
self.raw(
"q",
"feemarket",
"base-fee",
**(default_kwargs | kwargs),
)
)["base_fee"]
)

View File

@ -8,6 +8,7 @@ import web3
from pystarport import ports from pystarport import ports
from web3.middleware import geth_poa_middleware from web3.middleware import geth_poa_middleware
from .cosmoscli import CosmosCLI
from .utils import wait_for_port from .utils import wait_for_port
@ -53,6 +54,9 @@ class Ethermint:
self._w3 = None self._w3 = None
self._use_websockets = use self._use_websockets = use
def cosmos_cli(self, i=0):
return CosmosCLI(self.base_dir / f"node{i}", self.node_rpc(i), "ethermintd")
class Geth: class Geth:
def __init__(self, w3): def __init__(self, w3):

View File

@ -1,5 +1,7 @@
import sys
from .network import Ethermint from .network import Ethermint
from .utils import KEYS, sign_transaction from .utils import ADDRS, KEYS, eth_to_bech32, sign_transaction, wait_for_new_blocks
PRIORITY_REDUCTION = 1000000 PRIORITY_REDUCTION = 1000000
@ -101,14 +103,95 @@ def test_priority(ethermint: Ethermint):
# the later txs should be included earlier because of higher priority # the later txs should be included earlier because of higher priority
# FIXME there's some non-deterministics due to mempool logic # FIXME there's some non-deterministics due to mempool logic
assert all(included_earlier(r2, r1) for r1, r2 in zip(receipts, receipts[1:])) tx_indexes = [(r.blockNumber, r.transactionIndex) for r in receipts]
print(tx_indexes)
# the first sent tx are included later, because of lower priority
assert all(i1 > i2 for i1, i2 in zip(tx_indexes, tx_indexes[1:]))
def included_earlier(receipt1, receipt2): def included_earlier(receipt1, receipt2):
"returns true if receipt1 is earlier than receipt2" "returns true if receipt1 is included earlier than receipt2"
if receipt1.blockNumber < receipt2.blockNumber: return (receipt1.blockNumber, receipt1.transactionIndex) < (
return True receipt2.blockNumber,
elif receipt1.blockNumber == receipt2.blockNumber: receipt2.transactionIndex,
return receipt1.transactionIndex < receipt2.transactionIndex )
else:
return False
def test_native_tx_priority(ethermint: Ethermint):
cli = ethermint.cosmos_cli()
base_fee = cli.query_base_fee()
print("base_fee", base_fee)
test_cases = [
{
"from": eth_to_bech32(ADDRS["community"]),
"to": eth_to_bech32(ADDRS["validator"]),
"amount": "1000aphoton",
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 6}aphoton",
"max_priority_price": 0,
},
{
"from": eth_to_bech32(ADDRS["signer1"]),
"to": eth_to_bech32(ADDRS["signer2"]),
"amount": "1000aphoton",
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 6}aphoton",
"max_priority_price": PRIORITY_REDUCTION * 2,
},
{
"from": eth_to_bech32(ADDRS["signer2"]),
"to": eth_to_bech32(ADDRS["signer1"]),
"amount": "1000aphoton",
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 4}aphoton",
"max_priority_price": PRIORITY_REDUCTION * 4,
},
{
"from": eth_to_bech32(ADDRS["validator"]),
"to": eth_to_bech32(ADDRS["community"]),
"amount": "1000aphoton",
"gas_prices": f"{base_fee + PRIORITY_REDUCTION * 6}aphoton",
"max_priority_price": None, # no extension, maximum tipFeeCap
},
]
txs = []
expect_priorities = []
for tc in test_cases:
tx = cli.transfer(
tc["from"],
tc["to"],
tc["amount"],
gas_prices=tc["gas_prices"],
generate_only=True,
)
txs.append(
cli.sign_tx_json(
tx, tc["from"], max_priority_price=tc.get("max_priority_price")
)
)
gas_price = int(tc["gas_prices"].removesuffix("aphoton"))
expect_priorities.append(
min(
get_max_priority_price(tc.get("max_priority_price")),
gas_price - base_fee,
)
// PRIORITY_REDUCTION
)
assert expect_priorities == [0, 2, 4, 6]
txhashes = []
for tx in txs:
rsp = cli.broadcast_tx_json(tx, broadcast_mode="sync")
assert rsp["code"] == 0, rsp["raw_log"]
txhashes.append(rsp["txhash"])
print("wait for two new blocks, so the sent txs are all included")
wait_for_new_blocks(cli, 2)
tx_results = [cli.tx_search_rpc(f"tx.hash='{txhash}'")[0] for txhash in txhashes]
tx_indexes = [(int(r["height"]), r["index"]) for r in tx_results]
print(tx_indexes)
# the first sent tx are included later, because of lower priority
assert all(i1 > i2 for i1, i2 in zip(tx_indexes, tx_indexes[1:]))
def get_max_priority_price(max_priority_price):
"default to max int64 if None"
return max_priority_price if max_priority_price is not None else sys.maxsize

View File

@ -4,8 +4,10 @@ import socket
import time import time
from pathlib import Path from pathlib import Path
import bech32
from dotenv import load_dotenv from dotenv import load_dotenv
from eth_account import Account from eth_account import Account
from hexbytes import HexBytes
from web3._utils.transactions import fill_nonce, fill_transaction_defaults from web3._utils.transactions import fill_nonce, fill_transaction_defaults
load_dotenv(Path(__file__).parent.parent.parent / "scripts/.env") load_dotenv(Path(__file__).parent.parent.parent / "scripts/.env")
@ -64,6 +66,15 @@ def w3_wait_for_new_blocks(w3, n):
break break
def wait_for_new_blocks(cli, n):
begin_height = int((cli.status())["SyncInfo"]["latest_block_height"])
while True:
time.sleep(0.5)
cur_height = int((cli.status())["SyncInfo"]["latest_block_height"])
if cur_height - begin_height >= n:
break
def deploy_contract(w3, jsonfile, args=(), key=KEYS["validator"]): def deploy_contract(w3, jsonfile, args=(), key=KEYS["validator"]):
""" """
deploy contract and return the deployed contract instance deploy contract and return the deployed contract instance
@ -95,3 +106,8 @@ def send_transaction(w3, tx, key=KEYS["validator"]):
signed = sign_transaction(w3, tx, key) signed = sign_transaction(w3, tx, key)
txhash = w3.eth.send_raw_transaction(signed.rawTransaction) txhash = w3.eth.send_raw_transaction(signed.rawTransaction)
return w3.eth.wait_for_transaction_receipt(txhash) return w3.eth.wait_for_transaction_receipt(txhash)
def eth_to_bech32(addr, prefix=ETHERMINT_ADDRESS_PREFIX):
bz = bech32.convertbits(HexBytes(addr), 8, 5)
return bech32.bech32_encode(prefix, bz)

View File

@ -20,5 +20,6 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) {
registry.RegisterImplementations( registry.RegisterImplementations(
(*tx.TxExtensionOptionI)(nil), (*tx.TxExtensionOptionI)(nil),
&ExtensionOptionsWeb3Tx{}, &ExtensionOptionsWeb3Tx{},
&ExtensionOptionDynamicFeeTx{},
) )
} }

11
types/dynamic_fee.go Normal file
View File

@ -0,0 +1,11 @@
package types
import (
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
)
// HasDynamicFeeExtensionOption returns true if the tx implements the `ExtensionOptionDynamicFeeTx` extension option.
func HasDynamicFeeExtensionOption(any *codectypes.Any) bool {
_, ok := any.GetCachedValue().(*ExtensionOptionDynamicFeeTx)
return ok
}

321
types/dynamic_fee.pb.go generated Normal file
View File

@ -0,0 +1,321 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: ethermint/types/v1/dynamic_fee.proto
package types
import (
fmt "fmt"
github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types"
_ "github.com/gogo/protobuf/gogoproto"
proto "github.com/gogo/protobuf/proto"
io "io"
math "math"
math_bits "math/bits"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
// ExtensionOptionDynamicFeeTx is an extension option that specify the maxPrioPrice for cosmos tx
type ExtensionOptionDynamicFeeTx struct {
// the same as `max_priority_fee_per_gas` in eip-1559 spec
MaxPriorityPrice github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,1,opt,name=max_priority_price,json=maxPriorityPrice,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"max_priority_price"`
}
func (m *ExtensionOptionDynamicFeeTx) Reset() { *m = ExtensionOptionDynamicFeeTx{} }
func (m *ExtensionOptionDynamicFeeTx) String() string { return proto.CompactTextString(m) }
func (*ExtensionOptionDynamicFeeTx) ProtoMessage() {}
func (*ExtensionOptionDynamicFeeTx) Descriptor() ([]byte, []int) {
return fileDescriptor_9d7cf05c9992c480, []int{0}
}
func (m *ExtensionOptionDynamicFeeTx) XXX_Unmarshal(b []byte) error {
return m.Unmarshal(b)
}
func (m *ExtensionOptionDynamicFeeTx) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
if deterministic {
return xxx_messageInfo_ExtensionOptionDynamicFeeTx.Marshal(b, m, deterministic)
} else {
b = b[:cap(b)]
n, err := m.MarshalToSizedBuffer(b)
if err != nil {
return nil, err
}
return b[:n], nil
}
}
func (m *ExtensionOptionDynamicFeeTx) XXX_Merge(src proto.Message) {
xxx_messageInfo_ExtensionOptionDynamicFeeTx.Merge(m, src)
}
func (m *ExtensionOptionDynamicFeeTx) XXX_Size() int {
return m.Size()
}
func (m *ExtensionOptionDynamicFeeTx) XXX_DiscardUnknown() {
xxx_messageInfo_ExtensionOptionDynamicFeeTx.DiscardUnknown(m)
}
var xxx_messageInfo_ExtensionOptionDynamicFeeTx proto.InternalMessageInfo
func init() {
proto.RegisterType((*ExtensionOptionDynamicFeeTx)(nil), "ethermint.types.v1.ExtensionOptionDynamicFeeTx")
}
func init() {
proto.RegisterFile("ethermint/types/v1/dynamic_fee.proto", fileDescriptor_9d7cf05c9992c480)
}
var fileDescriptor_9d7cf05c9992c480 = []byte{
// 232 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x49, 0x2d, 0xc9, 0x48,
0x2d, 0xca, 0xcd, 0xcc, 0x2b, 0xd1, 0x2f, 0xa9, 0x2c, 0x48, 0x2d, 0xd6, 0x2f, 0x33, 0xd4, 0x4f,
0xa9, 0xcc, 0x4b, 0xcc, 0xcd, 0x4c, 0x8e, 0x4f, 0x4b, 0x4d, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9,
0x17, 0x12, 0x82, 0xab, 0xd2, 0x03, 0xab, 0xd2, 0x2b, 0x33, 0x94, 0x12, 0x49, 0xcf, 0x4f, 0xcf,
0x07, 0x4b, 0xeb, 0x83, 0x58, 0x10, 0x95, 0x4a, 0xd5, 0x5c, 0xd2, 0xae, 0x15, 0x25, 0xa9, 0x79,
0xc5, 0x99, 0xf9, 0x79, 0xfe, 0x05, 0x25, 0x99, 0xf9, 0x79, 0x2e, 0x10, 0xd3, 0xdc, 0x52, 0x53,
0x43, 0x2a, 0x84, 0x62, 0xb8, 0x84, 0x72, 0x13, 0x2b, 0xe2, 0x0b, 0x8a, 0x32, 0xf3, 0x8b, 0x32,
0x4b, 0x2a, 0x41, 0x8c, 0xe4, 0x54, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x4e, 0x27, 0xbd, 0x13, 0xf7,
0xe4, 0x19, 0x6e, 0xdd, 0x93, 0x57, 0x4b, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf,
0xd5, 0x4f, 0xce, 0x2f, 0xce, 0xcd, 0x2f, 0x86, 0x52, 0xba, 0xc5, 0x29, 0xd9, 0x10, 0x57, 0xea,
0x79, 0xe6, 0x95, 0x04, 0x09, 0xe4, 0x26, 0x56, 0x04, 0x40, 0x0d, 0x0a, 0x00, 0x99, 0xe3, 0x64,
0x75, 0xe2, 0x91, 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c,
0xc7, 0x70, 0xe1, 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, 0x0c, 0x51, 0x0a, 0x48, 0x66, 0xa6, 0x96,
0x81, 0x8c, 0x44, 0xf3, 0x77, 0x12, 0x1b, 0xd8, 0xfd, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff,
0x5c, 0xa9, 0x04, 0x48, 0x11, 0x01, 0x00, 0x00,
}
func (m *ExtensionOptionDynamicFeeTx) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalToSizedBuffer(dAtA[:size])
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *ExtensionOptionDynamicFeeTx) MarshalTo(dAtA []byte) (int, error) {
size := m.Size()
return m.MarshalToSizedBuffer(dAtA[:size])
}
func (m *ExtensionOptionDynamicFeeTx) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i := len(dAtA)
_ = i
var l int
_ = l
{
size := m.MaxPriorityPrice.Size()
i -= size
if _, err := m.MaxPriorityPrice.MarshalTo(dAtA[i:]); err != nil {
return 0, err
}
i = encodeVarintDynamicFee(dAtA, i, uint64(size))
}
i--
dAtA[i] = 0xa
return len(dAtA) - i, nil
}
func encodeVarintDynamicFee(dAtA []byte, offset int, v uint64) int {
offset -= sovDynamicFee(v)
base := offset
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return base
}
func (m *ExtensionOptionDynamicFeeTx) Size() (n int) {
if m == nil {
return 0
}
var l int
_ = l
l = m.MaxPriorityPrice.Size()
n += 1 + l + sovDynamicFee(uint64(l))
return n
}
func sovDynamicFee(x uint64) (n int) {
return (math_bits.Len64(x|1) + 6) / 7
}
func sozDynamicFee(x uint64) (n int) {
return sovDynamicFee(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *ExtensionOptionDynamicFeeTx) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDynamicFee
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: ExtensionOptionDynamicFeeTx: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: ExtensionOptionDynamicFeeTx: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field MaxPriorityPrice", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDynamicFee
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= uint64(b&0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthDynamicFee
}
postIndex := iNdEx + intStringLen
if postIndex < 0 {
return ErrInvalidLengthDynamicFee
}
if postIndex > l {
return io.ErrUnexpectedEOF
}
if err := m.MaxPriorityPrice.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipDynamicFee(dAtA[iNdEx:])
if err != nil {
return err
}
if (skippy < 0) || (iNdEx+skippy) < 0 {
return ErrInvalidLengthDynamicFee
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipDynamicFee(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
depth := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowDynamicFee
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowDynamicFee
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
case 1:
iNdEx += 8
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowDynamicFee
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if length < 0 {
return 0, ErrInvalidLengthDynamicFee
}
iNdEx += length
case 3:
depth++
case 4:
if depth == 0 {
return 0, ErrUnexpectedEndOfGroupDynamicFee
}
depth--
case 5:
iNdEx += 4
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
if iNdEx < 0 {
return 0, ErrInvalidLengthDynamicFee
}
if depth == 0 {
return iNdEx, nil
}
}
return 0, io.ErrUnexpectedEOF
}
var (
ErrInvalidLengthDynamicFee = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowDynamicFee = fmt.Errorf("proto: integer overflow")
ErrUnexpectedEndOfGroupDynamicFee = fmt.Errorf("proto: unexpected end of group")
)

View File

@ -15,11 +15,6 @@ import (
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
) )
// DefaultPriorityReduction is the default amount of price values required for 1 unit of priority.
// Because priority is `int64` while price is `big.Int`, it's necessary to scale down the range to keep it more pratical.
// The default value is the same as the `sdk.DefaultPowerReduction`.
var DefaultPriorityReduction = sdk.DefaultPowerReduction
// DeductTxCostsFromUserBalance it calculates the tx costs and deducts the fees // DeductTxCostsFromUserBalance it calculates the tx costs and deducts the fees
// returns (effectiveFee, priority, error) // returns (effectiveFee, priority, error)
func (k Keeper) DeductTxCostsFromUserBalance( func (k Keeper) DeductTxCostsFromUserBalance(
@ -91,7 +86,7 @@ func (k Keeper) DeductTxCostsFromUserBalance(
if baseFee != nil { if baseFee != nil {
tipPrice = new(big.Int).Sub(tipPrice, baseFee) tipPrice = new(big.Int).Sub(tipPrice, baseFee)
} }
priorityBig := new(big.Int).Quo(tipPrice, DefaultPriorityReduction.BigInt()) priorityBig := new(big.Int).Quo(tipPrice, evmtypes.DefaultPriorityReduction.BigInt())
if !priorityBig.IsInt64() { if !priorityBig.IsInt64() {
priority = math.MaxInt64 priority = math.MaxInt64
} else { } else {

View File

@ -7,7 +7,6 @@ import (
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
ethtypes "github.com/ethereum/go-ethereum/core/types" ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/evmos/ethermint/types" "github.com/evmos/ethermint/types"
@ -267,7 +266,7 @@ func (tx DynamicFeeTx) Cost() *big.Int {
// EffectiveGasPrice returns the effective gas price // EffectiveGasPrice returns the effective gas price
func (tx *DynamicFeeTx) EffectiveGasPrice(baseFee *big.Int) *big.Int { func (tx *DynamicFeeTx) EffectiveGasPrice(baseFee *big.Int) *big.Int {
return math.BigMin(new(big.Int).Add(tx.GasTipCap.BigInt(), baseFee), tx.GasFeeCap.BigInt()) return EffectiveGasPrice(baseFee, tx.GasFeeCap.BigInt(), tx.GasTipCap.BigInt())
} }
// EffectiveFee returns effective_gasprice * gaslimit. // EffectiveFee returns effective_gasprice * gaslimit.

2
x/evm/types/evm.pb.go generated
View File

@ -117,7 +117,7 @@ func (m *Params) GetAllowUnprotectedTxs() bool {
return false return false
} }
// ChainConfig defines the Ethereum ChainConfig parameters using *sdkmath.Int values // ChainConfig defines the Ethereum ChainConfig parameters using *sdk.Int values
// instead of *big.Int. // instead of *big.Int.
type ChainConfig struct { type ChainConfig struct {
// Homestead switch block (nil no fork, 0 = already homestead) // Homestead switch block (nil no fork, 0 = already homestead)

View File

@ -2,6 +2,7 @@ package types
import ( import (
"fmt" "fmt"
"math/big"
"github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/proto"
@ -9,9 +10,15 @@ import (
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
) )
// DefaultPriorityReduction is the default amount of price values required for 1 unit of priority.
// Because priority is `int64` while price is `big.Int`, it's necessary to scale down the range to keep it more pratical.
// The default value is the same as the `sdk.DefaultPowerReduction`.
var DefaultPriorityReduction = sdk.DefaultPowerReduction
var EmptyCodeHash = crypto.Keccak256(nil) var EmptyCodeHash = crypto.Keccak256(nil)
// DecodeTxResponse decodes an protobuf-encoded byte slice into TxResponse // DecodeTxResponse decodes an protobuf-encoded byte slice into TxResponse
@ -88,3 +95,9 @@ func BinSearch(lo, hi uint64, executable func(uint64) (bool, *MsgEthereumTxRespo
} }
return hi, nil return hi, nil
} }
// EffectiveGasPrice compute the effective gas price based on eip-1159 rules
// `effectiveGasPrice = min(baseFee + tipCap, feeCap)`
func EffectiveGasPrice(baseFee *big.Int, feeCap *big.Int, tipCap *big.Int) *big.Int {
return math.BigMin(new(big.Int).Add(tipCap, baseFee), feeCap)
}

View File

@ -126,7 +126,7 @@ var _ = Describe("Feemarket", func() {
Context("with MinGasPrices (feemarket param) < min-gas-prices (local)", func() { Context("with MinGasPrices (feemarket param) < min-gas-prices (local)", func() {
BeforeEach(func() { BeforeEach(func() {
privKey, msg = setupTestWithContext("5", sdk.NewDec(3), sdk.ZeroInt()) privKey, msg = setupTestWithContext("5", sdk.NewDec(3), sdk.NewInt(5))
}) })
Context("during CheckTx", func() { Context("during CheckTx", func() {
It("should reject transactions with gasPrice < MinGasPrices", func() { It("should reject transactions with gasPrice < MinGasPrices", func() {
@ -139,7 +139,7 @@ var _ = Describe("Feemarket", func() {
).To(BeTrue(), res.GetLog()) ).To(BeTrue(), res.GetLog())
}) })
It("should reject transactions with MinGasPrices < gasPrice < min-gas-prices", func() { It("should reject transactions with MinGasPrices < gasPrice < baseFee", func() {
gasPrice := sdkmath.NewInt(4) gasPrice := sdkmath.NewInt(4)
res := checkTx(privKey, &gasPrice, &msg) res := checkTx(privKey, &gasPrice, &msg)
Expect(res.IsOK()).To(Equal(false), "transaction should have failed") Expect(res.IsOK()).To(Equal(false), "transaction should have failed")
@ -149,7 +149,7 @@ var _ = Describe("Feemarket", func() {
).To(BeTrue(), res.GetLog()) ).To(BeTrue(), res.GetLog())
}) })
It("should accept transactions with gasPrice > min-gas-prices", func() { It("should accept transactions with gasPrice >= baseFee", func() {
gasPrice := sdkmath.NewInt(5) gasPrice := sdkmath.NewInt(5)
res := checkTx(privKey, &gasPrice, &msg) res := checkTx(privKey, &gasPrice, &msg)
Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog())
@ -167,13 +167,16 @@ var _ = Describe("Feemarket", func() {
).To(BeTrue(), res.GetLog()) ).To(BeTrue(), res.GetLog())
}) })
It("should accept transactions with MinGasPrices < gasPrice < than min-gas-prices", func() { It("should reject transactions with MinGasPrices < gasPrice < baseFee", func() {
gasPrice := sdkmath.NewInt(4) gasPrice := sdkmath.NewInt(4)
res := deliverTx(privKey, &gasPrice, &msg) res := checkTx(privKey, &gasPrice, &msg)
Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) Expect(res.IsOK()).To(Equal(false), "transaction should have failed")
Expect(
strings.Contains(res.GetLog(),
"insufficient fee"),
).To(BeTrue(), res.GetLog())
}) })
It("should accept transactions with gasPrice >= baseFee", func() {
It("should accept transactions with gasPrice >= min-gas-prices", func() {
gasPrice := sdkmath.NewInt(5) gasPrice := sdkmath.NewInt(5)
res := deliverTx(privKey, &gasPrice, &msg) res := deliverTx(privKey, &gasPrice, &msg)
Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog()) Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog())