aboutsummaryrefslogtreecommitdiffstats
path: root/vm.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'vm.cpp')
-rw-r--r--vm.cpp574
1 files changed, 574 insertions, 0 deletions
diff --git a/vm.cpp b/vm.cpp
new file mode 100644
index 00000000..f15dc048
--- /dev/null
+++ b/vm.cpp
@@ -0,0 +1,574 @@
+/*
+ This file is part of cpp-ethereum.
+
+ cpp-ethereum is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ cpp-ethereum is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
+*/
+/** @file vm.cpp
+ * @author Gav Wood <i@gavwood.com>
+ * @date 2014
+ * vm test functions.
+ */
+
+#include <chrono>
+#include <boost/filesystem.hpp>
+#include <libethereum/Executive.h>
+#include <libevm/VMFactory.h>
+#include "vm.h"
+
+using namespace std;
+using namespace json_spirit;
+using namespace dev;
+using namespace dev::eth;
+using namespace dev::test;
+
+FakeExtVM::FakeExtVM(eth::BlockInfo const& _previousBlock, eth::BlockInfo const& _currentBlock, unsigned _depth): /// TODO: XXX: remove the default argument & fix.
+ ExtVMFace(Address(), Address(), Address(), 0, 1, bytesConstRef(), bytes(), _previousBlock, _currentBlock, test::lastHashes(_currentBlock.number), _depth) {}
+
+h160 FakeExtVM::create(u256 _endowment, u256& io_gas, bytesConstRef _init, OnOpFunc const&)
+{
+ Address na = right160(sha3(rlpList(myAddress, get<1>(addresses[myAddress]))));
+
+ Transaction t(_endowment, gasPrice, io_gas, _init.toBytes());
+ callcreates.push_back(t);
+ return na;
+}
+
+bool FakeExtVM::call(Address _receiveAddress, u256 _value, bytesConstRef _data, u256& io_gas, bytesRef _out, OnOpFunc const&, Address _myAddressOverride, Address _codeAddressOverride)
+{
+ Transaction t(_value, gasPrice, io_gas, _receiveAddress, _data.toVector());
+ callcreates.push_back(t);
+ (void)_out;
+ (void)_myAddressOverride;
+ (void)_codeAddressOverride;
+ return true;
+}
+
+void FakeExtVM::setTransaction(Address _caller, u256 _value, u256 _gasPrice, bytes const& _data)
+{
+ caller = origin = _caller;
+ value = _value;
+ data = &(thisTxData = _data);
+ gasPrice = _gasPrice;
+}
+
+void FakeExtVM::setContract(Address _myAddress, u256 _myBalance, u256 _myNonce, map<u256, u256> const& _storage, bytes const& _code)
+{
+ myAddress = _myAddress;
+ set(myAddress, _myBalance, _myNonce, _storage, _code);
+}
+
+void FakeExtVM::set(Address _a, u256 _myBalance, u256 _myNonce, map<u256, u256> const& _storage, bytes const& _code)
+{
+ get<0>(addresses[_a]) = _myBalance;
+ get<1>(addresses[_a]) = _myNonce;
+ get<2>(addresses[_a]) = _storage;
+ get<3>(addresses[_a]) = _code;
+}
+
+void FakeExtVM::reset(u256 _myBalance, u256 _myNonce, map<u256, u256> const& _storage)
+{
+ callcreates.clear();
+ addresses.clear();
+ set(myAddress, _myBalance, _myNonce, _storage, get<3>(addresses[myAddress]));
+}
+
+void FakeExtVM::push(mObject& o, string const& _n, u256 _v)
+{
+ o[_n] = toString(_v);
+}
+
+void FakeExtVM::push(mArray& a, u256 _v)
+{
+ a.push_back(toString(_v));
+}
+
+mObject FakeExtVM::exportEnv()
+{
+ mObject ret;
+ ret["previousHash"] = toString(previousBlock.hash);
+ push(ret, "currentDifficulty", currentBlock.difficulty);
+ push(ret, "currentTimestamp", currentBlock.timestamp);
+ ret["currentCoinbase"] = toString(currentBlock.coinbaseAddress);
+ push(ret, "currentNumber", currentBlock.number);
+ push(ret, "currentGasLimit", currentBlock.gasLimit);
+ return ret;
+}
+
+void FakeExtVM::importEnv(mObject& _o)
+{
+ // cant use BOOST_REQUIRE, because this function is used outside boost test (createRandomTest)
+ assert(_o.count("previousHash") > 0);
+ assert(_o.count("currentGasLimit") > 0);
+ assert(_o.count("currentDifficulty") > 0);
+ assert(_o.count("currentTimestamp") > 0);
+ assert(_o.count("currentCoinbase") > 0);
+ assert(_o.count("currentNumber") > 0);
+
+ previousBlock.hash = h256(_o["previousHash"].get_str());
+ currentBlock.number = toInt(_o["currentNumber"]);
+ lastHashes = test::lastHashes(currentBlock.number);
+ currentBlock.gasLimit = toInt(_o["currentGasLimit"]);
+ currentBlock.difficulty = toInt(_o["currentDifficulty"]);
+ currentBlock.timestamp = toInt(_o["currentTimestamp"]);
+ currentBlock.coinbaseAddress = Address(_o["currentCoinbase"].get_str());
+}
+
+mObject FakeExtVM::exportState()
+{
+ mObject ret;
+ for (auto const& a: addresses)
+ {
+ mObject o;
+ push(o, "balance", get<0>(a.second));
+ push(o, "nonce", get<1>(a.second));
+
+ {
+ mObject store;
+ for (auto const& s: get<2>(a.second))
+ store["0x"+toHex(toCompactBigEndian(s.first))] = "0x"+toHex(toCompactBigEndian(s.second));
+ o["storage"] = store;
+ }
+ o["code"] = "0x" + toHex(get<3>(a.second));
+
+ ret[toString(a.first)] = o;
+ }
+ return ret;
+}
+
+void FakeExtVM::importState(mObject& _object)
+{
+ for (auto const& i: _object)
+ {
+ mObject o = i.second.get_obj();
+ // cant use BOOST_REQUIRE, because this function is used outside boost test (createRandomTest)
+ assert(o.count("balance") > 0);
+ assert(o.count("nonce") > 0);
+ assert(o.count("storage") > 0);
+ assert(o.count("code") > 0);
+
+ auto& a = addresses[Address(i.first)];
+ get<0>(a) = toInt(o["balance"]);
+ get<1>(a) = toInt(o["nonce"]);
+ for (auto const& j: o["storage"].get_obj())
+ get<2>(a)[toInt(j.first)] = toInt(j.second);
+
+ get<3>(a) = importCode(o);
+ }
+}
+
+mObject FakeExtVM::exportExec()
+{
+ mObject ret;
+ ret["address"] = toString(myAddress);
+ ret["caller"] = toString(caller);
+ ret["origin"] = toString(origin);
+ push(ret, "value", value);
+ push(ret, "gasPrice", gasPrice);
+ push(ret, "gas", gas);
+ ret["data"] = "0x" + toHex(data);
+ ret["code"] = "0x" + toHex(code);
+ return ret;
+}
+
+void FakeExtVM::importExec(mObject& _o)
+{
+ // cant use BOOST_REQUIRE, because this function is used outside boost test (createRandomTest)
+ assert(_o.count("address")> 0);
+ assert(_o.count("caller") > 0);
+ assert(_o.count("origin") > 0);
+ assert(_o.count("value") > 0);
+ assert(_o.count("data") > 0);
+ assert(_o.count("gasPrice") > 0);
+ assert(_o.count("gas") > 0);
+
+ myAddress = Address(_o["address"].get_str());
+ caller = Address(_o["caller"].get_str());
+ origin = Address(_o["origin"].get_str());
+ value = toInt(_o["value"]);
+ gasPrice = toInt(_o["gasPrice"]);
+ gas = toInt(_o["gas"]);
+
+ thisTxCode.clear();
+ code = thisTxCode;
+
+ thisTxCode = importCode(_o);
+ if (_o["code"].type() != str_type && _o["code"].type() != array_type)
+ code.clear();
+
+ thisTxData.clear();
+ thisTxData = importData(_o);
+
+ data = &thisTxData;
+}
+
+mArray FakeExtVM::exportCallCreates()
+{
+ mArray ret;
+ for (Transaction const& tx: callcreates)
+ {
+ mObject o;
+ o["destination"] = tx.isCreation() ? "" : toString(tx.receiveAddress());
+ push(o, "gasLimit", tx.gas());
+ push(o, "value", tx.value());
+ o["data"] = "0x" + toHex(tx.data());
+ ret.push_back(o);
+ }
+ return ret;
+}
+
+void FakeExtVM::importCallCreates(mArray& _callcreates)
+{
+ for (mValue& v: _callcreates)
+ {
+ auto tx = v.get_obj();
+ assert(tx.count("data") > 0);
+ assert(tx.count("value") > 0);
+ assert(tx.count("destination") > 0);
+ assert(tx.count("gasLimit") > 0);
+ Transaction t = tx["destination"].get_str().empty() ?
+ Transaction(toInt(tx["value"]), 0, toInt(tx["gasLimit"]), fromHex(tx["data"].get_str())) :
+ Transaction(toInt(tx["value"]), 0, toInt(tx["gasLimit"]), Address(tx["destination"].get_str()), fromHex(tx["data"].get_str()));
+ callcreates.push_back(t);
+ }
+}
+
+eth::OnOpFunc FakeExtVM::simpleTrace()
+{
+
+ return [](uint64_t steps, eth::Instruction inst, bigint newMemSize, bigint gasCost, dev::eth::VM* voidVM, dev::eth::ExtVMFace const* voidExt)
+ {
+ FakeExtVM const& ext = *static_cast<FakeExtVM const*>(voidExt);
+ eth::VM& vm = *voidVM;
+
+ std::ostringstream o;
+ o << std::endl << " STACK" << std::endl;
+ for (auto i: vm.stack())
+ o << (h256)i << std::endl;
+ o << " MEMORY" << std::endl << memDump(vm.memory());
+ o << " STORAGE" << std::endl;
+
+ for (auto const& i: std::get<2>(ext.addresses.find(ext.myAddress)->second))
+ o << std::showbase << std::hex << i.first << ": " << i.second << std::endl;
+
+ dev::LogOutputStream<eth::VMTraceChannel, false>(true) << o.str();
+ dev::LogOutputStream<eth::VMTraceChannel, false>(false) << " | " << std::dec << ext.depth << " | " << ext.myAddress << " | #" << steps << " | " << std::hex << std::setw(4) << std::setfill('0') << vm.curPC() << " : " << instructionInfo(inst).name << " | " << std::dec << vm.gas() << " | -" << std::dec << gasCost << " | " << newMemSize << "x32" << " ]";
+
+ /*creates json stack trace*/
+ if (eth::VMTraceChannel::verbosity <= g_logVerbosity)
+ {
+ Object o_step;
+
+ /*add the stack*/
+ Array a_stack;
+ for (auto i: vm.stack())
+ a_stack.push_back((string)i);
+
+ o_step.push_back(Pair( "stack", a_stack ));
+
+ /*add the memory*/
+ Array a_mem;
+ for(auto i: vm.memory())
+ a_mem.push_back(i);
+
+ o_step.push_back(Pair("memory", a_mem));
+
+ /*add the storage*/
+ Object storage;
+ for (auto const& i: std::get<2>(ext.addresses.find(ext.myAddress)->second))
+ storage.push_back(Pair( (string)i.first , (string)i.second));
+
+ /*add all the other details*/
+ o_step.push_back(Pair("storage", storage));
+ o_step.push_back(Pair("depth", to_string(ext.depth)));
+ o_step.push_back(Pair("gas", (string)vm.gas()));
+ o_step.push_back(Pair("address", "0x" + toString(ext.myAddress )));
+ o_step.push_back(Pair("step", steps ));
+ o_step.push_back(Pair("pc", (int)vm.curPC()));
+ o_step.push_back(Pair("opcode", instructionInfo(inst).name ));
+
+ /*append the JSON object to the log file*/
+ Value v(o_step);
+ ofstream os( "./stackTrace.json", ofstream::app);
+ os << write_string(v, true) << ",";
+ os.close();
+ }
+ };
+}
+
+namespace dev { namespace test {
+
+void doVMTests(json_spirit::mValue& v, bool _fillin)
+{
+ processCommandLineOptions();
+
+ for (auto& i: v.get_obj())
+ {
+ cnote << i.first;
+ mObject& o = i.second.get_obj();
+
+ BOOST_REQUIRE(o.count("env") > 0);
+ BOOST_REQUIRE(o.count("pre") > 0);
+ BOOST_REQUIRE(o.count("exec") > 0);
+
+ FakeExtVM fev;
+ fev.importEnv(o["env"].get_obj());
+ fev.importState(o["pre"].get_obj());
+
+ if (_fillin)
+ o["pre"] = mValue(fev.exportState());
+
+ fev.importExec(o["exec"].get_obj());
+ if (fev.code.empty())
+ {
+ fev.thisTxCode = get<3>(fev.addresses.at(fev.myAddress));
+ fev.code = fev.thisTxCode;
+ }
+
+ bytes output;
+ u256 gas;
+ bool vmExceptionOccured = false;
+ auto startTime = std::chrono::high_resolution_clock::now();
+ try
+ {
+ auto vm = eth::VMFactory::create(fev.gas);
+ output = vm->go(fev, fev.simpleTrace()).toBytes();
+ gas = vm->gas();
+ }
+ catch (VMException const&)
+ {
+ cnote << "Safe VM Exception";
+ vmExceptionOccured = true;
+ }
+ catch (Exception const& _e)
+ {
+ cnote << "VM did throw an exception: " << diagnostic_information(_e);
+ BOOST_ERROR("Failed VM Test with Exception: " << _e.what());
+ }
+ catch (std::exception const& _e)
+ {
+ cnote << "VM did throw an exception: " << _e.what();
+ BOOST_ERROR("Failed VM Test with Exception: " << _e.what());
+ }
+
+ auto endTime = std::chrono::high_resolution_clock::now();
+ auto argc = boost::unit_test::framework::master_test_suite().argc;
+ auto argv = boost::unit_test::framework::master_test_suite().argv;
+ for (auto i = 0; i < argc; ++i)
+ {
+ if (std::string(argv[i]) == "--show-times")
+ {
+ auto testDuration = endTime - startTime;
+ cnote << "Execution time: "
+ << std::chrono::duration_cast<std::chrono::milliseconds>(testDuration).count()
+ << " ms";
+ break;
+ }
+ }
+
+ // delete null entries in storage for the sake of comparison
+
+ for (auto &a: fev.addresses)
+ {
+ vector<u256> keystoDelete;
+ for (auto &s: get<2>(a.second))
+ {
+ if (s.second == 0)
+ keystoDelete.push_back(s.first);
+ }
+ for (auto const key: keystoDelete )
+ {
+ get<2>(a.second).erase(key);
+ }
+ }
+
+
+ if (_fillin)
+ {
+ o["env"] = mValue(fev.exportEnv());
+ o["exec"] = mValue(fev.exportExec());
+ if (!vmExceptionOccured)
+ {
+ o["post"] = mValue(fev.exportState());
+ o["callcreates"] = fev.exportCallCreates();
+ o["out"] = "0x" + toHex(output);
+ fev.push(o, "gas", gas);
+ o["logs"] = exportLog(fev.sub.logs);
+ }
+ }
+ else
+ {
+ if (o.count("post") > 0) // No exceptions expected
+ {
+ BOOST_CHECK(!vmExceptionOccured);
+
+ BOOST_REQUIRE(o.count("post") > 0);
+ BOOST_REQUIRE(o.count("callcreates") > 0);
+ BOOST_REQUIRE(o.count("out") > 0);
+ BOOST_REQUIRE(o.count("gas") > 0);
+ BOOST_REQUIRE(o.count("logs") > 0);
+
+ dev::test::FakeExtVM test;
+ test.importState(o["post"].get_obj());
+ test.importCallCreates(o["callcreates"].get_array());
+ test.sub.logs = importLog(o["logs"].get_array());
+
+ checkOutput(output, o);
+
+ BOOST_CHECK_EQUAL(toInt(o["gas"]), gas);
+
+ auto& expectedAddrs = test.addresses;
+ auto& resultAddrs = fev.addresses;
+ for (auto&& expectedPair : expectedAddrs)
+ {
+ auto& expectedAddr = expectedPair.first;
+ auto resultAddrIt = resultAddrs.find(expectedAddr);
+ if (resultAddrIt == resultAddrs.end())
+ BOOST_ERROR("Missing expected address " << expectedAddr);
+ else
+ {
+ auto& expectedState = expectedPair.second;
+ auto& resultState = resultAddrIt->second;
+ BOOST_CHECK_MESSAGE(std::get<0>(expectedState) == std::get<0>(resultState), expectedAddr << ": incorrect balance " << std::get<0>(resultState) << ", expected " << std::get<0>(expectedState));
+ BOOST_CHECK_MESSAGE(std::get<1>(expectedState) == std::get<1>(resultState), expectedAddr << ": incorrect txCount " << std::get<1>(resultState) << ", expected " << std::get<1>(expectedState));
+ BOOST_CHECK_MESSAGE(std::get<3>(expectedState) == std::get<3>(resultState), expectedAddr << ": incorrect code");
+
+ checkStorage(std::get<2>(expectedState), std::get<2>(resultState), expectedAddr);
+ }
+ }
+
+ checkAddresses<std::map<Address, std::tuple<u256, u256, std::map<u256, u256>, bytes> > >(test.addresses, fev.addresses);
+
+ checkCallCreates(fev.callcreates, test.callcreates);
+
+ checkLog(fev.sub.logs, test.sub.logs);
+ }
+ else // Exception expected
+ BOOST_CHECK(vmExceptionOccured);
+ }
+ }
+}
+
+} } // Namespace Close
+
+BOOST_AUTO_TEST_SUITE(VMTests)
+
+BOOST_AUTO_TEST_CASE(vm_tests)
+{
+ dev::test::executeTests("vmtests", "/VMTests", dev::test::doVMTests);
+}
+
+BOOST_AUTO_TEST_CASE(vmArithmeticTest)
+{
+ dev::test::executeTests("vmArithmeticTest", "/VMTests", dev::test::doVMTests);
+}
+
+BOOST_AUTO_TEST_CASE(vmBitwiseLogicOperationTest)
+{
+ dev::test::executeTests("vmBitwiseLogicOperationTest", "/VMTests", dev::test::doVMTests);
+}
+
+BOOST_AUTO_TEST_CASE(vmSha3Test)
+{
+ dev::test::executeTests("vmSha3Test", "/VMTests", dev::test::doVMTests);
+}
+
+BOOST_AUTO_TEST_CASE(vmEnvironmentalInfoTest)
+{
+ dev::test::executeTests("vmEnvironmentalInfoTest", "/VMTests", dev::test::doVMTests);
+}
+
+BOOST_AUTO_TEST_CASE(vmBlockInfoTest)
+{
+ dev::test::executeTests("vmBlockInfoTest", "/VMTests", dev::test::doVMTests);
+}
+
+BOOST_AUTO_TEST_CASE(vmIOandFlowOperationsTest)
+{
+ dev::test::executeTests("vmIOandFlowOperationsTest", "/VMTests", dev::test::doVMTests);
+}
+
+BOOST_AUTO_TEST_CASE(vmPushDupSwapTest)
+{
+ dev::test::executeTests("vmPushDupSwapTest", "/VMTests", dev::test::doVMTests);
+}
+
+BOOST_AUTO_TEST_CASE(vmLogTest)
+{
+ dev::test::executeTests("vmLogTest", "/VMTests", dev::test::doVMTests);
+}
+
+BOOST_AUTO_TEST_CASE(vmSystemOperationsTest)
+{
+ dev::test::executeTests("vmSystemOperationsTest", "/VMTests", dev::test::doVMTests);
+}
+
+BOOST_AUTO_TEST_CASE(vmInputLimitsTest1)
+{
+ for (int i = 1; i < boost::unit_test::framework::master_test_suite().argc; ++i)
+ {
+ string arg = boost::unit_test::framework::master_test_suite().argv[i];
+ if (arg == "--inputlimits")
+ dev::test::executeTests("vmInputLimitsTest1", "/VMTests", dev::test::doVMTests);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(vmInputLimitsTest2)
+{
+ for (int i = 1; i < boost::unit_test::framework::master_test_suite().argc; ++i)
+ {
+ string arg = boost::unit_test::framework::master_test_suite().argv[i];
+ if (arg == "--inputlimits")
+ dev::test::executeTests("vmInputLimitsTest2", "/VMTests", dev::test::doVMTests);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(vmRandom)
+{
+ string testPath = getTestPath();
+ testPath += "/VMTests/RandomTests";
+
+ vector<boost::filesystem::path> testFiles;
+ boost::filesystem::directory_iterator iterator(testPath);
+ for(; iterator != boost::filesystem::directory_iterator(); ++iterator)
+ if (boost::filesystem::is_regular_file(iterator->path()) && iterator->path().extension() == ".json")
+ testFiles.push_back(iterator->path());
+
+ for (auto& path: testFiles)
+ {
+ try
+ {
+ cnote << "Testing ..." << path.filename();
+ json_spirit::mValue v;
+ string s = asString(dev::contents(path.string()));
+ BOOST_REQUIRE_MESSAGE(s.length() > 0, "Content of " + path.string() + " is empty. Have you cloned the 'tests' repo branch develop and set ETHEREUM_TEST_PATH to its path?");
+ json_spirit::read_string(s, v);
+ doVMTests(v, false);
+ }
+ catch (Exception const& _e)
+ {
+ BOOST_ERROR("Failed test with Exception: " << diagnostic_information(_e));
+ }
+ catch (std::exception const& _e)
+ {
+ BOOST_ERROR("Failed test with Exception: " << _e.what());
+ }
+ }
+}
+
+BOOST_AUTO_TEST_CASE(userDefinedFileVM)
+{
+ dev::test::userDefinedTest("--vmtest", dev::test::doVMTests);
+}
+
+BOOST_AUTO_TEST_SUITE_END()