From f8e5a2b5c74686e5ab8373ca0de95744191cf88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 29 Jun 2026 11:53:08 +0200 Subject: [PATCH] test: Add a logs expectation to the state_transition fixture The state_transition fixture could assert state, gas, and trace, but not emitted logs. Add an optional Expectation::logs, compared field-by-field (count, address, data, topics, and order) in the runner, with a LOG1 smoke test pinning the mechanism. --- test/unittests/state_transition.cpp | 11 +++++++++++ test/unittests/state_transition.hpp | 4 ++++ test/unittests/state_transition_tx_test.cpp | 12 ++++++++++++ 3 files changed, 27 insertions(+) diff --git a/test/unittests/state_transition.cpp b/test/unittests/state_transition.cpp index 569e5f7fb9..447abd380c 100644 --- a/test/unittests/state_transition.cpp +++ b/test/unittests/state_transition.cpp @@ -88,6 +88,17 @@ void state_transition::TearDown() { EXPECT_EQ(receipt.gas_refund, *expect.gas_refund); } + if (expect.logs.has_value()) + { + ASSERT_EQ(receipt.logs.size(), expect.logs->size()) << "unexpected number of logs"; + for (size_t i = 0; i < expect.logs->size(); ++i) + { + EXPECT_EQ(receipt.logs[i].addr, (*expect.logs)[i].addr) << "log " << i << " addr"; + EXPECT_EQ(receipt.logs[i].data, (*expect.logs)[i].data) << "log " << i << " data"; + EXPECT_EQ(receipt.logs[i].topics, (*expect.logs)[i].topics) + << "log " << i << " topics"; + } + } // Update default expectations - valid transaction means coinbase exists unless explicitly // requested otherwise if (!expect.post.contains(Coinbase)) diff --git a/test/unittests/state_transition.hpp b/test/unittests/state_transition.hpp index e3e4b7cd58..603a57edce 100644 --- a/test/unittests/state_transition.hpp +++ b/test/unittests/state_transition.hpp @@ -62,6 +62,10 @@ class state_transition : public ExportableFixture /// (`gas_used + gas_refund` equals `max(pre-refund gas, EIP-7623 floor)`). std::optional gas_refund; + /// The expected logs emitted by the transaction. When set, the receipt's logs must match + /// exactly: count, address, data, topics, and order. + std::optional> logs; + /// The expected post-execution state. std::unordered_map post; diff --git a/test/unittests/state_transition_tx_test.cpp b/test/unittests/state_transition_tx_test.cpp index 71d92958c3..4aae9de14b 100644 --- a/test/unittests/state_transition_tx_test.cpp +++ b/test/unittests/state_transition_tx_test.cpp @@ -268,3 +268,15 @@ TEST_F(state_transition, invalid_access_list_amsterdam_gas_limit_below_floor) tx.gas_limit = 28679; expect.tx_error = INTRINSIC_GAS_TOO_LOW; } + +TEST_F(state_transition, tx_emits_log) +{ + // Smoke test for the `expect.logs` mechanism: assert a log with data and a topic from LOG1. + static constexpr auto TOPIC = 0xaa_bytes32; + tx.to = To; + // Store 0xaabbccdd at mem[28..31], then LOG1(offset=28, size=4, TOPIC) over those bytes. + pre[To] = {.code = mstore(0, 0xaabbccdd) + push(TOPIC) + push(4) + push(28) + OP_LOG1}; + + expect.post[To] = {}; // the contract survives (has code). + expect.logs = {Log{To, bytes{0xaa, 0xbb, 0xcc, 0xdd}, {TOPIC}}}; +}