From b1beb8b2bac29e4c0502fd8040c0c0d897996e87 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Fri, 13 Sep 2024 02:37:09 +0700 Subject: [PATCH 01/13] remove unused test flag --- x/evm/handler_test.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go index 7d391e955b..954347e2d6 100644 --- a/x/evm/handler_test.go +++ b/x/evm/handler_test.go @@ -19,8 +19,6 @@ import ( authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -55,8 +53,6 @@ type EvmTestSuite struct { ethSigner ethtypes.Signer from common.Address to sdk.AccAddress - - dynamicTxFee bool } // DoSetupTest setup test environment @@ -75,10 +71,6 @@ func (suite *EvmTestSuite) DoSetupTest() { consAddress := sdk.ConsAddress(priv.PubKey().Address()) suite.app = helpers.EthSetup(checkTx, func(chainApp *chainapp.Evermint, genesis chainapp.GenesisState) chainapp.GenesisState { - if suite.dynamicTxFee { // TODO ES: check this option - feemarketGenesis := feemarkettypes.DefaultGenesisState() - genesis[feemarkettypes.ModuleName] = chainApp.AppCodec().MustMarshalJSON(feemarketGenesis) - } return genesis }) From b95e5e9e2fc2c19c0798727bb237c9c4e910d05b Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Fri, 13 Sep 2024 02:42:30 +0700 Subject: [PATCH 02/13] refactor state transition, copy core vm --- x/evm/keeper/gas.go | 2 + x/evm/keeper/state_transition.go | 92 ++------ x/evm/keeper/state_transition_core.go | 304 ++++++++++++++++++++++++++ 3 files changed, 325 insertions(+), 73 deletions(-) create mode 100644 x/evm/keeper/state_transition_core.go diff --git a/x/evm/keeper/gas.go b/x/evm/keeper/gas.go index d9b03c13a2..b30f40c474 100644 --- a/x/evm/keeper/gas.go +++ b/x/evm/keeper/gas.go @@ -6,6 +6,7 @@ import ( // ResetGasMeterAndConsumeGas reset first the gas meter consumed value to zero and set it back to the new value // 'gasUsed' +// TODO ES: remove? func (k *Keeper) ResetGasMeterAndConsumeGas(ctx sdk.Context, gasUsed uint64) { // reset the gas count ctx.GasMeter().RefundGas(ctx.GasMeter().GasConsumed(), "reset the gas count") @@ -15,6 +16,7 @@ func (k *Keeper) ResetGasMeterAndConsumeGas(ctx sdk.Context, gasUsed uint64) { // GasToRefund calculates the amount of gas the state machine should refund to the sender. It is // capped by the refund quotient value. // Note: do not pass 0 to refundQuotient +// TODO ES: remove? func GasToRefund(availableRefund, gasConsumed, refundQuotient uint64) uint64 { // Apply refund counter refund := gasConsumed / refundQuotient diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index ebdca14d50..dbba7494f8 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -17,7 +17,6 @@ import ( "github.com/ethereum/go-ethereum/core" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" - ethparams "github.com/ethereum/go-ethereum/params" ) // NewEVM generates a go-ethereum VM from the provided Message fields and the chain parameters @@ -219,11 +218,6 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, cfg *statedb.EVMConfig, txConfig statedb.TxConfig, ) (*evmtypes.MsgEthereumTxResponse, error) { - var ( - ret []byte // return bytes from evm execution - vmErr error // vm errors do not effect consensus and are therefore not assigned to err - ) - // return error if contract creation or call are disabled through governance if !cfg.Params.EnableCreate && msg.To() == nil { return nil, errorsmod.Wrap(evmtypes.ErrCreateDisabled, "failed to create new contract") @@ -234,70 +228,22 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, stateDB := statedb.New(ctx, k, txConfig) evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB) - leftoverGas := msg.Gas() - - // Allow the tracer captures the tx level events, mainly the gas consumption. - vmCfg := evm.Config - if vmCfg.Debug { - vmCfg.Tracer.CaptureTxStart(leftoverGas) - defer func() { - vmCfg.Tracer.CaptureTxEnd(leftoverGas) - }() + var gasPool core.GasPool + { + // in geth, gas pool is block gas, but here we capped it to the gas limit of the message + gasPool = core.GasPool(msg.Gas()) } - sender := vm.AccountRef(msg.From()) - contractCreation := msg.To() == nil - - const homestead = true - const istanbul = true - intrinsicGas, err := core.IntrinsicGas(msg.Data(), msg.AccessList(), contractCreation, homestead, istanbul) + execResult, err := ApplyMessage(evm, msg, &gasPool) if err != nil { - // should have already been checked on Ante Handler - return nil, errorsmod.Wrap(err, "intrinsic gas failed") - } - - // Should check again even if it is checked on Ante Handler, because eth_call don't go through Ante Handler. - if leftoverGas < intrinsicGas { - // eth_estimateGas will check for this exact error - return nil, errorsmod.Wrap(core.ErrIntrinsicGas, "apply message") + return nil, err } - leftoverGas -= intrinsicGas - - // access list preparation is moved from ante handler to here, because it's needed when `ApplyMessage` is called - // under contexts where ante handlers are not run, for example `eth_call` and `eth_estimateGas`. - const mergeNetsplit = true - rules := cfg.ChainConfig.Rules(big.NewInt(ctx.BlockHeight()), mergeNetsplit) - stateDB.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) + gasUsed := execResult.UsedGas - if contractCreation { - ret, _, leftoverGas, vmErr = evm.Create(sender, msg.Data(), leftoverGas, msg.Value()) - } else { - stateDB.SetNonce(sender.Address(), msg.Nonce()+1) - ret, leftoverGas, vmErr = evm.Call(sender, *msg.To(), msg.Data(), leftoverGas, msg.Value()) - } - - // After EIP-3529: refunds are capped to gasUsed / 5 - const refundQuotient = ethparams.RefundQuotientEIP3529 - - // calculate gas refund - if msg.Gas() < leftoverGas { - return nil, errorsmod.Wrap(evmtypes.ErrGasOverflow, "apply message") - } - // refund gas - gasUsed := msg.Gas() - leftoverGas - refund := GasToRefund(stateDB.GetRefund(), gasUsed, refundQuotient) - - // update leftoverGas and temporaryGasUsed with refund amount - leftoverGas += refund - gasUsed -= refund - - var success bool - // EVM execution error needs to be available for the JSON-RPC client - var vmError string - if vmErr != nil { - vmError = vmErr.Error() - } else { - success = true + cumulativeGasUsed := gasUsed + var prevTxIdx uint64 + for prevTxIdx = 0; prevTxIdx < uint64(txConfig.TxIndex); prevTxIdx++ { + cumulativeGasUsed += k.GetGasUsedForTdxIndexTransient(ctx, prevTxIdx) } var txType uint8 @@ -307,12 +253,6 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, txType = uint8(txConfig.TxType) } - cumulativeGasUsed := gasUsed - var prevTxIdx uint64 - for prevTxIdx = 0; prevTxIdx < uint64(txConfig.TxIndex); prevTxIdx++ { - cumulativeGasUsed += k.GetGasUsedForTdxIndexTransient(ctx, prevTxIdx) - } - receipt := ethtypes.Receipt{ // consensus fields only Type: txType, @@ -322,7 +262,7 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, Bloom: ethtypes.Bloom{}, // compute bellow Logs: stateDB.Logs(), } - if success { + if execResult.Err == nil { receipt.Status = ethtypes.ReceiptStatusSuccessful } else { receipt.Status = ethtypes.ReceiptStatusFailed @@ -345,10 +285,16 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, k.SetGasUsedForCurrentTxTransient(ctx, gasUsed) k.SetTxReceiptForCurrentTxTransient(ctx, bzReceipt) + // EVM execution error needs to be available for the JSON-RPC client + var vmError string + if execResult.Err != nil { + vmError = execResult.Err.Error() + } + return &evmtypes.MsgEthereumTxResponse{ GasUsed: gasUsed, VmError: vmError, - Ret: ret, + Ret: execResult.ReturnData, Hash: txConfig.TxHash.Hex(), MarshalledReceipt: bzReceipt, }, nil diff --git a/x/evm/keeper/state_transition_core.go b/x/evm/keeper/state_transition_core.go new file mode 100644 index 0000000000..5849ed20e1 --- /dev/null +++ b/x/evm/keeper/state_transition_core.go @@ -0,0 +1,304 @@ +// Copyright 2014 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// This file copied from go-ethereum with some modification + +package keeper + +import ( + "fmt" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" + "math/big" +) + +/* +StateTransition Model + +A state transition is a change made when a transaction is applied to the current world state +The state transitioning model does all the necessary work to work out a valid new state root. + +1) Nonce handling +2) Pre pay gas +3) Create a new state object if the recipient is \0*32 +4) Value transfer +== If contract creation == + + 4a) Attempt to run transaction data + 4b) If valid, use result as code for the new state object + +== end == +5) Run Script section +6) Derive new state root +*/ +type StateTransition struct { + gp *core.GasPool + msg core.Message + gas uint64 + gasPrice *big.Int + gasFeeCap *big.Int + gasTipCap *big.Int + initialGas uint64 + value *big.Int + data []byte + state vm.StateDB + evm *vm.EVM +} + +// NewStateTransition initialises and returns a new state transition object. +func NewStateTransition(evm *vm.EVM, msg core.Message, gp *core.GasPool) *StateTransition { + return &StateTransition{ + gp: gp, + evm: evm, + msg: msg, + gasPrice: msg.GasPrice(), + gasFeeCap: msg.GasFeeCap(), + gasTipCap: msg.GasTipCap(), + value: msg.Value(), + data: msg.Data(), + state: evm.StateDB, + } +} + +// ApplyMessage computes the new state by applying the given message +// against the old state within the environment. +// +// ApplyMessage returns the bytes returned by any EVM execution (if it took place), +// the gas used (which includes gas refunds) and an error if it failed. An error always +// indicates a core error meaning that the message would always fail for that particular +// state and would never be accepted within a block. +func ApplyMessage(evm *vm.EVM, msg core.Message, gp *core.GasPool) (*core.ExecutionResult, error) { + return NewStateTransition(evm, msg, gp).TransitionDb() +} + +// to returns the recipient of the message. +func (st *StateTransition) to() common.Address { + if st.msg == nil || st.msg.To() == nil /* contract creation */ { + return common.Address{} + } + return *st.msg.To() +} + +func (st *StateTransition) buyGas() error { + /* This logic was disabled because AnteHandle already charges the fee + // TODO ES: try to let this work + mgval := new(big.Int).SetUint64(st.msg.Gas()) + mgval = mgval.Mul(mgval, st.gasPrice) + balanceCheck := mgval + if st.gasFeeCap != nil { + balanceCheck = new(big.Int).SetUint64(st.msg.Gas()) + balanceCheck = balanceCheck.Mul(balanceCheck, st.gasFeeCap) + balanceCheck.Add(balanceCheck, st.value) + } + if have, want := st.state.GetBalance(st.msg.From()), balanceCheck; have.Cmp(want) < 0 { + return fmt.Errorf("%w: address %v have %v want %v", core.ErrInsufficientFunds, st.msg.From().Hex(), have, want) + } + if err := st.gp.SubGas(st.msg.Gas()); err != nil { + return err + } + st.gas += st.msg.Gas() + + st.initialGas = st.msg.Gas() + st.state.SubBalance(st.msg.From(), mgval) + */ + if err := st.gp.SubGas(st.msg.Gas()); err != nil { + return err + } + st.initialGas = st.msg.Gas() + st.gas += st.msg.Gas() + return nil +} + +func (st *StateTransition) preCheck() error { + // Only check transactions that are not fake + if !st.msg.IsFake() { + // Make sure this transaction's nonce is correct. + stNonce := st.state.GetNonce(st.msg.From()) + if msgNonce := st.msg.Nonce(); stNonce < msgNonce { + return fmt.Errorf("%w: address %v, tx: %d state: %d", core.ErrNonceTooHigh, + st.msg.From().Hex(), msgNonce, stNonce) + } else if stNonce > msgNonce { + return fmt.Errorf("%w: address %v, tx: %d state: %d", core.ErrNonceTooLow, + st.msg.From().Hex(), msgNonce, stNonce) + } else if stNonce+1 < stNonce { + return fmt.Errorf("%w: address %v, nonce: %d", core.ErrNonceMax, + st.msg.From().Hex(), stNonce) + } + // Make sure the sender is an EOA + if codeHash := st.state.GetCodeHash(st.msg.From()); codeHash != common.BytesToHash(evmtypes.EmptyCodeHash) && codeHash != (common.Hash{}) { + return fmt.Errorf("%w: address %v, codehash: %s", core.ErrSenderNoEOA, + st.msg.From().Hex(), codeHash) + } + } + // Make sure that transaction gasFeeCap is greater than the baseFee (post london) + if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) { + // Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call) + if !st.evm.Config.NoBaseFee || st.gasFeeCap.BitLen() > 0 || st.gasTipCap.BitLen() > 0 { + if l := st.gasFeeCap.BitLen(); l > 256 { + return fmt.Errorf("%w: address %v, maxFeePerGas bit length: %d", core.ErrFeeCapVeryHigh, + st.msg.From().Hex(), l) + } + if l := st.gasTipCap.BitLen(); l > 256 { + return fmt.Errorf("%w: address %v, maxPriorityFeePerGas bit length: %d", core.ErrTipVeryHigh, + st.msg.From().Hex(), l) + } + if st.gasFeeCap.Cmp(st.gasTipCap) < 0 { + return fmt.Errorf("%w: address %v, maxPriorityFeePerGas: %s, maxFeePerGas: %s", core.ErrTipAboveFeeCap, + st.msg.From().Hex(), st.gasTipCap, st.gasFeeCap) + } + // This will panic if baseFee is nil, but basefee presence is verified + // as part of header validation. + if st.gasFeeCap.Cmp(st.evm.Context.BaseFee) < 0 { + return fmt.Errorf("%w: address %v, maxFeePerGas: %s baseFee: %s", core.ErrFeeCapTooLow, + st.msg.From().Hex(), st.gasFeeCap, st.evm.Context.BaseFee) + } + } + } + return st.buyGas() +} + +// TransitionDb will transition the state by applying the current message and +// returning the evm execution result with following fields. +// +// - used gas: +// total gas used (including gas being refunded) +// - returndata: +// the returned data from evm +// - concrete execution error: +// various **EVM** error which aborts the execution, +// e.g. ErrOutOfGas, ErrExecutionReverted +// +// However if any consensus issue encountered, return the error directly with +// nil evm execution result. +func (st *StateTransition) TransitionDb() (*core.ExecutionResult, error) { + // First check this message satisfies all consensus rules before + // applying the message. The rules include these clauses + // + // 1. the nonce of the message caller is correct + // 2. caller has enough balance to cover transaction fee(gaslimit * gasprice) + // 3. the amount of gas required is available in the block + // 4. the purchased gas is enough to cover intrinsic usage + // 5. there is no overflow when calculating intrinsic gas + // 6. caller has enough balance to cover asset transfer for **topmost** call + + // Check clauses 1-3, buy gas if everything is correct + if err := st.preCheck(); err != nil { + return nil, err + } + + if st.evm.Config.Debug { + st.evm.Config.Tracer.CaptureTxStart(st.initialGas) + defer func() { + st.evm.Config.Tracer.CaptureTxEnd(st.gas) + }() + } + + var ( + msg = st.msg + sender = vm.AccountRef(msg.From()) + rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil) + contractCreation = msg.To() == nil + ) + + // Check clauses 4-5, subtract intrinsic gas if everything is correct + gas, err := core.IntrinsicGas(st.data, st.msg.AccessList(), contractCreation, rules.IsHomestead, rules.IsIstanbul) + if err != nil { + return nil, err + } + if st.gas < gas { + return nil, fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, st.gas, gas) + } + st.gas -= gas + + // Check clause 6 + if msg.Value().Sign() > 0 && !st.evm.Context.CanTransfer(st.state, msg.From(), msg.Value()) { + return nil, fmt.Errorf("%w: address %v", core.ErrInsufficientFundsForTransfer, msg.From().Hex()) + } + + // Set up the initial access list. + if rules.IsBerlin { + st.state.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList()) + } + var ( + ret []byte + vmerr error // vm errors do not effect consensus and are therefore not assigned to err + ) + if contractCreation { + ret, _, st.gas, vmerr = st.evm.Create(sender, st.data, st.gas, st.value) + } else { + // Increment the nonce for the next transaction + st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) + ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value) + } + + if !rules.IsLondon { + // Before EIP-3529: refunds were capped to gasUsed / 2 + st.refundGas(params.RefundQuotient) + } else { + // After EIP-3529: refunds are capped to gasUsed / 5 + st.refundGas(params.RefundQuotientEIP3529) + } + + if st.evm.Config.NoBaseFee && st.gasFeeCap.Sign() == 0 && st.gasTipCap.Sign() == 0 { + // Skip fee payment when NoBaseFee is set and the fee fields + // are 0. This avoids a negative effectiveTip being applied to + // the coinbase when simulating calls. + } else { + /* This logic was disabled because it was not on Evermint + // TODO ES: consider make this work + + effectiveTip := st.gasPrice + if rules.IsLondon { + effectiveTip = cmath.BigMin(st.gasTipCap, new(big.Int).Sub(st.gasFeeCap, st.evm.Context.BaseFee)) + } + + fee := new(big.Int).SetUint64(st.gasUsed()) + fee.Mul(fee, effectiveTip) + st.state.AddBalance(st.evm.Context.Coinbase, fee) + */ + } + + return &core.ExecutionResult{ + UsedGas: st.gasUsed(), + Err: vmerr, + ReturnData: ret, + }, nil +} + +func (st *StateTransition) refundGas(refundQuotient uint64) { + // Apply refund counter, capped to a refund quotient + refund := st.gasUsed() / refundQuotient + if refund > st.state.GetRefund() { + refund = st.state.GetRefund() + } + st.gas += refund + + // Return ETH for remaining gas, exchanged at the original rate. + remaining := new(big.Int).Mul(new(big.Int).SetUint64(st.gas), st.gasPrice) + st.state.AddBalance(st.msg.From(), remaining) + + // Also return remaining gas to the block gas counter so it is + // available for the next transaction. + st.gp.AddGas(st.gas) +} + +// gasUsed returns the amount of gas used up by the state transition. +func (st *StateTransition) gasUsed() uint64 { + return st.initialGas - st.gas +} From 03df1fb41aff1af13d9a9689b28999dd3fa143ea Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Fri, 13 Sep 2024 02:52:16 +0700 Subject: [PATCH 03/13] add flag to toggle NoBaseFee --- x/evm/keeper/config.go | 4 ++-- x/evm/keeper/keeper.go | 30 +++++++++++++++++++++------ x/evm/keeper/state_transition.go | 2 +- x/evm/keeper/state_transition_core.go | 3 ++- x/evm/statedb/config.go | 2 +- x/evm/types/key.go | 2 ++ 6 files changed, 32 insertions(+), 11 deletions(-) diff --git a/x/evm/keeper/config.go b/x/evm/keeper/config.go index dc2a2adc14..50df06d80c 100644 --- a/x/evm/keeper/config.go +++ b/x/evm/keeper/config.go @@ -43,7 +43,7 @@ func (k *Keeper) TxConfig(ctx sdk.Context, txHash common.Hash) statedb.TxConfig // VMConfig creates an EVM configuration from the debug setting and the extra EIPs enabled on the // module parameters. The config generated uses the default JumpTable from the EVM. -func (k Keeper) VMConfig(cfg *statedb.EVMConfig, tracer vm.EVMLogger) vm.Config { +func (k Keeper) VMConfig(ctx sdk.Context, cfg *statedb.EVMConfig, tracer vm.EVMLogger) vm.Config { var debug bool if tracer != nil { if _, ok := tracer.(evmtypes.NoOpTracer); !ok { @@ -54,7 +54,7 @@ func (k Keeper) VMConfig(cfg *statedb.EVMConfig, tracer vm.EVMLogger) vm.Config return vm.Config{ Debug: debug, Tracer: tracer, - NoBaseFee: cfg.NoBaseFee, + NoBaseFee: cfg.NoBaseFee || k.ShouldEnableNoBaseFee(ctx), ExtraEips: cfg.Params.EIPs(), } } diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 0641cb3657..cfe21b802d 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -265,18 +265,36 @@ func (k Keeper) GetTxReceiptsTransient(ctx sdk.Context) (receipts ethtypes.Recei // SetFlagSenderNonceIncreasedByAnteHandle sets the flag whether the sender nonce has been increased by AnteHandler. func (k Keeper) SetFlagSenderNonceIncreasedByAnteHandle(ctx sdk.Context, increased bool) { + k.genericSetBoolFlagTransient(ctx, evmtypes.KeyTransientFlagIncreasedSenderNonce, increased) +} + +// IsSenderNonceIncreasedByAnteHandle returns the flag whether the sender nonce has been increased by AnteHandler. +func (k Keeper) IsSenderNonceIncreasedByAnteHandle(ctx sdk.Context) bool { + return k.genericGetBoolFlagTransient(ctx, evmtypes.KeyTransientFlagIncreasedSenderNonce) +} + +// SetFlagEnableNoBaseFee sets the flag whether to enable no-base-fee of EVM config. +func (k Keeper) SetFlagEnableNoBaseFee(ctx sdk.Context, enable bool) { + k.genericSetBoolFlagTransient(ctx, evmtypes.KeyTransientFlagNoBaseFee, enable) +} + +// ShouldEnableNoBaseFee returns the flag should enable no-base-fee of EVM config. +func (k Keeper) ShouldEnableNoBaseFee(ctx sdk.Context) bool { + return k.genericGetBoolFlagTransient(ctx, evmtypes.KeyTransientFlagNoBaseFee) +} + +func (k Keeper) genericSetBoolFlagTransient(ctx sdk.Context, key []byte, value bool) { store := ctx.TransientStore(k.transientKey) - if increased { - store.Set(evmtypes.KeyTransientFlagIncreasedSenderNonce, []byte{1}) + if value { + store.Set(key, []byte{1}) } else { - store.Delete(evmtypes.KeyTransientFlagIncreasedSenderNonce) + store.Delete(key) } } -// IsSenderNonceIncreasedByAnteHandle returns the flag whether the sender nonce has been increased by AnteHandler. -func (k Keeper) IsSenderNonceIncreasedByAnteHandle(ctx sdk.Context) bool { +func (k Keeper) genericGetBoolFlagTransient(ctx sdk.Context, key []byte) bool { store := ctx.TransientStore(k.transientKey) - bz := store.Get(evmtypes.KeyTransientFlagIncreasedSenderNonce) + bz := store.Get(key) return len(bz) > 0 && bz[0] == 1 } diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index dbba7494f8..f3b7759efc 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -52,7 +52,7 @@ func (k *Keeper) NewEVM( if tracer == nil { tracer = k.Tracer(ctx, msg, cfg.ChainConfig) } - vmConfig := k.VMConfig(cfg, tracer) + vmConfig := k.VMConfig(ctx, cfg, tracer) return vm.NewEVM(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig) } diff --git a/x/evm/keeper/state_transition_core.go b/x/evm/keeper/state_transition_core.go index 5849ed20e1..942ebb443b 100644 --- a/x/evm/keeper/state_transition_core.go +++ b/x/evm/keeper/state_transition_core.go @@ -20,12 +20,13 @@ package keeper import ( "fmt" + "math/big" + evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" - "math/big" ) /* diff --git a/x/evm/statedb/config.go b/x/evm/statedb/config.go index 05e1429678..17e8317a9a 100644 --- a/x/evm/statedb/config.go +++ b/x/evm/statedb/config.go @@ -61,5 +61,5 @@ type EVMConfig struct { ChainConfig *ethparams.ChainConfig CoinBase common.Address BaseFee *big.Int - NoBaseFee bool // TODO ES: use this + NoBaseFee bool } diff --git a/x/evm/types/key.go b/x/evm/types/key.go index 84917c091d..a8dd633603 100644 --- a/x/evm/types/key.go +++ b/x/evm/types/key.go @@ -41,6 +41,7 @@ const ( prefixTransientTxLogCount prefixTransientTxReceipt prefixTransientFlagIncreasedSenderNonce + prefixTransientFlagNoBaseFee ) // KVStore key prefixes @@ -62,6 +63,7 @@ var ( var ( KeyTransientTxCount = []byte{prefixTransientTxCount} KeyTransientFlagIncreasedSenderNonce = []byte{prefixTransientFlagIncreasedSenderNonce} + KeyTransientFlagNoBaseFee = []byte{prefixTransientFlagNoBaseFee} ) // AddressStoragePrefix returns a prefix to iterate over a given account storage. From 86e5f27e8ac703dd558036c7722b9b953cc3026a Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Fri, 13 Sep 2024 02:53:04 +0700 Subject: [PATCH 04/13] remove method `GasToRefund` --- x/evm/keeper/gas.go | 14 ------- x/evm/keeper/state_transition_test.go | 60 --------------------------- 2 files changed, 74 deletions(-) diff --git a/x/evm/keeper/gas.go b/x/evm/keeper/gas.go index b30f40c474..08afd5a12e 100644 --- a/x/evm/keeper/gas.go +++ b/x/evm/keeper/gas.go @@ -6,22 +6,8 @@ import ( // ResetGasMeterAndConsumeGas reset first the gas meter consumed value to zero and set it back to the new value // 'gasUsed' -// TODO ES: remove? func (k *Keeper) ResetGasMeterAndConsumeGas(ctx sdk.Context, gasUsed uint64) { // reset the gas count ctx.GasMeter().RefundGas(ctx.GasMeter().GasConsumed(), "reset the gas count") ctx.GasMeter().ConsumeGas(gasUsed, "apply evm transaction") } - -// GasToRefund calculates the amount of gas the state machine should refund to the sender. It is -// capped by the refund quotient value. -// Note: do not pass 0 to refundQuotient -// TODO ES: remove? -func GasToRefund(availableRefund, gasConsumed, refundQuotient uint64) uint64 { - // Apply refund counter - refund := gasConsumed / refundQuotient - if refund > availableRefund { - return availableRefund - } - return refund -} diff --git a/x/evm/keeper/state_transition_test.go b/x/evm/keeper/state_transition_test.go index 200dcbe9c1..183c11c787 100644 --- a/x/evm/keeper/state_transition_test.go +++ b/x/evm/keeper/state_transition_test.go @@ -180,66 +180,6 @@ func (suite *KeeperTestSuite) TestGetCoinbaseAddress() { } } -func (suite *KeeperTestSuite) TestGasToRefund() { - testCases := []struct { - name string - gasconsumed uint64 - refundQuotient uint64 - expGasRefund uint64 - expPanic bool - }{ - { - name: "pass - gas refund 5", - gasconsumed: 5, - refundQuotient: 1, - expGasRefund: 5, - expPanic: false, - }, - { - name: "pass - gas refund 10", - gasconsumed: 10, - refundQuotient: 1, - expGasRefund: 10, - expPanic: false, - }, - { - name: "pass - gas refund availableRefund", - gasconsumed: 11, - refundQuotient: 1, - expGasRefund: 10, - expPanic: false, - }, - { - name: "fail - gas refund quotient 0", - gasconsumed: 11, - refundQuotient: 0, - expGasRefund: 0, - expPanic: true, - }, - } - - for _, tc := range testCases { - suite.Run(tc.name, func() { - suite.mintFeeCollector = true - suite.SetupTest() // reset - vmdb := suite.StateDB() - vmdb.AddRefund(10) - - if tc.expPanic { - //nolint:all - panicF := func() { - evmkeeper.GasToRefund(vmdb.GetRefund(), tc.gasconsumed, tc.refundQuotient) - } - suite.Require().Panics(panicF) - } else { - gr := evmkeeper.GasToRefund(vmdb.GetRefund(), tc.gasconsumed, tc.refundQuotient) - suite.Require().Equal(tc.expGasRefund, gr) - } - }) - } - suite.mintFeeCollector = false -} - func (suite *KeeperTestSuite) TestResetGasMeterAndConsumeGas() { testCases := []struct { name string From 3f2e7a0d1b142517a56c4d775948dfb0e1214b4f Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Fri, 13 Sep 2024 03:07:24 +0700 Subject: [PATCH 05/13] make use of new flag `NoBaseFee` --- x/evm/handler_test.go | 2 ++ x/evm/keeper/grpc_query.go | 5 +++++ x/evm/keeper/keeper.go | 1 + 3 files changed, 8 insertions(+) diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go index 954347e2d6..5227c208c5 100644 --- a/x/evm/handler_test.go +++ b/x/evm/handler_test.go @@ -690,6 +690,8 @@ func (suite *EvmTestSuite) TestContractDeploymentRevert() { suite.SetupTest() k := suite.app.EvmKeeper + k.SetFlagEnableNoBaseFee(suite.ctx, true) + nonce := k.GetNonce(suite.ctx, suite.from) ctorArgs, err := evmtypes.ERC20Contract.ABI.Pack("", suite.from, big.NewInt(0)) suite.Require().NoError(err) diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go index 0b80bb6d2b..d94a083cbd 100644 --- a/x/evm/keeper/grpc_query.go +++ b/x/evm/keeper/grpc_query.go @@ -225,6 +225,7 @@ func (k Keeper) EthCall(c context.Context, req *evmtypes.EthCallRequest) (*evmty ctx := sdk.UnwrapSDKContext(c) ctx = utils.UseZeroGasConfig(ctx) + k.SetFlagEnableNoBaseFee(ctx, true) var args evmtypes.TransactionArgs err := json.Unmarshal(req.Args, &args) @@ -268,6 +269,8 @@ func (k Keeper) EstimateGas(c context.Context, req *evmtypes.EthCallRequest) (*e ctx := sdk.UnwrapSDKContext(c) ctx = utils.UseZeroGasConfig(ctx) + k.SetFlagEnableNoBaseFee(ctx, true) + chainID, err := getChainID(ctx, req.ChainId) if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) @@ -411,6 +414,7 @@ func (k Keeper) TraceTx(c context.Context, req *evmtypes.QueryTraceTxRequest) (* ctx = ctx.WithBlockTime(req.BlockTime) ctx = ctx.WithHeaderHash(common.Hex2Bytes(req.BlockHash)) ctx = utils.UseZeroGasConfig(ctx) + k.SetFlagEnableNoBaseFee(ctx, true) // Only the block max gas from the consensus params is needed to calculate base fee ctx = ctx.WithConsensusParams(tmproto.ConsensusParams{ @@ -509,6 +513,7 @@ func (k Keeper) TraceBlock(c context.Context, req *evmtypes.QueryTraceBlockReque ctx = ctx.WithBlockTime(req.BlockTime) ctx = ctx.WithHeaderHash(common.Hex2Bytes(req.BlockHash)) ctx = utils.UseZeroGasConfig(ctx) + k.SetFlagEnableNoBaseFee(ctx, true) // Only the block max gas from the consensus params is needed to calculate base fee ctx = ctx.WithConsensusParams(tmproto.ConsensusParams{ diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index cfe21b802d..a2fe2538a1 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -274,6 +274,7 @@ func (k Keeper) IsSenderNonceIncreasedByAnteHandle(ctx sdk.Context) bool { } // SetFlagEnableNoBaseFee sets the flag whether to enable no-base-fee of EVM config. +// Go-Ethereum used this setting for `eth_call` and smt like that. func (k Keeper) SetFlagEnableNoBaseFee(ctx sdk.Context, enable bool) { k.genericSetBoolFlagTransient(ctx, evmtypes.KeyTransientFlagNoBaseFee, enable) } From d7d56f9f596419642ae329067853790ad00c4694 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Fri, 13 Sep 2024 14:27:27 +0700 Subject: [PATCH 06/13] fix trace methods failed due to nonce check --- x/evm/keeper/grpc_query_test.go | 35 ++++++++++++++++++++++++--------- x/evm/keeper/utils_test.go | 15 ++++++++++++++ 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/x/evm/keeper/grpc_query_test.go b/x/evm/keeper/grpc_query_test.go index 38d6eb52e3..73a772c001 100644 --- a/x/evm/keeper/grpc_query_test.go +++ b/x/evm/keeper/grpc_query_test.go @@ -774,10 +774,12 @@ func (suite *KeeperTestSuite) TestEstimateGas() { func (suite *KeeperTestSuite) TestTraceTx() { // TODO deploy contract that triggers internal transactions var ( - txMsg *evmtypes.MsgEthereumTx - traceConfig *evmtypes.TraceConfig - predecessors []*evmtypes.MsgEthereumTx - chainID *sdkmath.Int + txMsg *evmtypes.MsgEthereumTx + traceConfig *evmtypes.TraceConfig + predecessors []*evmtypes.MsgEthereumTx + chainID *sdkmath.Int + backupCtx sdk.Context + backupQueryClient evmtypes.QueryClient ) testCases := []struct { @@ -861,6 +863,9 @@ func (suite *KeeperTestSuite) TestTraceTx() { contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) suite.Commit() + + backupCtx, backupQueryClient = suite.CreateBackupCtxAndEvmQueryClient() + // Generate token transfer transaction firstTx := suite.TransferERC20Token(suite.T(), contractAddr, suite.address, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), sdkmath.NewIntWithDecimal(1, 18).BigInt()) txMsg = suite.TransferERC20Token(suite.T(), contractAddr, suite.address, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), sdkmath.NewIntWithDecimal(1, 18).BigInt()) @@ -972,6 +977,9 @@ func (suite *KeeperTestSuite) TestTraceTx() { // Deploy contract contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) suite.Commit() + + backupCtx, backupQueryClient = suite.CreateBackupCtxAndEvmQueryClient() + // Generate token transfer transaction txMsg = suite.TransferERC20Token(suite.T(), contractAddr, suite.address, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), sdkmath.NewIntWithDecimal(1, 18).BigInt()) suite.Commit() @@ -986,7 +994,7 @@ func (suite *KeeperTestSuite) TestTraceTx() { if chainID != nil { traceReq.ChainId = chainID.Int64() } - res, err := suite.queryClient.TraceTx(suite.ctx, &traceReq) + res, err := backupQueryClient.TraceTx(backupCtx, &traceReq) if tc.expPass { suite.Require().NoError(err) @@ -1014,9 +1022,11 @@ func (suite *KeeperTestSuite) TestTraceTx() { func (suite *KeeperTestSuite) TestTraceBlock() { var ( - txs []*evmtypes.MsgEthereumTx - traceConfig *evmtypes.TraceConfig - chainID *sdkmath.Int + txs []*evmtypes.MsgEthereumTx + traceConfig *evmtypes.TraceConfig + chainID *sdkmath.Int + backupCtx sdk.Context + backupQueryClient evmtypes.QueryClient ) testCases := []struct { @@ -1095,6 +1105,9 @@ func (suite *KeeperTestSuite) TestTraceBlock() { contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) suite.Commit() + + backupCtx, backupQueryClient = suite.CreateBackupCtxAndEvmQueryClient() + // create multiple transactions in the same block firstTx := suite.TransferERC20Token(suite.T(), contractAddr, suite.address, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), sdkmath.NewIntWithDecimal(1, 18).BigInt()) secondTx := suite.TransferERC20Token(suite.T(), contractAddr, suite.address, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), sdkmath.NewIntWithDecimal(1, 18).BigInt()) @@ -1155,6 +1168,9 @@ func (suite *KeeperTestSuite) TestTraceBlock() { // Deploy contract contractAddr := suite.DeployTestContract(suite.T(), suite.address, sdkmath.NewIntWithDecimal(1000, 18).BigInt()) suite.Commit() + + backupCtx, backupQueryClient = suite.CreateBackupCtxAndEvmQueryClient() + // Generate token transfer transaction txMsg := suite.TransferERC20Token(suite.T(), contractAddr, suite.address, common.HexToAddress("0x378c50D9264C63F3F92B806d4ee56E9D86FfB3Ec"), sdkmath.NewIntWithDecimal(1, 18).BigInt()) suite.Commit() @@ -1162,6 +1178,7 @@ func (suite *KeeperTestSuite) TestTraceBlock() { txs = append(txs, txMsg) tc.malleate() + traceReq := evmtypes.QueryTraceBlockRequest{ Txs: txs, TraceConfig: traceConfig, @@ -1171,7 +1188,7 @@ func (suite *KeeperTestSuite) TestTraceBlock() { traceReq.ChainId = chainID.Int64() } - res, err := suite.queryClient.TraceBlock(suite.ctx, &traceReq) + res, err := backupQueryClient.TraceBlock(backupCtx, &traceReq) if tc.expPass { suite.Require().NoError(err) diff --git a/x/evm/keeper/utils_test.go b/x/evm/keeper/utils_test.go index adc7b2de0b..43a6c679fd 100644 --- a/x/evm/keeper/utils_test.go +++ b/x/evm/keeper/utils_test.go @@ -207,3 +207,18 @@ func (suite *KeeperTestSuite) FundDefaultAddress(amount int64) { ) suite.Require().NoError(err) } + +// CreateBackupCtxAndEvmQueryClient creates backup sdk.Context and x/evm Query Client, for tracing simulation purpose. +func (suite *KeeperTestSuite) CreateBackupCtxAndEvmQueryClient() (sdk.Context, evmtypes.QueryClient) { + backupCtx, _ := suite.ctx.CacheContext() + queryHelper := baseapp.NewQueryServerTestHelper(backupCtx, suite.app.InterfaceRegistry()) + evmtypes.RegisterQueryServer(queryHelper, suite.app.EvmKeeper) + backupQueryClient := evmtypes.NewQueryClient(queryHelper) + + // warm up + _, _ = backupQueryClient.Account(backupCtx, &evmtypes.QueryAccountRequest{ + Address: suite.address.Hex(), + }) + + return backupCtx, backupQueryClient +} From c6404c97bc840ae2a14e0b4e071aee7f2ff0d998 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Fri, 13 Sep 2024 15:15:41 +0700 Subject: [PATCH 07/13] fix tests --- x/evm/handler_test.go | 99 +++++++++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 42 deletions(-) diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go index 5227c208c5..cb4b5f6ebb 100644 --- a/x/evm/handler_test.go +++ b/x/evm/handler_test.go @@ -1,6 +1,9 @@ package evm_test import ( + "fmt" + feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" "math/big" "testing" "time" @@ -17,8 +20,6 @@ import ( tmjson "github.com/cometbft/cometbft/libs/json" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/ethereum/go-ethereum/common" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -57,7 +58,7 @@ type EvmTestSuite struct { // DoSetupTest setup test environment func (suite *EvmTestSuite) DoSetupTest() { - checkTx := false + const checkTx = false // account key priv, err := ethsecp256k1.GenerateKey() @@ -74,26 +75,7 @@ func (suite *EvmTestSuite) DoSetupTest() { return genesis }) - coins := sdk.NewCoins(sdk.NewCoin(evmtypes.DefaultEVMDenom, sdkmath.NewInt(100000000000000))) genesisState := helpers.NewTestGenesisState(suite.app.AppCodec()) - b32address := sdk.MustBech32ifyAddressBytes(sdk.GetConfig().GetBech32AccountAddrPrefix(), priv.PubKey().Address().Bytes()) - balances := []banktypes.Balance{ - { - Address: b32address, - Coins: coins, - }, - { - Address: suite.app.AccountKeeper.GetModuleAddress(authtypes.FeeCollectorName).String(), - Coins: coins, - }, - } - var bankGenesis banktypes.GenesisState - suite.app.AppCodec().MustUnmarshalJSON(genesisState[banktypes.ModuleName], &bankGenesis) - // Update balances and total supply - bankGenesis.Balances = append(bankGenesis.Balances, balances...) - bankGenesis.Supply = bankGenesis.Supply.Add(coins...).Add(coins...) - genesisState[banktypes.ModuleName] = suite.app.AppCodec().MustMarshalJSON(&bankGenesis) - stateBytes, err := tmjson.MarshalIndent(genesisState, "", " ") suite.Require().NoError(err) @@ -145,9 +127,20 @@ func (suite *EvmTestSuite) DoSetupTest() { suite.Require().NoError(err) err = suite.app.StakingKeeper.SetValidatorByConsAddr(suite.ctx, validator) suite.Require().NoError(err) - suite.app.StakingKeeper.SetValidator(suite.ctx, validator) + err = suite.app.StakingKeeper.SetValidator(suite.ctx, validator) + suite.Require().NoError(err) suite.ethSigner = ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID()) + + coins := sdk.NewCoins(sdk.NewCoin(constants.BaseDenom, sdkmath.NewInt(100000000000000))) + err = suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, coins) + suite.Require().NoError(err) + err = suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, coins) + suite.Require().NoError(err) + err = suite.app.BankKeeper.SendCoinsFromModuleToModule(suite.ctx, minttypes.ModuleName, authtypes.FeeCollectorName, coins) + suite.Require().NoError(err) + err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, suite.from.Bytes(), coins) + suite.Require().NoError(err) } func (suite *EvmTestSuite) SetupTest() { @@ -239,6 +232,10 @@ func (suite *EvmTestSuite) TestHandleMsgEthereumTx() { suite.Run(tc.name, func() { suite.SetupTest() // reset + suite.zeroFeeMarket() + + fmt.Println("Bal", suite.app.BankKeeper.GetAllBalances(suite.ctx, suite.from.Bytes())) + tc.malleate() res, err := suite.app.EvmKeeper.EthereumTx(suite.ctx, tx) @@ -281,7 +278,7 @@ func (suite *EvmTestSuite) TestHandlerLogs() { ethTxParams := &evmtypes.EvmTxArgs{ From: suite.from, ChainID: suite.chainID, - Nonce: 1, + Nonce: 0, Amount: big.NewInt(0), GasPrice: gasPrice, GasLimit: gasLimit, @@ -290,6 +287,8 @@ func (suite *EvmTestSuite) TestHandlerLogs() { tx := evmtypes.NewTx(ethTxParams) suite.SignTx(tx) + suite.zeroFeeMarket() + response, err := suite.app.EvmKeeper.EthereumTx(suite.ctx, tx) suite.Require().NoError(err, "failed to handle eth tx msg") @@ -364,7 +363,7 @@ func (suite *EvmTestSuite) TestDeployAndCallContract() { ethTxParams := &evmtypes.EvmTxArgs{ From: suite.from, ChainID: suite.chainID, - Nonce: 1, + Nonce: 0, Amount: big.NewInt(0), GasPrice: gasPrice, GasLimit: gasLimit, @@ -373,6 +372,8 @@ func (suite *EvmTestSuite) TestDeployAndCallContract() { tx := evmtypes.NewTx(ethTxParams) suite.SignTx(tx) + suite.zeroFeeMarket() + response, err := suite.app.EvmKeeper.EthereumTx(suite.ctx, tx) suite.Require().NoError(err, "failed to handle eth tx msg") suite.Require().Equal(response.VmError, "", "failed to handle eth tx msg") @@ -388,7 +389,7 @@ func (suite *EvmTestSuite) TestDeployAndCallContract() { ethTxParams = &evmtypes.EvmTxArgs{ From: suite.from, ChainID: suite.chainID, - Nonce: 2, + Nonce: 1, To: &receiver, Amount: big.NewInt(0), GasPrice: gasPrice, @@ -435,7 +436,7 @@ func (suite *EvmTestSuite) TestSendTransaction() { ethTxParams := &evmtypes.EvmTxArgs{ From: suite.from, ChainID: suite.chainID, - Nonce: 1, + Nonce: 0, To: &common.Address{0x1}, Amount: big.NewInt(1), GasPrice: gasPrice, @@ -546,7 +547,7 @@ func (suite *EvmTestSuite) TestErrorWhenDeployContract() { ethTxParams := &evmtypes.EvmTxArgs{ From: suite.from, ChainID: suite.chainID, - Nonce: 1, + Nonce: 0, Amount: big.NewInt(0), GasPrice: gasPrice, GasLimit: gasLimit, @@ -555,8 +556,12 @@ func (suite *EvmTestSuite) TestErrorWhenDeployContract() { tx := evmtypes.NewTx(ethTxParams) suite.SignTx(tx) - result, _ := suite.app.EvmKeeper.EthereumTx(suite.ctx, tx) - suite.Require().Equal("invalid opcode: opcode 0xa6 not defined", result.VmError, "correct evm error") + suite.zeroFeeMarket() + + result, err := suite.app.EvmKeeper.EthereumTx(suite.ctx, tx) + suite.Require().NoError(err) + suite.Require().NotNil(result) + suite.Equal("invalid opcode: opcode 0xa6 not defined", result.VmError, "correct evm error") // TODO: snapshot checking } @@ -567,17 +572,17 @@ func (suite *EvmTestSuite) deployERC20Contract() common.Address { ctorArgs, err := evmtypes.ERC20Contract.ABI.Pack("", suite.from, big.NewInt(10000000000)) suite.Require().NoError(err) msg := ethtypes.NewMessage( - suite.from, - nil, - nonce, - big.NewInt(0), - 2000000, - big.NewInt(1), - nil, - nil, - append(evmtypes.ERC20Contract.Bin, ctorArgs...), - nil, - true, + suite.from, // from + nil, // to + nonce, // nonce + big.NewInt(0), // amount + 2000000, // gas limit + big.NewInt(1), // gas price + big.NewInt(1), // gas fee cap + big.NewInt(1), // gas tip cap + append(evmtypes.ERC20Contract.Bin, ctorArgs...), // data + nil, // access list + true, // is fake ) rsp, err := k.ApplyMessage(suite.ctx, msg, nil, true) suite.Require().NoError(err) @@ -611,6 +616,8 @@ func (suite *EvmTestSuite) TestERC20TransferReverted() { err := k.SetBalance(suite.ctx, suite.from, big.NewInt(1000000000000001)) suite.Require().NoError(err) + suite.zeroFeeMarket() + contract := suite.deployERC20Contract() data, err := evmtypes.ERC20Contract.ABI.Pack("transfer", suite.from, big.NewInt(10)) @@ -637,7 +644,7 @@ func (suite *EvmTestSuite) TestERC20TransferReverted() { txData, err := evmtypes.UnpackTxData(tx.Data) suite.Require().NoError(err) - fees, err := evmkeeper.VerifyFee(txData, evmtypes.DefaultEVMDenom, baseFee, suite.ctx.IsCheckTx()) + fees, err := evmkeeper.VerifyFee(txData, constants.BaseDenom, baseFee, suite.ctx.IsCheckTx()) suite.Require().NoError(err) err = k.DeductTxCostsFromUserBalance(suite.ctx, fees, sdk.MustAccAddressFromBech32(tx.From)) suite.Require().NoError(err) @@ -721,3 +728,11 @@ func (suite *EvmTestSuite) TestContractDeploymentRevert() { }) } } + +func (suite *EvmTestSuite) zeroFeeMarket() { + err := suite.app.FeeMarketKeeper.SetParams(suite.ctx, feemarkettypes.Params{ + BaseFee: sdkmath.ZeroInt(), + MinGasPrice: sdkmath.LegacyZeroDec(), + }) + suite.Require().NoError(err) +} From c69023935d934ec55ede3ddc9ae18bc250bc3f5a Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Fri, 13 Sep 2024 15:40:14 +0700 Subject: [PATCH 08/13] system call to EVM from x/erc20 will not check base fee --- x/erc20/keeper/evm.go | 15 ++++++++++++--- x/erc20/keeper/mock_test.go | 9 +++++++++ x/erc20/types/interfaces.go | 2 ++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/x/erc20/keeper/evm.go b/x/erc20/keeper/evm.go index 921ccff29e..455f1c2ef0 100644 --- a/x/erc20/keeper/evm.go +++ b/x/erc20/keeper/evm.go @@ -1,10 +1,8 @@ package keeper import ( - "encoding/json" - "math/big" - errorsmod "cosmossdk.io/errors" + "encoding/json" "github.com/EscanBE/evermint/v12/server/config" evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -15,6 +13,7 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "math/big" "github.com/EscanBE/evermint/v12/contracts" erc20types "github.com/EscanBE/evermint/v12/x/erc20/types" @@ -207,6 +206,16 @@ func (k Keeper) CallEVMWithData( !commit, // isFake ) + // enable NoBaseFee for system call + enabled := k.evmKeeper.ShouldEnableNoBaseFee(ctx) + if !enabled { + // enable and restore + k.evmKeeper.SetFlagEnableNoBaseFee(ctx, true) + defer func() { + k.evmKeeper.SetFlagEnableNoBaseFee(ctx, false) + }() + } + res, err := k.evmKeeper.ApplyMessage(ctx, msg, evmtypes.NewNoOpTracer(), commit) if err != nil { return nil, err diff --git a/x/erc20/keeper/mock_test.go b/x/erc20/keeper/mock_test.go index fe57cab024..c416d83619 100644 --- a/x/erc20/keeper/mock_test.go +++ b/x/erc20/keeper/mock_test.go @@ -21,6 +21,7 @@ var _ erc20types.EVMKeeper = &MockEVMKeeper{} type MockEVMKeeper struct { mock.Mock + noBaseFee bool } func (m *MockEVMKeeper) GetParams(_ sdk.Context) evmtypes.Params { @@ -53,6 +54,14 @@ func (m *MockEVMKeeper) ApplyMessage(_ sdk.Context, _ core.Message, _ vm.EVMLogg return args.Get(0).(*evmtypes.MsgEthereumTxResponse), args.Error(1) } +func (m *MockEVMKeeper) SetFlagEnableNoBaseFee(_ sdk.Context, enable bool) { + m.noBaseFee = enable +} + +func (m *MockEVMKeeper) ShouldEnableNoBaseFee(_ sdk.Context) bool { + return m.noBaseFee +} + var _ bankkeeper.Keeper = &MockBankKeeper{} type MockBankKeeper struct { diff --git a/x/erc20/types/interfaces.go b/x/erc20/types/interfaces.go index aa3babc522..648c918d70 100644 --- a/x/erc20/types/interfaces.go +++ b/x/erc20/types/interfaces.go @@ -27,6 +27,8 @@ type EVMKeeper interface { GetAccountWithoutBalance(ctx sdk.Context, addr common.Address) *statedb.Account EstimateGas(c context.Context, req *evmtypes.EthCallRequest) (*evmtypes.EstimateGasResponse, error) ApplyMessage(ctx sdk.Context, msg core.Message, tracer vm.EVMLogger, commit bool) (*evmtypes.MsgEthereumTxResponse, error) + SetFlagEnableNoBaseFee(ctx sdk.Context, enable bool) + ShouldEnableNoBaseFee(ctx sdk.Context) bool } type ( From 1f40fd2d1594850a7138f9b7a611042b724f10de Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Fri, 13 Sep 2024 15:40:19 +0700 Subject: [PATCH 09/13] fix tests --- x/erc20/keeper/evm_test.go | 2 +- x/erc20/keeper/msg_server_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x/erc20/keeper/evm_test.go b/x/erc20/keeper/evm_test.go index 377ad513c2..73a76a9012 100644 --- a/x/erc20/keeper/evm_test.go +++ b/x/erc20/keeper/evm_test.go @@ -240,8 +240,8 @@ func (suite *KeeperTestSuite) TestCallEVMWithData() { res, err := suite.app.Erc20Keeper.CallEVMWithData(suite.ctx, tc.from, contract, data, false) if tc.expPass { - suite.Require().NotNil(res) suite.Require().NoError(err) + suite.Require().NotNil(res) } else { suite.Require().Errorf(err, "result: %v", res) } diff --git a/x/erc20/keeper/msg_server_test.go b/x/erc20/keeper/msg_server_test.go index 3ef4de17c5..f0db56e1c2 100644 --- a/x/erc20/keeper/msg_server_test.go +++ b/x/erc20/keeper/msg_server_test.go @@ -206,7 +206,7 @@ func (suite *KeeperTestSuite) TestConvertCoinNativeCoin() { cosmosBalance := suite.app.BankKeeper.GetBalance(suite.ctx, sender, metadataCoin.Base) if tc.expPass { - suite.Require().NoError(err, tc.name) + suite.Require().NoError(err) acc := suite.app.EvmKeeper.GetAccountWithoutBalance(suite.ctx, erc20) if tc.selfdestructed { @@ -225,7 +225,7 @@ func (suite *KeeperTestSuite) TestConvertCoinNativeCoin() { suite.Require().Equal(balance.(*big.Int).Int64(), big.NewInt(tc.burn).Int64()) } } else { - suite.Require().Error(err, tc.name) + suite.Require().Error(err) } }) } From 477ceb093d26be40d60227674526f9d9a78bea35 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Fri, 13 Sep 2024 15:41:15 +0700 Subject: [PATCH 10/13] refactor rename --- x/erc20/keeper/evm.go | 2 +- x/erc20/keeper/mock_test.go | 2 +- x/erc20/types/interfaces.go | 2 +- x/evm/keeper/config.go | 2 +- x/evm/keeper/keeper.go | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/x/erc20/keeper/evm.go b/x/erc20/keeper/evm.go index 455f1c2ef0..3df8131959 100644 --- a/x/erc20/keeper/evm.go +++ b/x/erc20/keeper/evm.go @@ -207,7 +207,7 @@ func (k Keeper) CallEVMWithData( ) // enable NoBaseFee for system call - enabled := k.evmKeeper.ShouldEnableNoBaseFee(ctx) + enabled := k.evmKeeper.IsNoBaseFeeEnabled(ctx) if !enabled { // enable and restore k.evmKeeper.SetFlagEnableNoBaseFee(ctx, true) diff --git a/x/erc20/keeper/mock_test.go b/x/erc20/keeper/mock_test.go index c416d83619..f7f2037802 100644 --- a/x/erc20/keeper/mock_test.go +++ b/x/erc20/keeper/mock_test.go @@ -58,7 +58,7 @@ func (m *MockEVMKeeper) SetFlagEnableNoBaseFee(_ sdk.Context, enable bool) { m.noBaseFee = enable } -func (m *MockEVMKeeper) ShouldEnableNoBaseFee(_ sdk.Context) bool { +func (m *MockEVMKeeper) IsNoBaseFeeEnabled(_ sdk.Context) bool { return m.noBaseFee } diff --git a/x/erc20/types/interfaces.go b/x/erc20/types/interfaces.go index 648c918d70..ac44afe710 100644 --- a/x/erc20/types/interfaces.go +++ b/x/erc20/types/interfaces.go @@ -28,7 +28,7 @@ type EVMKeeper interface { EstimateGas(c context.Context, req *evmtypes.EthCallRequest) (*evmtypes.EstimateGasResponse, error) ApplyMessage(ctx sdk.Context, msg core.Message, tracer vm.EVMLogger, commit bool) (*evmtypes.MsgEthereumTxResponse, error) SetFlagEnableNoBaseFee(ctx sdk.Context, enable bool) - ShouldEnableNoBaseFee(ctx sdk.Context) bool + IsNoBaseFeeEnabled(ctx sdk.Context) bool } type ( diff --git a/x/evm/keeper/config.go b/x/evm/keeper/config.go index 50df06d80c..31a7778038 100644 --- a/x/evm/keeper/config.go +++ b/x/evm/keeper/config.go @@ -54,7 +54,7 @@ func (k Keeper) VMConfig(ctx sdk.Context, cfg *statedb.EVMConfig, tracer vm.EVML return vm.Config{ Debug: debug, Tracer: tracer, - NoBaseFee: cfg.NoBaseFee || k.ShouldEnableNoBaseFee(ctx), + NoBaseFee: cfg.NoBaseFee || k.IsNoBaseFeeEnabled(ctx), ExtraEips: cfg.Params.EIPs(), } } diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index a2fe2538a1..3caa1135c4 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -279,8 +279,8 @@ func (k Keeper) SetFlagEnableNoBaseFee(ctx sdk.Context, enable bool) { k.genericSetBoolFlagTransient(ctx, evmtypes.KeyTransientFlagNoBaseFee, enable) } -// ShouldEnableNoBaseFee returns the flag should enable no-base-fee of EVM config. -func (k Keeper) ShouldEnableNoBaseFee(ctx sdk.Context) bool { +// IsNoBaseFeeEnabled returns the flag if no-base-fee enabled and should be used by EVM config. +func (k Keeper) IsNoBaseFeeEnabled(ctx sdk.Context) bool { return k.genericGetBoolFlagTransient(ctx, evmtypes.KeyTransientFlagNoBaseFee) } From dc0c8c3a585abd1d1f74254c58222ec0c4a0597d Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Fri, 13 Sep 2024 15:43:50 +0700 Subject: [PATCH 11/13] gofumpt --- rpc/backend/call_tx.go | 1 - x/erc20/keeper/evm.go | 6 ++++-- x/evm/handler_test.go | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/rpc/backend/call_tx.go b/rpc/backend/call_tx.go index 453a6b0fa3..243313a20c 100644 --- a/rpc/backend/call_tx.go +++ b/rpc/backend/call_tx.go @@ -194,7 +194,6 @@ func (b *Backend) SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.Transac return nil } - // TODO ES: recheck this logic, look like something wrong, are both fields required to be Dynamic fee tx? if args.MaxPriorityFeePerGas != nil && args.MaxFeePerGas != nil { if err := checkRelationMaxPriorityFeePerGasAndMaxFeePerGas(); err != nil { return args, err diff --git a/x/erc20/keeper/evm.go b/x/erc20/keeper/evm.go index 3df8131959..c59d646227 100644 --- a/x/erc20/keeper/evm.go +++ b/x/erc20/keeper/evm.go @@ -1,8 +1,11 @@ package keeper import ( - errorsmod "cosmossdk.io/errors" "encoding/json" + "math/big" + + errorsmod "cosmossdk.io/errors" + "github.com/EscanBE/evermint/v12/server/config" evmtypes "github.com/EscanBE/evermint/v12/x/evm/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -13,7 +16,6 @@ import ( "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "math/big" "github.com/EscanBE/evermint/v12/contracts" erc20types "github.com/EscanBE/evermint/v12/x/erc20/types" diff --git a/x/evm/handler_test.go b/x/evm/handler_test.go index cb4b5f6ebb..29f35bacb8 100644 --- a/x/evm/handler_test.go +++ b/x/evm/handler_test.go @@ -2,12 +2,13 @@ package evm_test import ( "fmt" - feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" - minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" "math/big" "testing" "time" + feemarkettypes "github.com/EscanBE/evermint/v12/x/feemarket/types" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + storetypes "cosmossdk.io/store/types" "github.com/EscanBE/evermint/v12/app/helpers" From 8657b900d4146a826a1c5c76865e169eaac62911 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Fri, 13 Sep 2024 15:45:16 +0700 Subject: [PATCH 12/13] update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc40b0f407..b969465af9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ - (test) [#150](https://github.com/EscanBE/evermint/pull/150) Improve readability for test - (evm) [#154](https://github.com/EscanBE/evermint/pull/154) Simplify code, force all ETH hardfork enabled +- (evm) [#156](https://github.com/EscanBE/evermint/pull/156) Refactor `x/evm` state transition code, use go-ethereum code and make usage of `NoBaseFee` flag. ### Bug Fixes From d35ed7ac4a2ed77cd249c4b74e38568e0cd76e56 Mon Sep 17 00:00:00 2001 From: VictorTrustyDev Date: Fri, 13 Sep 2024 15:54:51 +0700 Subject: [PATCH 13/13] refactor usage of EVM config flag NoBaseFee --- x/evm/keeper/config.go | 1 + x/evm/keeper/grpc_query.go | 10 ++++++---- x/evm/keeper/state_transition_test.go | 11 +++++++---- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/x/evm/keeper/config.go b/x/evm/keeper/config.go index 31a7778038..2af12b2e49 100644 --- a/x/evm/keeper/config.go +++ b/x/evm/keeper/config.go @@ -28,6 +28,7 @@ func (k *Keeper) EVMConfig(ctx sdk.Context, proposerAddress sdk.ConsAddress, cha ChainConfig: ethCfg, CoinBase: coinbase, BaseFee: baseFee.BigInt(), + NoBaseFee: k.IsNoBaseFeeEnabled(ctx), }, nil } diff --git a/x/evm/keeper/grpc_query.go b/x/evm/keeper/grpc_query.go index d94a083cbd..42561cf714 100644 --- a/x/evm/keeper/grpc_query.go +++ b/x/evm/keeper/grpc_query.go @@ -225,7 +225,6 @@ func (k Keeper) EthCall(c context.Context, req *evmtypes.EthCallRequest) (*evmty ctx := sdk.UnwrapSDKContext(c) ctx = utils.UseZeroGasConfig(ctx) - k.SetFlagEnableNoBaseFee(ctx, true) var args evmtypes.TransactionArgs err := json.Unmarshal(req.Args, &args) @@ -240,6 +239,7 @@ func (k Keeper) EthCall(c context.Context, req *evmtypes.EthCallRequest) (*evmty if err != nil { return nil, status.Error(codes.Internal, err.Error()) } + cfg.NoBaseFee = true // ApplyMessageWithConfig expect correct nonce set in msg nonce := k.GetNonce(ctx, args.GetFrom()) @@ -269,7 +269,6 @@ func (k Keeper) EstimateGas(c context.Context, req *evmtypes.EthCallRequest) (*e ctx := sdk.UnwrapSDKContext(c) ctx = utils.UseZeroGasConfig(ctx) - k.SetFlagEnableNoBaseFee(ctx, true) chainID, err := getChainID(ctx, req.ChainId) if err != nil { @@ -318,6 +317,7 @@ func (k Keeper) EstimateGas(c context.Context, req *evmtypes.EthCallRequest) (*e if err != nil { return nil, status.Error(codes.Internal, "failed to load evm config") } + cfg.NoBaseFee = true // ApplyMessageWithConfig expect correct nonce set in msg nonce := k.GetNonce(ctx, args.GetFrom()) @@ -414,7 +414,6 @@ func (k Keeper) TraceTx(c context.Context, req *evmtypes.QueryTraceTxRequest) (* ctx = ctx.WithBlockTime(req.BlockTime) ctx = ctx.WithHeaderHash(common.Hex2Bytes(req.BlockHash)) ctx = utils.UseZeroGasConfig(ctx) - k.SetFlagEnableNoBaseFee(ctx, true) // Only the block max gas from the consensus params is needed to calculate base fee ctx = ctx.WithConsensusParams(tmproto.ConsensusParams{ @@ -429,6 +428,8 @@ func (k Keeper) TraceTx(c context.Context, req *evmtypes.QueryTraceTxRequest) (* if err != nil { return nil, status.Errorf(codes.Internal, "failed to load evm config: %s", err.Error()) } + cfg.NoBaseFee = true + signer := ethtypes.MakeSigner(cfg.ChainConfig, big.NewInt(ctx.BlockHeight())) cfg.BaseFee = k.feeMarketKeeper.GetBaseFee(ctx).BigInt() @@ -513,7 +514,6 @@ func (k Keeper) TraceBlock(c context.Context, req *evmtypes.QueryTraceBlockReque ctx = ctx.WithBlockTime(req.BlockTime) ctx = ctx.WithHeaderHash(common.Hex2Bytes(req.BlockHash)) ctx = utils.UseZeroGasConfig(ctx) - k.SetFlagEnableNoBaseFee(ctx, true) // Only the block max gas from the consensus params is needed to calculate base fee ctx = ctx.WithConsensusParams(tmproto.ConsensusParams{ @@ -529,6 +529,8 @@ func (k Keeper) TraceBlock(c context.Context, req *evmtypes.QueryTraceBlockReque if err != nil { return nil, status.Error(codes.Internal, "failed to load evm config") } + cfg.NoBaseFee = true + signer := ethtypes.MakeSigner(cfg.ChainConfig, big.NewInt(ctx.BlockHeight())) cfg.BaseFee = k.feeMarketKeeper.GetBaseFee(ctx).BigInt() diff --git a/x/evm/keeper/state_transition_test.go b/x/evm/keeper/state_transition_test.go index 183c11c787..45686e6788 100644 --- a/x/evm/keeper/state_transition_test.go +++ b/x/evm/keeper/state_transition_test.go @@ -240,13 +240,16 @@ func (suite *KeeperTestSuite) TestResetGasMeterAndConsumeGas() { } func (suite *KeeperTestSuite) TestEVMConfig() { + suite.app.EvmKeeper.SetFlagEnableNoBaseFee(suite.ctx, true) + proposerAddress := suite.ctx.BlockHeader().ProposerAddress cfg, err := suite.app.EvmKeeper.EVMConfig(suite.ctx, proposerAddress, big.NewInt(constants.TestnetEIP155ChainId)) suite.Require().NoError(err) - suite.Require().Equal(evmtypes.DefaultParams(), cfg.Params) - suite.Require().Equal(big.NewInt(0), cfg.BaseFee) - suite.Require().Equal(suite.address, cfg.CoinBase) - suite.Require().Equal(evmtypes.DefaultParams().ChainConfig.EthereumConfig(big.NewInt(constants.TestnetEIP155ChainId)), cfg.ChainConfig) + suite.Equal(evmtypes.DefaultParams(), cfg.Params) + suite.Equal(big.NewInt(0), cfg.BaseFee) + suite.Equal(suite.address, cfg.CoinBase) + suite.Equal(evmtypes.DefaultParams().ChainConfig.EthereumConfig(big.NewInt(constants.TestnetEIP155ChainId)), cfg.ChainConfig) + suite.True(cfg.NoBaseFee) } func (suite *KeeperTestSuite) TestContractDeployment() {