From 629aea39d5527dcaeafd4b6f6472da3b8649c8aa Mon Sep 17 00:00:00 2001 From: Oleg Nikonychev Date: Thu, 3 Oct 2024 16:31:50 +0400 Subject: [PATCH 1/2] refactor(evm): converted untyped event to typed and cleaned up (#2053) * refactor(evm): made all eth events typed * refactor(evm): tests for evm events upgraded * chore: cleanup * chore: cleanup * chore: cleanup * chore: cosmetic fixes --------- Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> --- CHANGELOG.md | 1 + app/evmante/evmante_emit_event.go | 25 +- app/evmante/evmante_emit_event_test.go | 6 +- eth/indexer/evm_tx_indexer.go | 2 +- eth/indexer/evm_tx_indexer_test.go | 83 +++--- eth/rpc/backend/account_info_test.go | 2 +- eth/rpc/backend/backend_suite_test.go | 5 +- eth/rpc/backend/blocks.go | 16 +- eth/rpc/backend/tx_info.go | 10 +- eth/rpc/backend/utils.go | 29 +- eth/rpc/events.go | 265 ------------------ eth/rpc/events_parser.go | 176 ++++++++++++ eth/rpc/events_parser_test.go | 201 ++++++++++++++ eth/rpc/events_test.go | 193 ------------- eth/rpc/rpcapi/eth_api_test.go | 32 ++- eth/rpc/rpcapi/eth_filters_api.go | 2 +- eth/rpc/rpcapi/filter_utils.go | 15 +- proto/eth/evm/v1/events.proto | 10 - x/evm/events.go | 115 +++++++- x/evm/events.pb.go | 371 +++---------------------- x/evm/keeper/msg_server.go | 182 ++++++------ x/evm/msg.go | 11 +- x/evm/msg_test.go | 1 - 23 files changed, 730 insertions(+), 1023 deletions(-) delete mode 100644 eth/rpc/events.go create mode 100644 eth/rpc/events_parser.go create mode 100644 eth/rpc/events_parser_test.go delete mode 100644 eth/rpc/events_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 194df558a..737577fb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -123,6 +123,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#2039](https://github.com/NibiruChain/nibiru/pull/2039) - refactor(rpc-backend): remove unnecessary interface code - [#2044](https://github.com/NibiruChain/nibiru/pull/2044) - feat(evm): evm tx indexer service implemented - [#2045](https://github.com/NibiruChain/nibiru/pull/2045) - test(evm): backend tests with test network and real txs +- [#2053](https://github.com/NibiruChain/nibiru/pull/2053) - refactor(evm): converted untyped event to typed and cleaned up - [#2054](https://github.com/NibiruChain/nibiru/pull/2054) - feat(evm-precompile): Precompile for one-way EVM calls to invoke/execute Wasm contracts. #### Dapp modules: perp, spot, oracle, etc diff --git a/app/evmante/evmante_emit_event.go b/app/evmante/evmante_emit_event.go index 01e178d6f..a9c656284 100644 --- a/app/evmante/evmante_emit_event.go +++ b/app/evmante/evmante_emit_event.go @@ -41,24 +41,17 @@ func (eeed EthEmitEventDecorator) AnteHandle( msg, (*evm.MsgEthereumTx)(nil), ) } - - // emit ethereum tx hash as an event so that it can be indexed by - // Tendermint for query purposes it's emitted in ante handler, so we can - // query failed transaction (out of block gas limit). + // Untyped event "pending_ethereum_tx" is emitted for then indexing purposes. + // Tendermint tx_search can only search the untyped events. + // TxHash and TxIndex values are exposed in the ante handler (before the actual tx execution) + // to allow searching for txs which are failed due to "out of block gas limit" error. ctx.EventManager().EmitEvent( sdk.NewEvent( - evm.EventTypeEthereumTx, - sdk.NewAttribute(evm.AttributeKeyEthereumTxHash, msgEthTx.Hash), - sdk.NewAttribute( - evm.AttributeKeyTxIndex, strconv.FormatUint(txIndex+uint64(i), - 10, - ), - ), // #nosec G701 - // TODO: fix: It's odd that each event is emitted twice. Migrate to typed - // events and change EVM indexer to align. - // sdk.NewAttribute("emitted_from", "EthEmitEventDecorator"), - )) + evm.PendingEthereumTxEvent, + sdk.NewAttribute(evm.PendingEthereumTxEventAttrEthHash, msgEthTx.Hash), + sdk.NewAttribute(evm.PendingEthereumTxEventAttrIndex, strconv.FormatUint(txIndex+uint64(i), 10)), + ), + ) } - return next(ctx, tx, simulate) } diff --git a/app/evmante/evmante_emit_event_test.go b/app/evmante/evmante_emit_event_test.go index ff501e5ec..855165450 100644 --- a/app/evmante/evmante_emit_event_test.go +++ b/app/evmante/evmante_emit_event_test.go @@ -59,19 +59,19 @@ func (s *TestSuite) TestEthEmitEventDecorator() { s.Require().Greater(len(events), 0) event := events[len(events)-1] - s.Require().Equal(evm.EventTypeEthereumTx, event.Type) + s.Require().Equal(evm.PendingEthereumTxEvent, event.Type) // Convert tx to msg to get hash txMsg, ok := tx.GetMsgs()[0].(*evm.MsgEthereumTx) s.Require().True(ok) // TX hash attr must present - attr, ok := event.GetAttribute(evm.AttributeKeyEthereumTxHash) + attr, ok := event.GetAttribute(evm.PendingEthereumTxEventAttrEthHash) s.Require().True(ok, "tx hash attribute not found") s.Require().Equal(txMsg.Hash, attr.Value) // TX index attr must present - attr, ok = event.GetAttribute(evm.AttributeKeyTxIndex) + attr, ok = event.GetAttribute(evm.PendingEthereumTxEventAttrIndex) s.Require().True(ok, "tx index attribute not found") s.Require().Equal("0", attr.Value) }) diff --git a/eth/indexer/evm_tx_indexer.go b/eth/indexer/evm_tx_indexer.go index 83f5c8213..0b1ba8966 100644 --- a/eth/indexer/evm_tx_indexer.go +++ b/eth/indexer/evm_tx_indexer.go @@ -46,7 +46,7 @@ func NewEVMTxIndexer(db dbm.DB, logger log.Logger, clientCtx client.Context) *EV // - Iterates over all the Txs in Block // - Parses eth Tx infos from cosmos-sdk events for every TxResult // - Iterates over all the messages of the Tx -// - Builds and stores a indexer.TxResult based on parsed events for every message +// - Builds and stores indexer.TxResult based on parsed events for every message func (indexer *EVMTxIndexer) IndexBlock(block *tmtypes.Block, txResults []*abci.ResponseDeliverTx) error { height := block.Header.Height diff --git a/eth/indexer/evm_tx_indexer_test.go b/eth/indexer/evm_tx_indexer_test.go index 83931dcba..17e46bf2c 100644 --- a/eth/indexer/evm_tx_indexer_test.go +++ b/eth/indexer/evm_tx_indexer_test.go @@ -4,7 +4,6 @@ import ( "math/big" "testing" - "cosmossdk.io/simapp/params" dbm "github.com/cometbft/cometbft-db" abci "github.com/cometbft/cometbft/abci/types" tmlog "github.com/cometbft/cometbft/libs/log" @@ -17,7 +16,6 @@ import ( "github.com/NibiruChain/nibiru/v2/app" "github.com/NibiruChain/nibiru/v2/eth" "github.com/NibiruChain/nibiru/v2/eth/crypto/ethsecp256k1" - evmenc "github.com/NibiruChain/nibiru/v2/eth/encoding" "github.com/NibiruChain/nibiru/v2/eth/indexer" "github.com/NibiruChain/nibiru/v2/x/evm" evmtest "github.com/NibiruChain/nibiru/v2/x/evm/evmtest" @@ -50,16 +48,16 @@ func TestEVMTxIndexer(t *testing.T) { WithCodec(encCfg.Codec) // build cosmos-sdk wrapper tx - tmTx, err := tx.BuildTx(clientCtx.TxConfig.NewTxBuilder(), eth.EthBaseDenom) + validEVMTx, err := tx.BuildTx(clientCtx.TxConfig.NewTxBuilder(), eth.EthBaseDenom) require.NoError(t, err) - txBz, err := clientCtx.TxConfig.TxEncoder()(tmTx) + validEVMTxBz, err := clientCtx.TxConfig.TxEncoder()(validEVMTx) require.NoError(t, err) // build an invalid wrapper tx builder := clientCtx.TxConfig.NewTxBuilder() require.NoError(t, builder.SetMsgs(tx)) - tmTx2 := builder.GetTx() - txBz2, err := clientCtx.TxConfig.TxEncoder()(tmTx2) + invalidTx := builder.GetTx() + invalidTxBz, err := clientCtx.TxConfig.TxEncoder()(invalidTx) require.NoError(t, err) testCases := []struct { @@ -69,50 +67,58 @@ func TestEVMTxIndexer(t *testing.T) { expSuccess bool }{ { - "success, format 1", - &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz}}}, + "happy, only pending_ethereum_tx presents", + &tmtypes.Block{ + Header: tmtypes.Header{Height: 1}, + Data: tmtypes.Data{Txs: []tmtypes.Tx{validEVMTxBz}}, + }, []*abci.ResponseDeliverTx{ { Code: 0, Events: []abci.Event{ - {Type: evm.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ - {Key: "ethereumTxHash", Value: txHash.Hex()}, - {Key: "txIndex", Value: "0"}, - {Key: "amount", Value: "1000"}, - {Key: "txGasUsed", Value: "21000"}, - {Key: "txHash", Value: ""}, - {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, - }}, + { + Type: evm.PendingEthereumTxEvent, + Attributes: []abci.EventAttribute{ + {Key: evm.PendingEthereumTxEventAttrEthHash, Value: txHash.Hex()}, + {Key: evm.PendingEthereumTxEventAttrIndex, Value: "0"}, + }, + }, }, }, }, true, }, { - "success, format 2", - &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz}}}, + "happy: code 0, pending_ethereum_tx and typed EventEthereumTx present", + &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{validEVMTxBz}}}, []*abci.ResponseDeliverTx{ { Code: 0, Events: []abci.Event{ - {Type: evm.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ - {Key: "ethereumTxHash", Value: txHash.Hex()}, - {Key: "txIndex", Value: "0"}, - }}, - {Type: evm.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ - {Key: "amount", Value: "1000"}, - {Key: "txGasUsed", Value: "21000"}, - {Key: "txHash", Value: "14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57"}, - {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, - }}, + { + Type: evm.PendingEthereumTxEvent, + Attributes: []abci.EventAttribute{ + {Key: evm.PendingEthereumTxEventAttrEthHash, Value: txHash.Hex()}, + {Key: evm.PendingEthereumTxEventAttrIndex, Value: "0"}, + }, + }, + { + Type: evm.TypeUrlEventEthereumTx, + Attributes: []abci.EventAttribute{ + {Key: "amount", Value: `"1000"`}, + {Key: "gas_used", Value: `"21000"`}, + {Key: "index", Value: `"0"`}, + {Key: "hash", Value: `"14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57"`}, + }, + }, }, }, }, true, }, { - "success, exceed block gas limit", - &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz}}}, + "happy: code 11, exceed block gas limit", + &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{validEVMTxBz}}}, []*abci.ResponseDeliverTx{ { Code: 11, @@ -123,8 +129,8 @@ func TestEVMTxIndexer(t *testing.T) { true, }, { - "fail, failed eth tx", - &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz}}}, + "sad: failed eth tx", + &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{validEVMTxBz}}}, []*abci.ResponseDeliverTx{ { Code: 15, @@ -135,8 +141,8 @@ func TestEVMTxIndexer(t *testing.T) { false, }, { - "fail, invalid events", - &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz}}}, + "sad: invalid events", + &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{validEVMTxBz}}}, []*abci.ResponseDeliverTx{ { Code: 0, @@ -146,8 +152,8 @@ func TestEVMTxIndexer(t *testing.T) { false, }, { - "fail, not eth tx", - &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{txBz2}}}, + "sad: not eth tx", + &tmtypes.Block{Header: tmtypes.Header{Height: 1}, Data: tmtypes.Data{Txs: []tmtypes.Tx{invalidTxBz}}}, []*abci.ResponseDeliverTx{ { Code: 0, @@ -192,8 +198,3 @@ func TestEVMTxIndexer(t *testing.T) { }) } } - -// MakeEncodingConfig creates the EncodingConfig -func MakeEncodingConfig() params.EncodingConfig { - return evmenc.MakeConfig(app.ModuleBasics) -} diff --git a/eth/rpc/backend/account_info_test.go b/eth/rpc/backend/account_info_test.go index 109cc3d49..66491320a 100644 --- a/eth/rpc/backend/account_info_test.go +++ b/eth/rpc/backend/account_info_test.go @@ -195,7 +195,7 @@ func generateStorageKey(key gethcommon.Address, slot uint64) string { // Concatenate key and slot data := append(keyBytes, slotBytes...) - // Hash the data using Keccak256 + // Compute the data hash using Keccak256 hash := sha3.NewLegacyKeccak256() hash.Write(data) return gethcommon.BytesToHash(hash.Sum(nil)).Hex() diff --git a/eth/rpc/backend/backend_suite_test.go b/eth/rpc/backend/backend_suite_test.go index 0623557a5..1ba9bd03e 100644 --- a/eth/rpc/backend/backend_suite_test.go +++ b/eth/rpc/backend/backend_suite_test.go @@ -173,7 +173,10 @@ func WaitForReceipt(s *BackendSuite, txHash gethcommon.Hash) (*big.Int, *gethcom for { receipt, err := s.backend.GetTransactionReceipt(txHash) - if err == nil { + if err != nil { + return nil, nil + } + if receipt != nil { return receipt.BlockNumber, &receipt.BlockHash } select { diff --git a/eth/rpc/backend/blocks.go b/eth/rpc/backend/blocks.go index 2b90c55c6..d688415d0 100644 --- a/eth/rpc/backend/blocks.go +++ b/eth/rpc/backend/blocks.go @@ -6,7 +6,6 @@ import ( "math" "math/big" "strconv" - "strings" tmrpcclient "github.com/cometbft/cometbft/rpc/client" tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" @@ -334,18 +333,13 @@ func (b *Backend) BlockBloom(blockRes *tmrpctypes.ResultBlockResults) (gethcore. if event.Type != msgType { continue } - - for _, attr := range event.Attributes { - if attr.Key == evm.AttributeKeyEthereumBloom { - return gethcore.BytesToBloom( - hexutils.HexToBytes( // Bloom stores hex bytes - strings.ReplaceAll(attr.Value, `"`, ""), // Unquote typed event - ), - ), nil - } + blockBloomEvent, err := evm.EventBlockBloomFromABCIEvent(event) + if err != nil { + continue } + return gethcore.BytesToBloom(hexutils.HexToBytes(blockBloomEvent.Bloom)), nil } - return gethcore.Bloom{}, errors.New("block bloom event is not found") + return gethcore.Bloom{}, errors.New(msgType + " not found in end block results") } // RPCBlockFromTendermintBlock returns a JSON-RPC compatible Ethereum block from a diff --git a/eth/rpc/backend/tx_info.go b/eth/rpc/backend/tx_info.go index 9a5c85e09..8d4569d16 100644 --- a/eth/rpc/backend/tx_info.go +++ b/eth/rpc/backend/tx_info.go @@ -7,7 +7,6 @@ import ( "math/big" errorsmod "cosmossdk.io/errors" - tmrpcclient "github.com/cometbft/cometbft/rpc/client" tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -307,7 +306,8 @@ func (b *Backend) GetTxByEthHash(hash gethcommon.Hash) (*eth.TxResult, error) { } // fallback to tendermint tx evmTxIndexer - query := fmt.Sprintf("%s.%s='%s'", evm.TypeMsgEthereumTx, evm.AttributeKeyEthereumTxHash, hash.Hex()) + query := fmt.Sprintf("%s.%s='%s'", evm.PendingEthereumTxEvent, evm.PendingEthereumTxEventAttrEthHash, hash.Hex()) + txResult, err := b.queryTendermintTxIndexer(query, func(txs *rpc.ParsedTxs) *rpc.ParsedTx { return txs.GetTxByHash(hash) }) @@ -326,8 +326,10 @@ func (b *Backend) GetTxByTxIndex(height int64, index uint) (*eth.TxResult, error // fallback to tendermint tx evmTxIndexer query := fmt.Sprintf("tx.height=%d AND %s.%s=%d", - height, evm.TypeMsgEthereumTx, - evm.AttributeKeyTxIndex, index, + height, + evm.PendingEthereumTxEvent, + evm.PendingEthereumTxEventAttrIndex, + index, ) txResult, err := b.queryTendermintTxIndexer(query, func(txs *rpc.ParsedTxs) *rpc.ParsedTx { return txs.GetTxByTxIndex(int(index)) // #nosec G701 -- checked for int overflow already diff --git a/eth/rpc/backend/utils.go b/eth/rpc/backend/utils.go index 7083354c7..0f8506abd 100644 --- a/eth/rpc/backend/utils.go +++ b/eth/rpc/backend/utils.go @@ -8,8 +8,10 @@ import ( "sort" "strings" + "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/gogoproto/proto" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -212,7 +214,7 @@ func (b *Backend) retrieveEVMTxFeesFromBlock( func AllTxLogsFromEvents(events []abci.Event) ([][]*gethcore.Log, error) { allLogs := make([][]*gethcore.Log, 0, 4) for _, event := range events { - if event.Type != evm.EventTypeTxLog { + if event.Type != proto.MessageName(new(evm.EventTxLog)) { continue } @@ -229,7 +231,7 @@ func AllTxLogsFromEvents(events []abci.Event) ([][]*gethcore.Log, error) { // TxLogsFromEvents parses ethereum logs from cosmos events for specific msg index func TxLogsFromEvents(events []abci.Event, msgIndex int) ([]*gethcore.Log, error) { for _, event := range events { - if event.Type != evm.EventTypeTxLog { + if event.Type != proto.MessageName(new(evm.EventTxLog)) { continue } @@ -246,20 +248,19 @@ func TxLogsFromEvents(events []abci.Event, msgIndex int) ([]*gethcore.Log, error // ParseTxLogsFromEvent parse tx logs from one event func ParseTxLogsFromEvent(event abci.Event) ([]*gethcore.Log, error) { - logs := make([]*evm.Log, 0, len(event.Attributes)) - for _, attr := range event.Attributes { - if attr.Key != evm.AttributeKeyTxLog { - continue - } - - var log evm.Log - if err := json.Unmarshal([]byte(attr.Value), &log); err != nil { - return nil, err + eventTxLog, err := evm.EventTxLogFromABCIEvent(event) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse event tx log") + } + var evmLogs []*evm.Log + for _, logString := range eventTxLog.TxLogs { + var evmLog evm.Log + if err = json.Unmarshal([]byte(logString), &evmLog); err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal event tx log") } - - logs = append(logs, &log) + evmLogs = append(evmLogs, &evmLog) } - return evm.LogsToEthereum(logs), nil + return evm.LogsToEthereum(evmLogs), nil } // ShouldIgnoreGasUsed returns true if the gasUsed in result should be ignored diff --git a/eth/rpc/events.go b/eth/rpc/events.go deleted file mode 100644 index 8c0d8cacf..000000000 --- a/eth/rpc/events.go +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright (c) 2023-2024 Nibi, Inc. -package rpc - -import ( - "fmt" - "strconv" - - abci "github.com/cometbft/cometbft/abci/types" - tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" - - "github.com/NibiruChain/nibiru/v2/eth" - "github.com/NibiruChain/nibiru/v2/x/evm" -) - -// EventFormat is an enum type for an ethereum tx event. Each event format -// variant has json-rpc logic to make it so that clients using either format will -// be compatible, meaning nodes will able to sync without restarting from the -// first block. -type EventFormat int - -const ( - eventFormatUnknown EventFormat = iota - - // Event Format 1 - // ``` - // ethereum_tx(amount, ethereumTxHash, [txIndex, txGasUsed], txHash, [receipient], ethereumTxFailed) - // tx_log(txLog, txLog, ...) - // ethereum_tx(amount, ethereumTxHash, [txIndex, txGasUsed], txHash, [receipient], ethereumTxFailed) - // tx_log(txLog, txLog, ...) - // ... - // ``` - eventFormat1 - - // Event Format 2 - // ``` - // ethereum_tx(ethereumTxHash, txIndex) - // ethereum_tx(ethereumTxHash, txIndex) - // ... - // ethereum_tx(amount, ethereumTxHash, txIndex, txGasUsed, txHash, [receipient], ethereumTxFailed) - // tx_log(txLog, txLog, ...) - // ethereum_tx(amount, ethereumTxHash, txIndex, txGasUsed, txHash, [receipient], ethereumTxFailed) - // tx_log(txLog, txLog, ...) - // ... - // ``` - // If the transaction exceeds block gas limit, it only emits the first part. - eventFormat2 -) - -// ParsedTx is eth tx info parsed from ABCI events. Each `ParsedTx` corresponds -// to one eth tx msg ([evm.MsgEthereumTx]). -type ParsedTx struct { - MsgIndex int - - // the following fields are parsed from events - - Hash common.Hash - // -1 means uninitialized - EthTxIndex int32 - GasUsed uint64 - Failed bool -} - -// NewParsedTx initialize a ParsedTx -func NewParsedTx(msgIndex int) ParsedTx { - return ParsedTx{MsgIndex: msgIndex, EthTxIndex: -1} -} - -// ParsedTxs is the tx infos parsed from eth tx events. -type ParsedTxs struct { - // one item per message - Txs []ParsedTx - // map tx hash to msg index - TxHashes map[common.Hash]int -} - -// ParseTxResult: parses eth tx info from the ABCI events of Eth tx msgs -// ([evm.MsgEthereumTx]). It supports each [EventFormat]. -func ParseTxResult( - result *abci.ResponseDeliverTx, tx sdk.Tx, -) (*ParsedTxs, error) { - format := eventFormatUnknown - // the index of current ethereum_tx event in format 1 or the second part of format 2 - eventIndex := -1 - - p := &ParsedTxs{ - TxHashes: make(map[common.Hash]int), - } - for _, event := range result.Events { - if event.Type != evm.EventTypeEthereumTx { - continue - } - - if format == eventFormatUnknown { - // discover the format version by inspect the first ethereum_tx event. - if len(event.Attributes) > 2 { - format = eventFormat1 - } else { - format = eventFormat2 - } - } - - if len(event.Attributes) == 2 { - // the first part of format 2 - if err := p.newTx(event.Attributes); err != nil { - return nil, err - } - } else { - // format 1 or second part of format 2 - eventIndex++ - if format == eventFormat1 { - // append tx - if err := p.newTx(event.Attributes); err != nil { - return nil, err - } - } else { - // the second part of format 2, update tx fields - if err := p.updateTx(eventIndex, event.Attributes); err != nil { - return nil, err - } - } - } - } - - // some old versions miss some events, fill it with tx result - gasUsed := uint64(result.GasUsed) // #nosec G701 - if len(p.Txs) == 1 { - p.Txs[0].GasUsed = gasUsed - } - - // this could only happen if tx exceeds block gas limit - if result.Code != 0 && tx != nil { - for i := 0; i < len(p.Txs); i++ { - p.Txs[i].Failed = true - - // replace gasUsed with gasLimit because that's what's actually deducted. - gasLimit := tx.GetMsgs()[i].(*evm.MsgEthereumTx).GetGas() - p.Txs[i].GasUsed = gasLimit - } - } - return p, nil -} - -// ParseTxIndexerResult parse tm tx result to a format compatible with the custom tx indexer. -func ParseTxIndexerResult( - txResult *tmrpctypes.ResultTx, tx sdk.Tx, getter func(*ParsedTxs) *ParsedTx, -) (*eth.TxResult, error) { - txs, err := ParseTxResult(&txResult.TxResult, tx) - if err != nil { - return nil, fmt.Errorf("failed to parse tx events: block %d, index %d, %v", txResult.Height, txResult.Index, err) - } - - parsedTx := getter(txs) - if parsedTx == nil { - return nil, fmt.Errorf("ethereum tx not found in msgs: block %d, index %d", txResult.Height, txResult.Index) - } - index := uint32(parsedTx.MsgIndex) // #nosec G701 - return ð.TxResult{ - Height: txResult.Height, - TxIndex: txResult.Index, - MsgIndex: index, - EthTxIndex: parsedTx.EthTxIndex, - Failed: parsedTx.Failed, - GasUsed: parsedTx.GasUsed, - CumulativeGasUsed: txs.AccumulativeGasUsed(parsedTx.MsgIndex), - }, nil -} - -// newTx parse a new tx from events, called during parsing. -func (p *ParsedTxs) newTx(attrs []abci.EventAttribute) error { - msgIndex := len(p.Txs) - tx := NewParsedTx(msgIndex) - if err := fillTxAttributes(&tx, attrs); err != nil { - return err - } - p.Txs = append(p.Txs, tx) - p.TxHashes[tx.Hash] = msgIndex - return nil -} - -// updateTx updates an exiting tx from events, called during parsing. -// In event format 2, we update the tx with the attributes of the second `ethereum_tx` event, -func (p *ParsedTxs) updateTx(eventIndex int, attrs []abci.EventAttribute) error { - tx := NewParsedTx(eventIndex) - if err := fillTxAttributes(&tx, attrs); err != nil { - return err - } - if tx.Hash != p.Txs[eventIndex].Hash { - // if hash is different, index the new one too - p.TxHashes[tx.Hash] = eventIndex - } - // override the tx because the second event is more trustworthy - p.Txs[eventIndex] = tx - return nil -} - -// GetTxByHash find ParsedTx by tx hash, returns nil if not exists. -func (p *ParsedTxs) GetTxByHash(hash common.Hash) *ParsedTx { - if idx, ok := p.TxHashes[hash]; ok { - return &p.Txs[idx] - } - return nil -} - -// GetTxByMsgIndex returns ParsedTx by msg index -func (p *ParsedTxs) GetTxByMsgIndex(i int) *ParsedTx { - if i < 0 || i >= len(p.Txs) { - return nil - } - return &p.Txs[i] -} - -// GetTxByTxIndex returns ParsedTx by tx index -func (p *ParsedTxs) GetTxByTxIndex(txIndex int) *ParsedTx { - if len(p.Txs) == 0 { - return nil - } - // assuming the `EthTxIndex` increase continuously, - // convert TxIndex to MsgIndex by subtract the begin TxIndex. - msgIndex := txIndex - int(p.Txs[0].EthTxIndex) - // GetTxByMsgIndex will check the bound - return p.GetTxByMsgIndex(msgIndex) -} - -// AccumulativeGasUsed calculates the accumulated gas used within the batch of txs -func (p *ParsedTxs) AccumulativeGasUsed(msgIndex int) (result uint64) { - for i := 0; i <= msgIndex; i++ { - result += p.Txs[i].GasUsed - } - return result -} - -// fillTxAttribute parse attributes by name, less efficient than hardcode the -// index, but more stable against event format changes. -func fillTxAttribute(tx *ParsedTx, key string, value string) error { - switch key { - case evm.AttributeKeyEthereumTxHash: - tx.Hash = common.HexToHash(value) - case evm.AttributeKeyTxIndex: - txIndex, err := strconv.ParseUint(value, 10, 31) // #nosec G701 - if err != nil { - return err - } - tx.EthTxIndex = int32(txIndex) // #nosec G701 - case evm.AttributeKeyTxGasUsed: - gasUsed, err := strconv.ParseUint(value, 10, 64) - if err != nil { - return err - } - tx.GasUsed = gasUsed - case evm.AttributeKeyEthereumTxFailed: - tx.Failed = len(value) > 0 - } - return nil -} - -func fillTxAttributes(tx *ParsedTx, attrs []abci.EventAttribute) error { - for _, attr := range attrs { - if err := fillTxAttribute(tx, attr.Key, attr.Value); err != nil { - return err - } - } - return nil -} diff --git a/eth/rpc/events_parser.go b/eth/rpc/events_parser.go new file mode 100644 index 000000000..2c334ef7b --- /dev/null +++ b/eth/rpc/events_parser.go @@ -0,0 +1,176 @@ +// Copyright (c) 2023-2024 Nibi, Inc. +package rpc + +import ( + "errors" + "fmt" + "strconv" + + abci "github.com/cometbft/cometbft/abci/types" + tmrpctypes "github.com/cometbft/cometbft/rpc/core/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/gogoproto/proto" + gethcommon "github.com/ethereum/go-ethereum/common" + + "github.com/NibiruChain/nibiru/v2/eth" + "github.com/NibiruChain/nibiru/v2/x/evm" +) + +// ParsedTx is eth tx info parsed from ABCI events. Each `ParsedTx` corresponds +// to one eth tx msg ([evm.MsgEthereumTx]). +type ParsedTx struct { + MsgIndex int + + // the following fields are parsed from events + EthHash gethcommon.Hash + + EthTxIndex int32 // -1 means uninitialized + GasUsed uint64 + Failed bool +} + +// ParsedTxs is the tx infos parsed from eth tx events. +type ParsedTxs struct { + // one item per message + Txs []ParsedTx + // map tx hash to msg index + TxHashes map[gethcommon.Hash]int +} + +// ParseTxResult parses eth tx info from the ABCI events of Eth tx msgs +func ParseTxResult(result *abci.ResponseDeliverTx, tx sdk.Tx) (*ParsedTxs, error) { + eventTypePendingEthereumTx := evm.PendingEthereumTxEvent + eventTypeEthereumTx := proto.MessageName((*evm.EventEthereumTx)(nil)) + + // Parsed txs is the structure being populated from the events + // So far (until we allow ethereum_txs as cosmos tx messages) it'll have single tx + parsedTxs := &ParsedTxs{ + Txs: make([]ParsedTx, 0), + TxHashes: make(map[gethcommon.Hash]int), + } + + // msgIndex counts only ethereum tx messages. + msgIndex := -1 + for _, event := range result.Events { + // Pending tx event could be single if tx didn't succeed + if event.Type == eventTypePendingEthereumTx { + msgIndex++ + ethHash, txIndex, err := evm.GetEthHashAndIndexFromPendingEthereumTxEvent(event) + if err != nil { + return nil, err + } + pendingTx := ParsedTx{ + MsgIndex: msgIndex, + EthTxIndex: txIndex, + EthHash: ethHash, + } + parsedTxs.Txs = append(parsedTxs.Txs, pendingTx) + parsedTxs.TxHashes[ethHash] = msgIndex + } else if event.Type == eventTypeEthereumTx { // Full event replaces the pending tx + eventEthereumTx, err := evm.EventEthereumTxFromABCIEvent(event) + if err != nil { + return nil, err + } + ethTxIndexFromEvent, err := strconv.ParseUint(eventEthereumTx.Index, 10, 31) + if err != nil { + return nil, fmt.Errorf("failed to parse EthTxIndex from event: %w", err) + } + gasUsed, err := strconv.ParseUint(eventEthereumTx.GasUsed, 10, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse GasUsed from event: %w", err) + } + committedTx := ParsedTx{ + MsgIndex: msgIndex, + EthTxIndex: int32(ethTxIndexFromEvent), + EthHash: gethcommon.HexToHash(eventEthereumTx.EthHash), + GasUsed: gasUsed, + Failed: len(eventEthereumTx.EthTxFailed) > 0, + } + // replace pending tx with committed tx + if msgIndex >= 0 && len(parsedTxs.Txs) == msgIndex+1 { + parsedTxs.Txs[msgIndex] = committedTx + } else { + // EventEthereumTx without EventPendingEthereumTx + return nil, errors.New("EventEthereumTx without pending_ethereum_tx event") + } + } + } + + // this could only happen if tx exceeds block gas limit + if result.Code != 0 && tx != nil { + for i := 0; i < len(parsedTxs.Txs); i++ { + parsedTxs.Txs[i].Failed = true + + // replace gasUsed with gasLimit because that's what's actually deducted. + msgEthereumTx, ok := tx.GetMsgs()[i].(*evm.MsgEthereumTx) + if !ok { + return nil, fmt.Errorf("unexpected message type at index %d", i) + } + gasLimit := msgEthereumTx.GetGas() + parsedTxs.Txs[i].GasUsed = gasLimit + } + } + return parsedTxs, nil +} + +// ParseTxIndexerResult parse tm tx result to a format compatible with the custom tx indexer. +func ParseTxIndexerResult(txResult *tmrpctypes.ResultTx, tx sdk.Tx, getter func(*ParsedTxs) *ParsedTx) (*eth.TxResult, error) { + txs, err := ParseTxResult(&txResult.TxResult, tx) + if err != nil { + return nil, fmt.Errorf("failed to parse tx events: block %d, index %d, %v", txResult.Height, txResult.Index, err) + } + + parsedTx := getter(txs) + if parsedTx == nil { + return nil, fmt.Errorf("ethereum tx not found in msgs: block %d, index %d", txResult.Height, txResult.Index) + } + index := uint32(parsedTx.MsgIndex) // #nosec G701 + return ð.TxResult{ + Height: txResult.Height, + TxIndex: txResult.Index, + MsgIndex: index, + EthTxIndex: parsedTx.EthTxIndex, + Failed: parsedTx.Failed, + GasUsed: parsedTx.GasUsed, + CumulativeGasUsed: txs.AccumulativeGasUsed(parsedTx.MsgIndex), + }, nil +} + +// GetTxByHash find ParsedTx by tx hash, returns nil if not exists. +func (p *ParsedTxs) GetTxByHash(hash gethcommon.Hash) *ParsedTx { + if idx, ok := p.TxHashes[hash]; ok { + return &p.Txs[idx] + } + return nil +} + +// GetTxByMsgIndex returns ParsedTx by msg index +func (p *ParsedTxs) GetTxByMsgIndex(i int) *ParsedTx { + if i < 0 || i >= len(p.Txs) { + return nil + } + return &p.Txs[i] +} + +// GetTxByTxIndex returns ParsedTx by tx index +func (p *ParsedTxs) GetTxByTxIndex(txIndex int) *ParsedTx { + if len(p.Txs) == 0 { + return nil + } + // assuming the `EthTxIndex` increase continuously, + // convert TxIndex to MsgIndex by subtract the begin TxIndex. + msgIndex := txIndex - int(p.Txs[0].EthTxIndex) + // GetTxByMsgIndex will check the bound + return p.GetTxByMsgIndex(msgIndex) +} + +// AccumulativeGasUsed calculates the accumulated gas used within the batch of txs +func (p *ParsedTxs) AccumulativeGasUsed(msgIndex int) (result uint64) { + if msgIndex < 0 || msgIndex >= len(p.Txs) { + return 0 + } + for i := 0; i <= msgIndex; i++ { + result += p.Txs[i].GasUsed + } + return result +} diff --git a/eth/rpc/events_parser_test.go b/eth/rpc/events_parser_test.go new file mode 100644 index 000000000..e4a0c6b73 --- /dev/null +++ b/eth/rpc/events_parser_test.go @@ -0,0 +1,201 @@ +package rpc + +import ( + "math/big" + "strconv" + "testing" + + abci "github.com/cometbft/cometbft/abci/types" + sdk "github.com/cosmos/cosmos-sdk/types" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/NibiruChain/nibiru/v2/x/evm" +) + +func TestParseTxResult(t *testing.T) { + txHashOne := gethcommon.BigToHash(big.NewInt(1)) + txHashTwo := gethcommon.BigToHash(big.NewInt(2)) + + type TestCase struct { + name string + txResp abci.ResponseDeliverTx + wantEthTxs []*ParsedTx + wantErr bool + } + + testCases := []TestCase{ + { + name: "happy: valid single pending_ethereum_tx event", + txResp: abci.ResponseDeliverTx{ + Events: []abci.Event{ + pendingEthereumTxEvent(txHashOne.Hex(), 0), + }, + }, + wantEthTxs: []*ParsedTx{ + { + MsgIndex: 0, + EthHash: txHashOne, + EthTxIndex: 0, + GasUsed: 0, + Failed: false, + }, + }, + }, + { + name: "happy: two valid pending_ethereum_tx events", + txResp: abci.ResponseDeliverTx{ + Events: []abci.Event{ + pendingEthereumTxEvent(txHashOne.Hex(), 0), + pendingEthereumTxEvent(txHashTwo.Hex(), 1), + }, + }, + wantEthTxs: []*ParsedTx{ + { + MsgIndex: 0, + EthHash: txHashOne, + EthTxIndex: 0, + GasUsed: 0, + Failed: false, + }, + { + MsgIndex: 1, + EthHash: txHashTwo, + EthTxIndex: 1, + Failed: false, + }, + }, + }, + { + name: "happy: one pending_ethereum_tx and one EventEthereumTx", + txResp: abci.ResponseDeliverTx{ + Events: []abci.Event{ + pendingEthereumTxEvent(txHashOne.Hex(), 0), + ethereumTxEvent(txHashOne.Hex(), 0, 21000, false), + }, + }, + wantEthTxs: []*ParsedTx{ + { + MsgIndex: 0, + EthHash: txHashOne, + EthTxIndex: 0, + GasUsed: 21000, + Failed: false, + }, + }, + }, + { + name: "happy: two pending_ethereum_tx and one EventEthereumTx", + txResp: abci.ResponseDeliverTx{ + Events: []abci.Event{ + pendingEthereumTxEvent(txHashOne.Hex(), 0), + pendingEthereumTxEvent(txHashTwo.Hex(), 1), + ethereumTxEvent(txHashTwo.Hex(), 1, 21000, false), + }, + }, + wantEthTxs: []*ParsedTx{ + { + MsgIndex: 0, + EthHash: txHashOne, + EthTxIndex: 0, + GasUsed: 0, + Failed: false, + }, + { + MsgIndex: 1, + EthHash: txHashTwo, + EthTxIndex: 1, + GasUsed: 21000, + Failed: false, + }, + }, + }, + { + name: "happy: one pending_ethereum_tx and one EventEthereumTx failed", + txResp: abci.ResponseDeliverTx{ + Events: []abci.Event{ + pendingEthereumTxEvent(txHashOne.Hex(), 0), + ethereumTxEvent(txHashOne.Hex(), 0, 21000, true), + }, + }, + wantEthTxs: []*ParsedTx{ + { + MsgIndex: 0, + EthHash: txHashOne, + EthTxIndex: 0, + GasUsed: 21000, + Failed: true, + }, + }, + }, + { + name: "sad: EventEthereumTx without pending_ethereum_tx", + txResp: abci.ResponseDeliverTx{ + Events: []abci.Event{ + ethereumTxEvent(txHashOne.Hex(), 0, 21000, false), + }, + }, + wantEthTxs: nil, + wantErr: true, + }, + { + name: "sad: no events", + txResp: abci.ResponseDeliverTx{ + Events: []abci.Event{}, + }, + wantEthTxs: []*ParsedTx{}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + parsed, err := ParseTxResult(&tc.txResp, nil) //#nosec G601 -- fine for tests + if tc.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + for msgIndex, expTx := range tc.wantEthTxs { + require.Equal(t, expTx, parsed.GetTxByMsgIndex(msgIndex)) + require.Equal(t, expTx, parsed.GetTxByHash(expTx.EthHash)) + require.Equal(t, expTx, parsed.GetTxByTxIndex(int(expTx.EthTxIndex))) + require.Equal(t, expTx.GasUsed, parsed.GetTxByHash(expTx.EthHash).GasUsed) + require.Equal(t, expTx.Failed, parsed.GetTxByHash(expTx.EthHash).Failed) + } + // non-exists tx hash + require.Nil(t, parsed.GetTxByHash(gethcommon.Hash{})) + // out of range + require.Nil(t, parsed.GetTxByMsgIndex(len(tc.wantEthTxs))) + require.Nil(t, parsed.GetTxByTxIndex(99999999)) + }) + } +} + +func pendingEthereumTxEvent(txHash string, txIndex int) abci.Event { + return abci.Event{ + Type: evm.PendingEthereumTxEvent, + Attributes: []abci.EventAttribute{ + {Key: evm.PendingEthereumTxEventAttrEthHash, Value: txHash}, + {Key: evm.PendingEthereumTxEventAttrIndex, Value: strconv.Itoa(txIndex)}, + }, + } +} + +func ethereumTxEvent(txHash string, txIndex int, gasUsed int, failed bool) abci.Event { + failure := "" + if failed { + failure = "failed" + } + event, err := sdk.TypedEventToEvent( + &evm.EventEthereumTx{ + EthHash: txHash, + Index: strconv.Itoa(txIndex), + GasUsed: strconv.Itoa(gasUsed), + EthTxFailed: failure, + }, + ) + if err != nil { + panic(err) + } + return (abci.Event)(event) +} diff --git a/eth/rpc/events_test.go b/eth/rpc/events_test.go deleted file mode 100644 index c9d48e84e..000000000 --- a/eth/rpc/events_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package rpc - -import ( - "math/big" - "testing" - - abci "github.com/cometbft/cometbft/abci/types" - "github.com/ethereum/go-ethereum/common" - "github.com/stretchr/testify/require" - - "github.com/NibiruChain/nibiru/v2/x/evm" -) - -func TestParseTxResult(t *testing.T) { - address := "0x57f96e6B86CdeFdB3d412547816a82E3E0EbF9D2" - txHash := common.BigToHash(big.NewInt(1)) - txHash2 := common.BigToHash(big.NewInt(2)) - - type TestCase struct { - name string - txResp abci.ResponseDeliverTx - wantEthTxs []*ParsedTx // expected parse result, nil means expect error. - } - - testCases := []TestCase{ - { - name: "format 1 events", - txResp: abci.ResponseDeliverTx{ - GasUsed: 21000, - Events: []abci.Event{ - {Type: "coin_received", Attributes: []abci.EventAttribute{ - {Key: "receiver", Value: "ethm12luku6uxehhak02py4rcz65zu0swh7wjun6msa"}, - {Key: "amount", Value: "1252860basetcro"}, - }}, - {Type: "coin_spent", Attributes: []abci.EventAttribute{ - {Key: "spender", Value: "ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl"}, - {Key: "amount", Value: "1252860basetcro"}, - }}, - {Type: evm.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ - {Key: "ethereumTxHash", Value: txHash.Hex()}, - {Key: "txIndex", Value: "10"}, - {Key: "amount", Value: "1000"}, - {Key: "txGasUsed", Value: "21000"}, - {Key: "txHash", Value: "14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57"}, - {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, - }}, - {Type: "message", Attributes: []abci.EventAttribute{ - {Key: "action", Value: "/ehermint.evm.v1.MsgEthereumTx"}, - {Key: "key", Value: "ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl"}, - {Key: "module", Value: "evm"}, - {Key: "sender", Value: address}, - }}, - {Type: evm.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ - {Key: "ethereumTxHash", Value: txHash2.Hex()}, - {Key: "txIndex", Value: "11"}, - {Key: "amount", Value: "1000"}, - {Key: "txGasUsed", Value: "21000"}, - {Key: "txHash", Value: "14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57"}, - {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, - {Key: "ethereumTxFailed", Value: "contract everted"}, - }}, - {Type: evm.EventTypeTxLog, Attributes: []abci.EventAttribute{}}, - }, - }, - wantEthTxs: []*ParsedTx{ - { - MsgIndex: 0, - Hash: txHash, - EthTxIndex: 10, - GasUsed: 21000, - Failed: false, - }, - { - MsgIndex: 1, - Hash: txHash2, - EthTxIndex: 11, - GasUsed: 21000, - Failed: true, - }, - }, - }, - { - name: "format 2 events", - txResp: abci.ResponseDeliverTx{ - GasUsed: 21000, - Events: []abci.Event{ - {Type: "coin_received", Attributes: []abci.EventAttribute{ - {Key: "receiver", Value: "ethm12luku6uxehhak02py4rcz65zu0swh7wjun6msa"}, - {Key: "amount", Value: "1252860basetcro"}, - }}, - {Type: "coin_spent", Attributes: []abci.EventAttribute{ - {Key: "spender", Value: "ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl"}, - {Key: "amount", Value: "1252860basetcro"}, - }}, - {Type: evm.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ - {Key: "ethereumTxHash", Value: txHash.Hex()}, - {Key: "txIndex", Value: "0"}, - }}, - {Type: evm.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ - {Key: "amount", Value: "1000"}, - {Key: "ethereumTxHash", Value: txHash.Hex()}, - {Key: "txIndex", Value: "0"}, - {Key: "txGasUsed", Value: "21000"}, - {Key: "txHash", Value: "14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57"}, - {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, - }}, - {Type: "message", Attributes: []abci.EventAttribute{ - {Key: "action", Value: "/ehermint.evm.v1.MsgEthereumTx"}, - {Key: "key", Value: "ethm17xpfvakm2amg962yls6f84z3kell8c5lthdzgl"}, - {Key: "module", Value: "evm"}, - {Key: "sender", Value: address}, - }}, - }, - }, - wantEthTxs: []*ParsedTx{ - { - MsgIndex: 0, - Hash: txHash, - EthTxIndex: 0, - GasUsed: 21000, - Failed: false, - }, - }, - }, - { - "format 1 events, failed", - abci.ResponseDeliverTx{ - GasUsed: 21000, - Events: []abci.Event{ - {Type: evm.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ - {Key: "ethereumTxHash", Value: txHash.Hex()}, - {Key: "txIndex", Value: "10"}, - {Key: "amount", Value: "1000"}, - {Key: "txGasUsed", Value: "21000"}, - {Key: "txHash", Value: "14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57"}, - {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, - }}, - {Type: evm.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ - {Key: "ethereumTxHash", Value: txHash2.Hex()}, - {Key: "txIndex", Value: "10"}, - {Key: "amount", Value: "1000"}, - {Key: "txGasUsed", Value: "0x01"}, - {Key: "txHash", Value: "14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57"}, - {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, - {Key: "ethereumTxFailed", Value: "contract everted"}, - }}, - {Type: evm.EventTypeTxLog, Attributes: []abci.EventAttribute{}}, - }, - }, - nil, - }, - { - name: "format 2 events failed", - txResp: abci.ResponseDeliverTx{ - GasUsed: 21000, - Events: []abci.Event{ - {Type: evm.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ - {Key: "ethereumTxHash", Value: txHash.Hex()}, - {Key: "txIndex", Value: "10"}, - }}, - {Type: evm.EventTypeEthereumTx, Attributes: []abci.EventAttribute{ - {Key: "amount", Value: "1000"}, - {Key: "txGasUsed", Value: "0x01"}, - {Key: "txHash", Value: "14A84ED06282645EFBF080E0B7ED80D8D8D6A36337668A12B5F229F81CDD3F57"}, - {Key: "recipient", Value: "0x775b87ef5D82ca211811C1a02CE0fE0CA3a455d7"}, - }}, - }, - }, - wantEthTxs: nil, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - parsed, err := ParseTxResult(&tc.txResp, nil) //#nosec G601 -- fine for tests - if tc.wantEthTxs == nil { - require.Error(t, err) - return - } - require.NoError(t, err) - for msgIndex, expTx := range tc.wantEthTxs { - require.Equal(t, expTx, parsed.GetTxByMsgIndex(msgIndex)) - require.Equal(t, expTx, parsed.GetTxByHash(expTx.Hash)) - require.Equal(t, expTx, parsed.GetTxByTxIndex(int(expTx.EthTxIndex))) - } - // non-exists tx hash - require.Nil(t, parsed.GetTxByHash(common.Hash{})) - // out of range - require.Nil(t, parsed.GetTxByMsgIndex(len(tc.wantEthTxs))) - require.Nil(t, parsed.GetTxByTxIndex(99999999)) - }) - } -} diff --git a/eth/rpc/rpcapi/eth_api_test.go b/eth/rpc/rpcapi/eth_api_test.go index 54e992462..7ba8552f1 100644 --- a/eth/rpc/rpcapi/eth_api_test.go +++ b/eth/rpc/rpcapi/eth_api_test.go @@ -3,7 +3,6 @@ package rpcapi_test import ( "context" "crypto/ecdsa" - "encoding/json" "fmt" "math/big" "strings" @@ -171,8 +170,6 @@ func (s *NodeSuite) Test_CodeAt() { func (s *NodeSuite) Test_PendingCodeAt() { code, err := s.ethClient.PendingCodeAt(context.Background(), s.fundedAccEthAddr) s.NoError(err) - - // TODO: add more checks s.NotNil(code) } @@ -276,21 +273,30 @@ func (s *NodeSuite) Test_SimpleTransferTransaction() { blockHeightOfTx := int64(txReceipt.BlockNumber.Uint64()) blockOfTx, err := s.val.RPCClient.BlockResults(blankCtx, &blockHeightOfTx) s.NoError(err) - ethTxEvents := []sdk.Event{} events := blockOfTx.TxsResults[0].Events + pendingEthTxEventHash := gethcommon.Hash{} + pendingEthTxEventIndex := int32(-1) for _, event := range events { - if event.Type == "ethereum_tx" { - ethTxEvents = append(ethTxEvents, - sdk.Event{Type: event.Type, Attributes: event.Attributes}, + if event.Type == evm.PendingEthereumTxEvent { + pendingEthTxEventHash, pendingEthTxEventIndex, err = + evm.GetEthHashAndIndexFromPendingEthereumTxEvent(event) + s.Require().NoError(err) + } + if event.Type == evm.TypeUrlEventEthereumTx { + ethereumTx, err := evm.EventEthereumTxFromABCIEvent(event) + s.Require().NoError(err) + s.Require().Equal( + pendingEthTxEventHash.Hex(), + ethereumTx.EthHash, + "hash of pending ethereum tx event and ethereum tx event must be equal", + ) + s.Require().Equal( + fmt.Sprintf("%d", pendingEthTxEventIndex), + ethereumTx.Index, + "index of pending ethereum tx event and ethereum tx event must be equal", ) } } - - eventsJson, _ := json.Marshal(events) - s.Require().Equal(len(ethTxEvents), 2, "events: ", eventsJson) - hash0, _ := ethTxEvents[0].GetAttribute(evm.AttributeKeyEthereumTxHash) - hash1, _ := ethTxEvents[1].GetAttribute(evm.AttributeKeyEthereumTxHash) - s.Require().Equal(hash0, hash1) } s.T().Log("Assert balances") diff --git a/eth/rpc/rpcapi/eth_filters_api.go b/eth/rpc/rpcapi/eth_filters_api.go index 9a72a5042..782014bb5 100644 --- a/eth/rpc/rpcapi/eth_filters_api.go +++ b/eth/rpc/rpcapi/eth_filters_api.go @@ -389,7 +389,7 @@ func (api *FiltersAPI) Logs( } // filter only events from EVM module txs - _, isMsgEthereumTx := ev.Events[evm.TypeMsgEthereumTx] + _, isMsgEthereumTx := ev.Events[evm.TypeUrlEventEthereumTx] if !isMsgEthereumTx { // ignore transaction as it's not from the evm module diff --git a/eth/rpc/rpcapi/filter_utils.go b/eth/rpc/rpcapi/filter_utils.go index 53e7c609d..41983b84d 100644 --- a/eth/rpc/rpcapi/filter_utils.go +++ b/eth/rpc/rpcapi/filter_utils.go @@ -6,8 +6,6 @@ import ( "cosmossdk.io/errors" abci "github.com/cometbft/cometbft/abci/types" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/ethereum/go-ethereum/common" gethcore "github.com/ethereum/go-ethereum/core/types" @@ -117,24 +115,17 @@ func returnLogs(logs []*gethcore.Log) []*gethcore.Log { // ParseBloomFromEvents iterates through the slice of events func ParseBloomFromEvents(events []abci.Event) (bloom gethcore.Bloom, err error) { - bloomEvent := new(evm.EventBlockBloom) - bloomEventType := gogoproto.MessageName(bloomEvent) + bloomEventType := gogoproto.MessageName(new(evm.EventBlockBloom)) for _, event := range events { if event.Type != bloomEventType { continue } - typedProtoEvent, err := sdk.ParseTypedEvent(event) + bloomTypedEvent, err := evm.EventBlockBloomFromABCIEvent(event) if err != nil { return bloom, errors.Wrapf( err, "failed to parse event of type %s", bloomEventType) } - bloomEvent, ok := (typedProtoEvent).(*evm.EventBlockBloom) - if !ok { - return bloom, errors.Wrapf( - err, "failed to parse event of type %s", bloomEventType) - } - - return eth.BloomFromHex(bloomEvent.Bloom) + return eth.BloomFromHex(bloomTypedEvent.Bloom) } return bloom, err } diff --git a/proto/eth/evm/v1/events.proto b/proto/eth/evm/v1/events.proto index 039566fa0..826c8bf6d 100644 --- a/proto/eth/evm/v1/events.proto +++ b/proto/eth/evm/v1/events.proto @@ -31,16 +31,6 @@ message EventTxLog { repeated string tx_logs = 1; } -// EventMessage -message EventMessage { - // module which emits the event - string module = 1; - // sender of the message - string sender = 2; - // tx_type is the type of the message - string tx_type = 3; -} - // EventBlockBloom defines an Ethereum block bloom filter event message EventBlockBloom { // bloom is the bloom filter of the block diff --git a/x/evm/events.go b/x/evm/events.go index 3bf3dad0a..e6d0b217d 100644 --- a/x/evm/events.go +++ b/x/evm/events.go @@ -1,21 +1,106 @@ // Copyright (c) 2023-2024 Nibi, Inc. package evm +import ( + "fmt" + "strconv" + + "cosmossdk.io/errors" + abci "github.com/cometbft/cometbft/abci/types" + sdk "github.com/cosmos/cosmos-sdk/types" + gethcommon "github.com/ethereum/go-ethereum/common" +) + // Evm module events const ( - EventTypeEthereumTx = TypeMsgEthereumTx - EventTypeBlockBloom = "block_bloom" - EventTypeTxLog = "tx_log" - - AttributeKeyRecipient = "recipient" - AttributeKeyTxHash = "txHash" - AttributeKeyEthereumTxHash = "ethereumTxHash" - AttributeKeyTxIndex = "txIndex" - AttributeKeyTxGasUsed = "txGasUsed" - AttributeKeyTxType = "txType" - AttributeKeyTxLog = "txLog" - // tx failed in eth vm execution - AttributeKeyEthereumTxFailed = "ethereumTxFailed" - AttributeValueCategory = ModuleName - AttributeKeyEthereumBloom = "bloom" + // proto.MessageName(new(evm.EventBlockBloom)) + TypeUrlEventBlockBloom = "eth.evm.v1.EventBlockBloom" + + // proto.MessageName(new(evm.EventTxLog)) + TypeUrlEventTxLog = "eth.evm.v1.EventTxLog" + + // proto.MessageName(new(evm.TypeUrlEventEthereumTx)) + TypeUrlEventEthereumTx = "eth.evm.v1.EventEthereumTx" + + // Untyped events and attribuges + + // Used in non-typed event "message" + MessageEventAttrTxType = "tx_type" + + // Used in non-typed event "pending_ethereum_tx" + PendingEthereumTxEvent = "pending_ethereum_tx" + PendingEthereumTxEventAttrEthHash = "eth_hash" + PendingEthereumTxEventAttrIndex = "index" ) + +func EventTxLogFromABCIEvent(event abci.Event) (*EventTxLog, error) { + typeUrl := TypeUrlEventTxLog + typedProtoEvent, err := sdk.ParseTypedEvent(event) + if err != nil { + return nil, errors.Wrapf( + err, "failed to parse event of type %s", typeUrl) + } + typedEvent, ok := (typedProtoEvent).(*EventTxLog) + if !ok { + return nil, errors.Wrapf( + err, "failed to parse event of type %s", typeUrl) + } + return typedEvent, nil +} + +func EventBlockBloomFromABCIEvent(event abci.Event) (*EventBlockBloom, error) { + typeUrl := TypeUrlEventBlockBloom + typedProtoEvent, err := sdk.ParseTypedEvent(event) + if err != nil { + return nil, errors.Wrapf( + err, "failed to parse event of type %s", typeUrl) + } + typedEvent, ok := (typedProtoEvent).(*EventBlockBloom) + if !ok { + return nil, errors.Wrapf( + err, "failed to parse event of type %s", typeUrl) + } + return typedEvent, nil +} + +func EventEthereumTxFromABCIEvent(event abci.Event) (*EventEthereumTx, error) { + typeUrl := TypeUrlEventEthereumTx + typedProtoEvent, err := sdk.ParseTypedEvent(event) + if err != nil { + return nil, errors.Wrapf( + err, "failed to parse event of type %s", typeUrl) + } + typedEvent, ok := (typedProtoEvent).(*EventEthereumTx) + if !ok { + return nil, errors.Wrapf( + err, "failed to parse event of type %s", typeUrl) + } + return typedEvent, nil +} + +func GetEthHashAndIndexFromPendingEthereumTxEvent(event abci.Event) (gethcommon.Hash, int32, error) { + ethHash := gethcommon.Hash{} + txIndex := int32(-1) + + for _, attr := range event.Attributes { + if attr.Key == PendingEthereumTxEventAttrEthHash { + ethHash = gethcommon.HexToHash(attr.Value) + } + if attr.Key == PendingEthereumTxEventAttrIndex { + parsedIndex, err := strconv.ParseInt(attr.Value, 10, 32) + if err != nil { + return ethHash, -1, fmt.Errorf( + "failed to parse tx index from pending_ethereum_tx event, %s", attr.Value, + ) + } + txIndex = int32(parsedIndex) + } + } + if txIndex == -1 { + return ethHash, -1, fmt.Errorf("tx index not found in pending_ethereum_tx") + } + if ethHash.String() == "" { + return ethHash, -1, fmt.Errorf("eth hash not found in pending_ethereum_tx") + } + return ethHash, txIndex, nil +} diff --git a/x/evm/events.pb.go b/x/evm/events.pb.go index e9f40dbd1..918018491 100644 --- a/x/evm/events.pb.go +++ b/x/evm/events.pb.go @@ -170,70 +170,6 @@ func (m *EventTxLog) GetTxLogs() []string { return nil } -// EventMessage -type EventMessage struct { - // module which emits the event - Module string `protobuf:"bytes,1,opt,name=module,proto3" json:"module,omitempty"` - // sender of the message - Sender string `protobuf:"bytes,2,opt,name=sender,proto3" json:"sender,omitempty"` - // tx_type is the type of the message - TxType string `protobuf:"bytes,3,opt,name=tx_type,json=txType,proto3" json:"tx_type,omitempty"` -} - -func (m *EventMessage) Reset() { *m = EventMessage{} } -func (m *EventMessage) String() string { return proto.CompactTextString(m) } -func (*EventMessage) ProtoMessage() {} -func (*EventMessage) Descriptor() ([]byte, []int) { - return fileDescriptor_f8bc26b53c788f17, []int{2} -} -func (m *EventMessage) XXX_Unmarshal(b []byte) error { - return m.Unmarshal(b) -} -func (m *EventMessage) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { - if deterministic { - return xxx_messageInfo_EventMessage.Marshal(b, m, deterministic) - } else { - b = b[:cap(b)] - n, err := m.MarshalToSizedBuffer(b) - if err != nil { - return nil, err - } - return b[:n], nil - } -} -func (m *EventMessage) XXX_Merge(src proto.Message) { - xxx_messageInfo_EventMessage.Merge(m, src) -} -func (m *EventMessage) XXX_Size() int { - return m.Size() -} -func (m *EventMessage) XXX_DiscardUnknown() { - xxx_messageInfo_EventMessage.DiscardUnknown(m) -} - -var xxx_messageInfo_EventMessage proto.InternalMessageInfo - -func (m *EventMessage) GetModule() string { - if m != nil { - return m.Module - } - return "" -} - -func (m *EventMessage) GetSender() string { - if m != nil { - return m.Sender - } - return "" -} - -func (m *EventMessage) GetTxType() string { - if m != nil { - return m.TxType - } - return "" -} - // EventBlockBloom defines an Ethereum block bloom filter event type EventBlockBloom struct { // bloom is the bloom filter of the block @@ -244,7 +180,7 @@ func (m *EventBlockBloom) Reset() { *m = EventBlockBloom{} } func (m *EventBlockBloom) String() string { return proto.CompactTextString(m) } func (*EventBlockBloom) ProtoMessage() {} func (*EventBlockBloom) Descriptor() ([]byte, []int) { - return fileDescriptor_f8bc26b53c788f17, []int{3} + return fileDescriptor_f8bc26b53c788f17, []int{2} } func (m *EventBlockBloom) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -292,7 +228,7 @@ func (m *EventFunTokenCreated) Reset() { *m = EventFunTokenCreated{} } func (m *EventFunTokenCreated) String() string { return proto.CompactTextString(m) } func (*EventFunTokenCreated) ProtoMessage() {} func (*EventFunTokenCreated) Descriptor() ([]byte, []int) { - return fileDescriptor_f8bc26b53c788f17, []int{4} + return fileDescriptor_f8bc26b53c788f17, []int{3} } func (m *EventFunTokenCreated) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -361,7 +297,7 @@ func (m *EventConvertCoinToEvm) Reset() { *m = EventConvertCoinToEvm{} } func (m *EventConvertCoinToEvm) String() string { return proto.CompactTextString(m) } func (*EventConvertCoinToEvm) ProtoMessage() {} func (*EventConvertCoinToEvm) Descriptor() ([]byte, []int) { - return fileDescriptor_f8bc26b53c788f17, []int{5} + return fileDescriptor_f8bc26b53c788f17, []int{4} } func (m *EventConvertCoinToEvm) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -429,7 +365,7 @@ func (m *EventTransfer) Reset() { *m = EventTransfer{} } func (m *EventTransfer) String() string { return proto.CompactTextString(m) } func (*EventTransfer) ProtoMessage() {} func (*EventTransfer) Descriptor() ([]byte, []int) { - return fileDescriptor_f8bc26b53c788f17, []int{6} + return fileDescriptor_f8bc26b53c788f17, []int{5} } func (m *EventTransfer) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -489,7 +425,7 @@ func (m *EventContractDeployed) Reset() { *m = EventContractDeployed{} } func (m *EventContractDeployed) String() string { return proto.CompactTextString(m) } func (*EventContractDeployed) ProtoMessage() {} func (*EventContractDeployed) Descriptor() ([]byte, []int) { - return fileDescriptor_f8bc26b53c788f17, []int{7} + return fileDescriptor_f8bc26b53c788f17, []int{6} } func (m *EventContractDeployed) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -542,7 +478,7 @@ func (m *EventContractExecuted) Reset() { *m = EventContractExecuted{} } func (m *EventContractExecuted) String() string { return proto.CompactTextString(m) } func (*EventContractExecuted) ProtoMessage() {} func (*EventContractExecuted) Descriptor() ([]byte, []int) { - return fileDescriptor_f8bc26b53c788f17, []int{8} + return fileDescriptor_f8bc26b53c788f17, []int{7} } func (m *EventContractExecuted) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -588,7 +524,6 @@ func (m *EventContractExecuted) GetContractAddr() string { func init() { proto.RegisterType((*EventEthereumTx)(nil), "eth.evm.v1.EventEthereumTx") proto.RegisterType((*EventTxLog)(nil), "eth.evm.v1.EventTxLog") - proto.RegisterType((*EventMessage)(nil), "eth.evm.v1.EventMessage") proto.RegisterType((*EventBlockBloom)(nil), "eth.evm.v1.EventBlockBloom") proto.RegisterType((*EventFunTokenCreated)(nil), "eth.evm.v1.EventFunTokenCreated") proto.RegisterType((*EventConvertCoinToEvm)(nil), "eth.evm.v1.EventConvertCoinToEvm") @@ -600,49 +535,46 @@ func init() { func init() { proto.RegisterFile("eth/evm/v1/events.proto", fileDescriptor_f8bc26b53c788f17) } var fileDescriptor_f8bc26b53c788f17 = []byte{ - // 658 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0xcb, 0x6e, 0xd4, 0x3c, - 0x14, 0x9e, 0xf4, 0x32, 0xd3, 0x71, 0xdb, 0xff, 0x07, 0x6b, 0x68, 0xd3, 0x0a, 0xd2, 0x2a, 0x88, - 0xdb, 0x26, 0x61, 0x0a, 0x2b, 0x56, 0x30, 0xd3, 0xa9, 0x58, 0x50, 0x84, 0xaa, 0x41, 0x48, 0x48, - 0x28, 0x72, 0x92, 0xd3, 0x24, 0x6a, 0x62, 0x8f, 0x6c, 0x27, 0xca, 0xbc, 0x05, 0x8f, 0xc2, 0x63, - 0x54, 0x62, 0xd3, 0x1d, 0xac, 0x2a, 0xd4, 0xbe, 0x01, 0x4f, 0x80, 0xec, 0xb8, 0x9d, 0x16, 0xd4, - 0x0d, 0xec, 0xce, 0xf9, 0xce, 0xc5, 0xe7, 0xfb, 0x7c, 0x6c, 0xb4, 0x0e, 0x32, 0xf5, 0xa1, 0x2a, - 0xfc, 0xaa, 0xef, 0x43, 0x05, 0x54, 0x0a, 0x6f, 0xc2, 0x99, 0x64, 0x18, 0x81, 0x4c, 0x3d, 0xa8, - 0x0a, 0xaf, 0xea, 0x6f, 0x3a, 0x11, 0x13, 0x05, 0x13, 0x7e, 0x48, 0x04, 0xf8, 0x55, 0x3f, 0x04, - 0x49, 0xfa, 0x7e, 0xc4, 0x32, 0xda, 0xe4, 0x6e, 0xf6, 0x12, 0x96, 0x30, 0x6d, 0xfa, 0xca, 0x6a, - 0x50, 0xf7, 0xab, 0x85, 0xfe, 0x1f, 0xa9, 0x96, 0x23, 0x99, 0x02, 0x87, 0xb2, 0x18, 0xd7, 0x78, - 0x0d, 0xb5, 0x49, 0xc1, 0x4a, 0x2a, 0x6d, 0x6b, 0xdb, 0x7a, 0xdc, 0x3d, 0x30, 0x1e, 0xde, 0x40, - 0x4b, 0x20, 0xd3, 0x20, 0x25, 0x22, 0xb5, 0xe7, 0x74, 0xa4, 0x03, 0x32, 0x7d, 0x4d, 0x44, 0x8a, - 0x7b, 0x68, 0x31, 0xa3, 0x31, 0xd4, 0xf6, 0xbc, 0xc6, 0x1b, 0x47, 0x15, 0x24, 0x44, 0x04, 0xa5, - 0x80, 0xd8, 0x5e, 0x68, 0x0a, 0x12, 0x22, 0xde, 0x0b, 0x88, 0x31, 0x46, 0x0b, 0xba, 0xcf, 0xa2, - 0x86, 0xb5, 0x8d, 0xef, 0xa2, 0x2e, 0x87, 0x28, 0x9b, 0x64, 0x40, 0xa5, 0xdd, 0xd6, 0x81, 0x19, - 0x80, 0x5d, 0xb4, 0xaa, 0x4e, 0x97, 0x75, 0x70, 0x48, 0xb2, 0x1c, 0x62, 0xbb, 0xa3, 0x33, 0x96, - 0x41, 0xa6, 0xe3, 0x7a, 0x4f, 0x43, 0xee, 0x03, 0x84, 0x34, 0x99, 0x71, 0xfd, 0x86, 0x25, 0x78, - 0x1d, 0x75, 0x64, 0x1d, 0xe4, 0x2c, 0x11, 0xb6, 0xb5, 0x3d, 0xaf, 0x88, 0x48, 0x85, 0x0b, 0xf7, - 0x03, 0x5a, 0xd1, 0x69, 0xfb, 0x20, 0x04, 0x49, 0x40, 0x11, 0x2e, 0x58, 0x5c, 0xe6, 0x70, 0x41, - 0xb8, 0xf1, 0x14, 0x2e, 0x80, 0xc6, 0xc0, 0x0d, 0x5d, 0xe3, 0x99, 0xc6, 0x72, 0x3a, 0x01, 0xc3, - 0xb7, 0x2d, 0xeb, 0xf1, 0x74, 0x02, 0xee, 0x23, 0x23, 0xe6, 0x20, 0x67, 0xd1, 0xd1, 0x20, 0x67, - 0xac, 0x50, 0xca, 0x84, 0xca, 0x30, 0xad, 0x1b, 0xc7, 0xfd, 0x62, 0xa1, 0x9e, 0xce, 0xdc, 0x2b, - 0xe9, 0x98, 0x1d, 0x01, 0x1d, 0x72, 0x20, 0x12, 0x62, 0x7c, 0x0f, 0xa1, 0x90, 0xd0, 0xa3, 0x20, - 0x06, 0x7a, 0x59, 0xd3, 0x55, 0xc8, 0xae, 0x02, 0xf0, 0x73, 0xb4, 0x06, 0x3c, 0xda, 0x79, 0x1a, - 0x44, 0x8c, 0x4a, 0x4e, 0x22, 0x19, 0x90, 0x38, 0xe6, 0x20, 0x84, 0x99, 0xb0, 0xa7, 0xa3, 0x43, - 0x13, 0x7c, 0xd5, 0xc4, 0xb0, 0x8d, 0x3a, 0x91, 0xea, 0xcf, 0xb8, 0x99, 0xf7, 0xc2, 0xc5, 0x4f, - 0xd0, 0xed, 0x4c, 0x04, 0x05, 0x89, 0x21, 0x38, 0xe4, 0xac, 0x08, 0xd4, 0xbe, 0xe8, 0xab, 0x5a, - 0x3a, 0xf8, 0x2f, 0x13, 0xfb, 0x24, 0x86, 0x3d, 0xce, 0x8a, 0x21, 0xcb, 0xa8, 0xfb, 0xcd, 0x42, - 0x77, 0xf4, 0xc8, 0x43, 0x46, 0x2b, 0xe0, 0x52, 0x81, 0x63, 0x36, 0xaa, 0x8a, 0x2b, 0x32, 0x59, - 0xd7, 0x64, 0xfa, 0xbb, 0x61, 0x1d, 0xb4, 0x2c, 0x59, 0xa0, 0xae, 0x5a, 0x65, 0x9b, 0x81, 0xbb, - 0x92, 0x8d, 0x64, 0xaa, 0x52, 0xf0, 0x3b, 0xa4, 0xf5, 0x98, 0x8d, 0xba, 0xbc, 0xb3, 0xe1, 0x35, - 0xbb, 0xef, 0xa9, 0xdd, 0xf7, 0xcc, 0xee, 0x7b, 0x6a, 0xc0, 0x81, 0x7d, 0x7c, 0xba, 0xd5, 0xfa, - 0x79, 0xba, 0x75, 0x6b, 0x4a, 0x8a, 0xfc, 0x85, 0x7b, 0x59, 0xe9, 0x1e, 0x2c, 0x29, 0x5b, 0x33, - 0xfb, 0x84, 0x56, 0x9b, 0xad, 0xe1, 0x84, 0x8a, 0x43, 0xe0, 0x37, 0x12, 0xba, 0xb6, 0xa0, 0x73, - 0xbf, 0x2f, 0xe8, 0xec, 0xd9, 0xcc, 0x5f, 0x7d, 0x36, 0xee, 0x78, 0xa6, 0x9b, 0x26, 0xba, 0x0b, - 0x93, 0x9c, 0x4d, 0x21, 0xbe, 0xf1, 0x98, 0xfb, 0x68, 0xf5, 0x9a, 0x62, 0xe6, 0xa8, 0x95, 0xe8, - 0x8a, 0x52, 0x7f, 0x74, 0x1d, 0xd5, 0x10, 0x95, 0xf2, 0x1f, 0xbb, 0x0e, 0x5e, 0x1e, 0x9f, 0x39, - 0xd6, 0xc9, 0x99, 0x63, 0xfd, 0x38, 0x73, 0xac, 0xcf, 0xe7, 0x4e, 0xeb, 0xe4, 0xdc, 0x69, 0x7d, - 0x3f, 0x77, 0x5a, 0x1f, 0x1f, 0x26, 0x99, 0x4c, 0xcb, 0xd0, 0x8b, 0x58, 0xe1, 0xbf, 0xcd, 0xc2, - 0x8c, 0x97, 0xc3, 0x94, 0x64, 0xd4, 0xa7, 0xda, 0xf6, 0xab, 0x1d, 0xbf, 0x56, 0x5f, 0x54, 0xd8, - 0xd6, 0xff, 0xca, 0xb3, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x18, 0xd0, 0xf1, 0x1b, 0xb4, 0x04, - 0x00, 0x00, + // 618 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0x4b, 0x6e, 0xd4, 0x40, + 0x10, 0x1d, 0xe7, 0x37, 0x99, 0x0e, 0xe1, 0xd3, 0x1a, 0x12, 0x27, 0x02, 0x27, 0x32, 0xe2, 0xb7, + 0xb1, 0x99, 0xc0, 0x8a, 0x15, 0xcc, 0x64, 0x22, 0x16, 0x80, 0x50, 0x34, 0x6c, 0x90, 0x90, 0xd5, + 0xb6, 0x2b, 0xb6, 0x15, 0xbb, 0x2b, 0xea, 0x6e, 0x5b, 0xce, 0x2d, 0x38, 0x0a, 0xc7, 0x88, 0xc4, + 0x26, 0x3b, 0x58, 0x45, 0x28, 0xb9, 0x01, 0x27, 0x40, 0xdd, 0x76, 0x32, 0x09, 0x28, 0x1b, 0xd8, + 0x55, 0xbd, 0xfa, 0x74, 0xbd, 0xaa, 0x67, 0x93, 0x55, 0x50, 0xa9, 0x0f, 0x55, 0xe1, 0x57, 0x03, + 0x1f, 0x2a, 0xe0, 0x4a, 0x7a, 0x07, 0x02, 0x15, 0x52, 0x02, 0x2a, 0xf5, 0xa0, 0x2a, 0xbc, 0x6a, + 0xb0, 0xee, 0x44, 0x28, 0x0b, 0x94, 0x7e, 0xc8, 0x24, 0xf8, 0xd5, 0x20, 0x04, 0xc5, 0x06, 0x7e, + 0x84, 0x19, 0x6f, 0x72, 0xd7, 0xfb, 0x09, 0x26, 0x68, 0x4c, 0x5f, 0x5b, 0x0d, 0xea, 0x7e, 0xb3, + 0xc8, 0xad, 0xb1, 0x6e, 0x39, 0x56, 0x29, 0x08, 0x28, 0x8b, 0x49, 0x4d, 0x57, 0xc8, 0x02, 0x2b, + 0xb0, 0xe4, 0xca, 0xb6, 0x36, 0xad, 0x27, 0xbd, 0xdd, 0xd6, 0xa3, 0x6b, 0x64, 0x11, 0x54, 0x1a, + 0xa4, 0x4c, 0xa6, 0xf6, 0x8c, 0x89, 0x74, 0x41, 0xa5, 0x6f, 0x98, 0x4c, 0x69, 0x9f, 0xcc, 0x67, + 0x3c, 0x86, 0xda, 0x9e, 0x35, 0x78, 0xe3, 0xe8, 0x82, 0x84, 0xc9, 0xa0, 0x94, 0x10, 0xdb, 0x73, + 0x4d, 0x41, 0xc2, 0xe4, 0x47, 0x09, 0x31, 0xa5, 0x64, 0xce, 0xf4, 0x99, 0x37, 0xb0, 0xb1, 0xe9, + 0x3d, 0xd2, 0x13, 0x10, 0x65, 0x07, 0x19, 0x70, 0x65, 0x2f, 0x98, 0xc0, 0x14, 0xa0, 0x2e, 0x59, + 0xd6, 0xaf, 0xab, 0x3a, 0xd8, 0x63, 0x59, 0x0e, 0xb1, 0xdd, 0x35, 0x19, 0x4b, 0xa0, 0xd2, 0x49, + 0xbd, 0x63, 0x20, 0xf7, 0x21, 0x21, 0x86, 0xcc, 0xa4, 0x7e, 0x8b, 0x09, 0x5d, 0x25, 0x5d, 0x55, + 0x07, 0x39, 0x26, 0xd2, 0xb6, 0x36, 0x67, 0x35, 0x11, 0xa5, 0x71, 0xe9, 0x3e, 0x6e, 0x39, 0x0f, + 0x73, 0x8c, 0xf6, 0x87, 0x39, 0x62, 0xa1, 0x09, 0x84, 0xda, 0x68, 0x29, 0x37, 0x8e, 0xfb, 0xd5, + 0x22, 0x7d, 0x93, 0xb9, 0x53, 0xf2, 0x09, 0xee, 0x03, 0x1f, 0x09, 0x60, 0x0a, 0x62, 0x7a, 0x9f, + 0x90, 0x90, 0xf1, 0xfd, 0x20, 0x06, 0x7e, 0x51, 0xd3, 0xd3, 0xc8, 0xb6, 0x06, 0xe8, 0x0b, 0xb2, + 0x02, 0x22, 0xda, 0x7a, 0x16, 0x44, 0xc8, 0x95, 0x60, 0x91, 0x0a, 0x58, 0x1c, 0x0b, 0x90, 0xb2, + 0xdd, 0x5b, 0xdf, 0x44, 0x47, 0x6d, 0xf0, 0x75, 0x13, 0xa3, 0x36, 0xe9, 0x46, 0xba, 0x3f, 0x8a, + 0x76, 0x8d, 0xe7, 0x2e, 0x7d, 0x4a, 0xee, 0x64, 0x32, 0x28, 0x58, 0x0c, 0xc1, 0x9e, 0xc0, 0x22, + 0xd0, 0x67, 0x35, 0x1b, 0x5d, 0xdc, 0xbd, 0x99, 0xc9, 0x77, 0x2c, 0x86, 0x1d, 0x81, 0xc5, 0x08, + 0x33, 0xee, 0x7e, 0xb7, 0xc8, 0x5d, 0x33, 0xf2, 0x08, 0x79, 0x05, 0x42, 0x69, 0x70, 0x82, 0xe3, + 0xaa, 0xd0, 0x67, 0x95, 0xc0, 0x63, 0x10, 0xe7, 0x67, 0x6d, 0xbc, 0x7f, 0x1c, 0xd6, 0x21, 0x4b, + 0x0a, 0x03, 0x7d, 0x11, 0x9d, 0xdd, 0x0e, 0xdc, 0x53, 0x38, 0x56, 0xa9, 0x4e, 0xa1, 0x1f, 0x88, + 0xd9, 0xc7, 0x74, 0xd4, 0xa5, 0xad, 0x35, 0xaf, 0x91, 0xa8, 0xa7, 0x25, 0xea, 0xb5, 0x12, 0xf5, + 0xf4, 0x80, 0x43, 0xfb, 0xe8, 0x64, 0xa3, 0xf3, 0xeb, 0x64, 0xe3, 0xf6, 0x21, 0x2b, 0xf2, 0x97, + 0xee, 0x45, 0xa5, 0xbb, 0xbb, 0xa8, 0x6d, 0xc3, 0xec, 0x33, 0x59, 0x6e, 0x8e, 0x2b, 0x18, 0x97, + 0x7b, 0x20, 0xae, 0x25, 0x74, 0x45, 0x47, 0x33, 0x7f, 0xea, 0x68, 0xaa, 0xee, 0xd9, 0xcb, 0xea, + 0x76, 0x27, 0xd3, 0xbd, 0x19, 0xa2, 0xdb, 0x70, 0x90, 0xe3, 0x21, 0xc4, 0xd7, 0x3e, 0xf3, 0x80, + 0x2c, 0x5f, 0xd9, 0x58, 0xfb, 0xd4, 0x8d, 0xe8, 0xd2, 0xa6, 0xfe, 0xea, 0x3a, 0xae, 0x21, 0x2a, + 0xd5, 0x7f, 0x76, 0x1d, 0xbe, 0x3a, 0x3a, 0x75, 0xac, 0xe3, 0x53, 0xc7, 0xfa, 0x79, 0xea, 0x58, + 0x5f, 0xce, 0x9c, 0xce, 0xf1, 0x99, 0xd3, 0xf9, 0x71, 0xe6, 0x74, 0x3e, 0x3d, 0x4a, 0x32, 0x95, + 0x96, 0xa1, 0x17, 0x61, 0xe1, 0xbf, 0xcf, 0xc2, 0x4c, 0x94, 0xa3, 0x94, 0x65, 0xdc, 0xe7, 0xc6, + 0xf6, 0xab, 0x2d, 0xbf, 0xd6, 0x7f, 0x92, 0x70, 0xc1, 0x7c, 0xfe, 0xcf, 0x7f, 0x07, 0x00, 0x00, + 0xff, 0xff, 0x1c, 0xa0, 0xad, 0x46, 0x5b, 0x04, 0x00, 0x00, } func (m *EventEthereumTx) Marshal() (dAtA []byte, err error) { @@ -749,50 +681,6 @@ func (m *EventTxLog) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } -func (m *EventMessage) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalToSizedBuffer(dAtA[:size]) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *EventMessage) MarshalTo(dAtA []byte) (int, error) { - size := m.Size() - return m.MarshalToSizedBuffer(dAtA[:size]) -} - -func (m *EventMessage) MarshalToSizedBuffer(dAtA []byte) (int, error) { - i := len(dAtA) - _ = i - var l int - _ = l - if len(m.TxType) > 0 { - i -= len(m.TxType) - copy(dAtA[i:], m.TxType) - i = encodeVarintEvents(dAtA, i, uint64(len(m.TxType))) - i-- - dAtA[i] = 0x1a - } - if len(m.Sender) > 0 { - i -= len(m.Sender) - copy(dAtA[i:], m.Sender) - i = encodeVarintEvents(dAtA, i, uint64(len(m.Sender))) - i-- - dAtA[i] = 0x12 - } - if len(m.Module) > 0 { - i -= len(m.Module) - copy(dAtA[i:], m.Module) - i = encodeVarintEvents(dAtA, i, uint64(len(m.Module))) - i-- - dAtA[i] = 0xa - } - return len(dAtA) - i, nil -} - func (m *EventBlockBloom) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -1112,27 +1000,6 @@ func (m *EventTxLog) Size() (n int) { return n } -func (m *EventMessage) Size() (n int) { - if m == nil { - return 0 - } - var l int - _ = l - l = len(m.Module) - if l > 0 { - n += 1 + l + sovEvents(uint64(l)) - } - l = len(m.Sender) - if l > 0 { - n += 1 + l + sovEvents(uint64(l)) - } - l = len(m.TxType) - if l > 0 { - n += 1 + l + sovEvents(uint64(l)) - } - return n -} - func (m *EventBlockBloom) Size() (n int) { if m == nil { return 0 @@ -1610,152 +1477,6 @@ func (m *EventTxLog) Unmarshal(dAtA []byte) error { } return nil } -func (m *EventMessage) Unmarshal(dAtA []byte) error { - l := len(dAtA) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - wire |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: EventMessage: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: EventMessage: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Module", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Module = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Sender", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Sender = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field TxType", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowEvents - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthEvents - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return ErrInvalidLengthEvents - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.TxType = string(dAtA[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipEvents(dAtA[iNdEx:]) - if err != nil { - return err - } - if (skippy < 0) || (iNdEx+skippy) < 0 { - return ErrInvalidLengthEvents - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *EventBlockBloom) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/evm/keeper/msg_server.go b/x/evm/keeper/msg_server.go index 10fdfb131..36f2d2d9b 100644 --- a/x/evm/keeper/msg_server.go +++ b/x/evm/keeper/msg_server.go @@ -41,61 +41,6 @@ func (k *Keeper) EthereumTx( if err != nil { return nil, errors.Wrap(err, "failed to apply transaction") } - - attrs := []sdk.Attribute{ - sdk.NewAttribute(sdk.AttributeKeyAmount, tx.Value().String()), - // add event for ethereum transaction hash format - sdk.NewAttribute(evm.AttributeKeyEthereumTxHash, resp.Hash), - // add event for index of valid ethereum tx - sdk.NewAttribute(evm.AttributeKeyTxIndex, strconv.FormatUint(k.EvmState.BlockTxIndex.GetOr(ctx, 0), 10)), - // add event for eth tx gas used, we can't get it from cosmos tx result when it contains multiple eth tx msgs. - sdk.NewAttribute(evm.AttributeKeyTxGasUsed, strconv.FormatUint(resp.GasUsed, 10)), - // TODO: fix: It's odd that each event is emitted twice. Migrate to typed - // events and change EVM indexer to align. - // sdk.NewAttribute("emitted_from", "EthereumTx"), - } - - if len(ctx.TxBytes()) > 0 { - // add event for tendermint transaction hash format - hash := tmbytes.HexBytes(tmtypes.Tx(ctx.TxBytes()).Hash()) - attrs = append(attrs, sdk.NewAttribute(evm.AttributeKeyTxHash, hash.String())) - } - - if to := tx.To(); to != nil { - attrs = append(attrs, sdk.NewAttribute(evm.AttributeKeyRecipient, to.Hex())) - } - - if resp.Failed() { - attrs = append(attrs, sdk.NewAttribute(evm.AttributeKeyEthereumTxFailed, resp.VmError)) - } - - txLogAttrs := make([]sdk.Attribute, len(resp.Logs)) - for i, log := range resp.Logs { - value, err := json.Marshal(log) - if err != nil { - return nil, errors.Wrap(err, "failed to encode log") - } - txLogAttrs[i] = sdk.NewAttribute(evm.AttributeKeyTxLog, string(value)) - } - - // emit events - ctx.EventManager().EmitEvents(sdk.Events{ - sdk.NewEvent( - evm.EventTypeEthereumTx, - attrs..., - ), - sdk.NewEvent( - evm.EventTypeTxLog, - txLogAttrs..., - ), - sdk.NewEvent( - sdk.EventTypeMessage, - sdk.NewAttribute(sdk.AttributeKeyModule, evm.AttributeValueCategory), - sdk.NewAttribute(sdk.AttributeKeySender, msg.From), - sdk.NewAttribute(evm.AttributeKeyTxType, fmt.Sprintf("%d", tx.Type())), - ), - }) - return resp, nil } @@ -118,7 +63,7 @@ func (k *Keeper) ApplyEvmTx( tmpCtx, commit := ctx.CacheContext() // pass true to commit the StateDB - res, err := k.ApplyEvmMsg(tmpCtx, msg, nil, true, evmConfig, txConfig) + evmResp, err := k.ApplyEvmMsg(tmpCtx, msg, nil, true, evmConfig, txConfig) if err != nil { // when a transaction contains multiple msg, as long as one of the msg fails // all gas will be deducted. so is not msg.Gas() @@ -126,9 +71,9 @@ func (k *Keeper) ApplyEvmTx( return nil, errors.Wrap(err, "failed to apply ethereum core message") } - logs := evm.LogsToEthereum(res.Logs) + logs := evm.LogsToEthereum(evmResp.Logs) - cumulativeGasUsed := res.GasUsed + cumulativeGasUsed := evmResp.GasUsed if ctx.BlockGasMeter() != nil { limit := ctx.BlockGasMeter().Limit() cumulativeGasUsed += ctx.BlockGasMeter().GasConsumed() @@ -150,40 +95,24 @@ func (k *Keeper) ApplyEvmTx( Logs: logs, TxHash: txConfig.TxHash, ContractAddress: contractAddr, - GasUsed: res.GasUsed, + GasUsed: evmResp.GasUsed, BlockHash: txConfig.BlockHash, BlockNumber: big.NewInt(ctx.BlockHeight()), TransactionIndex: txConfig.TxIndex, } - if !res.Failed() { + if !evmResp.Failed() { receipt.Status = gethcore.ReceiptStatusSuccessful commit() - ctx.EventManager().EmitEvents(tmpCtx.EventManager().Events()) - - // Emit typed events - if msg.To() == nil { // contract creation - _ = ctx.EventManager().EmitTypedEvent(&evm.EventContractDeployed{ - Sender: msg.From().String(), - ContractAddr: contractAddr.String(), - }) - } else if len(msg.Data()) > 0 { // contract executed - _ = ctx.EventManager().EmitTypedEvent(&evm.EventContractExecuted{ - Sender: msg.From().String(), - ContractAddr: msg.To().String(), - }) - } else if msg.Value().Cmp(big.NewInt(0)) > 0 { // evm transfer - _ = ctx.EventManager().EmitTypedEvent(&evm.EventTransfer{ - Sender: msg.From().String(), - Recipient: msg.To().String(), - Amount: msg.Value().String(), - }) - } } // refund gas in order to match the Ethereum gas consumption instead of the default SDK one. - if err = k.RefundGas(ctx, msg, msg.Gas()-res.GasUsed, evmConfig.Params.EvmDenom); err != nil { - return nil, errors.Wrapf(err, "failed to refund gas leftover gas to sender %s", msg.From()) + refundGas := uint64(0) + if msg.Gas() > evmResp.GasUsed { + refundGas = msg.Gas() - evmResp.GasUsed + } + if err = k.RefundGas(ctx, msg, refundGas, evmConfig.Params.EvmDenom); err != nil { + return nil, errors.Wrapf(err, "failed to refund leftover gas to sender %s", msg.From()) } if len(receipt.Logs) > 0 { @@ -193,17 +122,23 @@ func (k *Keeper) ApplyEvmTx( k.EvmState.BlockLogSize.Set(ctx, blockLogSize) } - blockTxIdx := uint64(txConfig.TxIndex) + 1 - k.EvmState.BlockTxIndex.Set(ctx, blockTxIdx) - - totalGasUsed, err := k.AddToBlockGasUsed(ctx, res.GasUsed) + totalGasUsed, err := k.AddToBlockGasUsed(ctx, evmResp.GasUsed) if err != nil { return nil, errors.Wrap(err, "failed to add transient gas used") } // reset the gas meter for current cosmos transaction k.ResetGasMeterAndConsumeGas(ctx, totalGasUsed) - return res, nil + + err = k.EmitEthereumTxEvents(ctx, tx, msg, evmResp, contractAddr) + if err != nil { + return nil, errors.Wrap(err, "failed to emit ethereum tx events") + } + + blockTxIdx := uint64(txConfig.TxIndex) + 1 + k.EvmState.BlockTxIndex.Set(ctx, blockTxIdx) + + return evmResp, nil } // NewEVM generates a go-ethereum VM. @@ -734,3 +669,76 @@ func (k Keeper) convertCoinNativeERC20( return &evm.MsgConvertCoinToEvmResponse{}, nil } + +// EmitEthereumTxEvents emits all types of EVM events applicable to a particular execution case +func (k *Keeper) EmitEthereumTxEvents( + ctx sdk.Context, + tx *gethcore.Transaction, + msg gethcore.Message, + evmResp *evm.MsgEthereumTxResponse, + contractAddr gethcommon.Address, +) error { + // Typed event: eth.evm.v1.EventEthereumTx + eventEthereumTx := &evm.EventEthereumTx{ + EthHash: evmResp.Hash, + Index: strconv.FormatUint(k.EvmState.BlockTxIndex.GetOr(ctx, 0), 10), + GasUsed: strconv.FormatUint(evmResp.GasUsed, 10), + } + if len(ctx.TxBytes()) > 0 { + eventEthereumTx.Hash = tmbytes.HexBytes(tmtypes.Tx(ctx.TxBytes()).Hash()).String() + } + if to := tx.To(); to != nil { + eventEthereumTx.Recipient = to.Hex() + } + if evmResp.Failed() { + eventEthereumTx.EthTxFailed = evmResp.VmError + } + err := ctx.EventManager().EmitTypedEvent(eventEthereumTx) + if err != nil { + return errors.Wrap(err, "failed to emit event ethereum tx") + } + + // Typed event: eth.evm.v1.EventTxLog + txLogs := make([]string, len(evmResp.Logs)) + for i, log := range evmResp.Logs { + value, err := json.Marshal(log) + if err != nil { + return errors.Wrap(err, "failed to encode log") + } + txLogs[i] = string(value) + } + _ = ctx.EventManager().EmitTypedEvent(&evm.EventTxLog{TxLogs: txLogs}) + + // Untyped event: "message", used for tendermint subscription + ctx.EventManager().EmitEvent( + sdk.NewEvent( + sdk.EventTypeMessage, + sdk.NewAttribute(sdk.AttributeKeyModule, evm.ModuleName), + sdk.NewAttribute(sdk.AttributeKeySender, msg.From().Hex()), + sdk.NewAttribute(evm.MessageEventAttrTxType, fmt.Sprintf("%d", tx.Type())), + ), + ) + + // Emit typed events + if !evmResp.Failed() { + if tx.To() == nil { // contract creation + _ = ctx.EventManager().EmitTypedEvent(&evm.EventContractDeployed{ + Sender: msg.From().Hex(), + ContractAddr: contractAddr.String(), + }) + } else if len(msg.Data()) > 0 { // contract executed + _ = ctx.EventManager().EmitTypedEvent(&evm.EventContractExecuted{ + Sender: msg.From().Hex(), + ContractAddr: msg.To().String(), + }) + } else if msg.Value().Cmp(big.NewInt(0)) > 0 { // evm transfer + _ = ctx.EventManager().EmitTypedEvent(&evm.EventTransfer{ + Sender: msg.From().Hex(), + Recipient: msg.To().Hex(), + Amount: msg.Value().String(), + }) + } + } + + return nil +} diff --git a/x/evm/msg.go b/x/evm/msg.go index dc15727c5..f27cade13 100644 --- a/x/evm/msg.go +++ b/x/evm/msg.go @@ -38,12 +38,6 @@ var ( _ codectypes.UnpackInterfacesMessage = MsgEthereumTx{} ) -// message type and route constants -const ( - // TypeMsgEthereumTx defines the type string of an Ethereum transaction - TypeMsgEthereumTx = "ethereum_tx" -) - // NewTx returns a reference to a new Ethereum transaction message. func NewTx( tx *EvmTxArgs, @@ -147,8 +141,7 @@ func (msg *MsgEthereumTx) FromEthereumTx(tx *gethcore.Transaction) error { // Route returns the route value of an MsgEthereumTx. func (msg MsgEthereumTx) Route() string { return RouterKey } -// Type returns the type value of an MsgEthereumTx. -func (msg MsgEthereumTx) Type() string { return TypeMsgEthereumTx } +func (msg MsgEthereumTx) Type() string { return proto.MessageName(new(MsgEthereumTx)) } // ValidateBasic implements the sdk.Msg interface. It performs basic validation // checks of a Transaction. If returns an error if validation fails. @@ -185,7 +178,7 @@ func (msg MsgEthereumTx) ValidateBasic() error { return errorsmod.Wrap(err, "failed \"TxData.Validate\"") } - // Validate Hash field after validated txData to avoid panic + // Validate EthHash field after validated txData to avoid panic txHash := msg.AsTransaction().Hash().Hex() if msg.Hash != txHash { return errorsmod.Wrapf(errortypes.ErrInvalidRequest, "invalid tx hash %s, expected: %s", msg.Hash, txHash) diff --git a/x/evm/msg_test.go b/x/evm/msg_test.go index 98beaafdc..0dd7d8fb9 100644 --- a/x/evm/msg_test.go +++ b/x/evm/msg_test.go @@ -66,7 +66,6 @@ func (s *MsgsSuite) TestMsgEthereumTx_Constructor() { // suite.Require().Equal(msg.Data.To, suite.to.Hex()) s.Require().Equal(msg.Route(), evm.RouterKey) - s.Require().Equal(msg.Type(), evm.TypeMsgEthereumTx) // suite.Require().NotNil(msg.To()) s.Require().Equal(msg.GetMsgs(), []sdk.Msg{msg}) s.Require().Panics(func() { msg.GetSigners() }) From 158874497044d1e32e58ac44a54852da7778f982 Mon Sep 17 00:00:00 2001 From: Unique Divine <51418232+Unique-Divine@users.noreply.github.com> Date: Thu, 3 Oct 2024 07:32:25 -0500 Subject: [PATCH 2/2] fix(evm-precompiles): add assertNumArgs validation (#2060) --- CHANGELOG.md | 1 + x/evm/precompile/errors.go | 10 ++++++++++ x/evm/precompile/funtoken.go | 7 ++++--- x/evm/precompile/wasm.go | 7 ++++--- x/evm/precompile/wasm_parse.go | 28 ++++++++++++++++------------ 5 files changed, 35 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 737577fb3..50af1cb18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -125,6 +125,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#2045](https://github.com/NibiruChain/nibiru/pull/2045) - test(evm): backend tests with test network and real txs - [#2053](https://github.com/NibiruChain/nibiru/pull/2053) - refactor(evm): converted untyped event to typed and cleaned up - [#2054](https://github.com/NibiruChain/nibiru/pull/2054) - feat(evm-precompile): Precompile for one-way EVM calls to invoke/execute Wasm contracts. +- [#2060](https://github.com/NibiruChain/nibiru/pull/2060) - fix(evm-precompiles): add assertNumArgs validation #### Dapp modules: perp, spot, oracle, etc diff --git a/x/evm/precompile/errors.go b/x/evm/precompile/errors.go index 5f4ee88da..f22ed9f7e 100644 --- a/x/evm/precompile/errors.go +++ b/x/evm/precompile/errors.go @@ -43,3 +43,13 @@ func assertContractQuery(contract *vm.Contract) error { return nil } + +// assertNumArgs checks if the number of provided arguments matches the expected +// count. If lenArgs does not equal wantArgsLen, it returns an error describing +// the mismatch between expected and actual argument counts. +func assertNumArgs(lenArgs, wantArgsLen int) error { + if lenArgs != wantArgsLen { + return fmt.Errorf("expected %d arguments but got %d", wantArgsLen, lenArgs) + } + return nil +} diff --git a/x/evm/precompile/funtoken.go b/x/evm/precompile/funtoken.go index 6eaf1bbff..042544269 100644 --- a/x/evm/precompile/funtoken.go +++ b/x/evm/precompile/funtoken.go @@ -205,9 +205,10 @@ func (p precompileFunToken) decomposeBankSendArgs(args []any) ( to string, err error, ) { - // Note: The number of arguments is valiated before this function is called - // during "DecomposeInput". DecomposeInput calls "method.Inputs.Unpack", - // which validates against the the structure of the precompile's ABI. + if e := assertNumArgs(len(args), 3); e != nil { + err = e + return + } erc20, ok := args[0].(gethcommon.Address) if !ok { diff --git a/x/evm/precompile/wasm.go b/x/evm/precompile/wasm.go index 8e8c446dc..091999ee3 100644 --- a/x/evm/precompile/wasm.go +++ b/x/evm/precompile/wasm.go @@ -355,9 +355,10 @@ func (p precompileWasm) queryRaw( return bz, err } - // Note: The number of arguments is valiated before this function is called - // during "DecomposeInput". DecomposeInput calls "method.Inputs.Unpack", - // which validates against the the structure of the precompile's ABI. + if e := assertNumArgs(len(args), 2); e != nil { + err = e + return + } argIdx := 0 wasmContract, e := parseContractAddrArg(args[argIdx]) diff --git a/x/evm/precompile/wasm_parse.go b/x/evm/precompile/wasm_parse.go index 2f447c340..80d950622 100644 --- a/x/evm/precompile/wasm_parse.go +++ b/x/evm/precompile/wasm_parse.go @@ -84,9 +84,10 @@ func (p precompileWasm) parseInstantiateArgs(args []any, sender string) ( txMsg wasm.MsgInstantiateContract, err error, ) { - // Note: The number of arguments is valiated before this function is called - // during "DecomposeInput". DecomposeInput calls "method.Inputs.Unpack", - // which validates against the the structure of the precompile's ABI. + if e := assertNumArgs(len(args), 5); e != nil { + err = e + return + } argIdx := 0 admin, ok := args[argIdx].(string) @@ -142,9 +143,10 @@ func (p precompileWasm) parseExecuteArgs(args []any) ( funds sdk.Coins, err error, ) { - // Note: The number of arguments is valiated before this function is called - // during "DecomposeInput". DecomposeInput calls "method.Inputs.Unpack", - // which validates against the the structure of the precompile's ABI. + if e := assertNumArgs(len(args), 3); e != nil { + err = e + return + } argIdx := 0 contractAddrStr, ok := args[argIdx].(string) @@ -187,9 +189,10 @@ func (p precompileWasm) parseQueryArgs(args []any) ( req wasm.RawContractMessage, err error, ) { - // Note: The number of arguments is valiated before this function is called - // during "DecomposeInput". DecomposeInput calls "method.Inputs.Unpack", - // which validates against the the structure of the precompile's ABI. + if e := assertNumArgs(len(args), 2); e != nil { + err = e + return + } argsIdx := 0 wasmContract, e := parseContractAddrArg(args[argsIdx]) @@ -220,9 +223,10 @@ func (p precompileWasm) parseExecuteMultiArgs(args []any) ( }, err error, ) { - // Note: The number of arguments is valiated before this function is called - // during "DecomposeInput". DecomposeInput calls "method.Inputs.Unpack", - // which validates against the the structure of the precompile's ABI. + if e := assertNumArgs(len(args), 1); e != nil { + err = e + return + } arg := args[0] execMsgs, ok := arg.([]struct {