Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

imp(evm): stateless custom precompiles #1272

Merged
merged 18 commits into from
Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### State Machine Breaking

* (evm) [\#1272](https:/evmos/ethermint/pull/1272) Implement modular interface for the EVM.
* (deps) [\#1159](https:/evmos/ethermint/pull/1159) Bump Geth version to `v1.10.19`.
* (deps) [#1167](https:/evmos/ethermint/pull/1167) Bump ibc-go to [`v4.0.0-rc2`](https:/cosmos/ibc-go/releases/tag/v4.0.0-rc2)
* (ante) [#1176](https:/evmos/ethermint/pull/1176) Fix invalid tx hashes; Remove `Size_` field and validate `Hash`/`From` fields in ante handler,
Expand Down
2 changes: 1 addition & 1 deletion app/ante/eth.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ func (ctd CanTransferDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate

// 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, sdkerrors.Wrapf(
sdkerrors.ErrInsufficientFunds,
"failed to transfer %s from address %s using the EVM block context transfer function",
Expand Down
3 changes: 2 additions & 1 deletion app/ante/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/ethereum/go-ethereum/params"
"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"
)

Expand All @@ -26,7 +27,7 @@ type EVMKeeper interface {
statedb.Keeper
DynamicFeeEVMKeeper

NewEVM(ctx sdk.Context, msg core.Message, cfg *evmtypes.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB) *vm.EVM
NewEVM(ctx sdk.Context, msg core.Message, cfg *evmtypes.EVMConfig, tracer vm.EVMLogger, stateDB vm.StateDB) evm.EVM
DeductTxCostsFromUserBalance(
ctx sdk.Context, msgEthTx evmtypes.MsgEthereumTx, txData evmtypes.TxData, denom string, homestead, istanbul, london bool,
) (fees sdk.Coins, priority int64, err error)
Expand Down
3 changes: 2 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ 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"
Expand Down Expand Up @@ -396,7 +397,7 @@ func NewEthermintApp(
app.EvmKeeper = evmkeeper.NewKeeper(
appCodec, keys[evmtypes.StoreKey], tkeys[evmtypes.TransientKey], app.GetSubspace(evmtypes.ModuleName),
app.AccountKeeper, app.BankKeeper, app.StakingKeeper, app.FeeMarketKeeper,
tracer,
nil, geth.NewEVM, tracer,
)

// Create IBC Keeper
Expand Down
36 changes: 25 additions & 11 deletions x/evm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
ethermint "github.com/evmos/ethermint/types"
"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.
Expand Down Expand Up @@ -54,14 +55,25 @@ 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
}

// NewKeeper generates new evm module keeper
func NewKeeper(
cdc codec.BinaryCodec,
storeKey, transientKey storetypes.StoreKey, paramSpace paramtypes.Subspace,
ak types.AccountKeeper, bankKeeper types.BankKeeper, sk types.StakingKeeper,
storeKey, transientKey storetypes.StoreKey,
paramSpace paramtypes.Subspace,
ak types.AccountKeeper,
bankKeeper types.BankKeeper,
sk types.StakingKeeper,
fmk types.FeeMarketKeeper,
customPrecompiles evm.PrecompiledContracts,
evmConstructor evm.Constructor,
tracer string,
) *Keeper {
// ensure evm module account is set
Expand All @@ -76,15 +88,17 @@ func NewKeeper(

// NOTE: we pass in the parameter space to the CommitStateDB in order to use custom denominations for the EVM operations
return &Keeper{
cdc: cdc,
paramSpace: paramSpace,
accountKeeper: ak,
bankKeeper: bankKeeper,
stakingKeeper: sk,
feeMarketKeeper: fmk,
storeKey: storeKey,
transientKey: transientKey,
tracer: tracer,
cdc: cdc,
paramSpace: paramSpace,
accountKeeper: ak,
bankKeeper: bankKeeper,
stakingKeeper: sk,
feeMarketKeeper: fmk,
storeKey: storeKey,
transientKey: transientKey,
customPrecompiles: customPrecompiles,
evmConstructor: evmConstructor,
tracer: tracer,
}
}

Expand Down
17 changes: 10 additions & 7 deletions x/evm/keeper/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
ethermint "github.com/evmos/ethermint/types"
"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"
Expand Down Expand Up @@ -77,7 +78,7 @@ func (k *Keeper) NewEVM(
cfg *types.EVMConfig,
tracer vm.EVMLogger,
stateDB vm.StateDB,
) *vm.EVM {
) evm.EVM {
blockCtx := vm.BlockContext{
CanTransfer: core.CanTransfer,
Transfer: core.Transfer,
Expand All @@ -95,7 +96,7 @@ func (k *Keeper) NewEVM(
tracer = k.Tracer(ctx, msg, cfg.ChainConfig)
}
vmConfig := k.VMConfig(ctx, msg, cfg, tracer)
return vm.NewEVM(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig)
return k.evmConstructor(blockCtx, txCtx, stateDB, cfg.ChainConfig, vmConfig, k.customPrecompiles)
}

// VMConfig creates an EVM configuration from the debug setting and the extra EIPs enabled on the
Expand Down Expand Up @@ -366,17 +367,19 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context,
evm := k.NewEVM(ctx, msg, cfg, tracer, stateDB)

leftoverGas := msg.Gas()

// Allow the tracer captures the tx level events, mainly the gas consumption.
if evm.Config.Debug {
evm.Config.Tracer.CaptureTxStart(leftoverGas)
vmCfg := evm.Config()
if vmCfg.Debug {
vmCfg.Tracer.CaptureTxStart(leftoverGas)
defer func() {
evm.Config.Tracer.CaptureTxEnd(leftoverGas)
vmCfg.Tracer.CaptureTxEnd(leftoverGas)
}()
}

sender := vm.AccountRef(msg.From())
contractCreation := msg.To() == nil
isLondon := cfg.ChainConfig.IsLondon(evm.Context.BlockNumber)
isLondon := cfg.ChainConfig.IsLondon(evm.Context().BlockNumber)

intrinsicGas, err := k.GetEthIntrinsicGas(ctx, msg, cfg.ChainConfig, contractCreation)
if err != nil {
Expand All @@ -394,7 +397,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(), vm.ActivePrecompiles(rules), msg.AccessList())
stateDB.PrepareAccessList(msg.From(), msg.To(), evm.ActivePrecompiles(rules), msg.AccessList())
}

if contractCreation {
Expand Down
11 changes: 11 additions & 0 deletions x/evm/statedb/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,19 @@ 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
Expand Down
72 changes: 36 additions & 36 deletions x/evm/statedb/journal.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,21 @@ import (
"github.com/ethereum/go-ethereum/common"
)

// journalEntry is a modification entry in the state change journal that can be
// reverted on demand.
type journalEntry interface {
// revert undoes the changes introduced by this journal entry.
revert(*StateDB)
// JournalEntry is a modification entry in the state change journal that can be
// Reverted on demand.
type JournalEntry interface {
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
// Revert undoes the changes introduced by this journal entry.
Revert(*StateDB)

// dirtied returns the Ethereum address modified by this journal entry.
dirtied() *common.Address
// Dirtied returns the Ethereum address modified by this journal entry.
Dirtied() *common.Address
}

// journal contains the list of state modifications applied since the last state
// commit. These are tracked to be able to be reverted in the case of an execution
// exception or request for reversal.
type journal struct {
entries []journalEntry // Current changes tracked by the journal
entries []JournalEntry // Current changes tracked by the journal
dirties map[common.Address]int // Dirty accounts and the number of changes
}

Expand All @@ -64,22 +64,22 @@ func (j *journal) sortedDirties() []common.Address {
}

// append inserts a new modification entry to the end of the change journal.
func (j *journal) append(entry journalEntry) {
func (j *journal) append(entry JournalEntry) {
j.entries = append(j.entries, entry)
if addr := entry.dirtied(); addr != nil {
if addr := entry.Dirtied(); addr != nil {
j.dirties[*addr]++
}
}

// revert undoes a batch of journalled modifications along with any reverted
// Revert undoes a batch of journalled modifications along with any Reverted
// dirty handling too.
func (j *journal) revert(statedb *StateDB, snapshot int) {
func (j *journal) Revert(statedb *StateDB, snapshot int) {
for i := len(j.entries) - 1; i >= snapshot; i-- {
// Undo the changes made by the operation
j.entries[i].revert(statedb)
j.entries[i].Revert(statedb)

// Drop any dirty tracking induced by the change
if addr := j.entries[i].dirtied(); addr != nil {
if addr := j.entries[i].Dirtied(); addr != nil {
if j.dirties[*addr]--; j.dirties[*addr] == 0 {
delete(j.dirties, *addr)
}
Expand Down Expand Up @@ -141,83 +141,83 @@ type (
}
)

func (ch createObjectChange) revert(s *StateDB) {
func (ch createObjectChange) Revert(s *StateDB) {
delete(s.stateObjects, *ch.account)
}

func (ch createObjectChange) dirtied() *common.Address {
func (ch createObjectChange) Dirtied() *common.Address {
return ch.account
}

func (ch resetObjectChange) revert(s *StateDB) {
func (ch resetObjectChange) Revert(s *StateDB) {
s.setStateObject(ch.prev)
}

func (ch resetObjectChange) dirtied() *common.Address {
func (ch resetObjectChange) Dirtied() *common.Address {
return nil
}

func (ch suicideChange) revert(s *StateDB) {
func (ch suicideChange) Revert(s *StateDB) {
obj := s.getStateObject(*ch.account)
if obj != nil {
obj.suicided = ch.prev
obj.setBalance(ch.prevbalance)
}
}

func (ch suicideChange) dirtied() *common.Address {
func (ch suicideChange) Dirtied() *common.Address {
return ch.account
}

func (ch balanceChange) revert(s *StateDB) {
func (ch balanceChange) Revert(s *StateDB) {
s.getStateObject(*ch.account).setBalance(ch.prev)
}

func (ch balanceChange) dirtied() *common.Address {
func (ch balanceChange) Dirtied() *common.Address {
return ch.account
}

func (ch nonceChange) revert(s *StateDB) {
func (ch nonceChange) Revert(s *StateDB) {
s.getStateObject(*ch.account).setNonce(ch.prev)
}

func (ch nonceChange) dirtied() *common.Address {
func (ch nonceChange) Dirtied() *common.Address {
return ch.account
}

func (ch codeChange) revert(s *StateDB) {
func (ch codeChange) Revert(s *StateDB) {
s.getStateObject(*ch.account).setCode(common.BytesToHash(ch.prevhash), ch.prevcode)
}

func (ch codeChange) dirtied() *common.Address {
func (ch codeChange) Dirtied() *common.Address {
return ch.account
}

func (ch storageChange) revert(s *StateDB) {
func (ch storageChange) Revert(s *StateDB) {
s.getStateObject(*ch.account).setState(ch.key, ch.prevalue)
}

func (ch storageChange) dirtied() *common.Address {
func (ch storageChange) Dirtied() *common.Address {
return ch.account
}

func (ch refundChange) revert(s *StateDB) {
func (ch refundChange) Revert(s *StateDB) {
s.refund = ch.prev
}

func (ch refundChange) dirtied() *common.Address {
func (ch refundChange) Dirtied() *common.Address {
return nil
}

func (ch addLogChange) revert(s *StateDB) {
func (ch addLogChange) Revert(s *StateDB) {
s.logs = s.logs[:len(s.logs)-1]
}

func (ch addLogChange) dirtied() *common.Address {
func (ch addLogChange) Dirtied() *common.Address {
return nil
}

func (ch accessListAddAccountChange) revert(s *StateDB) {
func (ch accessListAddAccountChange) Revert(s *StateDB) {
/*
One important invariant here, is that whenever a (addr, slot) is added, if the
addr is not already present, the add causes two journal entries:
Expand All @@ -230,14 +230,14 @@ func (ch accessListAddAccountChange) revert(s *StateDB) {
s.accessList.DeleteAddress(*ch.address)
}

func (ch accessListAddAccountChange) dirtied() *common.Address {
func (ch accessListAddAccountChange) Dirtied() *common.Address {
return nil
}

func (ch accessListAddSlotChange) revert(s *StateDB) {
func (ch accessListAddSlotChange) Revert(s *StateDB) {
s.accessList.DeleteSlot(*ch.address, *ch.slot)
}

func (ch accessListAddSlotChange) dirtied() *common.Address {
func (ch accessListAddSlotChange) Dirtied() *common.Address {
return nil
}
2 changes: 1 addition & 1 deletion x/evm/statedb/statedb.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,7 +429,7 @@ func (s *StateDB) RevertToSnapshot(revid int) {
snapshot := s.validRevisions[idx].journalIndex

// Replay the journal to undo changes and remove invalidated snapshots
s.journal.revert(s, snapshot)
s.journal.Revert(s, snapshot)
s.validRevisions = s.validRevisions[:idx]
}

Expand Down
Loading