Skip to content

Commit 9b3ee17

Browse files
authored
Export state tests with invalid txs (#858)
1 parent ad99657 commit 9b3ee17

5 files changed

+48
-24
lines changed

test/statetest/statetest_runner.cpp

+10-3
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,18 @@ void run_state_test(const StateTransitionTest& test, evmc::VM& vm, bool trace_su
5050
std::clog << R"("stateRoot":"0x)" << hex(state_root) << "\"}\n";
5151
}
5252

53-
if (holds_alternative<state::TransactionReceipt>(res))
54-
EXPECT_EQ(logs_hash(get<state::TransactionReceipt>(res).logs), expected.logs_hash);
53+
if (expected.exception)
54+
{
55+
ASSERT_FALSE(holds_alternative<state::TransactionReceipt>(res))
56+
<< "unexpected valid transaction";
57+
EXPECT_EQ(logs_hash(std::vector<state::Log>()), expected.logs_hash);
58+
}
5559
else
56-
EXPECT_TRUE(expected.exception)
60+
{
61+
ASSERT_TRUE(holds_alternative<state::TransactionReceipt>(res))
5762
<< "unexpected invalid transaction: " << get<std::error_code>(res).message();
63+
EXPECT_EQ(logs_hash(get<state::TransactionReceipt>(res).logs), expected.logs_hash);
64+
}
5865

5966
EXPECT_EQ(state_root, expected.state_hash);
6067
}

test/unittests/state_transition.cpp

+29-18
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ void state_transition::SetUp()
1515
{
1616
pre.insert(tx.sender, {.nonce = 1, .balance = tx.gas_limit * tx.max_gas_price + tx.value + 1});
1717

18-
// Default expectations.
19-
expect.post[Coinbase].exists = true;
18+
// Default expectation (coinbase is added later for valid txs only).
2019
expect.post[tx.sender].exists = true;
2120
}
2221

@@ -69,20 +68,28 @@ void state_transition::TearDown()
6968
EXPECT_EQ(tx_error, expected_error)
7069
<< tx_error.message() << " vs " << expected_error.message();
7170

72-
// TODO: Compare states carefully, they should be identical.
7371
EXPECT_EQ(state.get_accounts().size(), pre.get_accounts().size());
7472
for (const auto& [addr, acc] : state.get_accounts())
7573
{
7674
EXPECT_TRUE(pre.get_accounts().contains(addr)) << "unexpected account " << addr;
7775
}
78-
79-
// TODO: Export also tests with invalid transactions.
80-
return; // Do not check anything else.
8176
}
77+
else
78+
{
79+
ASSERT_TRUE(holds_alternative<TransactionReceipt>(res))
80+
<< std::get<std::error_code>(res).message();
81+
const auto& receipt = std::get<TransactionReceipt>(res);
8282

83-
ASSERT_TRUE(holds_alternative<TransactionReceipt>(res))
84-
<< std::get<std::error_code>(res).message();
85-
const auto& receipt = std::get<TransactionReceipt>(res);
83+
EXPECT_EQ(receipt.status, expect.status);
84+
if (expect.gas_used.has_value())
85+
{
86+
EXPECT_EQ(receipt.gas_used, *expect.gas_used);
87+
}
88+
// Update default expectations - valid transaction means coinbase exists unless explicitly
89+
// requested otherwise
90+
if (expect.post.find(Coinbase) == expect.post.end())
91+
expect.post[Coinbase].exists = true;
92+
}
8693
state::finalize(state, rev, block.coinbase, block_reward, block.ommers, block.withdrawals);
8794

8895
if (trace)
@@ -92,12 +99,6 @@ void state_transition::TearDown()
9299
EXPECT_EQ(trace_capture->get_capture(), expect.trace);
93100
}
94101

95-
EXPECT_EQ(receipt.status, expect.status);
96-
if (expect.gas_used.has_value())
97-
{
98-
EXPECT_EQ(receipt.gas_used, *expect.gas_used);
99-
}
100-
101102
for (const auto& [addr, expected_acc] : expect.post)
102103
{
103104
const auto acc = state.find(addr);
@@ -146,7 +147,7 @@ void state_transition::TearDown()
146147
}
147148

148149
if (!export_file_path.empty())
149-
export_state_test(receipt, state);
150+
export_state_test(res, state);
150151
}
151152

152153
namespace
@@ -166,7 +167,8 @@ std::string_view to_test_fork_name(evmc_revision rev) noexcept
166167
}
167168
} // namespace
168169

169-
void state_transition::export_state_test(const TransactionReceipt& receipt, const State& post)
170+
void state_transition::export_state_test(
171+
const std::variant<TransactionReceipt, std::error_code>& res, const State& post)
170172
{
171173
json::json j;
172174
auto& jt = j[export_test_name];
@@ -225,7 +227,16 @@ void state_transition::export_state_test(const TransactionReceipt& receipt, cons
225227
auto& jpost = jt["post"][to_test_fork_name(rev)][0];
226228
jpost["indexes"] = {{"data", 0}, {"gas", 0}, {"value", 0}};
227229
jpost["hash"] = hex0x(mpt_hash(post.get_accounts()));
228-
jpost["logs"] = hex0x(logs_hash(receipt.logs));
230+
231+
if (holds_alternative<std::error_code>(res))
232+
{
233+
jpost["expectException"] = std::get<std::error_code>(res).message();
234+
jpost["logs"] = hex0x(logs_hash(std::vector<Log>()));
235+
}
236+
else
237+
{
238+
jpost["logs"] = hex0x(logs_hash(std::get<TransactionReceipt>(res).logs));
239+
}
229240

230241
std::ofstream{export_file_path} << std::setw(2) << j;
231242
}

test/unittests/state_transition.hpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ class state_transition : public ExportableFixture
9292
void TearDown() override;
9393

9494
/// Exports the test in the JSON State Test format to ExportableFixture::export_out.
95-
void export_state_test(const TransactionReceipt& receipt, const State& post);
95+
void export_state_test(
96+
const std::variant<TransactionReceipt, std::error_code>& res, const State& post);
9697
};
9798

9899
} // namespace evmone::test

test/unittests/state_transition_eof_create_test.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,7 @@ TEST_F(state_transition, txcreate_initcontainer_empty)
12431243
pre.insert(*tx.to, {.nonce = 1, .code = factory_container});
12441244

12451245
expect.tx_error = INIT_CODE_EMPTY;
1246+
expect.post[*tx.to].exists = true;
12461247
}
12471248

12481249
TEST_F(state_transition, txcreate_no_initcontainer)
@@ -1258,6 +1259,7 @@ TEST_F(state_transition, txcreate_no_initcontainer)
12581259
pre.insert(*tx.to, {.nonce = 1, .code = factory_container});
12591260

12601261
expect.tx_error = INIT_CODE_COUNT_ZERO;
1262+
expect.post[*tx.to].exists = true;
12611263
}
12621264

12631265
TEST_F(state_transition, txcreate_initcontainer_too_large)
@@ -1287,6 +1289,7 @@ TEST_F(state_transition, txcreate_initcontainer_too_large)
12871289
pre.insert(*tx.to, {.nonce = 1, .code = factory_container});
12881290

12891291
expect.tx_error = INIT_CODE_SIZE_LIMIT_EXCEEDED;
1292+
expect.post[*tx.to].exists = true;
12901293
}
12911294

12921295
TEST_F(state_transition, txcreate_too_many_initcontainers)
@@ -1312,6 +1315,7 @@ TEST_F(state_transition, txcreate_too_many_initcontainers)
13121315
pre.insert(*tx.to, {.nonce = 1, .code = factory_container});
13131316

13141317
expect.tx_error = INIT_CODE_COUNT_LIMIT_EXCEEDED;
1318+
expect.post[*tx.to].exists = true;
13151319
}
13161320

13171321
TEST_F(state_transition, initcode_transaction_before_prague)

test/unittests/state_transition_tx_test.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ TEST_F(state_transition, tx_non_existing_sender)
2929

3030
expect.status = EVMC_SUCCESS;
3131
expect.post.at(Sender).nonce = 1;
32-
expect.post.at(Coinbase).exists = false;
32+
expect.post[Coinbase].exists = false;
3333
}
3434

3535
TEST_F(state_transition, invalid_tx_non_existing_sender)
@@ -43,6 +43,7 @@ TEST_F(state_transition, invalid_tx_non_existing_sender)
4343
pre.get_accounts().erase(Sender);
4444

4545
expect.tx_error = INSUFFICIENT_FUNDS;
46+
expect.post[Sender].exists = false;
4647
}
4748

4849
TEST_F(state_transition, tx_blob_gas_price)
@@ -60,7 +61,7 @@ TEST_F(state_transition, tx_blob_gas_price)
6061

6162
pre.get(tx.sender).balance = 0x20000 + tx.gas_limit * tx.max_gas_price;
6263

63-
expect.post.at(Coinbase).exists = false; // all gas is burned, Coinbase gets nothing
64+
expect.post[Coinbase].exists = false; // all gas is burned, Coinbase gets nothing
6465
expect.status = EVMC_SUCCESS;
6566
}
6667

0 commit comments

Comments
 (0)