Skip to content

Commit eec004a

Browse files
committed
Add eip2930 tx type (access_list)
1 parent e4715be commit eec004a

File tree

5 files changed

+187
-3
lines changed

5 files changed

+187
-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, ///< Transaction with access list https://eips.ethereum.org/EIPS/eip-2930
87+
eip1559 ///< EIP1559 transaction https://eips.ethereum.org/EIPS/eip-1559
8788
};
8889

8990
Kind kind = Kind::legacy;

test/statetest/statetest_loader.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,17 @@ state::Transaction from_json<state::Transaction>(const json::json& j)
271271
o.value = from_json<intx::uint256>(j.at("value"));
272272

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

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

test/unittests/state_rlp_test.cpp

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

382+
TEST(state_rlp, tx_to_rlp_eip2930_invalid_v_value)
383+
{
384+
state::Transaction tx{};
385+
tx.kind = evmone::state::Transaction::Kind::eip2930;
386+
tx.data = ""_hex;
387+
tx.gas_limit = 1;
388+
tx.max_gas_price = 1;
389+
tx.max_priority_gas_price = 1;
390+
tx.sender = 0x0000000000000000000000000000000000000000_address;
391+
tx.to = 0x0000000000000000000000000000000000000000_address;
392+
tx.value = 0;
393+
tx.access_list = {};
394+
tx.nonce = 47;
395+
tx.r = 0x0000000000000000000000000000000000000000000000000000000000000000_u256;
396+
tx.s = 0x0000000000000000000000000000000000000000000000000000000000000000_u256;
397+
tx.v = 2;
398+
tx.chain_id = 1;
399+
400+
EXPECT_THROW(rlp::encode(tx), std::invalid_argument);
401+
}
402+
382403
TEST(state_rlp, tx_to_rlp_eip1559_with_non_empty_access_list)
383404
{
384405
constexpr std::string_view input = R"({
@@ -410,3 +431,35 @@ TEST(state_rlp, tx_to_rlp_eip1559_with_non_empty_access_list)
410431
EXPECT_EQ(keccak256(rlp::encode(tx)),
411432
0xfb18421827800adcf465688e303cc9863045fdb96971473a114677916a3a08a4_bytes32);
412433
}
434+
435+
TEST(state_rlp, tx_to_rlp_eip2930_with_non_empty_access_list)
436+
{
437+
// https://etherscan.io/tx/0xf076e75aa935552e20e5d9fd4d1dda4ff33399ff3d6ac22843ae646f82c385d4
438+
439+
constexpr std::string_view input = R"({
440+
"input" : "0x095ea7b3000000000000000000000000f17d23136b4fead139f54fb766c8795faae09660ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
441+
"gas" : "51253",
442+
"nonce" : "62",
443+
"to" : "0x9232a548dd9e81bac65500b5e0d918f8ba93675c",
444+
"value" : "0",
445+
"v" : "0x1",
446+
"r" : "0x2cfaa5ffa42172bfa9f83207a257c53ba3a106844ee58e9131466f655ecc11e9",
447+
"s" : "0x419366dadd905a16cd433f2953f9ed976560822bb2611ac192b939f7b9c2a98c",
448+
"chainId" : "0x1",
449+
"type" : "0x1",
450+
"gasPrice" : "15650965396",
451+
"accessList" : [
452+
{
453+
"address" : "0x9232a548dd9e81bac65500b5e0d918f8ba93675c",
454+
"storageKeys" : [
455+
"0x8e947fe742892ee6fffe7cfc013acac35d33a3892c58597344bed88b21eb1d2f"
456+
]
457+
}
458+
],
459+
"sender" : "0xcb0b99284784d9e400b1020b01fc40ff193d3540"})";
460+
461+
const auto tx = test::from_json<state::Transaction>(json::json::parse(input));
462+
463+
EXPECT_EQ(keccak256(rlp::encode(tx)),
464+
0xf076e75aa935552e20e5d9fd4d1dda4ff33399ff3d6ac22843ae646f82c385d4_bytes32);
465+
}

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)