Skip to content

Commit

Permalink
feat: precompiled contract poc (#19)
Browse files Browse the repository at this point in the history
* feat: app level precompile

* feat: Add cache context for commit

* feat: revert if precompile call is failed

* chore: fix lint

* chore: Add IsSendEnableCoins bank keeper method to evm module interface

* chore: Add GetSupply bank keeper method to evm module interface

* chore: Remove GetSupply, IsSendEnableCoins from bankkeeper interface

* feat: add precompiled contract logs using cosmos event converter

* feat: Change DefaultActivePrecompiles to ActivePrecomiles for access list

* chore: Bump up go-ethereum

* chore: Bump up go-ethereum

* chore: Remove SendCoins from BankKeeper interface in evm module

* chore: fix lint

* chore: Bump up cosmos-sdk, go-ethereum

* chore: Bump up cosmos-sdk, go-ethereum

---------

Co-authored-by: jason song <[email protected]>
Co-authored-by: jasonsong0 <[email protected]>
  • Loading branch information
3 people authored Sep 11, 2024
1 parent c0430f3 commit 397c773
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 61 deletions.
13 changes: 11 additions & 2 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,16 @@ 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,
appCodec,
keys[evmtypes.StoreKey],
tkeys[evmtypes.TransientKey],
app.GetSubspace(evmtypes.ModuleName),
app.AccountKeeper,
app.BankKeeper,
app.StakingKeeper,
app.FeeMarketKeeper,
tracer,
nil,
)

// Create IBC Keeper
Expand Down Expand Up @@ -646,6 +653,8 @@ func (app *EthermintApp) BlockedAddrs() map[string]bool {
blockedAddrs[authtypes.NewModuleAddress(acc).String()] = !allowedReceivingModAcc[acc]
}

// TODO(dudong2): need to add precompiled contract address?

return blockedAddrs
}

Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ require (
replace (
github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76
github.com/confio/ics23/go => github.com/cosmos/cosmos-sdk/ics23/go v0.8.0
github.com/cosmos/cosmos-sdk => github.com/b-harvest/cosmos-sdk v0.0.0-20240911104149-d2486fbebf5f
github.com/ethereum/go-ethereum => github.com/b-harvest/go-ethereum v0.0.0-20240911104320-ceb46abda914
github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1
github.com/tendermint/tendermint => github.com/cometbft/cometbft v0.34.33
google.golang.org/grpc => google.golang.org/grpc v1.33.2
Expand Down
60 changes: 16 additions & 44 deletions go.sum

Large diffs are not rendered by default.

36 changes: 24 additions & 12 deletions x/evm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,28 @@ package keeper
import (
"math/big"

"github.com/tendermint/tendermint/libs/log"

"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
paramtypes "github.com/cosmos/cosmos-sdk/x/params/types"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
"github.com/tendermint/tendermint/libs/log"

ethermint "github.com/evmos/ethermint/types"
"github.com/evmos/ethermint/x/evm/statedb"
"github.com/evmos/ethermint/x/evm/types"
)

// CustomContractFn defines a custom precompiled contract generator with ctx, rules and returns a precompiled contract.
type CustomContractFn func(sdk.Context, params.Rules) vm.PrecompiledContract

// Keeper grants access to the EVM module state and implements the go-ethereum StateDB interface.
type Keeper struct {
// Protobuf codec
Expand Down Expand Up @@ -53,15 +58,21 @@ type Keeper struct {

// EVM Hooks for tx post-processing
hooks types.EvmHooks

customContractFns []CustomContractFn
}

// NewKeeper generates new evm module keeper
func NewKeeper(
cdc codec.BinaryCodec,
storeKey, transientKey sdk.StoreKey, paramSpace paramtypes.Subspace,
ak types.AccountKeeper, bankKeeper types.BankKeeper, sk types.StakingKeeper,
storeKey, transientKey sdk.StoreKey,
paramSpace paramtypes.Subspace,
ak types.AccountKeeper,
bankKeeper types.BankKeeper,
sk types.StakingKeeper,
fmk types.FeeMarketKeeper,
tracer string,
customContractFns []CustomContractFn,
) *Keeper {
// ensure evm module account is set
if addr := ak.GetModuleAddress(types.ModuleName); addr == nil {
Expand All @@ -75,15 +86,16 @@ 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,
tracer: tracer,
customContractFns: customContractFns,
}
}

Expand Down
26 changes: 24 additions & 2 deletions x/evm/keeper/state_transition.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package keeper

import (
"bytes"
"math"
"math/big"
"sort"

tmtypes "github.com/tendermint/tendermint/types"

Expand Down Expand Up @@ -93,7 +95,27 @@ 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)

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 _, fn := range k.customContractFns {
c := fn(ctx, rules)
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
}

// VMConfig creates an EVM configuration from the debug setting and the extra EIPs enabled on the
Expand Down Expand Up @@ -392,7 +414,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
14 changes: 14 additions & 0 deletions x/evm/statedb/journal.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
"sort"

"github.com/ethereum/go-ethereum/common"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// journalEntry is a modification entry in the state change journal that can be
Expand Down Expand Up @@ -139,6 +141,10 @@ type (
address *common.Address
slot *common.Hash
}
precompileChange struct {
ms sdk.MultiStore
es sdk.Events
}
)

func (ch createObjectChange) revert(s *StateDB) {
Expand Down Expand Up @@ -241,3 +247,11 @@ func (ch accessListAddSlotChange) revert(s *StateDB) {
func (ch accessListAddSlotChange) dirtied() *common.Address {
return nil
}

func (ch precompileChange) revert(s *StateDB) {
s.RevertWithMultiStoreSnapshot(ch.ms)
}

func (ch precompileChange) dirtied() *common.Address {
return nil
}
63 changes: 63 additions & 0 deletions x/evm/statedb/statedb.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
package statedb

import (
"errors"
"fmt"
"math/big"
"sort"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
)

type EventConverter = func(sdk.Event) (*ethtypes.Log, error)

// revision is the identifier of a version of state.
// it consists of an auto-increment id and a journal index.
// it's safer to use than using journal index alone.
Expand All @@ -32,6 +36,9 @@ type StateDB struct {
keeper Keeper
ctx sdk.Context

cacheCtx sdk.Context
writeCache func()

// Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
journal *journal
Expand Down Expand Up @@ -283,6 +290,57 @@ func (s *StateDB) setStateObject(object *stateObject) {
s.stateObjects[object.Address()] = object
}

func (s *StateDB) GetCacheContext() (sdk.Context, error) {
if s.writeCache == nil {
if s.ctx.MultiStore() == nil {
return s.ctx, errors.New("ctx has no multi store")
}
s.cacheCtx, s.writeCache = s.ctx.CacheContext()
}

return s.cacheCtx, nil
}

func (s *StateDB) MultiStoreSnapshot() (sdk.CacheMultiStore, error) {
ctx, err := s.GetCacheContext()
if err != nil { // means s.ctx.MultiStore() == nil
return nil, err
}

cms := ctx.MultiStore().(sdk.CacheMultiStore)
snapshot := cms.Copy()
return snapshot, nil
}

func (s *StateDB) RevertWithMultiStoreSnapshot(snapshot sdk.MultiStore) {
s.cacheCtx = s.cacheCtx.
WithMultiStore(snapshot).
WithEventManager(sdk.NewEventManager())
}

// If revert is occurred, the snapshot of journal is overwrited to MultiStore of ctx.
// The events is just for debug.
func (s *StateDB) PostPrecompileProcessing(snapshot sdk.MultiStore, events sdk.Events, contract common.Address, converter EventConverter) {
// convert native events to evm logs
if converter != nil && len(events) > 0 {
for _, event := range events {
log, err := converter(event)
if err != nil {
s.ctx.Logger().Error("failed to convert event", "err", err)
continue
}
if log == nil {
continue
}

log.Address = contract
s.AddLog(log)
}
}

s.journal.append(precompileChange{snapshot, events})
}

/*
* SETTERS
*/
Expand Down Expand Up @@ -436,6 +494,11 @@ 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 {
// write all store updates from precompile
if s.writeCache != nil {
s.writeCache()
}

for _, addr := range s.journal.sortedDirties() {
obj := s.stateObjects[addr]
if obj.suicided {
Expand Down
2 changes: 1 addition & 1 deletion x/evm/types/tracer.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,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)
Expand Down

0 comments on commit 397c773

Please sign in to comment.