Skip to content

Commit

Permalink
Merge pull request #608 from ethereum/access-list-loading
Browse files Browse the repository at this point in the history
Add features required to properly calculate transaction hash
  • Loading branch information
chfast authored Apr 6, 2023
2 parents d1423c7 + 19abf30 commit 39357ec
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 11 deletions.
7 changes: 7 additions & 0 deletions test/state/rlp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,13 @@ inline bytes encode_tuple(const Types&... elements)
return internal::wrap_list((encode(elements) + ...));
}

/// Encodes a pair of values as RPL list.
template <typename T1, typename T2>
inline bytes encode(const std::pair<T1, T2>& p)
{
return encode_tuple(p.first, p.second);
}

/// Encodes the container as RLP list.
///
/// @tparam InputIterator Type of the input iterator.
Expand Down
15 changes: 13 additions & 2 deletions test/state/state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -203,19 +203,30 @@ std::variant<TransactionReceipt, std::error_code> transition(
return rlp::encode_tuple(tx.nonce, tx.max_gas_price, static_cast<uint64_t>(tx.gas_limit),
tx.to.has_value() ? tx.to.value() : bytes_view(), tx.value, tx.data, tx.v, tx.r, tx.s);
}
else if (tx.kind == Transaction::Kind::eip2930)
{
if (tx.v > 1)
throw std::invalid_argument("`v` value for eip2930 transaction must be 0 or 1");
// tx_type +
// rlp [nonce, gas_price, gas_limit, to, value, data, access_list, v, r, s];
return bytes{0x01} + // Transaction type (eip2930 type == 1)
rlp::encode_tuple(tx.chain_id, tx.nonce, tx.max_gas_price,
static_cast<uint64_t>(tx.gas_limit),
tx.to.has_value() ? tx.to.value() : bytes_view(), tx.value, tx.data,
tx.access_list, static_cast<bool>(tx.v), tx.r, tx.s);
}
else
{
if (tx.v > 1)
throw std::invalid_argument("`v` value for eip1559 transaction must be 0 or 1");
// TODO: Implement AccessList encoding
// tx_type +
// rlp [chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value,
// data, access_list, sig_parity, r, s];
return bytes{0x02} + // Transaction type (eip1559 type == 2)
rlp::encode_tuple(tx.chain_id, tx.nonce, tx.max_priority_gas_price, tx.max_gas_price,
static_cast<uint64_t>(tx.gas_limit),
tx.to.has_value() ? tx.to.value() : bytes_view(), tx.value, tx.data,
std::vector<uint8_t>(), static_cast<bool>(tx.v), tx.r, tx.s);
tx.access_list, static_cast<bool>(tx.v), tx.r, tx.s);
}
}

Expand Down
7 changes: 4 additions & 3 deletions test/state/state.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,11 @@ using AccessList = std::vector<std::pair<address, std::vector<bytes32>>>;

struct Transaction
{
enum class Kind
enum class Kind : uint8_t
{
legacy,
eip1559
legacy = 0,
eip2930 = 1, ///< Transaction with access list https://eips.ethereum.org/EIPS/eip-2930
eip1559 = 2 ///< EIP1559 transaction https://eips.ethereum.org/EIPS/eip-1559
};

Kind kind = Kind::legacy;
Expand Down
15 changes: 14 additions & 1 deletion test/statetest/statetest_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright 2022 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include "../utils/stdx/utility.hpp"
#include "statetest.hpp"
#include <evmone/eof.hpp>
#include <nlohmann/json.hpp>
Expand Down Expand Up @@ -249,7 +250,7 @@ static void from_json_tx_common(const json::json& j, state::Transaction& o)
if (j.contains("maxFeePerGas") || j.contains("maxPriorityFeePerGas"))
{
throw std::invalid_argument(
"Misformatted transaction -- contains both legacy and 1559 fees");
"invalid transaction: contains both legacy and EIP-1559 fees");
}
}
else
Expand All @@ -265,12 +266,24 @@ state::Transaction from_json<state::Transaction>(const json::json& j)
{
state::Transaction o;
from_json_tx_common(j, o);
if (const auto chain_id_it = j.find("chainId"); chain_id_it != j.end())
o.chain_id = from_json<uint8_t>(*chain_id_it);
o.data = from_json<bytes>(j.at("input"));
o.gas_limit = from_json<int64_t>(j.at("gas"));
o.value = from_json<intx::uint256>(j.at("value"));

if (const auto ac_it = j.find("accessList"); ac_it != j.end())
{
o.access_list = from_json<state::AccessList>(*ac_it);
if (o.kind == state::Transaction::Kind::legacy) // Upgrade tx type if tx has "accessList"
o.kind = state::Transaction::Kind::eip2930;
}

if (const auto type_it = j.find("type"); type_it != j.end())
{
if (stdx::to_underlying(o.kind) != from_json<uint8_t>(*type_it))
throw std::invalid_argument("wrong transaction type");
}

o.nonce = from_json<uint64_t>(j.at("nonce"));
o.r = from_json<intx::uint256>(j.at("r"));
Expand Down
78 changes: 76 additions & 2 deletions test/unittests/state_rlp_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// Copyright 2022 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <test/state/hash_utils.hpp>
#include <test/state/rlp.hpp>
#include <test/state/state.hpp>
Expand All @@ -12,6 +12,7 @@
using namespace evmone;
using namespace evmc::literals;
using namespace intx;
using namespace testing;

static constexpr auto emptyBytesHash =
0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470_bytes32;
Expand Down Expand Up @@ -375,5 +376,78 @@ TEST(state_rlp, tx_to_rlp_eip1559_invalid_v_value)
tx.v = 2;
tx.chain_id = 1;

EXPECT_THROW(rlp::encode(tx), std::invalid_argument);
EXPECT_THAT([tx]() { rlp::encode(tx); },
ThrowsMessage<std::invalid_argument>("`v` value for eip1559 transaction must be 0 or 1"));
}

TEST(state_rlp, tx_to_rlp_eip2930_invalid_v_value)
{
state::Transaction tx{};
tx.kind = evmone::state::Transaction::Kind::eip2930;
tx.data = ""_hex;
tx.gas_limit = 1;
tx.max_gas_price = 1;
tx.max_priority_gas_price = 1;
tx.sender = 0x0000000000000000000000000000000000000000_address;
tx.to = 0x0000000000000000000000000000000000000000_address;
tx.value = 0;
tx.access_list = {};
tx.nonce = 47;
tx.r = 0x0000000000000000000000000000000000000000000000000000000000000000_u256;
tx.s = 0x0000000000000000000000000000000000000000000000000000000000000000_u256;
tx.v = 2;
tx.chain_id = 1;

EXPECT_THAT([tx]() { rlp::encode(tx); },
ThrowsMessage<std::invalid_argument>("`v` value for eip2930 transaction must be 0 or 1"));
}

TEST(state_rlp, tx_to_rlp_eip1559_with_non_empty_access_list)
{
state::Transaction tx{};
tx.kind = evmone::state::Transaction::Kind::eip1559;
tx.data = "00"_hex;
tx.gas_limit = 0x3d0900;
tx.max_gas_price = 0x7d0;
tx.max_priority_gas_price = 0xa;
tx.sender = 0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b_address;
tx.to = 0xcccccccccccccccccccccccccccccccccccccccc_address;
tx.value = 0;
tx.access_list = {{0xcccccccccccccccccccccccccccccccccccccccc_address,
{0x0000000000000000000000000000000000000000000000000000000000000000_bytes32,
0x0000000000000000000000000000000000000000000000000000000000000001_bytes32}}};
tx.nonce = 1;
tx.r = 0xd671815898b8dd34321adbba4cb6a57baa7017323c26946f3719b00e70c755c2_u256;
tx.s = 0x3528b9efe3be57ea65a933d1e6bbf3b7d0c78830138883c1201e0c641fee6464_u256;
tx.v = 0;
tx.chain_id = 1;

EXPECT_EQ(keccak256(rlp::encode(tx)),
0xfb18421827800adcf465688e303cc9863045fdb96971473a114677916a3a08a4_bytes32);
}

TEST(state_rlp, tx_to_rlp_eip2930_with_non_empty_access_list)
{
// https://etherscan.io/tx/0xf076e75aa935552e20e5d9fd4d1dda4ff33399ff3d6ac22843ae646f82c385d4

state::Transaction tx{};
tx.kind = evmone::state::Transaction::Kind::eip2930;
tx.data =
"0x095ea7b3000000000000000000000000f17d23136b4fead139f54fb766c8795faae09660ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"_hex;
tx.gas_limit = 51253;
tx.max_gas_price = 15650965396;
tx.max_priority_gas_price = 15650965396;
tx.sender = 0xcb0b99284784d9e400b1020b01fc40ff193d3540_address;
tx.to = 0x9232a548dd9e81bac65500b5e0d918f8ba93675c_address;
tx.value = 0;
tx.access_list = {{0x9232a548dd9e81bac65500b5e0d918f8ba93675c_address,
{0x8e947fe742892ee6fffe7cfc013acac35d33a3892c58597344bed88b21eb1d2f_bytes32}}};
tx.nonce = 62;
tx.r = 0x2cfaa5ffa42172bfa9f83207a257c53ba3a106844ee58e9131466f655ecc11e9_u256;
tx.s = 0x419366dadd905a16cd433f2953f9ed976560822bb2611ac192b939f7b9c2a98c_u256;
tx.v = 1;
tx.chain_id = 1;

EXPECT_EQ(keccak256(rlp::encode(tx)),
0xf076e75aa935552e20e5d9fd4d1dda4ff33399ff3d6ac22843ae646f82c385d4_bytes32);
}
119 changes: 116 additions & 3 deletions test/unittests/statetest_loader_tx_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
// Copyright 2023 The evmone Authors.
// SPDX-License-Identifier: Apache-2.0

#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <intx/intx.hpp>
#include <test/statetest/statetest.hpp>

using namespace evmone;
using namespace intx;
using namespace testing;

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

Expand All @@ -16,6 +17,7 @@ TEST(statetest_loader, tx_create_legacy)
constexpr std::string_view input = R"({
"input": "b0b1",
"gas": "0x9091",
"chainId": "0x5",
"value": "0xe0e1",
"sender": "a0a1",
"to": "",
Expand All @@ -30,6 +32,7 @@ TEST(statetest_loader, tx_create_legacy)
EXPECT_EQ(tx.kind, state::Transaction::Kind::legacy);
EXPECT_EQ(tx.data, (bytes{0xb0, 0xb1}));
EXPECT_EQ(tx.gas_limit, 0x9091);
EXPECT_EQ(tx.chain_id, 5);
EXPECT_EQ(tx.value, 0xe0e1);
EXPECT_EQ(tx.sender, 0xa0a1_address);
EXPECT_FALSE(tx.to.has_value());
Expand Down Expand Up @@ -63,6 +66,7 @@ TEST(statetest_loader, tx_eip1559)
EXPECT_EQ(tx.kind, state::Transaction::Kind::eip1559);
EXPECT_EQ(tx.data, (bytes{0xb0, 0xb1}));
EXPECT_EQ(tx.gas_limit, 0x9091);
EXPECT_EQ(tx.chain_id, 0);
EXPECT_EQ(tx.value, 0xe0e1);
EXPECT_EQ(tx.sender, 0xa0a1_address);
EXPECT_EQ(tx.to, 0xc0c1_address);
Expand Down Expand Up @@ -133,8 +137,117 @@ TEST(statetest_loader, tx_confusing)
"v": "1"
})";

EXPECT_THROW(
test::from_json<state::Transaction>(json::json::parse(input)), std::invalid_argument);
EXPECT_THAT([&] { test::from_json<state::Transaction>(json::json::parse(input)); },
ThrowsMessage<std::invalid_argument>(
"invalid transaction: contains both legacy and EIP-1559 fees"));
}

TEST(statetest_loader, tx_type_1)
{
constexpr std::string_view input = R"({
"input": "",
"gas": "0",
"type": "1",
"value": "0",
"sender": "",
"to": "",
"gasPrice": "0",
"accessList": [
{"address": "ac01", "storageKeys": []},
{"address": "ac02", "storageKeys": ["fe", "00"]}
],
"nonce": "0",
"r": "0x1111111111111111111111111111111111111111111111111111111111111111",
"s": "0x2222222222222222222222222222222222222222222222222222222222222222",
"v": "1"
})";

const auto tx = test::from_json<state::Transaction>(json::json::parse(input));
EXPECT_EQ(tx.kind, state::Transaction::Kind::eip2930);
EXPECT_TRUE(tx.data.empty());
EXPECT_EQ(tx.gas_limit, 0);
EXPECT_EQ(tx.value, 0);
EXPECT_EQ(tx.sender, 0x00_address);
EXPECT_FALSE(tx.to.has_value());
EXPECT_EQ(tx.max_gas_price, 0);
EXPECT_EQ(tx.max_priority_gas_price, 0);
ASSERT_EQ(tx.access_list.size(), 2);
EXPECT_EQ(tx.access_list[0].first, 0xac01_address);
EXPECT_EQ(tx.access_list[0].second.size(), 0);
EXPECT_EQ(tx.access_list[1].first, 0xac02_address);
EXPECT_EQ(tx.access_list[1].second, (std::vector{0xfe_bytes32, 0x00_bytes32}));
EXPECT_EQ(tx.nonce, 0);
EXPECT_EQ(tx.r, 0x1111111111111111111111111111111111111111111111111111111111111111_u256);
EXPECT_EQ(tx.s, 0x2222222222222222222222222222222222222222222222222222222222222222_u256);
EXPECT_EQ(tx.v, 1);
}

TEST(statetest_loader, invalid_tx_type)
{
{
constexpr std::string_view input = R"({
"input": "",
"gas": "0",
"type": "2",
"value": "0",
"sender": "",
"to": "",
"gasPrice": "0",
"accessList": [
{"address": "ac01", "storageKeys": []},
{"address": "ac02", "storageKeys": ["fe", "00"]}
],
"nonce": "0",
"r": "0x1111111111111111111111111111111111111111111111111111111111111111",
"s": "0x2222222222222222222222222222222222222222222222222222222222222222",
"v": "1"
})";

EXPECT_THAT([&] { test::from_json<state::Transaction>(json::json::parse(input)); },
ThrowsMessage<std::invalid_argument>("wrong transaction type"));
}
{
constexpr std::string_view input = R"({
"input": "",
"gas": "0",
"type": "1",
"value": "0",
"sender": "",
"to": "",
"gasPrice": "0",
"nonce": "0",
"r": "0x1111111111111111111111111111111111111111111111111111111111111111",
"s": "0x2222222222222222222222222222222222222222222222222222222222222222",
"v": "1"
})";

EXPECT_THAT([&] { test::from_json<state::Transaction>(json::json::parse(input)); },
ThrowsMessage<std::invalid_argument>("wrong transaction type"));
}

{
constexpr std::string_view input = R"({
"input": "",
"gas": "0",
"type": "1",
"value": "0",
"sender": "",
"to": "",
"maxFeePerGas": "0",
"maxPriorityFeePerGas": "0",
"accessList": [
{"address": "ac01", "storageKeys": []},
{"address": "ac02", "storageKeys": ["fe", "00"]}
],
"nonce": "0",
"r": "0x1111111111111111111111111111111111111111111111111111111111111111",
"s": "0x2222222222222222222222222222222222222222222222222222222222222222",
"v": "1"
})";

EXPECT_THAT([&] { test::from_json<state::Transaction>(json::json::parse(input)); },
ThrowsMessage<std::invalid_argument>("wrong transaction type"));
}
}

namespace evmone::test
Expand Down

0 comments on commit 39357ec

Please sign in to comment.