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

Commit

Permalink
Allow evm to call native modules through logs
Browse files Browse the repository at this point in the history
Closes #416

comment

add txHash parameter

review suggestions

add hooks test
  • Loading branch information
yihuang committed Sep 1, 2021
1 parent 7f70429 commit 9f8f0dd
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 28 deletions.
33 changes: 33 additions & 0 deletions x/evm/keeper/hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package keeper

import (
"reflect"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/tharsis/ethermint/x/evm/types"
)

var (
_ types.EvmHooks = MultiEvmHooks{}
)

// MultiEvmHooks combine multiple evm hooks, all hook functions are run in array sequence
type MultiEvmHooks []types.EvmHooks

// NewMultiEvmHooks combine multiple evm hooks
func NewMultiEvmHooks(hooks ...types.EvmHooks) MultiEvmHooks {
return hooks
}

// PostTxProcessing delegate the call to underlying hooks
func (mh MultiEvmHooks) PostTxProcessing(ctx sdk.Context, txHash ethcmn.Hash, logs []*ethtypes.Log) error {
for i := range mh {
if err := mh[i].PostTxProcessing(ctx, txHash, logs); err != nil {
return sdkerrors.Wrapf(err, "EVM hook %s failed", reflect.TypeOf(mh[i]))
}
}
return nil
}
72 changes: 72 additions & 0 deletions x/evm/keeper/hooks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package keeper_test

import (
"errors"
"math/big"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"

ethermint "github.com/tharsis/ethermint/types"
"github.com/tharsis/ethermint/x/evm/keeper"
"github.com/tharsis/ethermint/x/evm/types"
)

// LogRecordHook records all the logs
type LogRecordHook struct {
Logs []*ethtypes.Log
}

func (dh *LogRecordHook) PostTxProcessing(ctx sdk.Context, txHash common.Hash, logs []*ethtypes.Log) error {
dh.Logs = logs
return nil
}

// FailureHook always fail
type FailureHook struct{}

func (dh FailureHook) PostTxProcessing(ctx sdk.Context, txHash common.Hash, logs []*ethtypes.Log) error {
return errors.New("post tx processing failed")
}
func (suite *KeeperTestSuite) TestEvmHooks() {
suite.SetupTest()
suite.Commit()

logRecordHook := LogRecordHook{}
suite.app.EvmKeeper.SetHooks(keeper.NewMultiEvmHooks(&logRecordHook))

k := suite.app.EvmKeeper

txHash := common.BigToHash(big.NewInt(1))

amt := sdk.Coins{ethermint.NewPhotonCoinInt64(100)}
err := suite.app.BankKeeper.MintCoins(suite.ctx, types.ModuleName, amt)
suite.Require().NoError(err)
err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, types.ModuleName, suite.address.Bytes(), amt)
suite.Require().NoError(err)

k.SetTxHashTransient(txHash)
k.AddLog(&ethtypes.Log{
Topics: []common.Hash{},
Address: suite.address,
})

logs := k.GetTxLogs(txHash)
suite.Require().Equal(1, len(logs))

err = k.PostTxProcessing(txHash, logs)
suite.Require().NoError(err)

suite.Require().Equal(1, len(logRecordHook.Logs))
}

func (suite *KeeperTestSuite) TestHookFailure() {
suite.SetupTest()
k := suite.app.EvmKeeper

// Test failure hook
suite.app.EvmKeeper.SetHooks(keeper.NewMultiEvmHooks(FailureHook{}))
err := k.PostTxProcessing(common.Hash{}, nil)
suite.Require().Error(err)
}
21 changes: 21 additions & 0 deletions x/evm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ type Keeper struct {
// trace EVM state transition execution. This value is obtained from the `--trace` flag.
// For more info check https://geth.ethereum.org/docs/dapp/tracing
debug bool

// EvmHooks
hooks types.EvmHooks
}

// NewKeeper generates new evm module keeper
Expand Down Expand Up @@ -416,3 +419,21 @@ func (k Keeper) ResetAccount(addr common.Address) {
k.DeleteCode(addr)
k.DeleteAccountStorage(addr)
}

// SetHooks sets the hooks for governance
func (k *Keeper) SetHooks(eh types.EvmHooks) *Keeper {
if k.hooks != nil {
panic("cannot set evm hooks twice")
}

k.hooks = eh
return k
}

// PostTxProcessing delegate the call to the hooks
func (k *Keeper) PostTxProcessing(txHash common.Hash, logs []*ethtypes.Log) error {
if k.hooks == nil {
return nil
}
return k.hooks.PostTxProcessing(k.Ctx(), txHash, logs)
}
28 changes: 0 additions & 28 deletions x/evm/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,8 @@ import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

"github.com/tharsis/ethermint/app"
Expand Down Expand Up @@ -150,13 +147,6 @@ func (suite *KeeperTestSuite) DoSetupTest(t require.TestingT) {
suite.clientCtx = client.Context{}.WithTxConfig(encodingConfig.TxConfig)
suite.ethSigner = ethtypes.LatestSignerForChainID(suite.app.EvmKeeper.ChainID())
suite.appCodec = encodingConfig.Marshaler

// mint some tokens to coinbase address
_, bankKeeper := suite.initKeepersWithmAccPerms()
require.NoError(t, err)
initCoin := sdk.NewCoins(sdk.NewCoin(suite.EvmDenom(), testTokens))
err = simapp.FundAccount(bankKeeper, suite.ctx, acc.GetAddress(), initCoin)
require.NoError(t, err)
}

func (suite *KeeperTestSuite) SetupTest() {
Expand Down Expand Up @@ -187,24 +177,6 @@ func (suite *KeeperTestSuite) Commit() {
suite.queryClient = types.NewQueryClient(queryHelper)
}

// initKeepersWithmAccPerms construct a bank keeper that can mint tokens out of thin air
func (suite *KeeperTestSuite) initKeepersWithmAccPerms() (authkeeper.AccountKeeper, bankkeeper.BaseKeeper) {
maccPerms := app.GetMaccPerms()

maccPerms[authtypes.Burner] = []string{authtypes.Burner}
maccPerms[authtypes.Minter] = []string{authtypes.Minter}
authKeeper := authkeeper.NewAccountKeeper(
suite.appCodec, suite.app.GetKey(types.StoreKey), suite.app.GetSubspace(types.ModuleName),
authtypes.ProtoBaseAccount, maccPerms,
)
keeper := bankkeeper.NewBaseKeeper(
suite.appCodec, suite.app.GetKey(types.StoreKey), authKeeper,
suite.app.GetSubspace(types.ModuleName), map[string]bool{},
)

return authKeeper, keeper
}

// DeployTestContract deploy a test erc20 contract and returns the contract address
func (suite *KeeperTestSuite) DeployTestContract(t require.TestingT, owner common.Address, supply *big.Int) common.Address {
ctx := sdk.WrapSDKContext(suite.ctx)
Expand Down
14 changes: 14 additions & 0 deletions x/evm/keeper/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT
panic("context stack shouldn't be dirty before apply message")
}

// Contains the tx processing and post processing in same scope
revision := k.Snapshot()

// pass false to execute in real mode, which do actual gas refunding
res, err := k.ApplyMessage(evm, msg, ethCfg, false)
if err != nil {
Expand All @@ -183,6 +186,17 @@ func (k *Keeper) ApplyTransaction(tx *ethtypes.Transaction) (*types.MsgEthereumT

res.Hash = txHash.Hex()
logs := k.GetTxLogs(txHash)

if !res.Failed() {
// Only call hooks if tx executed successfully.
if err = k.PostTxProcessing(txHash, logs); err != nil {
// If hooks return error, revert the whole tx.
k.RevertToSnapshot(revision)
res.VmError = types.ErrPostTxProcessing.Error()
k.Logger(ctx).Error("tx post processing failed", "error", err)
}
}

if len(logs) > 0 {
res.Logs = types.NewLogsFromEth(logs)
// Update transient block bloom filter
Expand Down
4 changes: 4 additions & 0 deletions x/evm/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ const (
codeErrInvalidBaseFee
)

var (
ErrPostTxProcessing = errors.New("failed to execute post processing")
)

var (
// ErrInvalidState returns an error resulting from an invalid Storage State.
ErrInvalidState = sdkerrors.Register(ModuleName, codeErrInvalidState, "invalid storage state")
Expand Down
11 changes: 11 additions & 0 deletions x/evm/types/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
)

// AccountKeeper defines the expected account keeper interface
Expand Down Expand Up @@ -33,3 +35,12 @@ type StakingKeeper interface {
GetHistoricalInfo(ctx sdk.Context, height int64) (stakingtypes.HistoricalInfo, bool)
GetValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) (validator stakingtypes.Validator, found bool)
}

// Event Hooks
// These can be utilized to customize evm transaction processing.

// EvmHooks event hooks for evm tx processing
type EvmHooks interface {
// Must be called after tx is processed, if failed, the whole evm transaction is reverted.
PostTxProcessing(ctx sdk.Context, txHash ethcmn.Hash, logs []*ethtypes.Log) error
}

0 comments on commit 9f8f0dd

Please sign in to comment.