Skip to content

Commit 39357ec

Browse files
authored
Merge pull request #608 from ethereum/access-list-loading
Add features required to properly calculate transaction hash
2 parents d1423c7 + 19abf30 commit 39357ec

File tree

6 files changed

+230
-11
lines changed

6 files changed

+230
-11
lines changed

test/state/rlp.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,13 @@ inline bytes encode_tuple(const Types&... elements)
9898
return internal::wrap_list((encode(elements) + ...));
9999
}
100100

101+
/// Encodes a pair of values as RPL list.
102+
template <typename T1, typename T2>
103+
inline bytes encode(const std::pair<T1, T2>& p)
104+
{
105+
return encode_tuple(p.first, p.second);
106+
}
107+
101108
/// Encodes the container as RLP list.
102109
///
103110
/// @tparam InputIterator Type of the input iterator.

test/state/state.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,19 +203,30 @@ 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)
209221
throw std::invalid_argument("`v` value for eip1559 transaction must be 0 or 1");
210-
// TODO: Implement AccessList encoding
211222
// tx_type +
212223
// rlp [chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value,
213224
// data, access_list, sig_parity, r, s];
214225
return bytes{0x02} + // Transaction type (eip1559 type == 2)
215226
rlp::encode_tuple(tx.chain_id, tx.nonce, tx.max_priority_gas_price, tx.max_gas_price,
216227
static_cast<uint64_t>(tx.gas_limit),
217228
tx.to.has_value() ? tx.to.value() : bytes_view(), tx.value, tx.data,
218-
std::vector<uint8_t>(), static_cast<bool>(tx.v), tx.r, tx.s);
229+
tx.access_list, static_cast<bool>(tx.v), tx.r, tx.s);
219230
}
220231
}
221232

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: 14 additions & 1 deletion
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 <evmone/eof.hpp>
78
#include <nlohmann/json.hpp>
@@ -249,7 +250,7 @@ static void from_json_tx_common(const json::json& j, state::Transaction& o)
249250
if (j.contains("maxFeePerGas") || j.contains("maxPriorityFeePerGas"))
250251
{
251252
throw std::invalid_argument(
252-
"Misformatted transaction -- contains both legacy and 1559 fees");
253+
"invalid transaction: contains both legacy and EIP-1559 fees");
253254
}
254255
}
255256
else
@@ -265,12 +266,24 @@ state::Transaction from_json<state::Transaction>(const json::json& j)
265266
{
266267
state::Transaction o;
267268
from_json_tx_common(j, o);
269+
if (const auto chain_id_it = j.find("chainId"); chain_id_it != j.end())
270+
o.chain_id = from_json<uint8_t>(*chain_id_it);
268271
o.data = from_json<bytes>(j.at("input"));
269272
o.gas_limit = from_json<int64_t>(j.at("gas"));
270273
o.value = from_json<intx::uint256>(j.at("value"));
271274

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

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

test/unittests/state_rlp_test.cpp

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

5-
#include <gtest/gtest.h>
5+
#include <gmock/gmock.h>
66
#include <test/state/hash_utils.hpp>
77
#include <test/state/rlp.hpp>
88
#include <test/state/state.hpp>
@@ -12,6 +12,7 @@
1212
using namespace evmone;
1313
using namespace evmc::literals;
1414
using namespace intx;
15+
using namespace testing;
1516

1617
static constexpr auto emptyBytesHash =
1718
0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470_bytes32;
@@ -375,5 +376,78 @@ TEST(state_rlp, tx_to_rlp_eip1559_invalid_v_value)
375376
tx.v = 2;
376377
tx.chain_id = 1;
377378

378-
EXPECT_THROW(rlp::encode(tx), std::invalid_argument);
379+
EXPECT_THAT([tx]() { rlp::encode(tx); },
380+
ThrowsMessage<std::invalid_argument>("`v` value for eip1559 transaction must be 0 or 1"));
381+
}
382+
383+
TEST(state_rlp, tx_to_rlp_eip2930_invalid_v_value)
384+
{
385+
state::Transaction tx{};
386+
tx.kind = evmone::state::Transaction::Kind::eip2930;
387+
tx.data = ""_hex;
388+
tx.gas_limit = 1;
389+
tx.max_gas_price = 1;
390+
tx.max_priority_gas_price = 1;
391+
tx.sender = 0x0000000000000000000000000000000000000000_address;
392+
tx.to = 0x0000000000000000000000000000000000000000_address;
393+
tx.value = 0;
394+
tx.access_list = {};
395+
tx.nonce = 47;
396+
tx.r = 0x0000000000000000000000000000000000000000000000000000000000000000_u256;
397+
tx.s = 0x0000000000000000000000000000000000000000000000000000000000000000_u256;
398+
tx.v = 2;
399+
tx.chain_id = 1;
400+
401+
EXPECT_THAT([tx]() { rlp::encode(tx); },
402+
ThrowsMessage<std::invalid_argument>("`v` value for eip2930 transaction must be 0 or 1"));
403+
}
404+
405+
TEST(state_rlp, tx_to_rlp_eip1559_with_non_empty_access_list)
406+
{
407+
state::Transaction tx{};
408+
tx.kind = evmone::state::Transaction::Kind::eip1559;
409+
tx.data = "00"_hex;
410+
tx.gas_limit = 0x3d0900;
411+
tx.max_gas_price = 0x7d0;
412+
tx.max_priority_gas_price = 0xa;
413+
tx.sender = 0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b_address;
414+
tx.to = 0xcccccccccccccccccccccccccccccccccccccccc_address;
415+
tx.value = 0;
416+
tx.access_list = {{0xcccccccccccccccccccccccccccccccccccccccc_address,
417+
{0x0000000000000000000000000000000000000000000000000000000000000000_bytes32,
418+
0x0000000000000000000000000000000000000000000000000000000000000001_bytes32}}};
419+
tx.nonce = 1;
420+
tx.r = 0xd671815898b8dd34321adbba4cb6a57baa7017323c26946f3719b00e70c755c2_u256;
421+
tx.s = 0x3528b9efe3be57ea65a933d1e6bbf3b7d0c78830138883c1201e0c641fee6464_u256;
422+
tx.v = 0;
423+
tx.chain_id = 1;
424+
425+
EXPECT_EQ(keccak256(rlp::encode(tx)),
426+
0xfb18421827800adcf465688e303cc9863045fdb96971473a114677916a3a08a4_bytes32);
427+
}
428+
429+
TEST(state_rlp, tx_to_rlp_eip2930_with_non_empty_access_list)
430+
{
431+
// https://etherscan.io/tx/0xf076e75aa935552e20e5d9fd4d1dda4ff33399ff3d6ac22843ae646f82c385d4
432+
433+
state::Transaction tx{};
434+
tx.kind = evmone::state::Transaction::Kind::eip2930;
435+
tx.data =
436+
"0x095ea7b3000000000000000000000000f17d23136b4fead139f54fb766c8795faae09660ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"_hex;
437+
tx.gas_limit = 51253;
438+
tx.max_gas_price = 15650965396;
439+
tx.max_priority_gas_price = 15650965396;
440+
tx.sender = 0xcb0b99284784d9e400b1020b01fc40ff193d3540_address;
441+
tx.to = 0x9232a548dd9e81bac65500b5e0d918f8ba93675c_address;
442+
tx.value = 0;
443+
tx.access_list = {{0x9232a548dd9e81bac65500b5e0d918f8ba93675c_address,
444+
{0x8e947fe742892ee6fffe7cfc013acac35d33a3892c58597344bed88b21eb1d2f_bytes32}}};
445+
tx.nonce = 62;
446+
tx.r = 0x2cfaa5ffa42172bfa9f83207a257c53ba3a106844ee58e9131466f655ecc11e9_u256;
447+
tx.s = 0x419366dadd905a16cd433f2953f9ed976560822bb2611ac192b939f7b9c2a98c_u256;
448+
tx.v = 1;
449+
tx.chain_id = 1;
450+
451+
EXPECT_EQ(keccak256(rlp::encode(tx)),
452+
0xf076e75aa935552e20e5d9fd4d1dda4ff33399ff3d6ac22843ae646f82c385d4_bytes32);
379453
}

test/unittests/statetest_loader_tx_test.cpp

Lines changed: 116 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
// Copyright 2023 The evmone Authors.
33
// SPDX-License-Identifier: Apache-2.0
44

5-
#include <gtest/gtest.h>
5+
#include <gmock/gmock.h>
66
#include <intx/intx.hpp>
77
#include <test/statetest/statetest.hpp>
88

99
using namespace evmone;
1010
using namespace intx;
11+
using namespace testing;
1112

1213
// TODO: Add specific test of loading nonce, chainId, r, s, v
1314

@@ -16,6 +17,7 @@ TEST(statetest_loader, tx_create_legacy)
1617
constexpr std::string_view input = R"({
1718
"input": "b0b1",
1819
"gas": "0x9091",
20+
"chainId": "0x5",
1921
"value": "0xe0e1",
2022
"sender": "a0a1",
2123
"to": "",
@@ -30,6 +32,7 @@ TEST(statetest_loader, tx_create_legacy)
3032
EXPECT_EQ(tx.kind, state::Transaction::Kind::legacy);
3133
EXPECT_EQ(tx.data, (bytes{0xb0, 0xb1}));
3234
EXPECT_EQ(tx.gas_limit, 0x9091);
35+
EXPECT_EQ(tx.chain_id, 5);
3336
EXPECT_EQ(tx.value, 0xe0e1);
3437
EXPECT_EQ(tx.sender, 0xa0a1_address);
3538
EXPECT_FALSE(tx.to.has_value());
@@ -63,6 +66,7 @@ TEST(statetest_loader, tx_eip1559)
6366
EXPECT_EQ(tx.kind, state::Transaction::Kind::eip1559);
6467
EXPECT_EQ(tx.data, (bytes{0xb0, 0xb1}));
6568
EXPECT_EQ(tx.gas_limit, 0x9091);
69+
EXPECT_EQ(tx.chain_id, 0);
6670
EXPECT_EQ(tx.value, 0xe0e1);
6771
EXPECT_EQ(tx.sender, 0xa0a1_address);
6872
EXPECT_EQ(tx.to, 0xc0c1_address);
@@ -133,8 +137,117 @@ TEST(statetest_loader, tx_confusing)
133137
"v": "1"
134138
})";
135139

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

140253
namespace evmone::test

0 commit comments

Comments
 (0)