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:
parent
42abb259cb
commit
b1cd16e5bf
@ -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,
|
||||
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.
|
||||
* (feemarket) [#1194](https://github.com/evmos/ethermint/pull/1194) Apply feemarket to native cosmos tx.
|
||||
|
||||
### API Breaking
|
||||
|
||||
|
@ -27,6 +27,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
|
||||
if err := options.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return func(
|
||||
ctx sdk.Context, tx sdk.Tx, sim bool,
|
||||
) (newCtx sdk.Context, err error) {
|
||||
@ -45,6 +46,9 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
|
||||
case "/ethermint.types.v1.ExtensionOptionsWeb3Tx":
|
||||
// handle as normal Cosmos SDK tx, except signature is checked for EIP712 representation
|
||||
anteHandler = newCosmosAnteHandlerEip712(options)
|
||||
case "/ethermint.types.v1.ExtensionOptionDynamicFeeTx":
|
||||
// cosmos-sdk tx with dynamic fee extension
|
||||
anteHandler = newCosmosAnteHandler(options)
|
||||
default:
|
||||
return ctx, sdkerrors.Wrapf(
|
||||
sdkerrors.ErrUnknownExtensionOptions,
|
||||
|
@ -305,8 +305,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
|
||||
"success - DeliverTx EIP712 signed Cosmos Tx with MsgSend",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
|
||||
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)
|
||||
return txBuilder.GetTx()
|
||||
}, false, false, true,
|
||||
@ -315,9 +315,9 @@ func (suite AnteTestSuite) TestAnteHandler() {
|
||||
"success - DeliverTx EIP712 signed Cosmos Tx with DelegateMsg",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
coinAmount := sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20))
|
||||
amount := sdk.NewCoins(coinAmount)
|
||||
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)
|
||||
return txBuilder.GetTx()
|
||||
}, false, false, true,
|
||||
@ -326,8 +326,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
|
||||
"fails - DeliverTx EIP712 signed Cosmos Tx with wrong Chain ID",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
|
||||
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)
|
||||
return txBuilder.GetTx()
|
||||
}, false, false, false,
|
||||
@ -336,8 +336,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
|
||||
"fails - DeliverTx EIP712 signed Cosmos Tx with different gas fees",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
|
||||
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.SetGasLimit(uint64(300000))
|
||||
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",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
|
||||
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)
|
||||
sigsV2 := signing.SignatureV2{}
|
||||
txBuilder.SetSignatures(sigsV2)
|
||||
@ -360,8 +360,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
|
||||
"fails - DeliverTx EIP712 signed Cosmos Tx with invalid sequence",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
|
||||
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)
|
||||
nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress())
|
||||
suite.Require().NoError(err)
|
||||
@ -380,8 +380,8 @@ func (suite AnteTestSuite) TestAnteHandler() {
|
||||
"fails - DeliverTx EIP712 signed Cosmos Tx with invalid signMode",
|
||||
func() sdk.Tx {
|
||||
from := acc.GetAddress()
|
||||
amount := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(20)))
|
||||
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)
|
||||
nonce, err := suite.app.AccountKeeper.GetSequence(suite.ctx, acc.GetAddress())
|
||||
suite.Require().NoError(err)
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"github.com/evmos/ethermint/app/ante"
|
||||
"github.com/evmos/ethermint/server/config"
|
||||
"github.com/evmos/ethermint/tests"
|
||||
evmkeeper "github.com/evmos/ethermint/x/evm/keeper"
|
||||
"github.com/evmos/ethermint/x/evm/statedb"
|
||||
evmtypes "github.com/evmos/ethermint/x/evm/types"
|
||||
|
||||
@ -227,7 +226,7 @@ func (suite AnteTestSuite) TestEthGasConsumeDecorator() {
|
||||
baseFee := suite.app.EvmKeeper.GetBaseFee(suite.ctx, ethCfg)
|
||||
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)
|
||||
tx2 := evmtypes.NewTxContract(suite.app.EvmKeeper.ChainID(), 1, big.NewInt(10), tx2GasLimit, gasPrice, nil, nil, nil, ðtypes.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,
|
||||
nil, // gasPrice
|
||||
new(big.Int).Add(baseFee, big.NewInt(evmkeeper.DefaultPriorityReduction.Int64()*2)), // gasFeeCap
|
||||
evmkeeper.DefaultPriorityReduction.BigInt(), // gasTipCap
|
||||
new(big.Int).Add(baseFee, big.NewInt(evmtypes.DefaultPriorityReduction.Int64()*2)), // gasFeeCap
|
||||
evmtypes.DefaultPriorityReduction.BigInt(), // gasTipCap
|
||||
nil, ðtypes.AccessList{{Address: addr, StorageKeys: nil}})
|
||||
dynamicFeeTx.From = addr.Hex()
|
||||
dynamicFeeTxPriority := int64(1)
|
||||
|
142
app/ante/fee_checker.go
Normal file
142
app/ante/fee_checker.go
Normal 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
|
||||
}
|
219
app/ante/fee_checker_test.go
Normal file
219
app/ante/fee_checker_test.go
Normal 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(ðermint.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(ðermint.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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -14,17 +14,22 @@ import (
|
||||
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
|
||||
type EVMKeeper interface {
|
||||
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
|
||||
DeductTxCostsFromUserBalance(
|
||||
ctx sdk.Context, msgEthTx evmtypes.MsgEthereumTx, txData evmtypes.TxData, denom string, homestead, istanbul, london bool,
|
||||
) (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
|
||||
ResetTransientGasUsed(ctx sdk.Context)
|
||||
GetTxIndexTransient(ctx sdk.Context) uint64
|
||||
|
20
app/app.go
20
app/app.go
@ -600,15 +600,17 @@ func NewEthermintApp(
|
||||
// use Ethermint's custom AnteHandler
|
||||
func (app *EthermintApp) setAnteHandler(txConfig client.TxConfig, maxGasWanted uint64) {
|
||||
anteHandler, err := ante.NewAnteHandler(ante.HandlerOptions{
|
||||
AccountKeeper: app.AccountKeeper,
|
||||
BankKeeper: app.BankKeeper,
|
||||
SignModeHandler: txConfig.SignModeHandler(),
|
||||
FeegrantKeeper: app.FeeGrantKeeper,
|
||||
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
|
||||
IBCKeeper: app.IBCKeeper,
|
||||
EvmKeeper: app.EvmKeeper,
|
||||
FeeMarketKeeper: app.FeeMarketKeeper,
|
||||
MaxTxGasWanted: maxGasWanted,
|
||||
AccountKeeper: app.AccountKeeper,
|
||||
BankKeeper: app.BankKeeper,
|
||||
SignModeHandler: txConfig.SignModeHandler(),
|
||||
FeegrantKeeper: app.FeeGrantKeeper,
|
||||
SigGasConsumer: ante.DefaultSigVerificationGasConsumer,
|
||||
IBCKeeper: app.IBCKeeper,
|
||||
EvmKeeper: app.EvmKeeper,
|
||||
FeeMarketKeeper: app.FeeMarketKeeper,
|
||||
MaxTxGasWanted: maxGasWanted,
|
||||
ExtensionOptionChecker: ethermint.HasDynamicFeeExtensionOption,
|
||||
TxFeeChecker: ante.NewDynamicFeeChecker(app.EvmKeeper),
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -32,6 +32,7 @@ import (
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
ibctransfertypes "github.com/cosmos/ibc-go/v5/modules/apps/transfer/types"
|
||||
ibchost "github.com/cosmos/ibc-go/v5/modules/core/24-host"
|
||||
"github.com/evmos/ethermint/app/ante"
|
||||
evmenc "github.com/evmos/ethermint/encoding"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
@ -62,6 +63,32 @@ func fauxMerkleModeOpt(bapp *baseapp.BaseApp) {
|
||||
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
|
||||
// inter-block write-through cache.
|
||||
func interBlockCacheOpt() func(*baseapp.BaseApp) {
|
||||
@ -82,8 +109,9 @@ func TestFullAppSimulation(t *testing.T) {
|
||||
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.NoError(t, err)
|
||||
|
||||
// run randomized simulation
|
||||
_, simParams, simErr := simulation.SimulateFromSeed(
|
||||
@ -121,8 +149,8 @@ func TestAppImportExport(t *testing.T) {
|
||||
require.NoError(t, db.Close())
|
||||
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.NoError(t, err)
|
||||
require.Equal(t, appName, app.Name())
|
||||
|
||||
// Run randomized simulation
|
||||
@ -163,8 +191,9 @@ func TestAppImportExport(t *testing.T) {
|
||||
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.NoError(t, err)
|
||||
|
||||
var genesisState simapp.GenesisState
|
||||
err = json.Unmarshal(exported.AppState, &genesisState)
|
||||
@ -236,8 +265,9 @@ func TestAppSimulationAfterImport(t *testing.T) {
|
||||
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.NoError(t, err)
|
||||
|
||||
// Run randomized simulation
|
||||
stopEarly, simParams, simErr := simulation.SimulateFromSeed(
|
||||
@ -281,8 +311,9 @@ func TestAppSimulationAfterImport(t *testing.T) {
|
||||
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.NoError(t, err)
|
||||
|
||||
newApp.InitChain(abci.RequestInitChain{
|
||||
ChainId: SimAppChainID,
|
||||
@ -333,14 +364,15 @@ func TestAppStateDeterminism(t *testing.T) {
|
||||
}
|
||||
|
||||
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(
|
||||
"running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n",
|
||||
config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed,
|
||||
)
|
||||
|
||||
_, _, err := simulation.SimulateFromSeed(
|
||||
_, _, err = simulation.SimulateFromSeed(
|
||||
t,
|
||||
os.Stdout,
|
||||
app.BaseApp,
|
||||
|
@ -79,6 +79,9 @@
|
||||
- [ethermint/types/v1/account.proto](#ethermint/types/v1/account.proto)
|
||||
- [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)
|
||||
- [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 enums -->
|
||||
|
15
proto/ethermint/types/v1/dynamic_fee.proto
Normal file
15
proto/ethermint/types/v1/dynamic_fee.proto
Normal 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
|
||||
];
|
||||
}
|
@ -973,7 +973,6 @@ func (suite *BackendTestSuite) TestGetEthereumMsgsFromTendermintBlock() {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
suite.Run(fmt.Sprintf("Case %s", tc.name), func() {
|
||||
|
||||
suite.SetupTest() // reset test and queries
|
||||
|
||||
msgs := suite.backend.GetEthereumMsgsFromTendermintBlock(tc.resBlock, tc.blockRes)
|
||||
|
828
tests/integration_tests/cosmoscli.py
Normal file
828
tests/integration_tests/cosmoscli.py
Normal 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"]
|
||||
)
|
@ -8,6 +8,7 @@ import web3
|
||||
from pystarport import ports
|
||||
from web3.middleware import geth_poa_middleware
|
||||
|
||||
from .cosmoscli import CosmosCLI
|
||||
from .utils import wait_for_port
|
||||
|
||||
|
||||
@ -53,6 +54,9 @@ class Ethermint:
|
||||
self._w3 = None
|
||||
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:
|
||||
def __init__(self, w3):
|
||||
|
@ -1,5 +1,7 @@
|
||||
import sys
|
||||
|
||||
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
|
||||
|
||||
@ -101,14 +103,95 @@ def test_priority(ethermint: Ethermint):
|
||||
|
||||
# the later txs should be included earlier because of higher priority
|
||||
# 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):
|
||||
"returns true if receipt1 is earlier than receipt2"
|
||||
if receipt1.blockNumber < receipt2.blockNumber:
|
||||
return True
|
||||
elif receipt1.blockNumber == receipt2.blockNumber:
|
||||
return receipt1.transactionIndex < receipt2.transactionIndex
|
||||
else:
|
||||
return False
|
||||
"returns true if receipt1 is included earlier than receipt2"
|
||||
return (receipt1.blockNumber, receipt1.transactionIndex) < (
|
||||
receipt2.blockNumber,
|
||||
receipt2.transactionIndex,
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
|
@ -4,8 +4,10 @@ import socket
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import bech32
|
||||
from dotenv import load_dotenv
|
||||
from eth_account import Account
|
||||
from hexbytes import HexBytes
|
||||
from web3._utils.transactions import fill_nonce, fill_transaction_defaults
|
||||
|
||||
load_dotenv(Path(__file__).parent.parent.parent / "scripts/.env")
|
||||
@ -64,6 +66,15 @@ def w3_wait_for_new_blocks(w3, n):
|
||||
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"]):
|
||||
"""
|
||||
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)
|
||||
txhash = w3.eth.send_raw_transaction(signed.rawTransaction)
|
||||
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)
|
||||
|
@ -20,5 +20,6 @@ func RegisterInterfaces(registry codectypes.InterfaceRegistry) {
|
||||
registry.RegisterImplementations(
|
||||
(*tx.TxExtensionOptionI)(nil),
|
||||
&ExtensionOptionsWeb3Tx{},
|
||||
&ExtensionOptionDynamicFeeTx{},
|
||||
)
|
||||
}
|
||||
|
11
types/dynamic_fee.go
Normal file
11
types/dynamic_fee.go
Normal 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
321
types/dynamic_fee.pb.go
generated
Normal 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")
|
||||
)
|
@ -15,11 +15,6 @@ import (
|
||||
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
|
||||
// returns (effectiveFee, priority, error)
|
||||
func (k Keeper) DeductTxCostsFromUserBalance(
|
||||
@ -91,7 +86,7 @@ func (k Keeper) DeductTxCostsFromUserBalance(
|
||||
if baseFee != nil {
|
||||
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() {
|
||||
priority = math.MaxInt64
|
||||
} else {
|
||||
|
@ -7,7 +7,6 @@ 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/evmos/ethermint/types"
|
||||
@ -267,7 +266,7 @@ func (tx DynamicFeeTx) Cost() *big.Int {
|
||||
|
||||
// EffectiveGasPrice returns the effective gas price
|
||||
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.
|
||||
|
2
x/evm/types/evm.pb.go
generated
2
x/evm/types/evm.pb.go
generated
@ -117,7 +117,7 @@ func (m *Params) GetAllowUnprotectedTxs() bool {
|
||||
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.
|
||||
type ChainConfig struct {
|
||||
// Homestead switch block (nil no fork, 0 = already homestead)
|
||||
|
@ -2,6 +2,7 @@ package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
|
||||
@ -9,9 +10,15 @@ import (
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"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)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ var _ = Describe("Feemarket", func() {
|
||||
|
||||
Context("with MinGasPrices (feemarket param) < min-gas-prices (local)", 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() {
|
||||
It("should reject transactions with gasPrice < MinGasPrices", func() {
|
||||
@ -139,7 +139,7 @@ var _ = Describe("Feemarket", func() {
|
||||
).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)
|
||||
res := checkTx(privKey, &gasPrice, &msg)
|
||||
Expect(res.IsOK()).To(Equal(false), "transaction should have failed")
|
||||
@ -149,7 +149,7 @@ var _ = Describe("Feemarket", func() {
|
||||
).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)
|
||||
res := checkTx(privKey, &gasPrice, &msg)
|
||||
Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog())
|
||||
@ -167,13 +167,16 @@ var _ = Describe("Feemarket", func() {
|
||||
).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)
|
||||
res := deliverTx(privKey, &gasPrice, &msg)
|
||||
Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog())
|
||||
res := checkTx(privKey, &gasPrice, &msg)
|
||||
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 >= min-gas-prices", func() {
|
||||
It("should accept transactions with gasPrice >= baseFee", func() {
|
||||
gasPrice := sdkmath.NewInt(5)
|
||||
res := deliverTx(privKey, &gasPrice, &msg)
|
||||
Expect(res.IsOK()).To(Equal(true), "transaction should have succeeded", res.GetLog())
|
||||
|
Loading…
Reference in New Issue
Block a user