From c8ce02eb507086ccde4a7f66d572af266de398d7 Mon Sep 17 00:00:00 2001 From: Andrew Low Date: Fri, 6 Oct 2023 16:52:12 -0700 Subject: [PATCH] init db schema update db schema initial version of abi analyzer don't parse result fix import cycle proper argument unwrapping rename migration nit update db schema address comments address more comments nit address comments address comments add startup logic testing fixes rename migration nits nits enable emerald abi in regression tests (no-op) --- analyzer/evmabibackfill/evm_abi_backfill.go | 324 ++++++++++++++++++ analyzer/evmcontractcode/evm_contract_code.go | 2 +- analyzer/queries/queries.go | 83 ++++- cmd/analyzer/analyzer.go | 11 + config/config.go | 6 + storage/migrations/02_runtimes.up.sql | 10 + storage/migrations/26_runtime_abi.up.sql | 29 ++ tests/e2e_regression/e2e_config_2.yml | 2 + 8 files changed, 465 insertions(+), 2 deletions(-) create mode 100644 analyzer/evmabibackfill/evm_abi_backfill.go create mode 100644 storage/migrations/26_runtime_abi.up.sql diff --git a/analyzer/evmabibackfill/evm_abi_backfill.go b/analyzer/evmabibackfill/evm_abi_backfill.go new file mode 100644 index 000000000..b69d6764c --- /dev/null +++ b/analyzer/evmabibackfill/evm_abi_backfill.go @@ -0,0 +1,324 @@ +package evmabibackfill + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + sdkEVM "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/evm" + + "github.com/oasisprotocol/nexus/analyzer" + "github.com/oasisprotocol/nexus/analyzer/item" + "github.com/oasisprotocol/nexus/analyzer/queries" + "github.com/oasisprotocol/nexus/analyzer/runtime/abiparse" + "github.com/oasisprotocol/nexus/common" + "github.com/oasisprotocol/nexus/config" + "github.com/oasisprotocol/nexus/log" + "github.com/oasisprotocol/nexus/storage" +) + +const ( + evmAbiAnalyzerPrefix = "evm_abi_" +) + +type processor struct { + runtime common.Runtime + target storage.TargetStorage + logger *log.Logger +} + +var _ item.ItemProcessor[*abiEncodedItem] = (*processor)(nil) + +type abiEncodedTx struct { + TxHash string + TxData []byte + TxRevertReason *string +} + +type abiEncodedEvent struct { + Round uint64 + TxIndex *int + EventBody sdkEVM.Event +} + +type abiEncodedItem struct { + Tx *abiEncodedTx + Event *abiEncodedEvent + ContractAddr string + Abi json.RawMessage +} + +type abiEncodedArg struct { + Name string `json:"name"` + EvmType string `json:"evm_type"` + Value interface{} `json:"value"` +} + +func NewAnalyzer( + runtime common.Runtime, + cfg config.ItemBasedAnalyzerConfig, + target storage.TargetStorage, + logger *log.Logger, +) (analyzer.Analyzer, error) { + logger = logger.With("analyzer", evmAbiAnalyzerPrefix+runtime) + p := &processor{ + runtime, + target, + logger, + } + return item.NewAnalyzer[*abiEncodedItem]( + evmAbiAnalyzerPrefix+string(runtime), + cfg, + p, + target, + logger, + ) +} + +// Transaction data is canonically represented as a byte array. However, +// the transaction body is stored as a JSONB column in postgres, which +// causes the tx body->>data to be returned as a base64-encoded string +// enclosed by escaped double quote characters. +func cleanTxData(raw string) ([]byte, error) { + s := strings.TrimPrefix(strings.TrimSuffix(raw, "\""), "\"") + return base64.StdEncoding.DecodeString(s) +} + +func (p *processor) GetItems(ctx context.Context, limit uint64) ([]*abiEncodedItem, error) { + // There are two types of data we process using a contract abi: transactions and events. + // Within a transaction, we process the call data and the revert reason. Since they are + // colocated in the same table we can fetch them using a single query. + var items []*abiEncodedItem + txRows, err := p.target.Query(ctx, queries.RuntimeEvmVerifiedContractTxs, p.runtime, limit) + if err != nil { + return nil, fmt.Errorf("querying verified contract txs: %w", err) + } + defer txRows.Close() + for txRows.Next() { + var rawTxData string + var tx abiEncodedTx + var item abiEncodedItem + item.Tx = &tx + if err = txRows.Scan( + &item.ContractAddr, + &item.Abi, + &tx.TxHash, + &rawTxData, + &tx.TxRevertReason, + ); err != nil { + return nil, fmt.Errorf("scanning verified contract tx: %w", err) + } + if tx.TxData, err = cleanTxData(rawTxData); err != nil { + return nil, fmt.Errorf("error decoding tx data from db: %w", err) + } + items = append(items, &item) + } + // Short circuit. + if len(items) == int(limit) { + return items, nil + } + eventRows, err := p.target.Query(ctx, queries.RuntimeEvmVerifiedContractEvents, p.runtime, int(limit)-len(items)) + if err != nil { + return nil, fmt.Errorf("querying verified contract evs: %w", err) + } + defer eventRows.Close() + for eventRows.Next() { + var ev abiEncodedEvent + var item abiEncodedItem + item.Event = &ev + if err = eventRows.Scan( + &item.ContractAddr, + &item.Abi, + &ev.Round, + &ev.TxIndex, + &ev.EventBody, + ); err != nil { + return nil, fmt.Errorf("scanning verified contract event: %w", err) + } + items = append(items, &item) + } + return items, nil +} + +// Transaction revert reasons for failed evm transactions have been encoded +// differently over the course of Oasis history. Older transaction revert +// reasons were returned as one of +// - "reverted: Incorrect premium amount" +// - "reverted: base64(up to 1024 bytes of revert data)" +// +// Note that if the revert reason was longer than 1024 bytes it was truncated. +// Newer transaction revert reasons are returned as +// - "reverted: base64(revert data)" +// +// In all cases the revert reason has the "reverted: " prefix, which we first +// strip. We then attempt to base64-decode the remaining string to recover +// the error message. It should be noted that if the b64 decoding fails, it's +// likely an older error message. +// +// See the docstring of tryParseErrorMessage in analyzer/runtime/extract.go +// for more info. +func cleanTxRevertReason(raw string) ([]byte, error) { + s := strings.TrimPrefix(raw, "reverted: ") + return base64.StdEncoding.DecodeString(s) +} + +func (p *processor) ProcessItem(ctx context.Context, batch *storage.QueryBatch, item *abiEncodedItem) error { + // Unmarshal abi + contractAbi, err := abi.JSON(bytes.NewReader(item.Abi)) + if err != nil { + return fmt.Errorf("error unmarshalling abi: %w", err) + } + // Parse data + if item.Event != nil { //nolint:nestif // has complex nested blocks (complexity: 12) (nestif) + abiEvent, abiEventArgs, err := abiparse.ParseEvent(item.Event.EventBody.Topics, item.Event.EventBody.Data, &contractAbi) + if err != nil { + queueIncompatibleEventUpdate(batch, p.runtime, item.Event.Round, item.Event.TxIndex) + p.logger.Warn("error processing event using abi", "contract address", item.ContractAddr, "err", err) + return nil + } + eventArgs, err := marshalArgs(abiEvent.Inputs, abiEventArgs) + if err != nil { + queueIncompatibleEventUpdate(batch, p.runtime, item.Event.Round, item.Event.TxIndex) + p.logger.Warn("error processing event args using abi", "contract address", item.ContractAddr, "err", err) + return nil + } + + batch.Queue( + queries.RuntimeEventEvmParsedFieldsUpdate, + p.runtime, + item.Event.Round, + item.Event.TxIndex, + abiEvent.RawName, + eventArgs, + abiEvent.ID, + ) + } else if item.Tx != nil { + method, abiTxArgs, err := abiparse.ParseData(item.Tx.TxData, &contractAbi) + if err != nil { + queueIncompatibleTxUpdate(batch, p.runtime, item.Tx.TxHash) + p.logger.Warn("error processing tx using abi", "contract address", item.ContractAddr, "err", err) + return nil + } + txArgs, err := marshalArgs(method.Inputs, abiTxArgs) + if err != nil { + queueIncompatibleTxUpdate(batch, p.runtime, item.Tx.TxHash) + p.logger.Warn("error processing tx args using abi", "contract address", item.ContractAddr, "err", err) + return nil + } + var abiErrName string + var abiErr *abi.Error + var abiErrArgs []interface{} + var errArgs []*abiEncodedArg + if item.Tx.TxRevertReason != nil { + txrr, err := cleanTxRevertReason(*item.Tx.TxRevertReason) + if err != nil { + // This is most likely an older tx with a plaintext revert reason, such + // as "reverted: Ownable: caller is not the owner". In this case, we do + // not parse the error with the abi, but we still update the tx table with + // the method and args. + batch.Queue( + queries.RuntimeTransactionEvmParsedFieldsUpdate, + p.runtime, + item.Tx.TxHash, + method.RawName, + txArgs, + nil, // error name + nil, // error args + ) + p.logger.Info("encountered likely old-style reverted transaction", "revert reason", item.Tx.TxRevertReason, "tx hash", item.Tx.TxHash, "contract address", item.ContractAddr, "err", err) + return nil + } + abiErr, abiErrArgs, err = abiparse.ParseError(txrr, &contractAbi) + if err != nil || abiErr == nil { + queueIncompatibleTxUpdate(batch, p.runtime, item.Tx.TxHash) + p.logger.Warn("error processing tx error using abi", "contract address", item.ContractAddr, "err", err) + return nil + } + abiErrName = abiErr.Name + errArgs, err = marshalArgs(abiErr.Inputs, abiErrArgs) + if err != nil { + queueIncompatibleTxUpdate(batch, p.runtime, item.Tx.TxHash) + p.logger.Warn("error processing tx error args", "contract address", item.ContractAddr, "err", err) + return nil + } + } + batch.Queue( + queries.RuntimeTransactionEvmParsedFieldsUpdate, + p.runtime, + item.Tx.TxHash, + method.RawName, + txArgs, + abiErrName, + errArgs, + ) + } + + return nil +} + +// If abi processing of an event fails, it is likely due to incompatible +// data+abi, which is the event's fault. We thus mark the incompatible +// event as processed and continue. +// +// Note that if the abi is ever updated, we may need to revisit these events. +func queueIncompatibleEventUpdate(batch *storage.QueryBatch, runtime common.Runtime, round uint64, txIndex *int) { + batch.Queue( + queries.RuntimeEventEvmParsedFieldsUpdate, + runtime, + round, + txIndex, + nil, // event name + nil, // event args + nil, // event signature + ) +} + +// If abi processing of an transaction fails, it is likely due to incompatible +// data+abi, which is the transaction's fault. We thus mark the incompatible +// transaction as processed and continue. +// +// Note that if the abi is ever updated, we may need to revisit these txs. +func queueIncompatibleTxUpdate(batch *storage.QueryBatch, runtime common.Runtime, txHash string) { + batch.Queue( + queries.RuntimeTransactionEvmParsedFieldsUpdate, + runtime, + txHash, + nil, // method name + nil, // method args + nil, // error name + nil, // error args + ) +} + +func marshalArgs(abiArgs abi.Arguments, argVals []interface{}) ([]*abiEncodedArg, error) { + if len(abiArgs) != len(argVals) { + return nil, fmt.Errorf("number of args does not match abi specification") + } + args := []*abiEncodedArg{} + for i, v := range argVals { + args = append(args, &abiEncodedArg{ + Name: abiArgs[i].Name, + EvmType: abiArgs[i].Type.String(), + Value: v, + }) + } + + return args, nil +} + +func (p *processor) QueueLength(ctx context.Context) (int, error) { + var txQueueLength int + if err := p.target.QueryRow(ctx, fmt.Sprintf("SELECT COUNT(*) FROM (%s) subquery", queries.RuntimeEvmVerifiedContractTxs), p.runtime, 1000).Scan(&txQueueLength); err != nil { + return 0, fmt.Errorf("querying number of verified abi txs: %w", err) + } + var evQueueLength int + // We limit the event count for performance reasons since the query requires a join of the transactions and events tables. + if err := p.target.QueryRow(ctx, fmt.Sprintf("SELECT COUNT(*) FROM (%s) subquery", queries.RuntimeEvmVerifiedContractEvents), p.runtime, 1000).Scan(&evQueueLength); err != nil { + return 0, fmt.Errorf("querying number of verified abi events: %w", err) + } + return txQueueLength + evQueueLength, nil +} diff --git a/analyzer/evmcontractcode/evm_contract_code.go b/analyzer/evmcontractcode/evm_contract_code.go index 10656217e..3bbbf4060 100644 --- a/analyzer/evmcontractcode/evm_contract_code.go +++ b/analyzer/evmcontractcode/evm_contract_code.go @@ -89,7 +89,7 @@ func (p *processor) GetItems(ctx context.Context, limit uint64) ([]*ContractCand } func (p *processor) ProcessItem(ctx context.Context, batch *storage.QueryBatch, candidate *ContractCandidate) error { - p.logger.Info("downloading code", "addr", candidate.Addr, "eth_addr", candidate.EthAddr.Hex()) + p.logger.Info("downloading code", "addr", candidate.Addr, "eth_addr", candidate.EthAddr.Hex(), "round", candidate.DownloadRound) code, err := p.source.EVMGetCode(ctx, candidate.DownloadRound, candidate.EthAddr.Bytes()) if err != nil { // Write nothing into the DB; we'll try again later. diff --git a/analyzer/queries/queries.go b/analyzer/queries/queries.go index 23963aa64..022e1cdae 100644 --- a/analyzer/queries/queries.go +++ b/analyzer/queries/queries.go @@ -457,10 +457,34 @@ var ( INSERT INTO chain.runtime_transactions (runtime, round, tx_index, tx_hash, tx_eth_hash, fee, gas_limit, gas_used, size, timestamp, method, body, "to", amount, evm_encrypted_format, evm_encrypted_public_key, evm_encrypted_data_nonce, evm_encrypted_data_data, evm_encrypted_result_nonce, evm_encrypted_result_data, success, error_module, error_code, error_message_raw, error_message) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25)` + RuntimeTransactionEvmParsedFieldsUpdate = ` + UPDATE chain.runtime_transactions + SET + evm_fn_name = $3, + evm_fn_params = $4, + error_message = $5, + error_params = $6, + abi_parsed_at = CURRENT_TIMESTAMP + WHERE + runtime = $1 AND + tx_hash = $2` + RuntimeEventInsert = ` INSERT INTO chain.runtime_events (runtime, round, tx_index, tx_hash, tx_eth_hash, timestamp, type, body, related_accounts, evm_log_name, evm_log_params, evm_log_signature) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)` + RuntimeEventEvmParsedFieldsUpdate = ` + UPDATE chain.runtime_events + SET + evm_log_name = $4, + evm_log_params = $5, + evm_log_signature = $6, + abi_parsed_at = CURRENT_TIMESTAMP + WHERE + runtime = $1 AND + round = $2 AND + tx_index = $3` + RuntimeMintInsert = ` INSERT INTO chain.runtime_transfers (runtime, round, sender, receiver, symbol, amount) VALUES ($1, $2, NULL, $3, $4, $5)` @@ -841,7 +865,9 @@ var ( address_preimages.address_data FROM chain.evm_contracts AS contracts LEFT JOIN chain.address_preimages AS address_preimages ON - address_preimages.address = contracts.contract_address + address_preimages.address = contracts.contract_address AND + address_preimages.context_identifier = 'oasis-runtime-sdk/address: secp256k1eth' AND + address_preimages.context_version = 0 WHERE runtime = $1 AND verification_info_downloaded_at IS NULL` @@ -854,4 +880,59 @@ var ( source_files = $5 WHERE runtime = $1 AND contract_address = $2` + + RuntimeEvmVerifiedContractTxs = ` + WITH abi_contracts AS ( + SELECT + runtime, + contract_address AS addr, + abi + FROM chain.evm_contracts + WHERE + runtime = $1 AND abi IS NOT NULL + ) + SELECT + abi_contracts.addr, + abi_contracts.abi, + txs.tx_hash, + txs.body->'data', + txs.error_message_raw + FROM abi_contracts + JOIN chain.runtime_transactions as txs ON + txs.runtime = abi_contracts.runtime AND + txs.to = abi_contracts.addr AND + txs.method = 'evm.Call' -- note: does not include evm.Create txs; their payload is never encrypted. + WHERE + body IS NOT NULL AND + abi_parsed_at IS NULL + ORDER BY addr + LIMIT $2` + + RuntimeEvmVerifiedContractEvents = ` + WITH abi_contracts AS ( + SELECT + runtime, + contract_address AS addr, + abi + FROM chain.evm_contracts + WHERE + runtime = $1 AND + abi IS NOT NULL + ) + SELECT + abi_contracts.addr, + abi_contracts.abi, + evs.round, + evs.tx_index, + evs.body + FROM abi_contracts + JOIN chain.address_preimages as preimages ON + abi_contracts.addr = preimages.address + JOIN chain.runtime_events as evs ON + evs.type = 'evm.log' AND + evs.runtime = abi_contracts.runtime AND + decode(body->>'address', 'base64') = preimages.address_data + WHERE + evs.abi_parsed_at IS NULL + LIMIT $2` ) diff --git a/cmd/analyzer/analyzer.go b/cmd/analyzer/analyzer.go index 2748fc5ac..a42c9d6db 100644 --- a/cmd/analyzer/analyzer.go +++ b/cmd/analyzer/analyzer.go @@ -20,6 +20,7 @@ import ( "github.com/oasisprotocol/nexus/analyzer" "github.com/oasisprotocol/nexus/analyzer/aggregate_stats" "github.com/oasisprotocol/nexus/analyzer/consensus" + "github.com/oasisprotocol/nexus/analyzer/evmabibackfill" "github.com/oasisprotocol/nexus/analyzer/evmcontractcode" "github.com/oasisprotocol/nexus/analyzer/evmnfts" "github.com/oasisprotocol/nexus/analyzer/evmnfts/ipfsclient" @@ -449,6 +450,16 @@ func NewService(cfg *config.AnalysisConfig) (*Service, error) { //nolint:gocyclo return evmverifier.NewAnalyzer(cfg.Source.ChainName, common.RuntimeSapphire, cfg.Analyzers.SapphireContractVerifier.ItemBasedAnalyzerConfig, cfg.Analyzers.SapphireContractVerifier.SourcifyServerUrl, dbClient, logger) }) } + if cfg.Analyzers.EmeraldAbi != nil { + analyzers, err = addAnalyzer(analyzers, err, syncTagEmerald, func() (A, error) { + return evmabibackfill.NewAnalyzer(common.RuntimeEmerald, cfg.Analyzers.EmeraldAbi.ItemBasedAnalyzerConfig, dbClient, logger) + }) + } + if cfg.Analyzers.SapphireAbi != nil { + analyzers, err = addAnalyzer(analyzers, err, syncTagSapphire, func() (A, error) { + return evmabibackfill.NewAnalyzer(common.RuntimeSapphire, cfg.Analyzers.SapphireAbi.ItemBasedAnalyzerConfig, dbClient, logger) + }) + } if cfg.Analyzers.MetadataRegistry != nil { analyzers, err = addAnalyzer(analyzers, err, "" /*syncTag*/, func() (A, error) { return metadata_registry.NewAnalyzer(cfg.Analyzers.MetadataRegistry.ItemBasedAnalyzerConfig, dbClient, logger) diff --git a/config/config.go b/config/config.go index 13cbf18f3..5b986db5b 100644 --- a/config/config.go +++ b/config/config.go @@ -139,6 +139,8 @@ type AnalyzersList struct { SapphireContractCode *EvmContractCodeAnalyzerConfig `koanf:"evm_contract_code_sapphire"` EmeraldContractVerifier *EVMContractVerifierConfig `koanf:"evm_contract_verifier_emerald"` SapphireContractVerifier *EVMContractVerifierConfig `koanf:"evm_contract_verifier_sapphire"` + EmeraldAbi *EvmAbiAnalyzerConfig `koanf:"evm_abi_emerald"` + SapphireAbi *EvmAbiAnalyzerConfig `koanf:"evm_abi_sapphire"` MetadataRegistry *MetadataRegistryConfig `koanf:"metadata_registry"` NodeStats *NodeStatsConfig `koanf:"node_stats"` @@ -411,6 +413,10 @@ func (cfg *EVMContractVerifierConfig) Validate() error { return nil } +type EvmAbiAnalyzerConfig struct { + ItemBasedAnalyzerConfig `koanf:",squash"` +} + // MetadataRegistryConfig is the configuration for the metadata registry analyzer. type MetadataRegistryConfig struct { ItemBasedAnalyzerConfig `koanf:",squash"` diff --git a/storage/migrations/02_runtimes.up.sql b/storage/migrations/02_runtimes.up.sql index 30e261dfb..2be2742f4 100644 --- a/storage/migrations/02_runtimes.up.sql +++ b/storage/migrations/02_runtimes.up.sql @@ -54,6 +54,10 @@ CREATE TABLE chain.runtime_transactions "to" oasis_addr, -- Exact semantics depend on method. Extracted from body; for convenience only. amount UINT_NUMERIC, -- Exact semantics depend on method. Extracted from body; for convenience only. + -- Added in 26_runtime_abi.up.sql + -- evm_fn_name TEXT, + -- evm_fn_params JSONB, + -- Encrypted data in encrypted Ethereum-format transactions. evm_encrypted_format call_format, evm_encrypted_public_key BYTEA, @@ -67,11 +71,17 @@ CREATE TABLE chain.runtime_transactions error_module TEXT, error_code UINT63, error_message TEXT + -- Added in 19_runtime_tx_errors.up.sql + -- error_message_raw TEXT + -- Added in 26_runtime_abi.up.sql + -- error_params JSONB + -- abi_parsed_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX ix_runtime_transactions_tx_hash ON chain.runtime_transactions USING hash (tx_hash); CREATE INDEX ix_runtime_transactions_tx_eth_hash ON chain.runtime_transactions USING hash (tx_eth_hash); CREATE INDEX ix_runtime_transactions_timestamp ON chain.runtime_transactions (runtime, timestamp); -- CREATE INDEX ix_runtime_transactions_to ON chain.runtime_transactions(runtime, "to"); -- Added in 12_evm_contract_gas.up.sql +-- CREATE INDEX ix_runtime_transactions_to_abi_parsed_at ON chain.runtime_transactions (runtime, "to", abi_parsed_at); -- Added in 25_runtime_abi.up.sql CREATE TABLE chain.runtime_transaction_signers ( diff --git a/storage/migrations/26_runtime_abi.up.sql b/storage/migrations/26_runtime_abi.up.sql new file mode 100644 index 000000000..4be011020 --- /dev/null +++ b/storage/migrations/26_runtime_abi.up.sql @@ -0,0 +1,29 @@ +BEGIN; + +ALTER TABLE chain.runtime_transactions + -- For evm.Call transactions, we store both the name of the function and + -- the function parameters. + ADD COLUMN evm_fn_name TEXT, + -- The function parameter values. Refer to the abi to see the parameter + -- names. Note that the parameters may be unnamed. + ADD COLUMN evm_fn_params JSONB, + -- Custom errors may be arbitrarily defined by the contract abi. This field + -- stores the full abi-decoded error object. Note that the error name is + -- stored separately in the existing error_message column. For example, if we + -- have an error like `InsufficientBalance{available: 4, required: 10}`. + -- the error_message column would hold `InsufficientBalance`, and + -- the error_params column would store `{available: 4, required: 10}`. + ADD COLUMN error_params JSONB, + -- Internal tracking for parsing evm.Call transactions using the contract + -- abi when available. + ADD COLUMN abi_parsed_at TIMESTAMP WITH TIME ZONE; +CREATE INDEX ix_runtime_transactions_to_abi_parsed_at ON chain.runtime_transactions (runtime, "to") + WHERE abi_parsed_at IS NULL; + +ALTER TABLE chain.runtime_events + -- Internal tracking for parsing evm.Call transactions using the contract + -- abi when available. + ADD COLUMN abi_parsed_at TIMESTAMP WITH TIME ZONE; +CREATE INDEX ix_runtime_events_type ON chain.runtime_events (runtime, type); + +COMMIT; diff --git a/tests/e2e_regression/e2e_config_2.yml b/tests/e2e_regression/e2e_config_2.yml index 2737cc773..c84c554ef 100644 --- a/tests/e2e_regression/e2e_config_2.yml +++ b/tests/e2e_regression/e2e_config_2.yml @@ -19,6 +19,8 @@ analysis: stop_on_empty_queue: true evm_contract_code_emerald: stop_on_empty_queue: true + evm_abi_emerald: + stop_on_empty_queue: true # Some non-block analyzers are not tested in e2e regressions. # They are largely not worth the trouble as they do not interact with rest of the system much. # metadata_registry: {} # Awkward to inject mock registry responses.