Skip to content

Commit 7677b67

Browse files
committed
Add eip2930 tx type (access_list)
1 parent 6e7182b commit 7677b67

File tree

5 files changed

+182
-3
lines changed

5 files changed

+182
-3
lines changed

test/state/state.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,18 @@ std::variant<TransactionReceipt, std::error_code> transition(
203203
return rlp::encode_tuple(tx.nonce, tx.max_gas_price, static_cast<uint64_t>(tx.gas_limit),
204204
tx.to.has_value() ? tx.to.value() : bytes_view(), tx.value, tx.data, tx.v, tx.r, tx.s);
205205
}
206+
else if (tx.kind == Transaction::Kind::eip2930)
207+
{
208+
if (tx.v > 1)
209+
throw std::invalid_argument("`v` value for eip2930 transaction must be 0 or 1");
210+
// tx_type +
211+
// rlp [nonce, gas_price, gas_limit, to, value, data, access_list, v, r, s];
212+
return bytes{0x01} + // Transaction type (eip2930 type == 1)
213+
rlp::encode_tuple(tx.chain_id, tx.nonce, tx.max_gas_price,
214+
static_cast<uint64_t>(tx.gas_limit),
215+
tx.to.has_value() ? tx.to.value() : bytes_view(), tx.value, tx.data,
216+
tx.access_list, static_cast<bool>(tx.v), tx.r, tx.s);
217+
}
206218
else
207219
{
208220
if (tx.v > 1)

test/state/state.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,11 @@ using AccessList = std::vector<std::pair<address, std::vector<bytes32>>>;
8080

8181
struct Transaction
8282
{
83-
enum class Kind
83+
enum class Kind : uint8_t
8484
{
85-
legacy,
86-
eip1559
85+
legacy = 0,
86+
eip2930 = 1, ///< Transaction with access list https://eips.ethereum.org/EIPS/eip-2930
87+
eip1559 = 2 ///< EIP1559 transaction https://eips.ethereum.org/EIPS/eip-1559
8788
};
8889

8990
Kind kind = Kind::legacy;

test/statetest/statetest_loader.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Copyright 2022 The evmone Authors.
33
// SPDX-License-Identifier: Apache-2.0
44

5+
#include "../utils/stdx/utility.hpp"
56
#include "statetest.hpp"
67
#include <nlohmann/json.hpp>
78

@@ -271,7 +272,17 @@ state::Transaction from_json<state::Transaction>(const json::json& j)
271272
o.value = from_json<intx::uint256>(j.at("value"));
272273

273274
if (const auto ac_it = j.find("accessList"); ac_it != j.end())
275+
{
274276
o.access_list = from_json<state::AccessList>(*ac_it);
277+
if (o.kind == state::Transaction::Kind::legacy) // Upgrade tx type if tx has "accessList"
278+
o.kind = state::Transaction::Kind::eip2930;
279+
}
280+
281+
if (const auto type_it = j.find("type"); type_it != j.end())
282+
{
283+
if (stdx::to_underlying(o.kind) != from_json<uint8_t>(*type_it))
284+
throw std::invalid_argument("Wrong transaction type");
285+
}
275286

276287
o.nonce = from_json<uint64_t>(j.at("nonce"));
277288
o.r = from_json<intx::uint256>(j.at("r"));

test/unittests/state_rlp_test.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,27 @@ TEST(state_rlp, tx_to_rlp_eip1559_invalid_v_value)
378378
EXPECT_THROW(rlp::encode(tx), std::invalid_argument);
379379
}
380380

381+
TEST(state_rlp, tx_to_rlp_eip2930_invalid_v_value)
382+
{
383+
state::Transaction tx{};
384+
tx.kind = evmone::state::Transaction::Kind::eip2930;
385+
tx.data = ""_hex;
386+
tx.gas_limit = 1;
387+
tx.max_gas_price = 1;
388+
tx.max_priority_gas_price = 1;
389+
tx.sender = 0x0000000000000000000000000000000000000000_address;
390+
tx.to = 0x0000000000000000000000000000000000000000_address;
391+
tx.value = 0;
392+
tx.access_list = {};
393+
tx.nonce = 47;
394+
tx.r = 0x0000000000000000000000000000000000000000000000000000000000000000_u256;
395+
tx.s = 0x0000000000000000000000000000000000000000000000000000000000000000_u256;
396+
tx.v = 2;
397+
tx.chain_id = 1;
398+
399+
EXPECT_THROW(rlp::encode(tx), std::invalid_argument);
400+
}
401+
381402
TEST(state_rlp, tx_to_rlp_eip1559_with_non_empty_access_list)
382403
{
383404
state::Transaction tx{};
@@ -401,3 +422,29 @@ TEST(state_rlp, tx_to_rlp_eip1559_with_non_empty_access_list)
401422
EXPECT_EQ(keccak256(rlp::encode(tx)),
402423
0xfb18421827800adcf465688e303cc9863045fdb96971473a114677916a3a08a4_bytes32);
403424
}
425+
426+
TEST(state_rlp, tx_to_rlp_eip2930_with_non_empty_access_list)
427+
{
428+
// https://etherscan.io/tx/0xf076e75aa935552e20e5d9fd4d1dda4ff33399ff3d6ac22843ae646f82c385d4
429+
430+
state::Transaction tx{};
431+
tx.kind = evmone::state::Transaction::Kind::eip2930;
432+
tx.data =
433+
"0x095ea7b3000000000000000000000000f17d23136b4fead139f54fb766c8795faae09660ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"_hex;
434+
tx.gas_limit = 51253;
435+
tx.max_gas_price = 15650965396;
436+
tx.max_priority_gas_price = 15650965396;
437+
tx.sender = 0xcb0b99284784d9e400b1020b01fc40ff193d3540_address;
438+
tx.to = 0x9232a548dd9e81bac65500b5e0d918f8ba93675c_address;
439+
tx.value = 0;
440+
tx.access_list = {{0x9232a548dd9e81bac65500b5e0d918f8ba93675c_address,
441+
{0x8e947fe742892ee6fffe7cfc013acac35d33a3892c58597344bed88b21eb1d2f_bytes32}}};
442+
tx.nonce = 62;
443+
tx.r = 0x2cfaa5ffa42172bfa9f83207a257c53ba3a106844ee58e9131466f655ecc11e9_u256;
444+
tx.s = 0x419366dadd905a16cd433f2953f9ed976560822bb2611ac192b939f7b9c2a98c_u256;
445+
tx.v = 1;
446+
tx.chain_id = 1;
447+
448+
EXPECT_EQ(keccak256(rlp::encode(tx)),
449+
0xf076e75aa935552e20e5d9fd4d1dda4ff33399ff3d6ac22843ae646f82c385d4_bytes32);
450+
}

test/unittests/statetest_loader_tx_test.cpp

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,114 @@ TEST(statetest_loader, tx_confusing)
140140
test::from_json<state::Transaction>(json::json::parse(input)), std::invalid_argument);
141141
}
142142

143+
TEST(statetest_loader, tx_type_1)
144+
{
145+
constexpr std::string_view input = R"({
146+
"input": "",
147+
"gas": "0",
148+
"type": "1",
149+
"value": "0",
150+
"sender": "",
151+
"to": "",
152+
"gasPrice": "0",
153+
"accessList": [
154+
{"address": "ac01", "storageKeys": []},
155+
{"address": "ac02", "storageKeys": ["fe", "00"]}
156+
],
157+
"nonce": "0",
158+
"r": "0x1111111111111111111111111111111111111111111111111111111111111111",
159+
"s": "0x2222222222222222222222222222222222222222222222222222222222222222",
160+
"v": "1"
161+
})";
162+
163+
const auto tx = test::from_json<state::Transaction>(json::json::parse(input));
164+
EXPECT_EQ(tx.kind, state::Transaction::Kind::eip2930);
165+
EXPECT_TRUE(tx.data.empty());
166+
EXPECT_EQ(tx.gas_limit, 0);
167+
EXPECT_EQ(tx.value, 0);
168+
EXPECT_EQ(tx.sender, 0x00_address);
169+
EXPECT_FALSE(tx.to.has_value());
170+
EXPECT_EQ(tx.max_gas_price, 0);
171+
EXPECT_EQ(tx.max_priority_gas_price, 0);
172+
ASSERT_EQ(tx.access_list.size(), 2);
173+
EXPECT_EQ(tx.access_list[0].first, 0xac01_address);
174+
EXPECT_EQ(tx.access_list[0].second.size(), 0);
175+
EXPECT_EQ(tx.access_list[1].first, 0xac02_address);
176+
EXPECT_EQ(tx.access_list[1].second, (std::vector{0xfe_bytes32, 0x00_bytes32}));
177+
EXPECT_EQ(tx.nonce, 0);
178+
EXPECT_EQ(tx.r, 0x1111111111111111111111111111111111111111111111111111111111111111_u256);
179+
EXPECT_EQ(tx.s, 0x2222222222222222222222222222222222222222222222222222222222222222_u256);
180+
EXPECT_EQ(tx.v, 1);
181+
}
182+
183+
TEST(statetest_loader, invalid_tx_type)
184+
{
185+
{
186+
constexpr std::string_view input = R"({
187+
"input": "",
188+
"gas": "0",
189+
"type": "2",
190+
"value": "0",
191+
"sender": "",
192+
"to": "",
193+
"gasPrice": "0",
194+
"accessList": [
195+
{"address": "ac01", "storageKeys": []},
196+
{"address": "ac02", "storageKeys": ["fe", "00"]}
197+
],
198+
"nonce": "0",
199+
"r": "0x1111111111111111111111111111111111111111111111111111111111111111",
200+
"s": "0x2222222222222222222222222222222222222222222222222222222222222222",
201+
"v": "1"
202+
})";
203+
204+
EXPECT_THROW(
205+
test::from_json<state::Transaction>(json::json::parse(input)), std::invalid_argument);
206+
}
207+
{
208+
constexpr std::string_view input = R"({
209+
"input": "",
210+
"gas": "0",
211+
"type": "1",
212+
"value": "0",
213+
"sender": "",
214+
"to": "",
215+
"gasPrice": "0",
216+
"nonce": "0",
217+
"r": "0x1111111111111111111111111111111111111111111111111111111111111111",
218+
"s": "0x2222222222222222222222222222222222222222222222222222222222222222",
219+
"v": "1"
220+
})";
221+
222+
EXPECT_THROW(
223+
test::from_json<state::Transaction>(json::json::parse(input)), std::invalid_argument);
224+
}
225+
226+
{
227+
constexpr std::string_view input = R"({
228+
"input": "",
229+
"gas": "0",
230+
"type": "1",
231+
"value": "0",
232+
"sender": "",
233+
"to": "",
234+
"maxFeePerGas": "0",
235+
"maxPriorityFeePerGas": "0",
236+
"accessList": [
237+
{"address": "ac01", "storageKeys": []},
238+
{"address": "ac02", "storageKeys": ["fe", "00"]}
239+
],
240+
"nonce": "0",
241+
"r": "0x1111111111111111111111111111111111111111111111111111111111111111",
242+
"s": "0x2222222222222222222222222222222222222222222222222222222222222222",
243+
"v": "1"
244+
})";
245+
246+
EXPECT_THROW(
247+
test::from_json<state::Transaction>(json::json::parse(input)), std::invalid_argument);
248+
}
249+
}
250+
143251
namespace evmone::test
144252
{
145253
// This function is used only by the following test case and in `statetest_loader.cpp` where it is

0 commit comments

Comments
 (0)