зеркало из https://github.com/microsoft/eEVM.git
349 строки
9.1 KiB
C++
349 строки
9.1 KiB
C++
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
#include "eEVM/disassembler.h"
|
|
#include "eEVM/opcode.h"
|
|
#include "eEVM/processor.h"
|
|
#include "eEVM/rlp.h"
|
|
#include "eEVM/simple/simpleglobalstate.h"
|
|
#include "eEVM/simple/simplestorage.h"
|
|
#include "eEVM/util.h"
|
|
|
|
#include <doctest/doctest.h>
|
|
#include <fmt/format_header_only.h>
|
|
#include <fstream>
|
|
#include <nlohmann/json.hpp>
|
|
|
|
using namespace std;
|
|
using namespace eevm;
|
|
using namespace nlohmann;
|
|
|
|
SimpleGlobalState::StateEntry parseAccount(json::const_iterator& it)
|
|
{
|
|
auto storage = it.value()["storage"];
|
|
auto account = it.value().get<SimpleAccount>();
|
|
account.set_address(to_uint256(it.key()));
|
|
return {account, storage};
|
|
}
|
|
|
|
void run_test_case(
|
|
const string& fileName,
|
|
const set<string>& skip,
|
|
const bool checkLogs,
|
|
const bool disasm = true)
|
|
{
|
|
constexpr auto env_var = "TEST_DIR";
|
|
auto test_dir = getenv(env_var);
|
|
string testPath = fileName;
|
|
if (test_dir)
|
|
{
|
|
testPath = fmt::format("{}/{}", string(test_dir), fileName);
|
|
}
|
|
else
|
|
{
|
|
throw std::logic_error(fmt::format(
|
|
"Must set path to test cases in {} environment variable", env_var));
|
|
}
|
|
|
|
#ifdef RECORD_TRACE
|
|
const string delim(15, '-');
|
|
cout << fmt::format("Test file {}\n{}", testPath, delim) << endl;
|
|
#endif
|
|
|
|
ifstream fs(testPath);
|
|
{
|
|
INFO("Looking for " << testPath);
|
|
REQUIRE(fs.good());
|
|
}
|
|
|
|
const auto cases = json::parse(fs);
|
|
int i = 0;
|
|
for (decltype(auto) it = cases.cbegin(); it != cases.cend(); it++, i++)
|
|
{
|
|
if (skip.find(it.key()) != skip.end())
|
|
continue;
|
|
|
|
auto subcase = it.key();
|
|
INFO("SUBCASE: " << subcase);
|
|
{
|
|
const auto& j = it.value();
|
|
Address callee = to_uint256(j["exec"]["address"]);
|
|
Address caller = to_uint256(j["exec"]["caller"]);
|
|
const auto c = to_bytes(j["exec"]["code"]);
|
|
const auto inp = to_bytes(j["exec"]["data"]);
|
|
uint64_t value = to_uint64(j["exec"]["value"]);
|
|
|
|
#ifdef RECORD_TRACE
|
|
cout << fmt::format("Case {} (#{})", it.key(), i) << endl;
|
|
if (disasm)
|
|
{
|
|
try
|
|
{
|
|
cout << "Disassembly:" << endl << Disassembler::dis(c) << endl;
|
|
}
|
|
catch (const exception& e)
|
|
{
|
|
cerr << fmt::format("Failed to disassemble: {}", e.what()) << endl;
|
|
continue;
|
|
}
|
|
cout << delim << endl;
|
|
}
|
|
#endif
|
|
|
|
Block b = j["env"];
|
|
SimpleGlobalState gs(b);
|
|
NullLogHandler ignore;
|
|
Transaction tx(
|
|
to_uint256(j["exec"]["origin"]),
|
|
ignore,
|
|
to_uint64(j["exec"]["value"]),
|
|
to_uint64(j["exec"]["gasPrice"]),
|
|
to_uint64(j["exec"]["gas"]));
|
|
|
|
// parse accounts
|
|
for (decltype(auto) it = j["pre"].cbegin(); it != j["pre"].cend(); it++)
|
|
{
|
|
gs.insert(parseAccount(it));
|
|
}
|
|
|
|
CHECK(gs.exists(callee));
|
|
|
|
Processor p(gs);
|
|
Trace* tr(nullptr);
|
|
|
|
#ifdef RECORD_TRACE
|
|
tr = new Trace();
|
|
#endif
|
|
|
|
const auto e = p.run(tx, caller, gs.get(callee), inp, value, tr);
|
|
|
|
#ifdef RECORD_TRACE
|
|
cout << fmt::format("Trace:\n{}{}", *tr, delim) << endl;
|
|
delete tr;
|
|
#endif
|
|
|
|
// was the run supposed to fail?
|
|
const auto post = j.find("post");
|
|
if (post == j.end())
|
|
{
|
|
CHECK((e.er == ExitReason::threw || e.er == ExitReason::halted));
|
|
continue;
|
|
}
|
|
|
|
uint64_t postAccounts = 0;
|
|
for (auto it = post.value().cbegin(); it != post.value().cend(); it++)
|
|
{
|
|
const auto expected = parseAccount(it);
|
|
const auto actual = gs.get(expected.first.get_address());
|
|
CHECK(expected.first == actual.acc);
|
|
|
|
decltype(auto) st = dynamic_cast<SimpleStorage&>(actual.st);
|
|
CHECK(st == expected.second);
|
|
}
|
|
|
|
// the != 0 check exists because there exists failure scenarios where post
|
|
// is not mentioned when an exception is raised but that does not mean
|
|
// systemState is lost
|
|
if (postAccounts != 0)
|
|
{
|
|
CHECK(postAccounts == gs.num_accounts());
|
|
}
|
|
|
|
// does the test case specify output?
|
|
if (j.find("out") != j.end())
|
|
{
|
|
CHECK(e.output == to_bytes(j.at("out")));
|
|
}
|
|
|
|
// TODO: Calculate hash of RLP of produced log entries, to compare against
|
|
// the value specified in the test case
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("vmBitwiseLogicOperationTest" * doctest::test_suite("logic"))
|
|
{
|
|
run_test_case("vmBitwiseLogicOperationTest.json", {}, true);
|
|
}
|
|
|
|
TEST_CASE("vmEnvironmentalInfoTest" * doctest::test_suite("logic"))
|
|
{
|
|
auto skip = set<string>{
|
|
// memory index > 2^64
|
|
"calldatacopy_DataIndexTooHigh",
|
|
"calldatacopy_DataIndexTooHigh2",
|
|
"calldatacopy_DataIndexTooHigh2_return",
|
|
"calldatacopy_DataIndexTooHigh_return",
|
|
"calldataloadSizeTooHigh",
|
|
"calldataload_BigOffset",
|
|
"codecopy_DataIndexTooHigh",
|
|
};
|
|
|
|
run_test_case("vmEnvironmentalInfoTest.json", skip, true);
|
|
}
|
|
|
|
TEST_CASE("vmTests" * doctest::test_suite("logic"))
|
|
{
|
|
auto skip = set<string>{};
|
|
|
|
run_test_case("vmTests.json", skip, true);
|
|
}
|
|
|
|
// These pass, but are kept hidden by default because they're slow
|
|
TEST_CASE(
|
|
"vmPerformanceTest" * doctest::test_suite(".performance") * doctest::skip())
|
|
{
|
|
auto skip = set<string>{// Missing post
|
|
"ackermann33",
|
|
|
|
// Empty output
|
|
"loop-exp-16b-100k",
|
|
"loop-exp-32b-100k",
|
|
|
|
// Incorrect output
|
|
"loop-exp-8b-100k"};
|
|
|
|
run_test_case("vmPerformanceTest.json", skip, false);
|
|
}
|
|
|
|
TEST_CASE("vmSystemOperationsTest" * doctest::test_suite("logic"))
|
|
{
|
|
auto skip = set<string>{};
|
|
|
|
run_test_case("vmSystemOperationsTest.json", skip, false);
|
|
}
|
|
|
|
// Passes, but slow, so disabled by default
|
|
TEST_CASE(
|
|
"vmInputLimitsLight" * doctest::test_suite(".input") * doctest::skip())
|
|
{
|
|
auto skip = set<string>{
|
|
// copies code to offset > 2^64
|
|
"012fd315e355bad0d1bdce9a44863f3c909bfdf9909779c431c9e0fdf9ae339f",
|
|
"01923ee9def56e347452847fd9be4577f8b663097620664ba24317f67a73122a",
|
|
"01a5cf9db140969b2a2410361164fc41c64c070805b82116d217240d4e304f6f",
|
|
"01d740c2964a008fc6998e2d0cf2df984c8451369737426ad5640a129be6c5dd",
|
|
|
|
// logs data with size > 2^64
|
|
"01854150aba4ddc54c4ac0a61e21b838cb53017d0fa83faf8e146233337cb1fb",
|
|
"0322751b60db071ea7c6885f6f3eaf0b83af83856ba5a72e3a87404cc171fac3"};
|
|
|
|
run_test_case("vmInputLimitsLight.json", skip, true);
|
|
}
|
|
|
|
TEST_CASE("vmArithmeticTest" * doctest::test_suite("logic"))
|
|
{
|
|
auto skip = set<string>{
|
|
// exponent wider than 64 bits
|
|
"exp1",
|
|
|
|
// exponents too large ~(256 ^ (256 ^ n))
|
|
"expPowerOf256Of256_4",
|
|
"expPowerOf256Of256_5",
|
|
"expPowerOf256Of256_6",
|
|
"expPowerOf256Of256_7",
|
|
"expPowerOf256Of256_8",
|
|
"expPowerOf256Of256_9",
|
|
"expPowerOf256Of256_10",
|
|
"expPowerOf256Of256_11",
|
|
"expPowerOf256Of256_12",
|
|
"expPowerOf256Of256_13",
|
|
"expPowerOf256Of256_14",
|
|
"expPowerOf256Of256_15",
|
|
"expPowerOf256Of256_16",
|
|
"expPowerOf256Of256_17",
|
|
"expPowerOf256Of256_18",
|
|
"expPowerOf256Of256_19",
|
|
"expPowerOf256Of256_20",
|
|
"expPowerOf256Of256_21",
|
|
"expPowerOf256Of256_22",
|
|
"expPowerOf256Of256_23",
|
|
"expPowerOf256Of256_24",
|
|
"expPowerOf256Of256_25",
|
|
"expPowerOf256Of256_26",
|
|
"expPowerOf256Of256_27",
|
|
"expPowerOf256Of256_28",
|
|
"expPowerOf256Of256_29",
|
|
"expPowerOf256Of256_30",
|
|
"expPowerOf256Of256_31",
|
|
"expPowerOf256Of256_32",
|
|
"expPowerOf256Of256_33",
|
|
|
|
// filler and code say store[2] = result, but it's not in the expected post
|
|
"expXY"};
|
|
|
|
run_test_case("vmArithmeticTest.json", skip, true);
|
|
}
|
|
|
|
// Passes, but not testing anything interesting because we're currently ignoring
|
|
// the logs
|
|
TEST_CASE("vmLogTest" * doctest::test_suite("env"))
|
|
{
|
|
auto skip = set<string>{
|
|
// mem access > 2^64
|
|
"log0_logMemStartTooHigh",
|
|
"log0_logMemsizeTooHigh",
|
|
"log1_logMemStartTooHigh",
|
|
"log1_logMemsizeTooHigh",
|
|
"log2_logMemStartTooHigh",
|
|
"log2_logMemsizeTooHigh",
|
|
"log3_logMemStartTooHigh",
|
|
"log3_logMemsizeTooHigh",
|
|
"log4_logMemStartTooHigh",
|
|
"log4_logMemsizeTooHigh",
|
|
};
|
|
|
|
run_test_case("vmLogTest.json", skip, true);
|
|
}
|
|
|
|
TEST_CASE("vmPushDupSwapTest" * doctest::test_suite("logic"))
|
|
{
|
|
auto skip = set<string>{
|
|
// clearly valid program, but no post defined
|
|
"push33",
|
|
};
|
|
|
|
run_test_case("vmPushDupSwapTest.json", skip, true);
|
|
}
|
|
|
|
TEST_CASE("vmIOandFlowOperationsTest" * doctest::test_suite("env"))
|
|
{
|
|
auto skip = set<string>{
|
|
// infinite loop, expects to run out of gas
|
|
"BlockNumberDynamicJump0_foreverOutOfGas",
|
|
"DynamicJump0_foreverOutOfGas",
|
|
"JDfromStorageDynamicJump0_foreverOutOfGas",
|
|
"jump0_foreverOutOfGas",
|
|
|
|
// missing gas
|
|
"gas0",
|
|
"gas1",
|
|
|
|
// seem like correct program, why no post here?
|
|
"return1",
|
|
};
|
|
|
|
run_test_case("vmIOandFlowOperationsTest.json", skip, true);
|
|
}
|
|
|
|
TEST_CASE("vmBlockInfoTest" * doctest::test_suite("env"))
|
|
{
|
|
auto skip = set<string>{"gaslimit"};
|
|
|
|
run_test_case("vmBlockInfoTest.json", skip, true);
|
|
}
|
|
|
|
TEST_CASE("vmRandomTest" * doctest::test_suite("rand"))
|
|
{
|
|
auto skip = set<string>{};
|
|
|
|
run_test_case("vmRandomTest.json", skip, true);
|
|
}
|
|
|
|
TEST_CASE("vmSha3Test" * doctest::test_suite("sha"))
|
|
{
|
|
auto skip = set<string>{};
|
|
|
|
run_test_case("vmSha3Test.json", skip, true);
|
|
}
|