diff --git a/app/ante/eth.go b/app/ante/eth.go index 2402167be7..3838e3be14 100644 --- a/app/ante/eth.go +++ b/app/ante/eth.go @@ -250,9 +250,7 @@ type CanTransferDecorator struct { // NewCanTransferDecorator creates a new CanTransferDecorator instance. func NewCanTransferDecorator(evmKeeper EVMKeeper) CanTransferDecorator { - return CanTransferDecorator{ - evmKeeper: evmKeeper, - } + return CanTransferDecorator{evmKeeper} } // AnteHandle creates an EVM from the message and calls the BlockContext CanTransfer function to @@ -303,11 +301,11 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate } stateDB := statedb.New(ctx, ctd.evmKeeper, statedb.NewEmptyTxConfig(common.BytesToHash(ctx.HeaderHash().Bytes()))) - evm := ctd.evmKeeper.NewEVM(ctx, coreMsg, cfg, evmtypes.NewNoOpTracer(), stateDB) + evm := ctd.evmKeeper.NewEVM(ctx, coreMsg, cfg, evmtypes.NewNoOpTracer(), stateDB, nil) // check that caller has enough balance to cover asset transfer for **topmost** call // NOTE: here the gas consumed is from the context with the infinite gas meter - if coreMsg.Value().Sign() > 0 && !evm.Context().CanTransfer(stateDB, coreMsg.From(), coreMsg.Value()) { + if coreMsg.Value().Sign() > 0 && !evm.Context.CanTransfer(stateDB, coreMsg.From(), coreMsg.Value()) { return ctx, errorsmod.Wrapf( errortypes.ErrInsufficientFunds, "failed to transfer %s from address %s using the EVM block context transfer function", diff --git a/app/ante/interfaces.go b/app/ante/interfaces.go index e48e0a50a4..e13646029a 100644 --- a/app/ante/interfaces.go +++ b/app/ante/interfaces.go @@ -25,10 +25,9 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" - + "github.com/evmos/ethermint/x/evm/keeper/precompiles" "github.com/evmos/ethermint/x/evm/statedb" evmtypes "github.com/evmos/ethermint/x/evm/types" - evm "github.com/evmos/ethermint/x/evm/vm" feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" ) @@ -44,7 +43,8 @@ type EVMKeeper interface { statedb.Keeper DynamicFeeEVMKeeper - NewEVM(ctx sdk.Context, msg core.Message, cfg *statedb.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB) evm.EVM + NewEVM(ctx sdk.Context, msg core.Message, cfg *statedb.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB, + customContracts []precompiles.StatefulPrecompiledContract) *vm.EVM DeductTxCostsFromUserBalance(ctx sdk.Context, fees sdk.Coins, from common.Address) error GetBalance(ctx sdk.Context, addr common.Address) *big.Int ResetTransientGasUsed(ctx sdk.Context) diff --git a/app/app.go b/app/app.go index 4c68dae86f..ee7f5b164f 100644 --- a/app/app.go +++ b/app/app.go @@ -133,7 +133,6 @@ import ( "github.com/evmos/ethermint/x/evm" evmkeeper "github.com/evmos/ethermint/x/evm/keeper" evmtypes "github.com/evmos/ethermint/x/evm/types" - "github.com/evmos/ethermint/x/evm/vm/geth" "github.com/evmos/ethermint/x/feemarket" feemarketkeeper "github.com/evmos/ethermint/x/feemarket/keeper" feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" @@ -465,7 +464,8 @@ func NewEthermintApp( app.EvmKeeper = evmkeeper.NewKeeper( appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], authtypes.NewModuleAddress(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.FeeMarketKeeper, - nil, geth.NewEVM, tracer, evmSs, + app.IBCKeeper, tracer, + evmSs, nil, ) // Create IBC Keeper diff --git a/go.mod b/go.mod index 146edab7d1..7f1d10d5c9 100644 --- a/go.mod +++ b/go.mod @@ -220,6 +220,7 @@ replace ( github.com/cometbft/cometbft => github.com/cometbft/cometbft v0.37.2-0.20230905115601-790d57e1748f github.com/cometbft/cometbft-db => github.com/crypto-org-chain/cometbft-db v0.0.0-20230412133340-ac70df4b45f6 github.com/cosmos/cosmos-sdk => github.com/crypto-org-chain/cosmos-sdk v0.46.0-beta2.0.20230905040840-b3af5590283b + github.com/ethereum/go-ethereum => github.com/evmos/go-ethereum v1.10.26-evmos-rc1 // Fix upstream GHSA-h395-qcrw-5vmq vulnerability. // TODO Remove it: https://github.com/cosmos/cosmos-sdk/issues/10409 github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.7.0 diff --git a/go.sum b/go.sum index d1ba1c07bc..3407e23b3a 100644 --- a/go.sum +++ b/go.sum @@ -445,8 +445,8 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.m github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/go-ethereum v1.10.26 h1:i/7d9RBBwiXCEuyduBQzJw/mKmnvzsN14jqBmytw72s= -github.com/ethereum/go-ethereum v1.10.26/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= +github.com/evmos/go-ethereum v1.10.26-evmos-rc1 h1:8+jrotZVyO0eIJGRGa1Kga3Fo7DjgFUE9rd6A8Yrbcg= +github.com/evmos/go-ethereum v1.10.26-evmos-rc1/go.mod h1:EYFyF19u3ezGLD4RqOkLq+ZCXzYbLoNDdZlMt7kyKFg= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o= diff --git a/gomod2nix.toml b/gomod2nix.toml index 3b5b6060d1..8865fc9c65 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -207,8 +207,9 @@ schema = 3 version = "v1.0.0" hash = "sha256-k1DYvCqO3BKNcGEve/nMW0RxzMkK2tGfXbUbycqcVSo=" [mod."github.com/ethereum/go-ethereum"] - version = "v1.10.26" - hash = "sha256-gkMEwJ4rOgn12amD4QpZ4th/10uyTTeoFmpseuKDQPs=" + version = "v1.10.26-evmos-rc1" + hash = "sha256-GgcReGsIIuBE2TabDYqDO9sBGogdVr9RSh4arQzdPnE=" + replaced = "github.com/evmos/go-ethereum" [mod."github.com/felixge/httpsnoop"] version = "v1.0.2" hash = "sha256-hj6FZQ1fDAV+1wGIViAt8XaAkWZ1I5vJzgjIJa7XRBA=" diff --git a/rpc/backend/utils.go b/rpc/backend/utils.go index 43610c3217..8b55ec645a 100644 --- a/rpc/backend/utils.go +++ b/rpc/backend/utils.go @@ -315,20 +315,17 @@ func TxLogsFromEvents(events []abci.Event, msgIndex int) ([]*ethtypes.Log, error // ParseTxLogsFromEvent parse tx logs from one event func ParseTxLogsFromEvent(event abci.Event) ([]*ethtypes.Log, error) { - logs := make([]*evmtypes.Log, 0, len(event.Attributes)) + var ethLogs []*ethtypes.Log for _, attr := range event.Attributes { - if attr.Key != evmtypes.AttributeKeyTxLog { - continue - } - var log evmtypes.Log - if err := json.Unmarshal([]byte(attr.Value), &log); err != nil { - return nil, err + if attr.Key == evmtypes.AttributeKeyTxLog { + if err := json.Unmarshal([]byte(attr.Value), &log); err != nil { + return nil, err + } + ethLogs = append(ethLogs, log.ToEthereum()) } - - logs = append(logs, &log) } - return evmtypes.LogsToEthereum(logs), nil + return ethLogs, nil } // ShouldIgnoreGasUsed returns true if the gasUsed in result should be ignored diff --git a/x/evm/keeper/keeper.go b/x/evm/keeper/keeper.go index 58621e310a..df2577eebf 100644 --- a/x/evm/keeper/keeper.go +++ b/x/evm/keeper/keeper.go @@ -31,10 +31,11 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" + ibckeeper "github.com/cosmos/ibc-go/v7/modules/core/keeper" ethermint "github.com/evmos/ethermint/types" + "github.com/evmos/ethermint/x/evm/keeper/precompiles" "github.com/evmos/ethermint/x/evm/statedb" "github.com/evmos/ethermint/x/evm/types" - evm "github.com/evmos/ethermint/x/evm/vm" ) // Keeper grants access to the EVM module state and implements the go-ethereum StateDB interface. @@ -44,8 +45,7 @@ type Keeper struct { // Store key required for the EVM Prefix KVStore. It is required by: // - storing account's Storage State // - storing account's Code - // - storing transaction Logs - // - storing Bloom filters by block height. Needed for the Web3 API. + // - storing module parameters storeKey storetypes.StoreKey // key to access the transient store, which is reset on every block during Commit @@ -62,6 +62,8 @@ type Keeper struct { // fetch EIP1559 base fee and parameters feeMarketKeeper types.FeeMarketKeeper + ibcKeeper *ibckeeper.Keeper + // chain ID number obtained from the context's chain id eip155ChainID *big.Int @@ -71,13 +73,9 @@ type Keeper struct { // EVM Hooks for tx post-processing hooks types.EvmHooks - // custom stateless precompiled smart contracts - customPrecompiles evm.PrecompiledContracts - - // evm constructor function - evmConstructor evm.Constructor // Legacy subspace - ss paramstypes.Subspace + ss paramstypes.Subspace + customContractsFn func(ctx sdk.Context, stateDB vm.StateDB) []precompiles.StatefulPrecompiledContract } // NewKeeper generates new evm module keeper @@ -89,10 +87,10 @@ func NewKeeper( bankKeeper types.BankKeeper, sk types.StakingKeeper, fmk types.FeeMarketKeeper, - customPrecompiles evm.PrecompiledContracts, - evmConstructor evm.Constructor, + ik *ibckeeper.Keeper, tracer string, ss paramstypes.Subspace, + customContractsFn func(ctx sdk.Context, stateDB vm.StateDB) []precompiles.StatefulPrecompiledContract, ) *Keeper { // ensure evm module account is set if addr := ak.GetModuleAddress(types.ModuleName); addr == nil { @@ -112,12 +110,12 @@ func NewKeeper( bankKeeper: bankKeeper, stakingKeeper: sk, feeMarketKeeper: fmk, + ibcKeeper: ik, storeKey: storeKey, transientKey: transientKey, - customPrecompiles: customPrecompiles, - evmConstructor: evmConstructor, tracer: tracer, ss: ss, + customContractsFn: customContractsFn, } } diff --git a/x/evm/keeper/precompiles/interface.go b/x/evm/keeper/precompiles/interface.go new file mode 100644 index 0000000000..b541f8b49c --- /dev/null +++ b/x/evm/keeper/precompiles/interface.go @@ -0,0 +1,16 @@ +package precompiles + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/core/vm" +) + +type StatefulPrecompiledContract interface { + vm.PrecompiledContract +} + +// ExtStateDB defines extra methods of statedb to support stateful precompiled contracts +type ExtStateDB interface { + vm.StateDB + ExecuteNativeAction(action func(ctx sdk.Context) error) error +} diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go index a13d2cfd43..6387df6ba2 100644 --- a/x/evm/keeper/state_transition.go +++ b/x/evm/keeper/state_transition.go @@ -16,7 +16,9 @@ package keeper import ( + "bytes" "math/big" + "sort" tmtypes "github.com/cometbft/cometbft/types" @@ -24,9 +26,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ethermint "github.com/evmos/ethermint/types" + "github.com/evmos/ethermint/x/evm/keeper/precompiles" "github.com/evmos/ethermint/x/evm/statedb" "github.com/evmos/ethermint/x/evm/types" - evm "github.com/evmos/ethermint/x/evm/vm" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" @@ -51,7 +53,8 @@ func (k *Keeper) NewEVM( cfg *statedb.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB, -) evm.EVM { + customContracts []precompiles.StatefulPrecompiledContract, +) *vm.EVM { blockCtx := vm.BlockContext{ CanTransfer: core.CanTransfer, Transfer: core.Transfer, @@ -64,13 +67,29 @@ func (k *Keeper) NewEVM( BaseFee: cfg.BaseFee, Random: nil, // not supported } - txCtx := core.NewEVMTxContext(msg) if tracer == nil { tracer = k.Tracer(ctx, msg, cfg.ChainConfig) } vmConfig := k.VMConfig(ctx, msg, cfg, tracer) - return k.evmConstructor(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig, k.customPrecompiles) + rules := cfg.ChainConfig.Rules(big.NewInt(ctx.BlockHeight()), cfg.ChainConfig.MergeNetsplitBlock != nil) + contracts := make(map[common.Address]vm.PrecompiledContract) + active := make([]common.Address, 0) + for addr, c := range vm.DefaultPrecompiles(rules) { + contracts[addr] = c + active = append(active, addr) + } + for _, c := range customContracts { + addr := c.Address() + contracts[addr] = c + active = append(active, addr) + } + sort.SliceStable(active, func(i, j int) bool { + return bytes.Compare(active[i].Bytes(), active[j].Bytes()) < 0 + }) + evm := vm.NewEVM(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig) + evm.WithPrecompiles(contracts, active) + return evm } // GetHashFn implements vm.GetHashFunc for Ethermint. It handles 3 cases: @@ -331,12 +350,14 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, } stateDB := statedb.New(ctx, k, txConfig) - evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB) - + var customContracts []precompiles.StatefulPrecompiledContract + if k.customContractsFn != nil { + customContracts = k.customContractsFn(ctx, stateDB) + } + evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB, customContracts) leftoverGas := msg.Gas() - // Allow the tracer captures the tx level events, mainly the gas consumption. - vmCfg := evm.Config() + vmCfg := evm.Config if vmCfg.Debug { vmCfg.Tracer.CaptureTxStart(leftoverGas) defer func() { @@ -344,10 +365,9 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, }() } - sender := vm.AccountRef(msg.From()) + isLondon := cfg.ChainConfig.IsLondon(evm.Context.BlockNumber) contractCreation := msg.To() == nil - isLondon := cfg.ChainConfig.IsLondon(evm.Context().BlockNumber) - + sender := vm.AccountRef(msg.From()) intrinsicGas, err := k.GetEthIntrinsicGas(ctx, msg, cfg.ChainConfig, contractCreation) if err != nil { // should have already been checked on Ante Handler @@ -364,7 +384,7 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, // 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`. if rules := cfg.ChainConfig.Rules(big.NewInt(ctx.BlockHeight()), cfg.ChainConfig.MergeNetsplitBlock != nil); rules.IsBerlin { - stateDB.PrepareAccessList(msg.From(), msg.To(), evm.ActivePrecompiles(rules), msg.AccessList()) + stateDB.PrepareAccessList(msg.From(), msg.To(), vm.DefaultActivePrecompiles(rules), msg.AccessList()) } if contractCreation { diff --git a/x/evm/statedb/interfaces.go b/x/evm/statedb/interfaces.go index e4e83e09c3..2f1a119aa2 100644 --- a/x/evm/statedb/interfaces.go +++ b/x/evm/statedb/interfaces.go @@ -18,19 +18,8 @@ package statedb import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/vm" ) -// ExtStateDB defines an extension to the interface provided by the go-ethereum -// codebase to support additional state transition functionalities. In particular -// it supports appending a new entry to the state journal through -// AppendJournalEntry so that the state can be reverted after running -// stateful precompiled contracts. -type ExtStateDB interface { - vm.StateDB - AppendJournalEntry(JournalEntry) -} - // Keeper provide underlying storage of StateDB type Keeper interface { // Read methods diff --git a/x/evm/statedb/native.go b/x/evm/statedb/native.go new file mode 100644 index 0000000000..e454452d49 --- /dev/null +++ b/x/evm/statedb/native.go @@ -0,0 +1,20 @@ +package statedb + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/ethereum/go-ethereum/common" +) + +var _ JournalEntry = nativeChange{} + +type nativeChange struct { + snapshot sdk.CacheMultiStore +} + +func (native nativeChange) Dirtied() *common.Address { + return nil +} + +func (native nativeChange) Revert(s *StateDB) { + s.restoreNativeState(native.snapshot) +} diff --git a/x/evm/statedb/statedb.go b/x/evm/statedb/statedb.go index c03d501cbf..4e6c16298b 100644 --- a/x/evm/statedb/statedb.go +++ b/x/evm/statedb/statedb.go @@ -44,8 +44,10 @@ var _ vm.StateDB = &StateDB{} // * Contracts // * Accounts type StateDB struct { - keeper Keeper - ctx sdk.Context + keeper Keeper + ctx sdk.Context + cacheCtx sdk.Context + writeCache func() // Journal of state modifications. This is the backbone of // Snapshot and RevertToSnapshot. @@ -69,7 +71,7 @@ type StateDB struct { // New creates a new state from a given trie. func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB { - return &StateDB{ + db := &StateDB{ keeper: keeper, ctx: ctx, stateObjects: make(map[common.Address]*stateObject), @@ -78,6 +80,10 @@ func New(ctx sdk.Context, keeper Keeper, txConfig TxConfig) *StateDB { txConfig: txConfig, } + if ctx.MultiStore() != nil { + db.cacheCtx, db.writeCache = ctx.CacheContext() + } + return db } // Keeper returns the underlying `Keeper` @@ -298,6 +304,29 @@ func (s *StateDB) setStateObject(object *stateObject) { s.stateObjects[object.Address()] = object } +func (s *StateDB) restoreNativeState(cms sdk.CacheMultiStore) { + manager := sdk.NewEventManager() + s.cacheCtx = s.cacheCtx.WithMultiStore(cms).WithEventManager(manager) + s.writeCache = func() { + manager.EmitEvents(manager.Events()) + cms.Write() + } +} + +// ExecuteNativeAction executes native action in isolate, +// the writes will be revert when either the native action itself fail +// or the wrapping message call reverted. +func (s *StateDB) ExecuteNativeAction(action func(ctx sdk.Context) error) error { + snapshot := s.ctx.MultiStore().CacheMultiStore() + err := action(s.cacheCtx) + if err != nil { + s.restoreNativeState(snapshot) + return err + } + s.journal.append(nativeChange{snapshot: snapshot}) + return nil +} + /* * SETTERS */ @@ -451,6 +480,9 @@ func (s *StateDB) RevertToSnapshot(revid int) { // Commit writes the dirty states to keeper // the StateDB object should be discarded after committed. func (s *StateDB) Commit() error { + if s.writeCache != nil { + s.writeCache() + } for _, addr := range s.journal.sortedDirties() { obj := s.stateObjects[addr] if obj.suicided { diff --git a/x/evm/types/interfaces.go b/x/evm/types/interfaces.go index 89ba85afe7..8253dd22cd 100644 --- a/x/evm/types/interfaces.go +++ b/x/evm/types/interfaces.go @@ -49,6 +49,8 @@ type BankKeeper interface { SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error BurnCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error + IsSendEnabledCoins(ctx sdk.Context, coins ...sdk.Coin) error + BlockedAddr(addr sdk.AccAddress) bool } // StakingKeeper returns the historical headers kept in store. diff --git a/x/evm/types/tracer.go b/x/evm/types/tracer.go index 1c535178a2..003b211629 100644 --- a/x/evm/types/tracer.go +++ b/x/evm/types/tracer.go @@ -45,7 +45,7 @@ func NewTracer(tracer string, msg core.Message, cfg *params.ChainConfig, height switch tracer { case TracerAccessList: - preCompiles := vm.ActivePrecompiles(cfg.Rules(big.NewInt(height), cfg.MergeNetsplitBlock != nil)) + preCompiles := vm.DefaultActivePrecompiles(cfg.Rules(big.NewInt(height), cfg.MergeNetsplitBlock != nil)) return logger.NewAccessListTracer(msg.AccessList(), msg.From(), *msg.To(), preCompiles) case TracerJSON: return logger.NewJSONLogger(logCfg, os.Stderr)