yann300
10 years ago
99 changed files with 5048 additions and 1385 deletions
@ -0,0 +1,16 @@ |
|||
cmake_policy(SET CMP0015 NEW) |
|||
set(CMAKE_AUTOMOC OFF) |
|||
|
|||
aux_source_directory(. SRC_LIST) |
|||
|
|||
include_directories(BEFORE ..) |
|||
include_directories(${LEVELDB_INCLUDE_DIRS}) |
|||
|
|||
set(EXECUTABLE abi) |
|||
|
|||
add_executable(${EXECUTABLE} ${SRC_LIST}) |
|||
|
|||
target_link_libraries(${EXECUTABLE} ethereum) |
|||
|
|||
install( TARGETS ${EXECUTABLE} DESTINATION bin) |
|||
|
@ -0,0 +1,596 @@ |
|||
/*
|
|||
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 main.cpp
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
* RLP tool. |
|||
*/ |
|||
#include <fstream> |
|||
#include <iostream> |
|||
#include <boost/regex.hpp> |
|||
#include <boost/algorithm/string.hpp> |
|||
#include "../test/JsonSpiritHeaders.h" |
|||
#include <libdevcore/CommonIO.h> |
|||
#include <libdevcore/RLP.h> |
|||
#include <libdevcrypto/SHA3.h> |
|||
#include <libethereum/Client.h> |
|||
using namespace std; |
|||
using namespace dev; |
|||
namespace js = json_spirit; |
|||
|
|||
void help() |
|||
{ |
|||
cout |
|||
<< "Usage abi enc <method_name> (<arg1>, (<arg2>, ... ))" << endl |
|||
<< " abi enc -a <abi.json> <method_name> (<arg1>, (<arg2>, ... ))" << endl |
|||
<< " abi dec -a <abi.json> [ <signature> | <unique_method_name> ]" << endl |
|||
<< "Options:" << endl |
|||
<< " -a,--abi-file <filename> Specify the JSON ABI file." << endl |
|||
<< " -h,--help Print this help message and exit." << endl |
|||
<< " -V,--version Show the version and exit." << endl |
|||
<< "Input options:" << endl |
|||
<< " -p,--prefix Require all input formats to be prefixed e.g. 0x for hex, . for decimal, @ for binary." << endl |
|||
<< " -P,--no-prefix Require no input format to be prefixed." << endl |
|||
<< " -t,--typing Require all arguments to be typed e.g. b32: (bytes32), u64: (uint64), b[]: (byte[]), i: (int256)." << endl |
|||
<< " -T,--no-typing Require no arguments to be typed." << endl |
|||
<< "Output options:" << endl |
|||
<< " -i,--index <n> Output only the nth (counting from 0) return value." << endl |
|||
<< " -d,--decimal All data should be displayed as decimal." << endl |
|||
<< " -x,--hex Display all data as hex." << endl |
|||
<< " -b,--binary Display all data as binary." << endl |
|||
<< " -p,--prefix Prefix by a base identifier." << endl |
|||
<< " -z,--no-zeroes Remove any leading zeroes from the data." << endl |
|||
<< " -n,--no-nulls Remove any trailing nulls from the data." << endl |
|||
; |
|||
exit(0); |
|||
} |
|||
|
|||
void version() |
|||
{ |
|||
cout << "abi version " << dev::Version << endl; |
|||
exit(0); |
|||
} |
|||
|
|||
enum class Mode |
|||
{ |
|||
Encode, |
|||
Decode |
|||
}; |
|||
|
|||
enum class Encoding |
|||
{ |
|||
Auto, |
|||
Decimal, |
|||
Hex, |
|||
Binary, |
|||
}; |
|||
|
|||
enum class Tristate |
|||
{ |
|||
False = false, |
|||
True = true, |
|||
Mu |
|||
}; |
|||
|
|||
enum class Format |
|||
{ |
|||
Binary, |
|||
Hex, |
|||
Decimal |
|||
}; |
|||
|
|||
struct InvalidUserString: public Exception {}; |
|||
struct InvalidFormat: public Exception {}; |
|||
|
|||
enum class Base |
|||
{ |
|||
Unknown, |
|||
Bytes, |
|||
Address, |
|||
Int, |
|||
Uint, |
|||
Fixed |
|||
}; |
|||
|
|||
static const map<Base, string> s_bases = |
|||
{ |
|||
{ Base::Bytes, "bytes" }, |
|||
{ Base::Address, "address" }, |
|||
{ Base::Int, "int" }, |
|||
{ Base::Uint, "uint" }, |
|||
{ Base::Fixed, "fixed" } |
|||
}; |
|||
|
|||
struct ABIType |
|||
{ |
|||
Base base = Base::Unknown; |
|||
unsigned size = 32; |
|||
unsigned ssize = 0; |
|||
vector<int> dims; |
|||
string name; |
|||
ABIType() = default; |
|||
ABIType(std::string const& _type, std::string const& _name): |
|||
name(_name) |
|||
{ |
|||
string rest; |
|||
for (auto const& i: s_bases) |
|||
if (boost::algorithm::starts_with(_type, i.second)) |
|||
{ |
|||
base = i.first; |
|||
rest = _type.substr(i.second.size()); |
|||
} |
|||
if (base == Base::Unknown) |
|||
throw InvalidFormat(); |
|||
boost::regex r("(\\d*)(x(\\d+))?((\\[\\d*\\])*)"); |
|||
boost::smatch res; |
|||
boost::regex_match(rest, res, r); |
|||
size = res[1].length() > 0 ? stoi(res[1]) : 0; |
|||
ssize = res[3].length() > 0 ? stoi(res[3]) : 0; |
|||
boost::regex r2("\\[(\\d*)\\](.*)"); |
|||
for (rest = res[4]; boost::regex_match(rest, res, r2); rest = res[2]) |
|||
dims.push_back(!res[1].length() ? -1 : stoi(res[1])); |
|||
} |
|||
|
|||
ABIType(std::string const& _s) |
|||
{ |
|||
if (_s.size() < 1) |
|||
return; |
|||
switch (_s[0]) |
|||
{ |
|||
case 'b': base = Base::Bytes; break; |
|||
case 'a': base = Base::Address; break; |
|||
case 'i': base = Base::Int; break; |
|||
case 'u': base = Base::Uint; break; |
|||
case 'f': base = Base::Fixed; break; |
|||
default: throw InvalidFormat(); |
|||
} |
|||
if (_s.size() < 2) |
|||
{ |
|||
if (base == Base::Fixed) |
|||
size = ssize = 16; |
|||
else if (base == Base::Address || base == Base::Bytes) |
|||
size = 0; |
|||
else |
|||
size = 32; |
|||
return; |
|||
} |
|||
strings d; |
|||
boost::algorithm::split(d, _s, boost::is_any_of("*")); |
|||
string s = d[0]; |
|||
if (s.find_first_of('x') == string::npos) |
|||
size = stoi(s.substr(1)); |
|||
else |
|||
{ |
|||
size = stoi(s.substr(1, s.find_first_of('x') - 1)); |
|||
ssize = stoi(s.substr(s.find_first_of('x') + 1)); |
|||
} |
|||
for (unsigned i = 1; i < d.size(); ++i) |
|||
if (d[i].empty()) |
|||
dims.push_back(-1); |
|||
else |
|||
dims.push_back(stoi(d[i])); |
|||
} |
|||
|
|||
string canon() const |
|||
{ |
|||
string ret; |
|||
switch (base) |
|||
{ |
|||
case Base::Bytes: ret = "bytes" + (size > 0 ? toString(size) : ""); break; |
|||
case Base::Address: ret = "address"; break; |
|||
case Base::Int: ret = "int" + toString(size); break; |
|||
case Base::Uint: ret = "uint" + toString(size); break; |
|||
case Base::Fixed: ret = "fixed" + toString(size) + "x" + toString(ssize); break; |
|||
default: throw InvalidFormat(); |
|||
} |
|||
for (int i: dims) |
|||
ret += "[" + ((i > -1) ? toString(i) : "") + "]"; |
|||
return ret; |
|||
} |
|||
|
|||
void noteHexInput(unsigned _nibbles) { if (base == Base::Unknown) { if (_nibbles == 40) base = Base::Address; else { base = Base::Bytes; size = _nibbles / 2; } } } |
|||
void noteBinaryInput() { if (base == Base::Unknown) { base = Base::Bytes; size = 32; } } |
|||
void noteDecimalInput() { if (base == Base::Unknown) { base = Base::Uint; size = 32; } } |
|||
}; |
|||
|
|||
bytes aligned(bytes const& _b, ABIType _t, Format _f, unsigned _length) |
|||
{ |
|||
(void)_t; |
|||
bytes ret = _b; |
|||
while (ret.size() < _length) |
|||
if (_f == Format::Binary) |
|||
ret.push_back(0); |
|||
else |
|||
ret.insert(ret.begin(), 0); |
|||
while (ret.size() > _length) |
|||
if (_f == Format::Binary) |
|||
ret.pop_back(); |
|||
else |
|||
ret.erase(ret.begin()); |
|||
return ret; |
|||
} |
|||
|
|||
tuple<bytes, ABIType, Format> fromUser(std::string const& _arg, Tristate _prefix, Tristate _typing) |
|||
{ |
|||
ABIType type; |
|||
string val; |
|||
if (_typing == Tristate::True || (_typing == Tristate::Mu && _arg.find(':') != string::npos)) |
|||
{ |
|||
if (_arg.find(':') == string::npos) |
|||
throw InvalidUserString(); |
|||
type = ABIType(_arg.substr(0, _arg.find(':'))); |
|||
val = _arg.substr(_arg.find(':') + 1); |
|||
} |
|||
else |
|||
val = _arg; |
|||
|
|||
if (_prefix != Tristate::False) |
|||
{ |
|||
if (val.substr(0, 2) == "0x") |
|||
{ |
|||
type.noteHexInput(val.size() - 2); |
|||
return make_tuple(fromHex(val), type, Format::Hex); |
|||
} |
|||
if (val.substr(0, 1) == "+") |
|||
{ |
|||
type.noteDecimalInput(); |
|||
return make_tuple(toCompactBigEndian(bigint(val.substr(1))), type, Format::Decimal); |
|||
} |
|||
if (val.substr(0, 1) == "@") |
|||
{ |
|||
type.noteBinaryInput(); |
|||
return make_tuple(asBytes(val.substr(1)), type, Format::Binary); |
|||
} |
|||
} |
|||
if (_prefix != Tristate::True) |
|||
{ |
|||
if (_arg.find_first_not_of("0123456789") == string::npos) |
|||
{ |
|||
type.noteDecimalInput(); |
|||
return make_tuple(toCompactBigEndian(bigint(val)), type, Format::Decimal); |
|||
} |
|||
if (_arg.find_first_not_of("0123456789abcdefABCDEF") == string::npos) |
|||
{ |
|||
type.noteHexInput(val.size()); |
|||
return make_tuple(fromHex(val), type, Format::Hex); |
|||
} |
|||
type.noteBinaryInput(); |
|||
return make_tuple(asBytes(_arg), type, Format::Binary); |
|||
} |
|||
throw InvalidUserString(); |
|||
} |
|||
|
|||
struct ABIMethod |
|||
{ |
|||
string name; |
|||
vector<ABIType> ins; |
|||
vector<ABIType> outs; |
|||
bool isConstant = false; |
|||
|
|||
// isolation *IS* documentation.
|
|||
|
|||
ABIMethod() = default; |
|||
|
|||
ABIMethod(js::mObject _o) |
|||
{ |
|||
name = _o["name"].get_str(); |
|||
isConstant = _o["constant"].get_bool(); |
|||
if (_o.count("inputs")) |
|||
for (auto const& i: _o["inputs"].get_array()) |
|||
{ |
|||
js::mObject a = i.get_obj(); |
|||
ins.push_back(ABIType(a["type"].get_str(), a["name"].get_str())); |
|||
} |
|||
if (_o.count("outputs")) |
|||
for (auto const& i: _o["outputs"].get_array()) |
|||
{ |
|||
js::mObject a = i.get_obj(); |
|||
outs.push_back(ABIType(a["type"].get_str(), a["name"].get_str())); |
|||
} |
|||
} |
|||
|
|||
ABIMethod(string const& _name, vector<ABIType> const& _args) |
|||
{ |
|||
name = _name; |
|||
ins = _args; |
|||
} |
|||
|
|||
string sig() const |
|||
{ |
|||
string methodArgs; |
|||
for (auto const& arg: ins) |
|||
methodArgs += (methodArgs.empty() ? "" : ",") + arg.canon(); |
|||
return name + "(" + methodArgs + ")"; |
|||
} |
|||
FixedHash<4> id() const { return FixedHash<4>(sha3(sig())); } |
|||
|
|||
std::string solidityDeclaration() const |
|||
{ |
|||
ostringstream ss; |
|||
ss << "function " << name << "("; |
|||
int f = 0; |
|||
for (ABIType const& i: ins) |
|||
ss << (f++ ? ", " : "") << i.canon() << " " << i.name; |
|||
ss << ") "; |
|||
if (isConstant) |
|||
ss << "constant "; |
|||
if (!outs.empty()) |
|||
{ |
|||
ss << "returns ("; |
|||
f = 0; |
|||
for (ABIType const& i: outs) |
|||
ss << (f ? ", " : "") << i.canon() << " " << i.name; |
|||
ss << ")"; |
|||
} |
|||
return ss.str(); |
|||
} |
|||
|
|||
bytes encode(vector<pair<bytes, Format>> const& _params) const |
|||
{ |
|||
bytes ret = name.empty() ? bytes() : id().asBytes(); |
|||
unsigned pi = 0; |
|||
vector<unsigned> inArity; |
|||
for (ABIType const& i: ins) |
|||
{ |
|||
unsigned arity = 1; |
|||
for (auto j: i.dims) |
|||
if (j == -1) |
|||
{ |
|||
ret += aligned(_params[pi].first, ABIType(), Format::Decimal, 32); |
|||
arity *= fromBigEndian<unsigned>(_params[pi].first); |
|||
pi++; |
|||
} |
|||
else |
|||
arity *= j; |
|||
inArity.push_back(arity); |
|||
} |
|||
unsigned ii = 0; |
|||
for (ABIType const& i: ins) |
|||
{ |
|||
for (unsigned j = 0; j < inArity[ii]; ++j) |
|||
{ |
|||
if (i.base == Base::Bytes && !i.size) |
|||
{ |
|||
ret += _params[pi].first; |
|||
while (ret.size() % 32 != 0) |
|||
ret.push_back(0); |
|||
} |
|||
else |
|||
ret += aligned(_params[pi].first, i, _params[pi].second, 32); |
|||
++pi; |
|||
} |
|||
++ii; |
|||
} |
|||
return ret; |
|||
} |
|||
string decode(bytes const& _data, int _index = -1) |
|||
{ |
|||
stringstream out; |
|||
if (_index == -1) |
|||
out << "["; |
|||
(void)_data; |
|||
if (_index == -1) |
|||
out << "]"; |
|||
return out.str(); |
|||
} |
|||
}; |
|||
|
|||
string canonSig(string const& _name, vector<ABIType> const& _args) |
|||
{ |
|||
string methodArgs; |
|||
for (auto const& arg: _args) |
|||
methodArgs += (methodArgs.empty() ? "" : ",") + arg.canon(); |
|||
return _name + "(" + methodArgs + ")"; |
|||
} |
|||
|
|||
struct UnknownMethod: public Exception {}; |
|||
struct OverloadedMethod: public Exception {}; |
|||
|
|||
class ABI |
|||
{ |
|||
public: |
|||
ABI() = default; |
|||
ABI(std::string const& _json) |
|||
{ |
|||
js::mValue v; |
|||
js::read_string(_json, v); |
|||
for (auto const& i: v.get_array()) |
|||
{ |
|||
js::mObject o = i.get_obj(); |
|||
if (o["type"].get_str() != "function") |
|||
continue; |
|||
ABIMethod m(o); |
|||
m_methods[m.id()] = m; |
|||
} |
|||
} |
|||
|
|||
ABIMethod method(string _nameOrSig, vector<ABIType> const& _args) const |
|||
{ |
|||
auto id = FixedHash<4>(sha3(_nameOrSig)); |
|||
if (!m_methods.count(id)) |
|||
id = FixedHash<4>(sha3(canonSig(_nameOrSig, _args))); |
|||
if (!m_methods.count(id)) |
|||
for (auto const& m: m_methods) |
|||
if (m.second.name == _nameOrSig) |
|||
{ |
|||
if (m_methods.count(id)) |
|||
throw OverloadedMethod(); |
|||
id = m.first; |
|||
} |
|||
if (m_methods.count(id)) |
|||
return m_methods.at(id); |
|||
throw UnknownMethod(); |
|||
} |
|||
|
|||
friend ostream& operator<<(ostream& _out, ABI const& _abi); |
|||
|
|||
private: |
|||
map<FixedHash<4>, ABIMethod> m_methods; |
|||
}; |
|||
|
|||
ostream& operator<<(ostream& _out, ABI const& _abi) |
|||
{ |
|||
_out << "contract {" << endl; |
|||
for (auto const& i: _abi.m_methods) |
|||
_out << " " << i.second.solidityDeclaration() << "; // " << i.first.abridged() << endl; |
|||
_out << "}" << endl; |
|||
return _out; |
|||
} |
|||
|
|||
void userOutput(ostream& _out, bytes const& _data, Encoding _e) |
|||
{ |
|||
switch (_e) |
|||
{ |
|||
case Encoding::Binary: |
|||
_out.write((char const*)_data.data(), _data.size()); |
|||
break; |
|||
default: |
|||
_out << toHex(_data) << endl; |
|||
} |
|||
} |
|||
|
|||
template <unsigned n, class T> vector<typename std::remove_reference<decltype(get<n>(T()))>::type> retrieve(vector<T> const& _t) |
|||
{ |
|||
vector<typename std::remove_reference<decltype(get<n>(T()))>::type> ret; |
|||
for (T const& i: _t) |
|||
ret.push_back(get<n>(i)); |
|||
return ret; |
|||
} |
|||
|
|||
int main(int argc, char** argv) |
|||
{ |
|||
Encoding encoding = Encoding::Auto; |
|||
Mode mode = Mode::Encode; |
|||
string abiFile; |
|||
string method; |
|||
Tristate prefix = Tristate::Mu; |
|||
Tristate typePrefix = Tristate::Mu; |
|||
bool clearZeroes = false; |
|||
bool clearNulls = false; |
|||
bool verbose = false; |
|||
int outputIndex = -1; |
|||
vector<pair<bytes, Format>> params; |
|||
vector<ABIType> args; |
|||
|
|||
for (int i = 1; i < argc; ++i) |
|||
{ |
|||
string arg = argv[i]; |
|||
if (arg == "-h" || arg == "--help") |
|||
help(); |
|||
else if (arg == "enc" && i == 1) |
|||
mode = Mode::Encode; |
|||
else if (arg == "dec" && i == 1) |
|||
mode = Mode::Decode; |
|||
else if ((arg == "-a" || arg == "--abi") && argc > i) |
|||
abiFile = argv[++i]; |
|||
else if ((arg == "-i" || arg == "--index") && argc > i) |
|||
outputIndex = atoi(argv[++i]); |
|||
else if (arg == "-p" || arg == "--prefix") |
|||
prefix = Tristate::True; |
|||
else if (arg == "-P" || arg == "--no-prefix") |
|||
prefix = Tristate::False; |
|||
else if (arg == "-t" || arg == "--typing") |
|||
typePrefix = Tristate::True; |
|||
else if (arg == "-T" || arg == "--no-typing") |
|||
typePrefix = Tristate::False; |
|||
else if (arg == "-z" || arg == "--no-zeroes") |
|||
clearZeroes = true; |
|||
else if (arg == "-n" || arg == "--no-nulls") |
|||
clearNulls = true; |
|||
else if (arg == "-v" || arg == "--verbose") |
|||
verbose = true; |
|||
else if (arg == "-x" || arg == "--hex") |
|||
encoding = Encoding::Hex; |
|||
else if (arg == "-d" || arg == "--decimal" || arg == "--dec") |
|||
encoding = Encoding::Decimal; |
|||
else if (arg == "-b" || arg == "--binary" || arg == "--bin") |
|||
encoding = Encoding::Binary; |
|||
else if (arg == "-v" || arg == "--verbose") |
|||
version(); |
|||
else if (arg == "-V" || arg == "--version") |
|||
version(); |
|||
else if (method.empty()) |
|||
method = arg; |
|||
else |
|||
{ |
|||
auto u = fromUser(arg, prefix, typePrefix); |
|||
args.push_back(get<1>(u)); |
|||
params.push_back(make_pair(get<0>(u), get<2>(u))); |
|||
} |
|||
} |
|||
|
|||
string abiData; |
|||
if (abiFile.empty()) |
|||
abiData = contentsString(abiFile); |
|||
|
|||
if (mode == Mode::Encode) |
|||
{ |
|||
ABIMethod m; |
|||
if (abiData.empty()) |
|||
m = ABIMethod(method, args); |
|||
else |
|||
{ |
|||
ABI abi(abiData); |
|||
if (verbose) |
|||
cerr << "ABI:" << endl << abi; |
|||
try { |
|||
m = abi.method(method, args); |
|||
} |
|||
catch(...) |
|||
{ |
|||
cerr << "Unknown method in ABI." << endl; |
|||
exit(-1); |
|||
} |
|||
} |
|||
userOutput(cout, m.encode(params), encoding); |
|||
} |
|||
else if (mode == Mode::Decode) |
|||
{ |
|||
if (abiData.empty()) |
|||
{ |
|||
cerr << "Please specify an ABI file." << endl; |
|||
exit(-1); |
|||
} |
|||
else |
|||
{ |
|||
ABI abi(abiData); |
|||
ABIMethod m; |
|||
if (verbose) |
|||
cerr << "ABI:" << endl << abi; |
|||
try { |
|||
m = abi.method(method, args); |
|||
} |
|||
catch(...) |
|||
{ |
|||
cerr << "Unknown method in ABI." << endl; |
|||
exit(-1); |
|||
} |
|||
string encoded; |
|||
for (int i = cin.get(); i != -1; i = cin.get()) |
|||
encoded.push_back((char)i); |
|||
cout << m.decode(fromHex(encoded)); |
|||
} |
|||
|
|||
// TODO: read abi to determine output format.
|
|||
(void)encoding; |
|||
(void)clearZeroes; |
|||
(void)clearNulls; |
|||
(void)outputIndex; |
|||
} |
|||
|
|||
return 0; |
|||
} |
@ -0,0 +1,15 @@ |
|||
# Should be used to run ctest |
|||
# |
|||
# example usage: |
|||
# cmake -DETH_TEST_NAME=TestInterfaceStub -DCTEST_COMMAND=/path/to/ctest -P scripts/runtest.cmake |
|||
|
|||
if (NOT CTEST_COMMAND) |
|||
message(FATAL_ERROR "ctest could not be found!") |
|||
endif() |
|||
|
|||
# verbosity is off, cause BOOST_MESSAGE is not thread safe and output is a trash |
|||
# see https://codecrafter.wordpress.com/2012/11/01/c-unit-test-framework-adapter-part-3/ |
|||
# |
|||
# output might not be usefull cause of thread safety issue |
|||
execute_process(COMMAND ${CTEST_COMMAND} --force-new-ctest-process -C Debug --output-on-failure -j 4 -R "${ETH_TEST_NAME}[.].*") |
|||
|
@ -0,0 +1,35 @@ |
|||
cmake_policy(SET CMP0015 NEW) |
|||
set(CMAKE_AUTOMOC OFF) |
|||
|
|||
aux_source_directory(. SRC_LIST) |
|||
|
|||
include_directories(BEFORE ${JSONCPP_INCLUDE_DIRS}) |
|||
include_directories(BEFORE ..) |
|||
include_directories(${Boost_INCLUDE_DIRS}) |
|||
include_directories(${JSON_RPC_CPP_INCLUDE_DIRS}) |
|||
|
|||
set(EXECUTABLE ethrpctest) |
|||
|
|||
file(GLOB HEADERS "*.h") |
|||
|
|||
add_executable(${EXECUTABLE} ${SRC_LIST} ${HEADERS}) |
|||
|
|||
add_dependencies(${EXECUTABLE} BuildInfo.h) |
|||
|
|||
target_link_libraries(${EXECUTABLE} ${Boost_REGEX_LIBRARIES}) |
|||
|
|||
if (READLINE_FOUND) |
|||
target_link_libraries(${EXECUTABLE} ${READLINE_LIBRARIES}) |
|||
endif() |
|||
|
|||
target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARIES}) |
|||
target_link_libraries(${EXECUTABLE} ${Boost_PROGRAM_OPTIONS_LIBRARIES}) |
|||
target_link_libraries(${EXECUTABLE} testutils) |
|||
target_link_libraries(${EXECUTABLE} web3jsonrpc) |
|||
|
|||
if (DEFINED WIN32 AND NOT DEFINED CMAKE_COMPILER_IS_MINGW) |
|||
add_custom_command(TARGET ${EXECUTABLE} POST_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E copy ${MHD_DLL_RELEASE} "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}") |
|||
endif() |
|||
|
|||
install( TARGETS ${EXECUTABLE} DESTINATION bin ) |
|||
|
@ -0,0 +1,129 @@ |
|||
/*
|
|||
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 CommandLineInterface.cpp
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include <string> |
|||
#include <iostream> |
|||
#include <fstream> |
|||
#include <csignal> |
|||
#include <thread> |
|||
#include <boost/filesystem.hpp> |
|||
#include <jsonrpccpp/server/connectors/httpserver.h> |
|||
#include <libtestutils/Common.h> |
|||
#include <libtestutils/BlockChainLoader.h> |
|||
#include <libtestutils/FixedClient.h> |
|||
#include <libtestutils/FixedWebThreeServer.h> |
|||
#include "CommandLineInterface.h" |
|||
#include "BuildInfo.h" |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
using namespace dev::test; |
|||
namespace po = boost::program_options; |
|||
|
|||
bool CommandLineInterface::parseArguments(int argc, char** argv) |
|||
{ |
|||
// Declare the supported options.
|
|||
po::options_description desc("Allowed options"); |
|||
desc.add_options() |
|||
("help", "Show help message and exit") |
|||
("json", po::value<vector<string>>()->required(), "input file") |
|||
("test", po::value<vector<string>>()->required(), "test case name"); |
|||
|
|||
// All positional options should be interpreted as input files
|
|||
po::positional_options_description p; |
|||
|
|||
// parse the compiler arguments
|
|||
try |
|||
{ |
|||
po::store(po::command_line_parser(argc, argv).options(desc).positional(p).allow_unregistered().run(), m_args); |
|||
|
|||
if (m_args.count("help")) |
|||
{ |
|||
cout << desc; |
|||
return false; |
|||
} |
|||
|
|||
po::notify(m_args); |
|||
} |
|||
catch (po::error const& _exception) |
|||
{ |
|||
cout << _exception.what() << endl; |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
bool CommandLineInterface::processInput() |
|||
{ |
|||
string infile = m_args["json"].as<vector<string>>()[0]; |
|||
|
|||
auto path = boost::filesystem::path(infile); |
|||
if (!boost::filesystem::exists(path)) |
|||
{ |
|||
cout << "Non existant input file \"" << infile << "\"" << endl; |
|||
return false; |
|||
} |
|||
|
|||
string test = m_args["test"].as<vector<string>>()[0]; |
|||
Json::Value j = dev::test::loadJsonFromFile(path.string()); |
|||
|
|||
if (j[test].empty()) |
|||
{ |
|||
cout << "Non existant test case \"" << infile << "\"" << endl; |
|||
return false; |
|||
} |
|||
|
|||
if (!j[test].isObject()) |
|||
{ |
|||
cout << "Incorrect JSON file \"" << infile << "\"" << endl; |
|||
return false; |
|||
} |
|||
|
|||
m_json = j[test]; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
bool g_exit = false; |
|||
|
|||
void sighandler(int) |
|||
{ |
|||
g_exit = true; |
|||
} |
|||
|
|||
void CommandLineInterface::actOnInput() |
|||
{ |
|||
BlockChainLoader bcl(m_json); |
|||
FixedClient client(bcl.bc(), bcl.state()); |
|||
unique_ptr<FixedWebThreeServer> jsonrpcServer; |
|||
auto server = new jsonrpc::HttpServer(8080, "", "", 2); |
|||
jsonrpcServer.reset(new FixedWebThreeServer(*server, {}, &client)); |
|||
jsonrpcServer->StartListening(); |
|||
|
|||
signal(SIGABRT, &sighandler); |
|||
signal(SIGTERM, &sighandler); |
|||
signal(SIGINT, &sighandler); |
|||
|
|||
while (!g_exit) |
|||
this_thread::sleep_for(chrono::milliseconds(1000)); |
|||
} |
@ -0,0 +1,46 @@ |
|||
/*
|
|||
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/>.
|
|||
*/ |
|||
/** CommandLineInterface.h
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
#pragma once |
|||
|
|||
#include <json/json.h> |
|||
#include <boost/program_options.hpp> |
|||
|
|||
class CommandLineInterface |
|||
{ |
|||
public: |
|||
CommandLineInterface() {} |
|||
|
|||
/// Parse command line arguments and return false if we should not continue
|
|||
bool parseArguments(int argc, char** argv); |
|||
/// Parse input file and check if test exists
|
|||
bool processInput(); |
|||
/// Start FixedJsonRpcServer
|
|||
void actOnInput(); |
|||
|
|||
private: |
|||
|
|||
/// Compiler arguments variable map
|
|||
boost::program_options::variables_map m_args; |
|||
|
|||
/// loaded json test case
|
|||
Json::Value m_json; |
|||
}; |
|||
|
@ -0,0 +1,34 @@ |
|||
/*
|
|||
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/>.
|
|||
*/ |
|||
/** main.cpp
|
|||
* @author Marek Kotewicz <c@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "CommandLineInterface.h" |
|||
|
|||
int main(int argc, char** argv) |
|||
{ |
|||
CommandLineInterface cli; |
|||
if (!cli.parseArguments(argc, argv)) |
|||
return 1; |
|||
if (!cli.processInput()) |
|||
return 1; |
|||
cli.actOnInput(); |
|||
|
|||
return 0; |
|||
} |
@ -0,0 +1,27 @@ |
|||
/*
|
|||
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 ABI.cpp
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#include "ABI.h" |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
@ -0,0 +1,64 @@ |
|||
/*
|
|||
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 ABI.h
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/FixedHash.h> |
|||
#include <libdevcore/CommonData.h> |
|||
#include <libdevcrypto/SHA3.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth |
|||
{ |
|||
|
|||
template <class T> struct ABISerialiser {}; |
|||
template <unsigned N> struct ABISerialiser<FixedHash<N>> { static bytes serialise(FixedHash<N> const& _t) { static_assert(N <= 32, "Cannot serialise hash > 32 bytes."); static_assert(N > 0, "Cannot serialise zero-length hash."); return bytes(32 - N, 0) + _t.asBytes(); } }; |
|||
template <> struct ABISerialiser<u256> { static bytes serialise(u256 const& _t) { return h256(_t).asBytes(); } }; |
|||
template <> struct ABISerialiser<u160> { static bytes serialise(u160 const& _t) { return bytes(12, 0) + h160(_t).asBytes(); } }; |
|||
template <> struct ABISerialiser<string32> { static bytes serialise(string32 const& _t) { return bytesConstRef((byte const*)_t.data(), 32).toBytes(); } }; |
|||
|
|||
inline bytes abiInAux() { return {}; } |
|||
template <class T, class ... U> bytes abiInAux(T const& _t, U const& ... _u) |
|||
{ |
|||
return ABISerialiser<T>::serialise(_t) + abiInAux(_u ...); |
|||
} |
|||
|
|||
template <class ... T> bytes abiIn(std::string _id, T const& ... _t) |
|||
{ |
|||
return sha3(_id).ref().cropped(0, 4).toBytes() + abiInAux(_t ...); |
|||
} |
|||
|
|||
template <class T> struct ABIDeserialiser {}; |
|||
template <unsigned N> struct ABIDeserialiser<FixedHash<N>> { static FixedHash<N> deserialise(bytesConstRef& io_t) { static_assert(N <= 32, "Parameter sizes must be at most 32 bytes."); FixedHash<N> ret; io_t.cropped(32 - N, N).populate(ret.ref()); io_t = io_t.cropped(32); return ret; } }; |
|||
template <> struct ABIDeserialiser<u256> { static u256 deserialise(bytesConstRef& io_t) { u256 ret = fromBigEndian<u256>(io_t.cropped(0, 32)); io_t = io_t.cropped(32); return ret; } }; |
|||
template <> struct ABIDeserialiser<u160> { static u160 deserialise(bytesConstRef& io_t) { u160 ret = fromBigEndian<u160>(io_t.cropped(12, 20)); io_t = io_t.cropped(32); return ret; } }; |
|||
template <> struct ABIDeserialiser<string32> { static string32 deserialise(bytesConstRef& io_t) { string32 ret; io_t.cropped(0, 32).populate(bytesRef((byte*)ret.data(), 32)); io_t = io_t.cropped(32); return ret; } }; |
|||
|
|||
template <class T> T abiOut(bytes const& _data) |
|||
{ |
|||
bytesConstRef o(&_data); |
|||
return ABIDeserialiser<T>::deserialise(o); |
|||
} |
|||
|
|||
} |
|||
} |
@ -0,0 +1,404 @@ |
|||
/*
|
|||
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 ClientBase.cpp
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include <libdevcore/StructuredLogger.h> |
|||
#include "ClientBase.h" |
|||
#include "BlockChain.h" |
|||
#include "Executive.h" |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
|||
State ClientBase::asOf(BlockNumber _h) const |
|||
{ |
|||
if (_h == PendingBlock) |
|||
return postMine(); |
|||
else if (_h == LatestBlock) |
|||
return preMine(); |
|||
return asOf(bc().numberHash(_h)); |
|||
} |
|||
|
|||
void ClientBase::submitTransaction(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) |
|||
{ |
|||
prepareForTransaction(); |
|||
|
|||
u256 n = postMine().transactionsFrom(toAddress(_secret)); |
|||
Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); |
|||
m_tq.attemptImport(t.rlp()); |
|||
|
|||
StructuredLogger::transactionReceived(t.sha3().abridged(), t.sender().abridged()); |
|||
cnote << "New transaction " << t; |
|||
} |
|||
|
|||
Address ClientBase::submitTransaction(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) |
|||
{ |
|||
prepareForTransaction(); |
|||
|
|||
u256 n = postMine().transactionsFrom(toAddress(_secret)); |
|||
Transaction t(_endowment, _gasPrice, _gas, _init, n, _secret); |
|||
m_tq.attemptImport(t.rlp()); |
|||
|
|||
StructuredLogger::transactionReceived(t.sha3().abridged(), t.sender().abridged()); |
|||
cnote << "New transaction " << t; |
|||
|
|||
return right160(sha3(rlpList(t.sender(), t.nonce()))); |
|||
} |
|||
|
|||
// TODO: remove try/catch, allow exceptions
|
|||
ExecutionResult ClientBase::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice, BlockNumber _blockNumber) |
|||
{ |
|||
ExecutionResult ret; |
|||
try |
|||
{ |
|||
State temp = asOf(_blockNumber); |
|||
u256 n = temp.transactionsFrom(toAddress(_secret)); |
|||
Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); |
|||
ret = temp.execute(bc(), t.rlp(), Permanence::Reverted); |
|||
} |
|||
catch (...) |
|||
{ |
|||
// TODO: Some sort of notification of failure.
|
|||
} |
|||
return ret; |
|||
} |
|||
|
|||
ExecutionResult ClientBase::create(Secret _secret, u256 _value, bytes const& _data, u256 _gas, u256 _gasPrice, BlockNumber _blockNumber) |
|||
{ |
|||
ExecutionResult ret; |
|||
try |
|||
{ |
|||
State temp = asOf(_blockNumber); |
|||
u256 n = temp.transactionsFrom(toAddress(_secret)); |
|||
// cdebug << "Nonce at " << toAddress(_secret) << " pre:" << m_preMine.transactionsFrom(toAddress(_secret)) << " post:" << m_postMine.transactionsFrom(toAddress(_secret));
|
|||
|
|||
Transaction t(_value, _gasPrice, _gas, _data, n, _secret); |
|||
ret = temp.execute(bc(), t.rlp(), Permanence::Reverted); |
|||
} |
|||
catch (...) |
|||
{ |
|||
// TODO: Some sort of notification of failure.
|
|||
} |
|||
return ret; |
|||
} |
|||
|
|||
u256 ClientBase::balanceAt(Address _a, BlockNumber _block) const |
|||
{ |
|||
return asOf(_block).balance(_a); |
|||
} |
|||
|
|||
u256 ClientBase::countAt(Address _a, BlockNumber _block) const |
|||
{ |
|||
return asOf(_block).transactionsFrom(_a); |
|||
} |
|||
|
|||
u256 ClientBase::stateAt(Address _a, u256 _l, BlockNumber _block) const |
|||
{ |
|||
return asOf(_block).storage(_a, _l); |
|||
} |
|||
|
|||
bytes ClientBase::codeAt(Address _a, BlockNumber _block) const |
|||
{ |
|||
return asOf(_block).code(_a); |
|||
} |
|||
|
|||
map<u256, u256> ClientBase::storageAt(Address _a, BlockNumber _block) const |
|||
{ |
|||
return asOf(_block).storage(_a); |
|||
} |
|||
|
|||
// TODO: remove try/catch, allow exceptions
|
|||
LocalisedLogEntries ClientBase::logs(unsigned _watchId) const |
|||
{ |
|||
LogFilter f; |
|||
try |
|||
{ |
|||
Guard l(x_filtersWatches); |
|||
f = m_filters.at(m_watches.at(_watchId).id).filter; |
|||
} |
|||
catch (...) |
|||
{ |
|||
return LocalisedLogEntries(); |
|||
} |
|||
return logs(f); |
|||
} |
|||
|
|||
LocalisedLogEntries ClientBase::logs(LogFilter const& _f) const |
|||
{ |
|||
LocalisedLogEntries ret; |
|||
unsigned begin = min<unsigned>(bc().number() + 1, (unsigned)_f.latest()); |
|||
unsigned end = min(bc().number(), min(begin, (unsigned)_f.earliest())); |
|||
|
|||
// Handle pending transactions differently as they're not on the block chain.
|
|||
if (begin > bc().number()) |
|||
{ |
|||
State temp = postMine(); |
|||
for (unsigned i = 0; i < temp.pending().size(); ++i) |
|||
{ |
|||
// Might have a transaction that contains a matching log.
|
|||
TransactionReceipt const& tr = temp.receipt(i); |
|||
auto th = temp.pending()[i].sha3(); |
|||
LogEntries le = _f.matches(tr); |
|||
if (le.size()) |
|||
for (unsigned j = 0; j < le.size(); ++j) |
|||
ret.insert(ret.begin(), LocalisedLogEntry(le[j], begin, th)); |
|||
} |
|||
begin = bc().number(); |
|||
} |
|||
|
|||
set<unsigned> matchingBlocks; |
|||
for (auto const& i: _f.bloomPossibilities()) |
|||
for (auto u: bc().withBlockBloom(i, end, begin)) |
|||
matchingBlocks.insert(u); |
|||
|
|||
unsigned falsePos = 0; |
|||
for (auto n: matchingBlocks) |
|||
{ |
|||
int total = 0; |
|||
auto h = bc().numberHash(n); |
|||
auto receipts = bc().receipts(h).receipts; |
|||
for (size_t i = 0; i < receipts.size(); i++) |
|||
{ |
|||
TransactionReceipt receipt = receipts[i]; |
|||
if (_f.matches(receipt.bloom())) |
|||
{ |
|||
auto info = bc().info(h); |
|||
auto th = transaction(info.hash, i).sha3(); |
|||
LogEntries le = _f.matches(receipt); |
|||
if (le.size()) |
|||
{ |
|||
total += le.size(); |
|||
for (unsigned j = 0; j < le.size(); ++j) |
|||
ret.insert(ret.begin(), LocalisedLogEntry(le[j], n, th)); |
|||
} |
|||
} |
|||
|
|||
if (!total) |
|||
falsePos++; |
|||
} |
|||
} |
|||
|
|||
cdebug << matchingBlocks.size() << "searched from" << (end - begin) << "skipped; " << falsePos << "false +ves"; |
|||
return ret; |
|||
} |
|||
|
|||
unsigned ClientBase::installWatch(LogFilter const& _f, Reaping _r) |
|||
{ |
|||
h256 h = _f.sha3(); |
|||
{ |
|||
Guard l(x_filtersWatches); |
|||
if (!m_filters.count(h)) |
|||
{ |
|||
cwatch << "FFF" << _f << h.abridged(); |
|||
m_filters.insert(make_pair(h, _f)); |
|||
} |
|||
} |
|||
return installWatch(h, _r); |
|||
} |
|||
|
|||
unsigned ClientBase::installWatch(h256 _h, Reaping _r) |
|||
{ |
|||
unsigned ret; |
|||
{ |
|||
Guard l(x_filtersWatches); |
|||
ret = m_watches.size() ? m_watches.rbegin()->first + 1 : 0; |
|||
m_watches[ret] = ClientWatch(_h, _r); |
|||
cwatch << "+++" << ret << _h.abridged(); |
|||
} |
|||
#if INITIAL_STATE_AS_CHANGES |
|||
auto ch = logs(ret); |
|||
if (ch.empty()) |
|||
ch.push_back(InitialChange); |
|||
{ |
|||
Guard l(x_filtersWatches); |
|||
swap(m_watches[ret].changes, ch); |
|||
} |
|||
#endif |
|||
return ret; |
|||
} |
|||
|
|||
bool ClientBase::uninstallWatch(unsigned _i) |
|||
{ |
|||
cwatch << "XXX" << _i; |
|||
|
|||
Guard l(x_filtersWatches); |
|||
|
|||
auto it = m_watches.find(_i); |
|||
if (it == m_watches.end()) |
|||
return false; |
|||
auto id = it->second.id; |
|||
m_watches.erase(it); |
|||
|
|||
auto fit = m_filters.find(id); |
|||
if (fit != m_filters.end()) |
|||
if (!--fit->second.refCount) |
|||
{ |
|||
cwatch << "*X*" << fit->first << ":" << fit->second.filter; |
|||
m_filters.erase(fit); |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
LocalisedLogEntries ClientBase::peekWatch(unsigned _watchId) const |
|||
{ |
|||
Guard l(x_filtersWatches); |
|||
|
|||
// cwatch << "peekWatch" << _watchId;
|
|||
auto& w = m_watches.at(_watchId); |
|||
// cwatch << "lastPoll updated to " << chrono::duration_cast<chrono::seconds>(chrono::system_clock::now().time_since_epoch()).count();
|
|||
w.lastPoll = chrono::system_clock::now(); |
|||
return w.changes; |
|||
} |
|||
|
|||
LocalisedLogEntries ClientBase::checkWatch(unsigned _watchId) |
|||
{ |
|||
Guard l(x_filtersWatches); |
|||
LocalisedLogEntries ret; |
|||
|
|||
// cwatch << "checkWatch" << _watchId;
|
|||
auto& w = m_watches.at(_watchId); |
|||
// cwatch << "lastPoll updated to " << chrono::duration_cast<chrono::seconds>(chrono::system_clock::now().time_since_epoch()).count();
|
|||
std::swap(ret, w.changes); |
|||
w.lastPoll = chrono::system_clock::now(); |
|||
|
|||
return ret; |
|||
} |
|||
|
|||
h256 ClientBase::hashFromNumber(unsigned _number) const |
|||
{ |
|||
return bc().numberHash(_number); |
|||
} |
|||
|
|||
BlockInfo ClientBase::blockInfo(h256 _hash) const |
|||
{ |
|||
return BlockInfo(bc().block(_hash)); |
|||
} |
|||
|
|||
BlockDetails ClientBase::blockDetails(h256 _hash) const |
|||
{ |
|||
return bc().details(_hash); |
|||
} |
|||
|
|||
Transaction ClientBase::transaction(h256 _transactionHash) const |
|||
{ |
|||
return Transaction(bc().transaction(_transactionHash), CheckSignature::Range); |
|||
} |
|||
|
|||
Transaction ClientBase::transaction(h256 _blockHash, unsigned _i) const |
|||
{ |
|||
auto bl = bc().block(_blockHash); |
|||
RLP b(bl); |
|||
if (_i < b[1].itemCount()) |
|||
return Transaction(b[1][_i].data(), CheckSignature::Range); |
|||
else |
|||
return Transaction(); |
|||
} |
|||
|
|||
Transactions ClientBase::transactions(h256 _blockHash) const |
|||
{ |
|||
auto bl = bc().block(_blockHash); |
|||
RLP b(bl); |
|||
Transactions res; |
|||
for (unsigned i = 0; i < b[1].itemCount(); i++) |
|||
res.emplace_back(b[1][i].data(), CheckSignature::Range); |
|||
return res; |
|||
} |
|||
|
|||
TransactionHashes ClientBase::transactionHashes(h256 _blockHash) const |
|||
{ |
|||
return bc().transactionHashes(_blockHash); |
|||
} |
|||
|
|||
BlockInfo ClientBase::uncle(h256 _blockHash, unsigned _i) const |
|||
{ |
|||
auto bl = bc().block(_blockHash); |
|||
RLP b(bl); |
|||
if (_i < b[2].itemCount()) |
|||
return BlockInfo::fromHeader(b[2][_i].data()); |
|||
else |
|||
return BlockInfo(); |
|||
} |
|||
|
|||
UncleHashes ClientBase::uncleHashes(h256 _blockHash) const |
|||
{ |
|||
return bc().uncleHashes(_blockHash); |
|||
} |
|||
|
|||
unsigned ClientBase::transactionCount(h256 _blockHash) const |
|||
{ |
|||
auto bl = bc().block(_blockHash); |
|||
RLP b(bl); |
|||
return b[1].itemCount(); |
|||
} |
|||
|
|||
unsigned ClientBase::uncleCount(h256 _blockHash) const |
|||
{ |
|||
auto bl = bc().block(_blockHash); |
|||
RLP b(bl); |
|||
return b[2].itemCount(); |
|||
} |
|||
|
|||
unsigned ClientBase::number() const |
|||
{ |
|||
return bc().number(); |
|||
} |
|||
|
|||
Transactions ClientBase::pending() const |
|||
{ |
|||
return postMine().pending(); |
|||
} |
|||
|
|||
|
|||
StateDiff ClientBase::diff(unsigned _txi, h256 _block) const |
|||
{ |
|||
State st = asOf(_block); |
|||
return st.fromPending(_txi).diff(st.fromPending(_txi + 1)); |
|||
} |
|||
|
|||
StateDiff ClientBase::diff(unsigned _txi, BlockNumber _block) const |
|||
{ |
|||
State st = asOf(_block); |
|||
return st.fromPending(_txi).diff(st.fromPending(_txi + 1)); |
|||
} |
|||
|
|||
Addresses ClientBase::addresses(BlockNumber _block) const |
|||
{ |
|||
Addresses ret; |
|||
for (auto const& i: asOf(_block).addresses()) |
|||
ret.push_back(i.first); |
|||
return ret; |
|||
} |
|||
|
|||
u256 ClientBase::gasLimitRemaining() const |
|||
{ |
|||
return postMine().gasLimitRemaining(); |
|||
} |
|||
|
|||
void ClientBase::setAddress(Address _us) |
|||
{ |
|||
preMine().setAddress(_us); |
|||
} |
|||
|
|||
Address ClientBase::address() const |
|||
{ |
|||
return preMine().address(); |
|||
} |
@ -0,0 +1,170 @@ |
|||
/*
|
|||
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 ClientBase.h
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <chrono> |
|||
#include "Interface.h" |
|||
#include "LogFilter.h" |
|||
|
|||
namespace dev { |
|||
|
|||
namespace eth { |
|||
|
|||
struct InstalledFilter |
|||
{ |
|||
InstalledFilter(LogFilter const& _f): filter(_f) {} |
|||
|
|||
LogFilter filter; |
|||
unsigned refCount = 1; |
|||
LocalisedLogEntries changes; |
|||
}; |
|||
|
|||
static const h256 PendingChangedFilter = u256(0); |
|||
static const h256 ChainChangedFilter = u256(1); |
|||
|
|||
static const LogEntry SpecialLogEntry = LogEntry(Address(), h256s(), bytes()); |
|||
static const LocalisedLogEntry InitialChange(SpecialLogEntry, 0); |
|||
|
|||
struct ClientWatch |
|||
{ |
|||
ClientWatch(): lastPoll(std::chrono::system_clock::now()) {} |
|||
explicit ClientWatch(h256 _id, Reaping _r): id(_id), lastPoll(_r == Reaping::Automatic ? std::chrono::system_clock::now() : std::chrono::system_clock::time_point::max()) {} |
|||
|
|||
h256 id; |
|||
#if INITIAL_STATE_AS_CHANGES |
|||
LocalisedLogEntries changes = LocalisedLogEntries{ InitialChange }; |
|||
#else |
|||
LocalisedLogEntries changes; |
|||
#endif |
|||
mutable std::chrono::system_clock::time_point lastPoll = std::chrono::system_clock::now(); |
|||
}; |
|||
|
|||
struct WatchChannel: public LogChannel { static const char* name() { return "(o)"; } static const int verbosity = 7; }; |
|||
#define cwatch dev::LogOutputStream<dev::eth::WatchChannel, true>() |
|||
struct WorkInChannel: public LogChannel { static const char* name() { return ">W>"; } static const int verbosity = 16; }; |
|||
struct WorkOutChannel: public LogChannel { static const char* name() { return "<W<"; } static const int verbosity = 16; }; |
|||
struct WorkChannel: public LogChannel { static const char* name() { return "-W-"; } static const int verbosity = 16; }; |
|||
#define cwork dev::LogOutputStream<dev::eth::WorkChannel, true>() |
|||
#define cworkin dev::LogOutputStream<dev::eth::WorkInChannel, true>() |
|||
#define cworkout dev::LogOutputStream<dev::eth::WorkOutChannel, true>() |
|||
|
|||
class ClientBase: public dev::eth::Interface |
|||
{ |
|||
public: |
|||
ClientBase() {} |
|||
virtual ~ClientBase() {} |
|||
|
|||
/// Submits the given message-call transaction.
|
|||
virtual void submitTransaction(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo) override; |
|||
|
|||
/// Submits a new contract-creation transaction.
|
|||
/// @returns the new contract's address (assuming it all goes through).
|
|||
virtual Address submitTransaction(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas = 10000, u256 _gasPrice = 10 * szabo) override; |
|||
|
|||
/// Makes the given call. Nothing is recorded into the state.
|
|||
virtual ExecutionResult call(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo, BlockNumber _blockNumber = PendingBlock) override; |
|||
|
|||
virtual ExecutionResult create(Secret _secret, u256 _value, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo, BlockNumber _blockNumber = PendingBlock) override; |
|||
|
|||
using Interface::balanceAt; |
|||
using Interface::countAt; |
|||
using Interface::stateAt; |
|||
using Interface::codeAt; |
|||
using Interface::storageAt; |
|||
|
|||
virtual u256 balanceAt(Address _a, BlockNumber _block) const override; |
|||
virtual u256 countAt(Address _a, BlockNumber _block) const override; |
|||
virtual u256 stateAt(Address _a, u256 _l, BlockNumber _block) const override; |
|||
virtual bytes codeAt(Address _a, BlockNumber _block) const override; |
|||
virtual std::map<u256, u256> storageAt(Address _a, BlockNumber _block) const override; |
|||
|
|||
virtual LocalisedLogEntries logs(unsigned _watchId) const override; |
|||
virtual LocalisedLogEntries logs(LogFilter const& _filter) const override; |
|||
|
|||
/// Install, uninstall and query watches.
|
|||
virtual unsigned installWatch(LogFilter const& _filter, Reaping _r = Reaping::Automatic) override; |
|||
virtual unsigned installWatch(h256 _filterId, Reaping _r = Reaping::Automatic) override; |
|||
virtual bool uninstallWatch(unsigned _watchId) override; |
|||
virtual LocalisedLogEntries peekWatch(unsigned _watchId) const override; |
|||
virtual LocalisedLogEntries checkWatch(unsigned _watchId) override; |
|||
|
|||
virtual h256 hashFromNumber(unsigned _number) const override; |
|||
virtual eth::BlockInfo blockInfo(h256 _hash) const override; |
|||
virtual eth::BlockDetails blockDetails(h256 _hash) const override; |
|||
virtual eth::Transaction transaction(h256 _transactionHash) const override; |
|||
virtual eth::Transaction transaction(h256 _blockHash, unsigned _i) const override; |
|||
virtual eth::Transactions transactions(h256 _blockHash) const override; |
|||
virtual eth::TransactionHashes transactionHashes(h256 _blockHash) const override; |
|||
virtual eth::BlockInfo uncle(h256 _blockHash, unsigned _i) const override; |
|||
virtual eth::UncleHashes uncleHashes(h256 _blockHash) const override; |
|||
virtual unsigned transactionCount(h256 _blockHash) const override; |
|||
virtual unsigned uncleCount(h256 _blockHash) const override; |
|||
virtual unsigned number() const override; |
|||
virtual eth::Transactions pending() const override; |
|||
|
|||
using Interface::diff; |
|||
virtual StateDiff diff(unsigned _txi, h256 _block) const override; |
|||
virtual StateDiff diff(unsigned _txi, BlockNumber _block) const override; |
|||
|
|||
using Interface::addresses; |
|||
virtual Addresses addresses(BlockNumber _block) const override; |
|||
virtual u256 gasLimitRemaining() const override; |
|||
|
|||
/// Set the coinbase address
|
|||
virtual void setAddress(Address _us) override; |
|||
|
|||
/// Get the coinbase address
|
|||
virtual Address address() const override; |
|||
|
|||
/// TODO: consider moving it to a separate interface
|
|||
|
|||
virtual void setMiningThreads(unsigned _threads) override { (void)_threads; BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::setMiningThreads")); } |
|||
virtual unsigned miningThreads() const override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::miningThreads")); } |
|||
virtual void startMining() override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::startMining")); } |
|||
virtual void stopMining() override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::stopMining")); } |
|||
virtual bool isMining() override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::isMining")); } |
|||
virtual eth::MineProgress miningProgress() const override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::miningProgress")); } |
|||
virtual std::pair<h256, u256> getWork() override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::getWork")); } |
|||
virtual bool submitWork(eth::ProofOfWork::Proof const&) override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::submitWork")); } |
|||
|
|||
State asOf(BlockNumber _h) const; |
|||
|
|||
protected: |
|||
/// The interface that must be implemented in any class deriving this.
|
|||
/// {
|
|||
virtual BlockChain const& bc() const = 0; |
|||
virtual State asOf(h256 const& _h) const = 0; |
|||
virtual State preMine() const = 0; |
|||
virtual State postMine() const = 0; |
|||
virtual void prepareForTransaction() = 0; |
|||
/// }
|
|||
|
|||
TransactionQueue m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain.
|
|||
|
|||
// filters
|
|||
mutable Mutex x_filtersWatches; ///< Our lock.
|
|||
std::map<h256, InstalledFilter> m_filters; ///< The dictionary of filters that are active.
|
|||
std::map<unsigned, ClientWatch> m_watches; ///< Each and every watch - these reference a filter.
|
|||
}; |
|||
|
|||
}} |
@ -0,0 +1,541 @@ |
|||
/*
|
|||
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 CommonSubexpressionEliminator.cpp |
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Optimizer step for common subexpression elimination and stack reorganisation. |
|||
*/ |
|||
|
|||
#include <functional> |
|||
#include <boost/range/adaptor/reversed.hpp> |
|||
#include <libevmcore/CommonSubexpressionEliminator.h> |
|||
#include <libevmcore/Assembly.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
|||
vector<AssemblyItem> CommonSubexpressionEliminator::getOptimizedItems() |
|||
{ |
|||
map<int, EquivalenceClassId> initialStackContents; |
|||
map<int, EquivalenceClassId> targetStackContents; |
|||
int minHeight = m_stackHeight + 1; |
|||
if (!m_stackElements.empty()) |
|||
minHeight = min(minHeight, m_stackElements.begin()->first.first); |
|||
for (int height = minHeight; height <= max(0, m_stackHeight); ++height) |
|||
{ |
|||
// make sure it is created
|
|||
EquivalenceClassId c = getStackElement(height); |
|||
if (height <= 0) |
|||
initialStackContents[height] = getClass(AssemblyItem(dupInstruction(1 - height))); |
|||
if (height <= m_stackHeight) |
|||
targetStackContents[height] = c; |
|||
} |
|||
|
|||
// Debug info:
|
|||
//stream(cout, currentStackContents, targetStackContents);
|
|||
|
|||
return CSECodeGenerator().generateCode(initialStackContents, targetStackContents, m_equivalenceClasses); |
|||
} |
|||
|
|||
ostream& CommonSubexpressionEliminator::stream( |
|||
ostream& _out, |
|||
map<int, EquivalenceClassId> _currentStack, |
|||
map<int, EquivalenceClassId> _targetStack |
|||
) const |
|||
{ |
|||
auto streamEquivalenceClass = [this](ostream& _out, EquivalenceClassId _id) |
|||
{ |
|||
auto const& eqClass = m_equivalenceClasses.at(_id); |
|||
_out << " " << _id << ": " << *eqClass.first; |
|||
_out << "("; |
|||
for (EquivalenceClassId arg: eqClass.second) |
|||
_out << dec << arg << ","; |
|||
_out << ")" << endl; |
|||
}; |
|||
|
|||
_out << "Optimizer analysis:" << endl; |
|||
_out << "Final stack height: " << dec << m_stackHeight << endl; |
|||
_out << "Stack elements: " << endl; |
|||
for (auto const& it: m_stackElements) |
|||
{ |
|||
_out << " " << dec << it.first.first << "(" << it.first.second << ") = "; |
|||
streamEquivalenceClass(_out, it.second); |
|||
} |
|||
_out << "Equivalence classes: " << endl; |
|||
for (EquivalenceClassId eqClass = 0; eqClass < m_equivalenceClasses.size(); ++eqClass) |
|||
streamEquivalenceClass(_out, eqClass); |
|||
|
|||
_out << "Current stack: " << endl; |
|||
for (auto const& it: _currentStack) |
|||
{ |
|||
_out << " " << dec << it.first << ": "; |
|||
streamEquivalenceClass(_out, it.second); |
|||
} |
|||
_out << "Target stack: " << endl; |
|||
for (auto const& it: _targetStack) |
|||
{ |
|||
_out << " " << dec << it.first << ": "; |
|||
streamEquivalenceClass(_out, it.second); |
|||
} |
|||
|
|||
return _out; |
|||
} |
|||
|
|||
void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item) |
|||
{ |
|||
if (_item.type() != Operation) |
|||
{ |
|||
if (_item.deposit() != 1) |
|||
BOOST_THROW_EXCEPTION(InvalidDeposit()); |
|||
setStackElement(++m_stackHeight, getClass(_item, {})); |
|||
} |
|||
else |
|||
{ |
|||
Instruction instruction = _item.instruction(); |
|||
InstructionInfo info = instructionInfo(instruction); |
|||
if (SemanticInformation::isDupInstruction(_item)) |
|||
setStackElement( |
|||
m_stackHeight + 1, |
|||
getStackElement(m_stackHeight - int(instruction) + int(Instruction::DUP1)) |
|||
); |
|||
else if (SemanticInformation::isSwapInstruction(_item)) |
|||
swapStackElements( |
|||
m_stackHeight, |
|||
m_stackHeight - 1 - int(instruction) + int(Instruction::SWAP1) |
|||
); |
|||
else if (instruction != Instruction::POP) |
|||
{ |
|||
vector<EquivalenceClassId> arguments(info.args); |
|||
for (int i = 0; i < info.args; ++i) |
|||
arguments[i] = getStackElement(m_stackHeight - i); |
|||
setStackElement(m_stackHeight + _item.deposit(), getClass(_item, arguments)); |
|||
} |
|||
m_stackHeight += _item.deposit(); |
|||
} |
|||
} |
|||
|
|||
void CommonSubexpressionEliminator::setStackElement(int _stackHeight, EquivalenceClassId _class) |
|||
{ |
|||
unsigned nextSequence = getNextStackElementSequence(_stackHeight); |
|||
m_stackElements[make_pair(_stackHeight, nextSequence)] = _class; |
|||
} |
|||
|
|||
void CommonSubexpressionEliminator::swapStackElements(int _stackHeightA, int _stackHeightB) |
|||
{ |
|||
if (_stackHeightA == _stackHeightB) |
|||
BOOST_THROW_EXCEPTION(OptimizerException() << errinfo_comment("Swap on same stack elements.")); |
|||
EquivalenceClassId classA = getStackElement(_stackHeightA); |
|||
EquivalenceClassId classB = getStackElement(_stackHeightB); |
|||
|
|||
unsigned nextSequenceA = getNextStackElementSequence(_stackHeightA); |
|||
unsigned nextSequenceB = getNextStackElementSequence(_stackHeightB); |
|||
m_stackElements[make_pair(_stackHeightA, nextSequenceA)] = classB; |
|||
m_stackElements[make_pair(_stackHeightB, nextSequenceB)] = classA; |
|||
} |
|||
|
|||
EquivalenceClassId CommonSubexpressionEliminator::getStackElement(int _stackHeight) |
|||
{ |
|||
// retrieve class by last sequence number
|
|||
unsigned nextSequence = getNextStackElementSequence(_stackHeight); |
|||
if (nextSequence > 0) |
|||
return m_stackElements[make_pair(_stackHeight, nextSequence - 1)]; |
|||
|
|||
// Stack element not found (not assigned yet), create new equivalence class.
|
|||
if (_stackHeight > 0) |
|||
BOOST_THROW_EXCEPTION(OptimizerException() << errinfo_comment("Stack element accessed before assignment.")); |
|||
if (_stackHeight <= -16) |
|||
BOOST_THROW_EXCEPTION(OptimizerException() << errinfo_comment("Stack too deep.")); |
|||
// This is a special assembly item that refers to elements pre-existing on the initial stack.
|
|||
m_spareAssemblyItem.push_back(make_shared<AssemblyItem>(dupInstruction(1 - _stackHeight))); |
|||
m_equivalenceClasses.push_back(make_pair(m_spareAssemblyItem.back().get(), EquivalenceClassIds())); |
|||
return m_stackElements[make_pair(_stackHeight, nextSequence)] = EquivalenceClassId(m_equivalenceClasses.size() - 1); |
|||
} |
|||
|
|||
EquivalenceClassId CommonSubexpressionEliminator::getClass( |
|||
const AssemblyItem& _item, |
|||
EquivalenceClassIds const& _arguments |
|||
) |
|||
{ |
|||
// TODO: do a clever search, i.e.
|
|||
// - check for the presence of constants in the argument classes and do arithmetic
|
|||
// - check whether the two items are equal for a SUB instruction
|
|||
// - check whether 0 or 1 is in one of the classes for a MUL
|
|||
|
|||
EquivalenceClassIds args = _arguments; |
|||
if (SemanticInformation::isCommutativeOperation(_item)) |
|||
sort(args.begin(), args.end()); |
|||
|
|||
//@todo use a better data structure for search here
|
|||
for (EquivalenceClassId c = 0; c < m_equivalenceClasses.size(); ++c) |
|||
{ |
|||
AssemblyItem const& classItem = *m_equivalenceClasses.at(c).first; |
|||
if (classItem != _item) |
|||
continue; |
|||
|
|||
assertThrow( |
|||
args.size() == m_equivalenceClasses.at(c).second.size(), |
|||
OptimizerException, |
|||
"Equal assembly items with different number of arguments." |
|||
); |
|||
if (equal(args.begin(), args.end(), m_equivalenceClasses.at(c).second.begin())) |
|||
return c; |
|||
} |
|||
// constant folding
|
|||
if (_item.type() == Operation && args.size() == 2 && all_of( |
|||
args.begin(), |
|||
args.end(), |
|||
[this](EquivalenceClassId eqc) { return m_equivalenceClasses.at(eqc).first->match(Push); })) |
|||
{ |
|||
auto signextend = [](u256 const& _a, u256 const& _b) -> u256 |
|||
{ |
|||
if (_a >= 31) |
|||
return _b; |
|||
unsigned testBit = unsigned(_a) * 8 + 7; |
|||
u256 mask = (u256(1) << testBit) - 1; |
|||
return boost::multiprecision::bit_test(_b, testBit) ? _b | ~mask : _b & mask; |
|||
}; |
|||
map<Instruction, function<u256(u256 const&, u256 const&)>> const arithmetics = |
|||
{ |
|||
{ Instruction::SUB, [](u256 const& _a, u256 const& _b) -> u256 {return _a - _b; } }, |
|||
{ Instruction::DIV, [](u256 const& _a, u256 const& _b) -> u256 {return _b == 0 ? 0 : _a / _b; } }, |
|||
{ Instruction::SDIV, [](u256 const& _a, u256 const& _b) -> u256 { return _b == 0 ? 0 : s2u(u2s(_a) / u2s(_b)); } }, |
|||
{ Instruction::MOD, [](u256 const& _a, u256 const& _b) -> u256 { return _b == 0 ? 0 : _a % _b; } }, |
|||
{ Instruction::SMOD, [](u256 const& _a, u256 const& _b) -> u256 { return _b == 0 ? 0 : s2u(u2s(_a) % u2s(_b)); } }, |
|||
{ Instruction::EXP, [](u256 const& _a, u256 const& _b) -> u256 { return (u256)boost::multiprecision::powm(bigint(_a), bigint(_b), bigint(1) << 256); } }, |
|||
{ Instruction::SIGNEXTEND, signextend }, |
|||
{ Instruction::LT, [](u256 const& _a, u256 const& _b) -> u256 { return _a < _b ? 1 : 0; } }, |
|||
{ Instruction::GT, [](u256 const& _a, u256 const& _b) -> u256 { return _a > _b ? 1 : 0; } }, |
|||
{ Instruction::SLT, [](u256 const& _a, u256 const& _b) -> u256 { return u2s(_a) < u2s(_b) ? 1 : 0; } }, |
|||
{ Instruction::SGT, [](u256 const& _a, u256 const& _b) -> u256 { return u2s(_a) > u2s(_b) ? 1 : 0; } }, |
|||
{ Instruction::EQ, [](u256 const& _a, u256 const& _b) -> u256 { return _a == _b ? 1 : 0; } }, |
|||
{ Instruction::ADD, [](u256 const& _a, u256 const& _b) -> u256 { return _a + _b; } }, |
|||
{ Instruction::MUL, [](u256 const& _a, u256 const& _b) -> u256 { return _a * _b; } }, |
|||
{ Instruction::AND, [](u256 const& _a, u256 const& _b) -> u256 { return _a & _b; } }, |
|||
{ Instruction::OR, [](u256 const& _a, u256 const& _b) -> u256 { return _a | _b; } }, |
|||
{ Instruction::XOR, [](u256 const& _a, u256 const& _b) -> u256 { return _a ^ _b; } }, |
|||
}; |
|||
if (arithmetics.count(_item.instruction())) |
|||
{ |
|||
u256 result = arithmetics.at(_item.instruction())( |
|||
m_equivalenceClasses.at(args[0]).first->data(), |
|||
m_equivalenceClasses.at(args[1]).first->data() |
|||
); |
|||
m_spareAssemblyItem.push_back(make_shared<AssemblyItem>(result)); |
|||
return getClass(*m_spareAssemblyItem.back()); |
|||
} |
|||
} |
|||
m_equivalenceClasses.push_back(make_pair(&_item, args)); |
|||
return m_equivalenceClasses.size() - 1; |
|||
} |
|||
|
|||
unsigned CommonSubexpressionEliminator::getNextStackElementSequence(int _stackHeight) |
|||
{ |
|||
auto it = m_stackElements.upper_bound(make_pair(_stackHeight, unsigned(-1))); |
|||
if (it == m_stackElements.begin()) |
|||
return 0; |
|||
--it; |
|||
if (it->first.first == _stackHeight) |
|||
return it->first.second + 1; |
|||
else |
|||
return 0; |
|||
} |
|||
|
|||
bool SemanticInformation::breaksBasicBlock(AssemblyItem const& _item) |
|||
{ |
|||
switch (_item.type()) |
|||
{ |
|||
default: |
|||
case UndefinedItem: |
|||
case Tag: |
|||
return true; |
|||
case Push: |
|||
case PushString: |
|||
case PushTag: |
|||
case PushSub: |
|||
case PushSubSize: |
|||
case PushProgramSize: |
|||
case PushData: |
|||
return false; |
|||
case Operation: |
|||
{ |
|||
if (isSwapInstruction(_item) || isDupInstruction(_item)) |
|||
return false; |
|||
if (_item.instruction() == Instruction::GAS || _item.instruction() == Instruction::PC) |
|||
return true; // GAS and PC assume a specific order of opcodes
|
|||
InstructionInfo info = instructionInfo(_item.instruction()); |
|||
// the second requirement will be lifted once it is implemented
|
|||
return info.sideEffects || info.args > 2; |
|||
} |
|||
} |
|||
} |
|||
|
|||
bool SemanticInformation::isCommutativeOperation(AssemblyItem const& _item) |
|||
{ |
|||
if (_item.type() != Operation) |
|||
return false; |
|||
switch (_item.instruction()) |
|||
{ |
|||
case Instruction::ADD: |
|||
case Instruction::MUL: |
|||
case Instruction::EQ: |
|||
case Instruction::AND: |
|||
case Instruction::OR: |
|||
case Instruction::XOR: |
|||
return true; |
|||
default: |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
bool SemanticInformation::isDupInstruction(AssemblyItem const& _item) |
|||
{ |
|||
if (_item.type() != Operation) |
|||
return false; |
|||
return Instruction::DUP1 <= _item.instruction() && _item.instruction() <= Instruction::DUP16; |
|||
} |
|||
|
|||
bool SemanticInformation::isSwapInstruction(AssemblyItem const& _item) |
|||
{ |
|||
if (_item.type() != Operation) |
|||
return false; |
|||
return Instruction::SWAP1 <= _item.instruction() && _item.instruction() <= Instruction::SWAP16; |
|||
} |
|||
|
|||
AssemblyItems CSECodeGenerator::generateCode( |
|||
map<int, EquivalenceClassId> const& _initialStack, |
|||
map<int, EquivalenceClassId> const& _targetStackContents, |
|||
vector<pair<AssemblyItem const*, EquivalenceClassIds>> const& _equivalenceClasses |
|||
) |
|||
{ |
|||
// reset
|
|||
*this = move(CSECodeGenerator()); |
|||
m_stack = _initialStack; |
|||
m_equivalenceClasses = _equivalenceClasses; |
|||
for (auto const& item: m_stack) |
|||
if (!m_classPositions.count(item.second)) |
|||
m_classPositions[item.second] = item.first; |
|||
|
|||
// @todo: provide information about the positions of copies of class elements
|
|||
|
|||
// generate the dependency graph
|
|||
for (auto const& targetItem: _targetStackContents) |
|||
{ |
|||
m_finalClasses.insert(targetItem.second); |
|||
addDependencies(targetItem.second); |
|||
} |
|||
|
|||
// generate the actual elements
|
|||
for (auto const& targetItem: _targetStackContents) |
|||
{ |
|||
removeStackTopIfPossible(); |
|||
int position = generateClassElement(targetItem.second); |
|||
if (position == targetItem.first) |
|||
continue; |
|||
if (position < targetItem.first) |
|||
// it is already at its target, we need another copy
|
|||
appendDup(position); |
|||
else |
|||
appendSwapOrRemove(position); |
|||
appendSwapOrRemove(targetItem.first); |
|||
} |
|||
|
|||
// remove surplus elements
|
|||
while (removeStackTopIfPossible()) |
|||
{ |
|||
// no-op
|
|||
} |
|||
|
|||
// check validity
|
|||
int finalHeight = 0; |
|||
if (!_targetStackContents.empty()) |
|||
// have target stack, so its height should be the final height
|
|||
finalHeight = (--_targetStackContents.end())->first; |
|||
else if (!_initialStack.empty()) |
|||
// no target stack, only erase the initial stack
|
|||
finalHeight = _initialStack.begin()->first - 1; |
|||
else |
|||
// neither initial no target stack, no change in height
|
|||
finalHeight = 0; |
|||
assertThrow(finalHeight == m_stackHeight, OptimizerException, "Incorrect final stack height."); |
|||
|
|||
return m_generatedItems; |
|||
} |
|||
|
|||
void CSECodeGenerator::addDependencies(EquivalenceClassId _c) |
|||
{ |
|||
if (m_neededBy.count(_c)) |
|||
return; |
|||
for (EquivalenceClassId argument: m_equivalenceClasses.at(_c).second) |
|||
{ |
|||
addDependencies(argument); |
|||
m_neededBy.insert(make_pair(argument, _c)); |
|||
} |
|||
} |
|||
|
|||
int CSECodeGenerator::generateClassElement(EquivalenceClassId _c) |
|||
{ |
|||
if (m_classPositions.count(_c)) |
|||
{ |
|||
assertThrow( |
|||
m_classPositions[_c] != c_invalidPosition, |
|||
OptimizerException, |
|||
"Element already removed but still needed." |
|||
); |
|||
return m_classPositions[_c]; |
|||
} |
|||
EquivalenceClassIds const& arguments = m_equivalenceClasses.at(_c).second; |
|||
for (EquivalenceClassId arg: boost::adaptors::reverse(arguments)) |
|||
generateClassElement(arg); |
|||
|
|||
// The arguments are somewhere on the stack now, so it remains to move them at the correct place.
|
|||
// This is quite difficult as sometimes, the values also have to removed in this process
|
|||
// (if canBeRemoved() returns true) and the two arguments can be equal. For now, this is
|
|||
// implemented for every single case for combinations of up to two arguments manually.
|
|||
if (arguments.size() == 1) |
|||
{ |
|||
if (canBeRemoved(arguments[0], _c)) |
|||
appendSwapOrRemove(generateClassElement(arguments[0])); |
|||
else |
|||
appendDup(generateClassElement(arguments[0])); |
|||
} |
|||
else if (arguments.size() == 2) |
|||
{ |
|||
if (canBeRemoved(arguments[1], _c)) |
|||
{ |
|||
appendSwapOrRemove(generateClassElement(arguments[1])); |
|||
if (arguments[0] == arguments[1]) |
|||
appendDup(m_stackHeight); |
|||
else if (canBeRemoved(arguments[0], _c)) |
|||
{ |
|||
appendSwapOrRemove(m_stackHeight - 1); |
|||
appendSwapOrRemove(generateClassElement(arguments[0])); |
|||
} |
|||
else |
|||
appendDup(generateClassElement(arguments[0])); |
|||
} |
|||
else |
|||
{ |
|||
if (arguments[0] == arguments[1]) |
|||
{ |
|||
appendDup(generateClassElement(arguments[0])); |
|||
appendDup(m_stackHeight); |
|||
} |
|||
else if (canBeRemoved(arguments[0], _c)) |
|||
{ |
|||
appendSwapOrRemove(generateClassElement(arguments[0])); |
|||
appendDup(generateClassElement(arguments[1])); |
|||
appendSwapOrRemove(m_stackHeight - 1); |
|||
} |
|||
else |
|||
{ |
|||
appendDup(generateClassElement(arguments[1])); |
|||
appendDup(generateClassElement(arguments[0])); |
|||
} |
|||
} |
|||
} |
|||
else |
|||
assertThrow( |
|||
arguments.size() <= 2, |
|||
OptimizerException, |
|||
"Opcodes with more than two arguments not implemented yet." |
|||
); |
|||
for (size_t i = 0; i < arguments.size(); ++i) |
|||
assertThrow(m_stack[m_stackHeight - i] == arguments[i], OptimizerException, "Expected arguments not present." ); |
|||
|
|||
AssemblyItem const& item = *m_equivalenceClasses.at(_c).first; |
|||
while (SemanticInformation::isCommutativeOperation(item) && |
|||
!m_generatedItems.empty() && |
|||
m_generatedItems.back() == AssemblyItem(Instruction::SWAP1)) |
|||
// this will not append a swap but remove the one that is already there
|
|||
appendSwapOrRemove(m_stackHeight - 1); |
|||
for (auto arg: arguments) |
|||
if (canBeRemoved(arg, _c)) |
|||
m_classPositions[arg] = c_invalidPosition; |
|||
for (size_t i = 0; i < arguments.size(); ++i) |
|||
m_stack.erase(m_stackHeight - i); |
|||
appendItem(*m_equivalenceClasses.at(_c).first); |
|||
m_stack[m_stackHeight] = _c; |
|||
return m_classPositions[_c] = m_stackHeight; |
|||
} |
|||
|
|||
bool CSECodeGenerator::canBeRemoved(EquivalenceClassId _element, EquivalenceClassId _result) |
|||
{ |
|||
// Returns false if _element is finally needed or is needed by a class that has not been
|
|||
// computed yet. Note that m_classPositions also includes classes that were deleted in the meantime.
|
|||
if (m_finalClasses.count(_element)) |
|||
return false; |
|||
|
|||
auto range = m_neededBy.equal_range(_element); |
|||
for (auto it = range.first; it != range.second; ++it) |
|||
if (it->second != _result && !m_classPositions.count(it->second)) |
|||
return false; |
|||
return true; |
|||
} |
|||
|
|||
bool CSECodeGenerator::removeStackTopIfPossible() |
|||
{ |
|||
if (m_stack.empty()) |
|||
return false; |
|||
assertThrow(m_stack.count(m_stackHeight), OptimizerException, ""); |
|||
EquivalenceClassId top = m_stack[m_stackHeight]; |
|||
if (!canBeRemoved(top)) |
|||
return false; |
|||
m_generatedItems.push_back(AssemblyItem(Instruction::POP)); |
|||
m_stack.erase(m_stackHeight); |
|||
m_stackHeight--; |
|||
return true; |
|||
} |
|||
|
|||
void CSECodeGenerator::appendDup(int _fromPosition) |
|||
{ |
|||
int nr = 1 + m_stackHeight - _fromPosition; |
|||
assertThrow(1 <= nr && nr <= 16, OptimizerException, "Stack too deep."); |
|||
m_generatedItems.push_back(AssemblyItem(dupInstruction(nr))); |
|||
m_stackHeight++; |
|||
m_stack[m_stackHeight] = m_stack[_fromPosition]; |
|||
} |
|||
|
|||
void CSECodeGenerator::appendSwapOrRemove(int _fromPosition) |
|||
{ |
|||
if (_fromPosition == m_stackHeight) |
|||
return; |
|||
int nr = m_stackHeight - _fromPosition; |
|||
assertThrow(1 <= nr && nr <= 16, OptimizerException, "Stack too deep."); |
|||
m_generatedItems.push_back(AssemblyItem(swapInstruction(nr))); |
|||
// The value of a class can be present in multiple locations on the stack. We only update the
|
|||
// "canonical" one that is tracked by m_classPositions
|
|||
if (m_classPositions[m_stack[m_stackHeight]] == m_stackHeight) |
|||
m_classPositions[m_stack[m_stackHeight]] = _fromPosition; |
|||
if (m_classPositions[m_stack[_fromPosition]] == _fromPosition) |
|||
m_classPositions[m_stack[_fromPosition]] = m_stackHeight; |
|||
swap(m_stack[m_stackHeight], m_stack[_fromPosition]); |
|||
if (m_generatedItems.size() >= 2 && |
|||
SemanticInformation::isSwapInstruction(m_generatedItems.back()) && |
|||
*(m_generatedItems.end() - 2) == m_generatedItems.back()) |
|||
{ |
|||
m_generatedItems.pop_back(); |
|||
m_generatedItems.pop_back(); |
|||
} |
|||
} |
|||
|
|||
void CSECodeGenerator::appendItem(AssemblyItem const& _item) |
|||
{ |
|||
m_generatedItems.push_back(_item); |
|||
m_stackHeight += _item.deposit(); |
|||
} |
@ -0,0 +1,187 @@ |
|||
/*
|
|||
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 CommonSubexpressionEliminator.h |
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Optimizer step for common subexpression elimination and stack reorganisation. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
#include <map> |
|||
#include <ostream> |
|||
#include <libdevcore/CommonIO.h> |
|||
#include <libdevcore/Exceptions.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth |
|||
{ |
|||
|
|||
class AssemblyItem; |
|||
using AssemblyItems = std::vector<AssemblyItem>; |
|||
|
|||
using EquivalenceClassId = unsigned; |
|||
using EquivalenceClassIds = std::vector<EquivalenceClassId>; |
|||
|
|||
/**
|
|||
* Optimizer step that performs common subexpression elimination and stack reorganisation, |
|||
* i.e. it tries to infer equality among expressions and compute the values of two expressions |
|||
* known to be equal only once. |
|||
* |
|||
* The general workings are that for each assembly item that is fed into the eliminator, an |
|||
* equivalence class is derived from the operation and the equivalence class of its arguments and |
|||
* it is assigned to the next sequence number of a stack item. DUPi, SWAPi and some arithmetic |
|||
* instructions are used to infer equivalences while these classes are determined. |
|||
* |
|||
* When the list of optimized items is requested, they are generated in a bottom-up fashion, |
|||
* adding code for equivalence classes that were not yet computed. |
|||
*/ |
|||
class CommonSubexpressionEliminator |
|||
{ |
|||
public: |
|||
/// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first
|
|||
/// item that must be fed into a new instance of the eliminator.
|
|||
template <class _AssemblyItemIterator> |
|||
_AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end); |
|||
|
|||
/// @returns the resulting items after optimization.
|
|||
AssemblyItems getOptimizedItems(); |
|||
|
|||
/// Streams debugging information to @a _out.
|
|||
std::ostream& stream( |
|||
std::ostream& _out, |
|||
std::map<int, EquivalenceClassId> _currentStack = std::map<int, EquivalenceClassId>(), |
|||
std::map<int, EquivalenceClassId> _targetStack = std::map<int, EquivalenceClassId>() |
|||
) const; |
|||
|
|||
private: |
|||
/// Feeds the item into the system for analysis.
|
|||
void feedItem(AssemblyItem const& _item); |
|||
|
|||
/// Assigns a new equivalence class to the next sequence number of the given stack element.
|
|||
void setStackElement(int _stackHeight, EquivalenceClassId _class); |
|||
/// Swaps the given stack elements in their next sequence number.
|
|||
void swapStackElements(int _stackHeightA, int _stackHeightB); |
|||
/// Retrieves the current equivalence class fo the given stack element (or generates a new
|
|||
/// one if it does not exist yet).
|
|||
EquivalenceClassId getStackElement(int _stackHeight); |
|||
/// Retrieves the equivalence class resulting from the given item applied to the given classes,
|
|||
/// might also create a new one.
|
|||
EquivalenceClassId getClass(AssemblyItem const& _item, EquivalenceClassIds const& _arguments = {}); |
|||
|
|||
/// @returns the next sequence number of the given stack element.
|
|||
unsigned getNextStackElementSequence(int _stackHeight); |
|||
|
|||
/// Current stack height, can be negative.
|
|||
int m_stackHeight = 0; |
|||
/// Mapping (stack height, sequence number) -> equivalence class
|
|||
std::map<std::pair<int, unsigned>, EquivalenceClassId> m_stackElements; |
|||
/// Vector of equivalence class representatives - we only store one item of an equivalence
|
|||
/// class and the index is used as identifier.
|
|||
std::vector<std::pair<AssemblyItem const*, EquivalenceClassIds>> m_equivalenceClasses; |
|||
/// List of items generated during analysis.
|
|||
std::vector<std::shared_ptr<AssemblyItem>> m_spareAssemblyItem; |
|||
}; |
|||
|
|||
/**
|
|||
* Helper functions to provide context-independent information about assembly items. |
|||
*/ |
|||
struct SemanticInformation |
|||
{ |
|||
/// @returns true if the given items starts a new basic block
|
|||
static bool breaksBasicBlock(AssemblyItem const& _item); |
|||
/// @returns true if the item is a two-argument operation whose value does not depend on the
|
|||
/// order of its arguments.
|
|||
static bool isCommutativeOperation(AssemblyItem const& _item); |
|||
static bool isDupInstruction(AssemblyItem const& _item); |
|||
static bool isSwapInstruction(AssemblyItem const& _item); |
|||
}; |
|||
|
|||
/**
|
|||
* Unit that generates code from current stack layout, target stack layout and information about |
|||
* the equivalence classes. |
|||
*/ |
|||
class CSECodeGenerator |
|||
{ |
|||
public: |
|||
/// @returns the assembly items generated from the given requirements
|
|||
/// @param _initialStack current contents of the stack (up to stack height of zero)
|
|||
/// @param _targetStackContents final contents of the stack, by stack height relative to initial
|
|||
/// @param _equivalenceClasses equivalence classes as expressions of how to compute them
|
|||
/// @note resuts the state of the object for each call.
|
|||
AssemblyItems generateCode( |
|||
std::map<int, EquivalenceClassId> const& _initialStack, |
|||
std::map<int, EquivalenceClassId> const& _targetStackContents, |
|||
std::vector<std::pair<AssemblyItem const*, EquivalenceClassIds>> const& _equivalenceClasses |
|||
); |
|||
|
|||
private: |
|||
/// Recursively discovers all dependencies to @a m_requests.
|
|||
void addDependencies(EquivalenceClassId _c); |
|||
|
|||
/// Produce code that generates the given element if it is not yet present.
|
|||
/// @returns the stack position of the element.
|
|||
int generateClassElement(EquivalenceClassId _c); |
|||
|
|||
/// @returns true if @a _element can be removed - in general or, if given, while computing @a _result.
|
|||
bool canBeRemoved(EquivalenceClassId _element, EquivalenceClassId _result = EquivalenceClassId(-1)); |
|||
|
|||
/// Appends code to remove the topmost stack element if it can be removed.
|
|||
bool removeStackTopIfPossible(); |
|||
|
|||
/// Appends a dup instruction to m_generatedItems to retrieve the element at the given stack position.
|
|||
void appendDup(int _fromPosition); |
|||
/// Appends a swap instruction to m_generatedItems to retrieve the element at the given stack position.
|
|||
/// @note this might also remove the last item if it exactly the same swap instruction.
|
|||
void appendSwapOrRemove(int _fromPosition); |
|||
/// Appends the given assembly item.
|
|||
void appendItem(AssemblyItem const& _item); |
|||
|
|||
static const int c_invalidPosition = -0x7fffffff; |
|||
|
|||
AssemblyItems m_generatedItems; |
|||
/// Current height of the stack relative to the start.
|
|||
int m_stackHeight = 0; |
|||
/// If (b, a) is in m_requests then b is needed to compute a.
|
|||
std::multimap<EquivalenceClassId, EquivalenceClassId> m_neededBy; |
|||
/// Current content of the stack.
|
|||
std::map<int, EquivalenceClassId> m_stack; |
|||
/// Current positions of equivalence classes, equal to c_invalidPosition if already deleted.
|
|||
std::map<EquivalenceClassId, int> m_classPositions; |
|||
|
|||
/// The actual eqivalence class items and how to compute them.
|
|||
std::vector<std::pair<AssemblyItem const*, EquivalenceClassIds>> m_equivalenceClasses; |
|||
/// The set of equivalence classes that should be present on the stack at the end.
|
|||
std::set<EquivalenceClassId> m_finalClasses; |
|||
}; |
|||
|
|||
template <class _AssemblyItemIterator> |
|||
_AssemblyItemIterator CommonSubexpressionEliminator::feedItems( |
|||
_AssemblyItemIterator _iterator, |
|||
_AssemblyItemIterator _end |
|||
) |
|||
{ |
|||
for (; _iterator != _end && !SemanticInformation::breaksBasicBlock(*_iterator); ++_iterator) |
|||
feedItem(*_iterator); |
|||
return _iterator; |
|||
} |
|||
|
|||
} |
|||
} |
@ -0,0 +1,100 @@ |
|||
/*
|
|||
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 BlockChainLoader.cpp
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "BlockChainLoader.h" |
|||
#include "StateLoader.h" |
|||
#include "Common.h" |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::test; |
|||
using namespace dev::eth; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace test |
|||
{ |
|||
dev::eth::BlockInfo toBlockInfo(Json::Value const& _json); |
|||
bytes toGenesisBlock(Json::Value const& _json); |
|||
} |
|||
} |
|||
|
|||
dev::eth::BlockInfo dev::test::toBlockInfo(Json::Value const& _json) |
|||
{ |
|||
RLPStream rlpStream; |
|||
auto size = _json.getMemberNames().size(); |
|||
rlpStream.appendList(_json["hash"].empty() ? size : (size - 1)); |
|||
rlpStream << fromHex(_json["parentHash"].asString()); |
|||
rlpStream << fromHex(_json["uncleHash"].asString()); |
|||
rlpStream << fromHex(_json["coinbase"].asString()); |
|||
rlpStream << fromHex(_json["stateRoot"].asString()); |
|||
rlpStream << fromHex(_json["transactionsTrie"].asString()); |
|||
rlpStream << fromHex(_json["receiptTrie"].asString()); |
|||
rlpStream << fromHex(_json["bloom"].asString()); |
|||
rlpStream << bigint(_json["difficulty"].asString()); |
|||
rlpStream << bigint(_json["number"].asString()); |
|||
rlpStream << bigint(_json["gasLimit"].asString()); |
|||
rlpStream << bigint(_json["gasUsed"].asString()); |
|||
rlpStream << bigint(_json["timestamp"].asString()); |
|||
rlpStream << fromHex(_json["extraData"].asString()); |
|||
rlpStream << fromHex(_json["mixHash"].asString()); |
|||
rlpStream << fromHex(_json["nonce"].asString()); |
|||
|
|||
BlockInfo result; |
|||
RLP rlp(rlpStream.out()); |
|||
result.populateFromHeader(rlp, IgnoreNonce); |
|||
return result; |
|||
} |
|||
|
|||
bytes dev::test::toGenesisBlock(Json::Value const& _json) |
|||
{ |
|||
BlockInfo bi = toBlockInfo(_json); |
|||
RLPStream rlpStream; |
|||
bi.streamRLP(rlpStream, WithNonce); |
|||
|
|||
RLPStream fullStream(3); |
|||
fullStream.appendRaw(rlpStream.out()); |
|||
fullStream.appendRaw(RLPEmptyList); |
|||
fullStream.appendRaw(RLPEmptyList); |
|||
bi.verifyInternals(&fullStream.out()); |
|||
|
|||
return fullStream.out(); |
|||
} |
|||
|
|||
BlockChainLoader::BlockChainLoader(Json::Value const& _json) |
|||
{ |
|||
// load pre state
|
|||
StateLoader sl(_json["pre"]); |
|||
m_state = sl.state(); |
|||
|
|||
// load genesisBlock
|
|||
m_bc.reset(new BlockChain(toGenesisBlock(_json["genesisBlockHeader"]), m_dir.path(), true)); |
|||
|
|||
// load blocks
|
|||
for (auto const& block: _json["blocks"]) |
|||
{ |
|||
bytes rlp = fromHex(block["rlp"].asString()); |
|||
m_bc->import(rlp, m_state.db()); |
|||
} |
|||
|
|||
// sync state
|
|||
m_state.sync(*m_bc); |
|||
} |
@ -0,0 +1,52 @@ |
|||
/*
|
|||
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 BlockChainLoader.h
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
#include <string> |
|||
#include <json/json.h> |
|||
#include <libethereum/BlockChain.h> |
|||
#include <libethereum/State.h> |
|||
#include "TransientDirectory.h" |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
/**
|
|||
* @brief Should be used to load test blockchain from json file |
|||
* Loads the blockchain from json, creates temporary directory to store it, removes the directory on dealloc |
|||
*/ |
|||
class BlockChainLoader |
|||
{ |
|||
public: |
|||
BlockChainLoader(Json::Value const& _json); |
|||
eth::BlockChain const& bc() const { return *m_bc; } |
|||
eth::State const& state() const { return m_state; } |
|||
|
|||
private: |
|||
TransientDirectory m_dir; |
|||
std::auto_ptr<eth::BlockChain> m_bc; |
|||
eth::State m_state; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,34 @@ |
|||
cmake_policy(SET CMP0015 NEW) |
|||
# this policy was introduced in cmake 3.0 |
|||
# remove if, once 3.0 will be used on unix |
|||
if (${CMAKE_MAJOR_VERSION} GREATER 2) |
|||
# old policy do not use MACOSX_RPATH |
|||
cmake_policy(SET CMP0042 OLD) |
|||
endif() |
|||
set(CMAKE_AUTOMOC OFF) |
|||
|
|||
aux_source_directory(. SRC_LIST) |
|||
|
|||
include_directories(BEFORE ${JSONCPP_INCLUDE_DIRS}) |
|||
include_directories(BEFORE ..) |
|||
include_directories(${JSON_RPC_CPP_INCLUDE_DIRS}) |
|||
include_directories(${Boost_INCLUDE_DIRS}) |
|||
|
|||
set(EXECUTABLE testutils) |
|||
|
|||
file(GLOB HEADERS "*.h") |
|||
|
|||
if (ETH_STATIC) |
|||
add_library(${EXECUTABLE} STATIC ${SRC_LIST} ${HEADERS}) |
|||
else() |
|||
add_library(${EXECUTABLE} SHARED ${SRC_LIST} ${HEADERS}) |
|||
endif() |
|||
|
|||
target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARIES}) |
|||
target_link_libraries(${EXECUTABLE} ${JSONCPP_LIBRARIES}) |
|||
target_link_libraries(${EXECUTABLE} ethereum) |
|||
target_link_libraries(${EXECUTABLE} web3jsonrpc) |
|||
|
|||
install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) |
|||
install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) |
|||
|
@ -0,0 +1,80 @@ |
|||
/*
|
|||
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 Common.cpp
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include <random> |
|||
#include <libdevcore/CommonData.h> |
|||
#include <libdevcore/CommonIO.h> |
|||
#include <libdevcrypto/FileSystem.h> |
|||
#include "Common.h" |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::test; |
|||
|
|||
std::string dev::test::getTestPath() |
|||
{ |
|||
string testPath; |
|||
const char* ptestPath = getenv("ETHEREUM_TEST_PATH"); |
|||
|
|||
if (ptestPath == NULL) |
|||
{ |
|||
ctest << " could not find environment variable ETHEREUM_TEST_PATH \n"; |
|||
testPath = "../../../tests"; |
|||
} |
|||
else |
|||
testPath = ptestPath; |
|||
|
|||
return testPath; |
|||
} |
|||
|
|||
int dev::test::randomNumber() |
|||
{ |
|||
static std::mt19937 randomGenerator(time(0)); |
|||
randomGenerator.seed(std::random_device()()); |
|||
return std::uniform_int_distribution<int>(1)(randomGenerator); |
|||
} |
|||
|
|||
Json::Value dev::test::loadJsonFromFile(std::string const& _path) |
|||
{ |
|||
Json::Reader reader; |
|||
Json::Value result; |
|||
string s = asString(dev::contents(_path)); |
|||
if (!s.length()) |
|||
ctest << "Contents of " + _path + " is empty. Have you cloned the 'tests' repo branch develop and set ETHEREUM_TEST_PATH to its path?"; |
|||
else |
|||
ctest << "FIXTURE: loaded test from file: " << _path; |
|||
|
|||
reader.parse(s, result); |
|||
return result; |
|||
} |
|||
|
|||
std::string dev::test::toTestFilePath(std::string const& _filename) |
|||
{ |
|||
return getTestPath() + "/" + _filename + ".json"; |
|||
} |
|||
|
|||
std::string dev::test::getRandomPath() |
|||
{ |
|||
std::stringstream stream; |
|||
stream << getDataDir() << "/EthereumTests/" << randomNumber(); |
|||
return stream.str(); |
|||
} |
|||
|
@ -0,0 +1,44 @@ |
|||
/*
|
|||
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 Common.h
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <string> |
|||
#include <json/json.h> |
|||
#include <libdevcore/Log.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
struct TestChannel: public LogChannel { static const char* name() { return "TEST"; } }; |
|||
#define ctest dev::LogOutputStream<dev::test::TestChannel, true>() |
|||
|
|||
std::string getTestPath(); |
|||
int randomNumber(); |
|||
Json::Value loadJsonFromFile(std::string const& _path); |
|||
std::string toTestFilePath(std::string const& _filename); |
|||
std::string getRandomPath(); |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,32 @@ |
|||
/*
|
|||
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 FixedClient.cpp
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "FixedClient.h" |
|||
|
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
using namespace dev::test; |
|||
|
|||
eth::State FixedClient::asOf(h256 const& _h) const |
|||
{ |
|||
ReadGuard l(x_stateDB); |
|||
return State(m_state.db(), bc(), _h); |
|||
} |
@ -0,0 +1,59 @@ |
|||
/*
|
|||
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 FixedClient.h
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <libethereum/ClientBase.h> |
|||
#include <libethereum/BlockChain.h> |
|||
#include <libethereum/State.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
/**
|
|||
* @brief mvp implementation of ClientBase |
|||
* Doesn't support mining interface |
|||
*/ |
|||
class FixedClient: public dev::eth::ClientBase |
|||
{ |
|||
public: |
|||
FixedClient(eth::BlockChain const& _bc, eth::State _state) : m_bc(_bc), m_state(_state) {} |
|||
virtual ~FixedClient() {} |
|||
|
|||
// stub
|
|||
virtual void flushTransactions() override {} |
|||
virtual eth::BlockChain const& bc() const override { return m_bc; } |
|||
using ClientBase::asOf; |
|||
virtual eth::State asOf(h256 const& _h) const override; |
|||
virtual eth::State preMine() const override { ReadGuard l(x_stateDB); return m_state; } |
|||
virtual eth::State postMine() const override { ReadGuard l(x_stateDB); return m_state; } |
|||
virtual void prepareForTransaction() override {} |
|||
|
|||
private: |
|||
eth::BlockChain const& m_bc; |
|||
eth::State m_state; |
|||
mutable SharedMutex x_stateDB; ///< Lock on the state DB, effectively a lock on m_postMine.
|
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,22 @@ |
|||
/*
|
|||
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 FixedWebThreeStubServer.cpp
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "FixedWebThreeServer.h" |
@ -0,0 +1,57 @@ |
|||
/*
|
|||
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 FixedWebThreeStubServer.h
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <libdevcore/Exceptions.h> |
|||
#include <libweb3jsonrpc/WebThreeStubServerBase.h> |
|||
|
|||
/**
|
|||
* @brief dummy JSON-RPC api implementation |
|||
* Should be used for test purposes only |
|||
* Supports eth && db interfaces |
|||
* Doesn't support shh && net interfaces |
|||
*/ |
|||
class FixedWebThreeServer: public dev::WebThreeStubServerBase, public dev::WebThreeStubDatabaseFace |
|||
{ |
|||
public: |
|||
FixedWebThreeServer(jsonrpc::AbstractServerConnector& _conn, std::vector<dev::KeyPair> const& _accounts, dev::eth::Interface* _client): WebThreeStubServerBase(_conn, _accounts), m_client(_client) {}; |
|||
|
|||
private: |
|||
dev::eth::Interface* client() override { return m_client; } |
|||
std::shared_ptr<dev::shh::Interface> face() override { BOOST_THROW_EXCEPTION(dev::InterfaceNotSupported("dev::shh::Interface")); } |
|||
dev::WebThreeNetworkFace* network() override { BOOST_THROW_EXCEPTION(dev::InterfaceNotSupported("dev::WebThreeNetworkFace")); } |
|||
dev::WebThreeStubDatabaseFace* db() override { return this; } |
|||
std::string get(std::string const& _name, std::string const& _key) override |
|||
{ |
|||
std::string k(_name + "/" + _key); |
|||
return m_db[k]; |
|||
} |
|||
void put(std::string const& _name, std::string const& _key, std::string const& _value) override |
|||
{ |
|||
std::string k(_name + "/" + _key); |
|||
m_db[k] = _value; |
|||
} |
|||
|
|||
private: |
|||
dev::eth::Interface* m_client; |
|||
std::map<std::string, std::string> m_db; |
|||
}; |
@ -0,0 +1,56 @@ |
|||
/*
|
|||
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 StateLoader.cpp
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "StateLoader.h" |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
using namespace dev::test; |
|||
|
|||
StateLoader::StateLoader(Json::Value const& _json) : m_state(Address(), OverlayDB(), BaseState::Empty) |
|||
{ |
|||
for (string const& name: _json.getMemberNames()) |
|||
{ |
|||
Json::Value o = _json[name]; |
|||
|
|||
Address address = Address(name); |
|||
bytes code = fromHex(o["code"].asString().substr(2)); |
|||
|
|||
if (code.size()) |
|||
{ |
|||
m_state.m_cache[address] = Account(u256(o["balance"].asString()), Account::ContractConception); |
|||
m_state.m_cache[address].setCode(code); |
|||
} |
|||
else |
|||
m_state.m_cache[address] = Account(u256(o["balance"].asString()), Account::NormalCreation); |
|||
|
|||
for (string const& j: o["storage"].getMemberNames()) |
|||
m_state.setStorage(address, u256(j), u256(o["storage"][j].asString())); |
|||
|
|||
for (auto i = 0; i < u256(o["nonce"].asString()); ++i) |
|||
m_state.noteSending(address); |
|||
|
|||
m_state.ensureCached(address, false, false); |
|||
} |
|||
|
|||
m_state.commit(); |
|||
} |
@ -0,0 +1,45 @@ |
|||
/*
|
|||
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 StateLoader.h
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <json/json.h> |
|||
#include <libethereum/State.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
/**
|
|||
* @brief Friend of State, loads State from given JSON object |
|||
*/ |
|||
class StateLoader |
|||
{ |
|||
public: |
|||
StateLoader(Json::Value const& _json); |
|||
eth::State const& state() const { return m_state; } |
|||
|
|||
private: |
|||
eth::State m_state; |
|||
}; |
|||
} |
|||
} |
@ -0,0 +1,42 @@ |
|||
/*
|
|||
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 TransientDirectory.cpp
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include <boost/filesystem.hpp> |
|||
#include <libdevcore/Exceptions.h> |
|||
#include "TransientDirectory.h" |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::test; |
|||
|
|||
TransientDirectory::TransientDirectory(std::string const& _path) : m_path(_path) |
|||
{ |
|||
// we never ever want to delete a directory (including all its contents) that we did not create ourselves.
|
|||
if (boost::filesystem::exists(m_path)) |
|||
BOOST_THROW_EXCEPTION(FileError()); |
|||
|
|||
boost::filesystem::create_directories(m_path); |
|||
} |
|||
|
|||
TransientDirectory::~TransientDirectory() |
|||
{ |
|||
boost::filesystem::remove_all(m_path); |
|||
} |
@ -0,0 +1,50 @@ |
|||
/*
|
|||
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 TransientDirectory.h
|
|||
* @author Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <string> |
|||
#include "Common.h" |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace test |
|||
{ |
|||
|
|||
/**
|
|||
* @brief temporary directory implementation |
|||
* It creates temporary directory in the given path. On dealloc it removes the directory |
|||
* @throws if the given path already exists, throws an exception |
|||
*/ |
|||
class TransientDirectory |
|||
{ |
|||
public: |
|||
TransientDirectory(std::string const& _path = getRandomPath()); |
|||
~TransientDirectory(); |
|||
|
|||
std::string const& path() const { return m_path; } |
|||
|
|||
private: |
|||
std::string m_path; |
|||
}; |
|||
|
|||
} |
|||
} |
Loading…
Reference in new issue