Ali Mashatan
10 years ago
220 changed files with 19242 additions and 3487 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,769 @@ |
|||
/*
|
|||
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 |
|||
<< " -f,--format-prefix Require all input formats to be prefixed e.g. 0x for hex, . for decimal, @ for binary." << endl |
|||
<< " -F,--no-format-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 |
|||
; |
|||
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, |
|||
Open, |
|||
Close |
|||
}; |
|||
|
|||
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 EncodingPrefs |
|||
{ |
|||
Encoding e = Encoding::Auto; |
|||
bool prefix = true; |
|||
}; |
|||
|
|||
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; |
|||
} |
|||
|
|||
bool isBytes() const { return base == Base::Bytes && !size; } |
|||
|
|||
string render(bytes const& _data, EncodingPrefs _e) const |
|||
{ |
|||
if (base == Base::Uint || base == Base::Int) |
|||
{ |
|||
if (_e.e == Encoding::Hex) |
|||
return (_e.prefix ? "0x" : "") + toHex(toCompactBigEndian(fromBigEndian<bigint>(bytesConstRef(&_data).cropped(32 - size / 8)))); |
|||
else |
|||
{ |
|||
bigint i = fromBigEndian<bigint>(bytesConstRef(&_data).cropped(32 - size / 8)); |
|||
if (base == Base::Int && i > (bigint(1) << (size - 1))) |
|||
i -= (bigint(1) << size); |
|||
return toString(i); |
|||
} |
|||
} |
|||
else if (base == Base::Address) |
|||
{ |
|||
Address a = Address(h256(_data), Address::AlignRight); |
|||
return _e.e == Encoding::Binary ? asString(a.asBytes()) : ((_e.prefix ? "0x" : "") + toString(a)); |
|||
} |
|||
else if (isBytes()) |
|||
{ |
|||
return _e.e == Encoding::Binary ? asString(_data) : ((_e.prefix ? "0x" : "") + toHex(_data)); |
|||
} |
|||
else if (base == Base::Bytes) |
|||
{ |
|||
bytesConstRef b(&_data); |
|||
b = b.cropped(0, size); |
|||
return _e.e == Encoding::Binary ? asString(b) : ((_e.prefix ? "0x" : "") + toHex(b)); |
|||
} |
|||
else |
|||
throw InvalidFormat(); |
|||
} |
|||
|
|||
bytes unrender(bytes const& _data, Format _f) const |
|||
{ |
|||
if (isBytes()) |
|||
{ |
|||
auto ret = _data; |
|||
while (ret.size() % 32 != 0) |
|||
ret.push_back(0); |
|||
return ret; |
|||
} |
|||
else |
|||
return aligned(_data, _f, 32); |
|||
} |
|||
|
|||
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, Format _f, unsigned _length) const |
|||
{ |
|||
bytes ret = _b; |
|||
while (ret.size() < _length) |
|||
if (base == Base::Bytes || (base == Base::Unknown && _f == Format::Binary)) |
|||
ret.push_back(0); |
|||
else |
|||
ret.insert(ret.begin(), 0); |
|||
while (ret.size() > _length) |
|||
if (base == Base::Bytes || (base == Base::Unknown && _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 (val == "[") |
|||
return make_tuple(bytes(), type, Format::Open); |
|||
if (val == "]") |
|||
return make_tuple(bytes(), type, Format::Close); |
|||
} |
|||
if (_prefix != Tristate::True) |
|||
{ |
|||
if (val.find_first_not_of("0123456789") == string::npos) |
|||
{ |
|||
type.noteDecimalInput(); |
|||
return make_tuple(toCompactBigEndian(bigint(val)), type, Format::Decimal); |
|||
} |
|||
if (val.find_first_not_of("0123456789abcdefABCDEF") == string::npos) |
|||
{ |
|||
type.noteHexInput(val.size()); |
|||
return make_tuple(fromHex(val), type, Format::Hex); |
|||
} |
|||
if (val == "[") |
|||
return make_tuple(bytes(), type, Format::Open); |
|||
if (val == "]") |
|||
return make_tuple(bytes(), type, Format::Close); |
|||
type.noteBinaryInput(); |
|||
return make_tuple(asBytes(val), type, Format::Binary); |
|||
} |
|||
throw InvalidUserString(); |
|||
} |
|||
|
|||
struct ExpectedAdditionalParameter: public Exception {}; |
|||
struct ExpectedOpen: public Exception {}; |
|||
struct ExpectedClose: public Exception {}; |
|||
|
|||
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(); |
|||
bytes suffix; |
|||
|
|||
// int int[] int
|
|||
// example: 42 [ 1 2 3 ] 69
|
|||
// int[2][][3]
|
|||
// example: [ [ [ 1 2 3 ] [ 4 5 6 ] ] [ ] ]
|
|||
|
|||
unsigned pi = 0; |
|||
|
|||
for (ABIType const& a: ins) |
|||
{ |
|||
if (pi >= _params.size()) |
|||
throw ExpectedAdditionalParameter(); |
|||
auto put = [&]() { |
|||
if (a.isBytes()) |
|||
ret += h256(u256(_params[pi].first.size())).asBytes(); |
|||
suffix += a.unrender(_params[pi].first, _params[pi].second); |
|||
pi++; |
|||
if (pi >= _params.size()) |
|||
throw ExpectedAdditionalParameter(); |
|||
}; |
|||
function<void(vector<int>, unsigned)> putDim = [&](vector<int> addr, unsigned q) { |
|||
if (addr.size() == a.dims.size()) |
|||
put(); |
|||
else |
|||
{ |
|||
if (_params[pi].second != Format::Open) |
|||
throw ExpectedOpen(); |
|||
++pi; |
|||
int l = a.dims[addr.size()]; |
|||
if (l == -1) |
|||
{ |
|||
// read ahead in params and discover the arity.
|
|||
unsigned depth = 0; |
|||
l = 0; |
|||
for (unsigned pi2 = pi; depth || _params[pi2].second != Format::Close;) |
|||
{ |
|||
if (_params[pi2].second == Format::Open) |
|||
++depth; |
|||
if (_params[pi2].second == Format::Close) |
|||
--depth; |
|||
if (!depth) |
|||
++l; |
|||
if (++pi2 == _params.size()) |
|||
throw ExpectedClose(); |
|||
} |
|||
ret += h256(u256(l)).asBytes(); |
|||
} |
|||
q *= l; |
|||
for (addr.push_back(0); addr.back() < l; ++addr.back()) |
|||
putDim(addr, q); |
|||
if (_params[pi].second != Format::Close) |
|||
throw ExpectedClose(); |
|||
++pi; |
|||
} |
|||
}; |
|||
putDim(vector<int>(), 1); |
|||
} |
|||
return ret + suffix; |
|||
} |
|||
string decode(bytes const& _data, int _index, EncodingPrefs _ep) |
|||
{ |
|||
stringstream out; |
|||
unsigned di = 0; |
|||
vector<unsigned> catDims; |
|||
for (ABIType const& a: outs) |
|||
{ |
|||
auto put = [&]() { |
|||
if (a.isBytes()) |
|||
{ |
|||
catDims.push_back(fromBigEndian<unsigned>(bytesConstRef(&_data).cropped(di, 32))); |
|||
di += 32; |
|||
} |
|||
}; |
|||
function<void(vector<int>, unsigned)> putDim = [&](vector<int> addr, unsigned q) { |
|||
if (addr.size() == a.dims.size()) |
|||
put(); |
|||
else |
|||
{ |
|||
int l = a.dims[addr.size()]; |
|||
if (l == -1) |
|||
{ |
|||
l = fromBigEndian<unsigned>(bytesConstRef(&_data).cropped(di, 32)); |
|||
catDims.push_back(l); |
|||
di += 32; |
|||
} |
|||
q *= l; |
|||
for (addr.push_back(0); addr.back() < l; ++addr.back()) |
|||
putDim(addr, q); |
|||
} |
|||
}; |
|||
putDim(vector<int>(), 1); |
|||
} |
|||
unsigned d = 0; |
|||
for (ABIType const& a: outs) |
|||
{ |
|||
if (_index == -1 && out.tellp() > 0) |
|||
out << ", "; |
|||
auto put = [&]() { |
|||
if (a.isBytes()) |
|||
{ |
|||
out << a.render(bytesConstRef(&_data).cropped(di, catDims[d]).toBytes(), _ep); |
|||
di += ((catDims[d] + 31) / 32) * 32; |
|||
d++; |
|||
} |
|||
else |
|||
{ |
|||
out << a.render(bytesConstRef(&_data).cropped(di, 32).toBytes(), _ep); |
|||
di += 32; |
|||
} |
|||
}; |
|||
function<void(vector<int>)> putDim = [&](vector<int> addr) { |
|||
if (addr.size() == a.dims.size()) |
|||
put(); |
|||
else |
|||
{ |
|||
out << "["; |
|||
addr.push_back(0); |
|||
int l = a.dims[addr.size() - 1]; |
|||
if (l == -1) |
|||
l = catDims[d++]; |
|||
for (addr.back() = 0; addr.back() < l; ++addr.back()) |
|||
{ |
|||
if (addr.back()) |
|||
out << ", "; |
|||
putDim(addr); |
|||
} |
|||
out << "]"; |
|||
} |
|||
}; |
|||
putDim(vector<int>()); |
|||
} |
|||
return out.str(); |
|||
} |
|||
}; |
|||
|
|||
string canonSig(string const& _name, vector<ABIType> const& _args) |
|||
{ |
|||
try { |
|||
string methodArgs; |
|||
for (auto const& arg: _args) |
|||
methodArgs += (methodArgs.empty() ? "" : ",") + arg.canon(); |
|||
return _name + "(" + methodArgs + ")"; |
|||
} |
|||
catch (...) { |
|||
return string(); |
|||
} |
|||
} |
|||
|
|||
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 formatPrefix = Tristate::Mu; |
|||
Tristate typePrefix = Tristate::Mu; |
|||
EncodingPrefs prefs; |
|||
bool verbose = false; |
|||
int outputIndex = -1; |
|||
vector<pair<bytes, Format>> params; |
|||
vector<ABIType> args; |
|||
string incoming; |
|||
|
|||
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") |
|||
prefs.prefix = true; |
|||
else if (arg == "-f" || arg == "--format-prefix") |
|||
formatPrefix = Tristate::True; |
|||
else if (arg == "-F" || arg == "--no-format-prefix") |
|||
formatPrefix = Tristate::False; |
|||
else if (arg == "-t" || arg == "--typing") |
|||
typePrefix = Tristate::True; |
|||
else if (arg == "-T" || arg == "--no-typing") |
|||
typePrefix = Tristate::False; |
|||
else if (arg == "-x" || arg == "--hex") |
|||
prefs.e = Encoding::Hex; |
|||
else if (arg == "-d" || arg == "--decimal" || arg == "--dec") |
|||
prefs.e = Encoding::Decimal; |
|||
else if (arg == "-b" || arg == "--binary" || arg == "--bin") |
|||
prefs.e = Encoding::Binary; |
|||
else if (arg == "-v" || arg == "--verbose") |
|||
verbose = true; |
|||
else if (arg == "-V" || arg == "--version") |
|||
version(); |
|||
else if (method.empty()) |
|||
method = arg; |
|||
else if (mode == Mode::Encode) |
|||
{ |
|||
auto u = fromUser(arg, formatPrefix, typePrefix); |
|||
args.push_back(get<1>(u)); |
|||
params.push_back(make_pair(get<0>(u), get<2>(u))); |
|||
} |
|||
else if (mode == Mode::Decode) |
|||
incoming += arg; |
|||
} |
|||
|
|||
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); |
|||
} |
|||
} |
|||
try { |
|||
userOutput(cout, m.encode(params), encoding); |
|||
} |
|||
catch (ExpectedAdditionalParameter const&) |
|||
{ |
|||
cerr << "Expected additional parameter in input." << endl; |
|||
exit(-1); |
|||
} |
|||
catch (ExpectedOpen const&) |
|||
{ |
|||
cerr << "Expected open-bracket '[' in input." << endl; |
|||
exit(-1); |
|||
} |
|||
catch (ExpectedClose const&) |
|||
{ |
|||
cerr << "Expected close-bracket ']' in input." << endl; |
|||
exit(-1); |
|||
} |
|||
} |
|||
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; |
|||
if (incoming == "--" || incoming.empty()) |
|||
for (int i = cin.get(); i != -1; i = cin.get()) |
|||
encoded.push_back((char)i); |
|||
else |
|||
{ |
|||
encoded = contentsString(incoming); |
|||
} |
|||
cout << m.decode(fromHex(boost::trim_copy(encoded)), outputIndex, prefs) << endl; |
|||
} |
|||
} |
|||
|
|||
return 0; |
|||
} |
@ -0,0 +1,63 @@ |
|||
/*
|
|||
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 Connect.cpp
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#include "Connect.h" |
|||
|
|||
#include <libp2p/Host.h> |
|||
#include "ui_Connect.h" |
|||
|
|||
Connect::Connect(QWidget *parent) : |
|||
QDialog(parent), |
|||
ui(new Ui::Connect) |
|||
{ |
|||
ui->setupUi(this); |
|||
} |
|||
|
|||
Connect::~Connect() |
|||
{ |
|||
delete ui; |
|||
} |
|||
|
|||
void Connect::setEnvironment(QStringList const& _nodes) |
|||
{ |
|||
ui->host->addItems(_nodes); |
|||
} |
|||
|
|||
void Connect::reset() |
|||
{ |
|||
ui->nodeId->clear(); |
|||
ui->required->setChecked(false); |
|||
} |
|||
|
|||
QString Connect::host() |
|||
{ |
|||
return ui->host->currentText(); |
|||
} |
|||
|
|||
QString Connect::nodeId() |
|||
{ |
|||
return ui->nodeId->text(); |
|||
} |
|||
|
|||
bool Connect::required() |
|||
{ |
|||
return ui->required->isChecked(); |
|||
} |
@ -0,0 +1,55 @@ |
|||
/*
|
|||
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 Connect.h
|
|||
* @author Alex Leverington <nessence@gmail.com> |
|||
* @date 2015 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <QDialog> |
|||
#include <QList> |
|||
|
|||
namespace Ui { class Connect; } |
|||
namespace dev { namespace p2p { class Host; } } |
|||
|
|||
class Connect : public QDialog |
|||
{ |
|||
Q_OBJECT |
|||
|
|||
public: |
|||
explicit Connect(QWidget* _parent = 0); |
|||
~Connect(); |
|||
|
|||
/// Populate host chooser with default host entries.
|
|||
void setEnvironment(QStringList const& _nodes); |
|||
|
|||
/// Clear dialogue inputs.
|
|||
void reset(); |
|||
|
|||
/// @returns the host string, as chosen or entered by the user. Assumed to be "hostOrIP:port" (:port is optional).
|
|||
QString host(); |
|||
|
|||
/// @returns the identity of the node, as entered by the user. Assumed to be a 64-character hex string.
|
|||
QString nodeId(); |
|||
|
|||
/// @returns true if Required is checked by the user, indicating that the host is a required Peer.
|
|||
bool required(); |
|||
|
|||
private: |
|||
Ui::Connect* ui; |
|||
}; |
@ -0,0 +1,123 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<ui version="4.0"> |
|||
<class>Connect</class> |
|||
<widget class="QDialog" name="Connect"> |
|||
<property name="windowModality"> |
|||
<enum>Qt::WindowModal</enum> |
|||
</property> |
|||
<property name="geometry"> |
|||
<rect> |
|||
<x>0</x> |
|||
<y>0</y> |
|||
<width>343</width> |
|||
<height>178</height> |
|||
</rect> |
|||
</property> |
|||
<property name="sizePolicy"> |
|||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding"> |
|||
<horstretch>0</horstretch> |
|||
<verstretch>0</verstretch> |
|||
</sizepolicy> |
|||
</property> |
|||
<property name="maximumSize"> |
|||
<size> |
|||
<width>343</width> |
|||
<height>178</height> |
|||
</size> |
|||
</property> |
|||
<property name="windowTitle"> |
|||
<string>Connect to Peer</string> |
|||
</property> |
|||
<property name="modal"> |
|||
<bool>true</bool> |
|||
</property> |
|||
<layout class="QFormLayout" name="formLayout_2"> |
|||
<item row="1" column="0"> |
|||
<layout class="QFormLayout" name="formLayout"> |
|||
<item row="1" column="0" colspan="2"> |
|||
<widget class="QComboBox" name="host"> |
|||
<property name="minimumSize"> |
|||
<size> |
|||
<width>311</width> |
|||
<height>0</height> |
|||
</size> |
|||
</property> |
|||
<property name="editable"> |
|||
<bool>true</bool> |
|||
</property> |
|||
</widget> |
|||
</item> |
|||
<item row="2" column="0" colspan="2"> |
|||
<widget class="QLineEdit" name="nodeId"> |
|||
<property name="placeholderText"> |
|||
<string>Node Id</string> |
|||
</property> |
|||
</widget> |
|||
</item> |
|||
<item row="3" column="0" colspan="2"> |
|||
<widget class="QCheckBox" name="required"> |
|||
<property name="text"> |
|||
<string>Required (Always Connect to this Peer)</string> |
|||
</property> |
|||
<property name="tristate"> |
|||
<bool>false</bool> |
|||
</property> |
|||
</widget> |
|||
</item> |
|||
<item row="4" column="0" colspan="2"> |
|||
<widget class="QDialogButtonBox" name="buttonBox"> |
|||
<property name="orientation"> |
|||
<enum>Qt::Horizontal</enum> |
|||
</property> |
|||
<property name="standardButtons"> |
|||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> |
|||
</property> |
|||
</widget> |
|||
</item> |
|||
</layout> |
|||
</item> |
|||
<item row="0" column="0"> |
|||
<widget class="QLabel" name="formLabel"> |
|||
<property name="text"> |
|||
<string>Enter a peer to which a connection may be made:</string> |
|||
</property> |
|||
</widget> |
|||
</item> |
|||
</layout> |
|||
</widget> |
|||
<resources/> |
|||
<connections> |
|||
<connection> |
|||
<sender>buttonBox</sender> |
|||
<signal>accepted()</signal> |
|||
<receiver>Connect</receiver> |
|||
<slot>accept()</slot> |
|||
<hints> |
|||
<hint type="sourcelabel"> |
|||
<x>248</x> |
|||
<y>254</y> |
|||
</hint> |
|||
<hint type="destinationlabel"> |
|||
<x>157</x> |
|||
<y>274</y> |
|||
</hint> |
|||
</hints> |
|||
</connection> |
|||
<connection> |
|||
<sender>buttonBox</sender> |
|||
<signal>rejected()</signal> |
|||
<receiver>Connect</receiver> |
|||
<slot>reject()</slot> |
|||
<hints> |
|||
<hint type="sourcelabel"> |
|||
<x>316</x> |
|||
<y>260</y> |
|||
</hint> |
|||
<hint type="destinationlabel"> |
|||
<x>286</x> |
|||
<y>274</y> |
|||
</hint> |
|||
</hints> |
|||
</connection> |
|||
</connections> |
|||
</ui> |
@ -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,399 @@ |
|||
/*
|
|||
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().lastHashes(), t, 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().lastHashes(), t, 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(); |
|||
} |
|||
|
|||
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) = 0; |
|||
|
|||
/// 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,135 @@ |
|||
/*
|
|||
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 Assembly.cpp
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#include "AssemblyItem.h" |
|||
#include <fstream> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
|||
unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const |
|||
{ |
|||
switch (m_type) |
|||
{ |
|||
case Operation: |
|||
case Tag: // 1 byte for the JUMPDEST
|
|||
return 1; |
|||
case PushString: |
|||
return 33; |
|||
case Push: |
|||
return 1 + max<unsigned>(1, dev::bytesRequired(m_data)); |
|||
case PushSubSize: |
|||
case PushProgramSize: |
|||
return 4; // worst case: a 16MB program
|
|||
case PushTag: |
|||
case PushData: |
|||
case PushSub: |
|||
return 1 + _addressLength; |
|||
default: |
|||
break; |
|||
} |
|||
BOOST_THROW_EXCEPTION(InvalidOpcode()); |
|||
} |
|||
|
|||
int AssemblyItem::deposit() const |
|||
{ |
|||
switch (m_type) |
|||
{ |
|||
case Operation: |
|||
return instructionInfo(instruction()).ret - instructionInfo(instruction()).args; |
|||
case Push: |
|||
case PushString: |
|||
case PushTag: |
|||
case PushData: |
|||
case PushSub: |
|||
case PushSubSize: |
|||
case PushProgramSize: |
|||
return 1; |
|||
case Tag: |
|||
return 0; |
|||
default:; |
|||
} |
|||
return 0; |
|||
} |
|||
|
|||
string AssemblyItem::getJumpTypeAsString() const |
|||
{ |
|||
switch (m_jumpType) |
|||
{ |
|||
case JumpType::IntoFunction: |
|||
return "[in]"; |
|||
case JumpType::OutOfFunction: |
|||
return "[out]"; |
|||
case JumpType::Ordinary: |
|||
default: |
|||
return ""; |
|||
} |
|||
} |
|||
|
|||
ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) |
|||
{ |
|||
switch (_item.type()) |
|||
{ |
|||
case Operation: |
|||
_out << " " << instructionInfo(_item.instruction()).name; |
|||
if (_item.instruction() == eth::Instruction::JUMP || _item.instruction() == eth::Instruction::JUMPI) |
|||
_out << "\t" << _item.getJumpTypeAsString(); |
|||
break; |
|||
case Push: |
|||
_out << " PUSH " << hex << _item.data(); |
|||
break; |
|||
case PushString: |
|||
_out << " PushString" << hex << (unsigned)_item.data(); |
|||
break; |
|||
case PushTag: |
|||
_out << " PushTag " << _item.data(); |
|||
break; |
|||
case Tag: |
|||
_out << " Tag " << _item.data(); |
|||
break; |
|||
case PushData: |
|||
_out << " PushData " << hex << (unsigned)_item.data(); |
|||
break; |
|||
case PushSub: |
|||
_out << " PushSub " << hex << h256(_item.data()).abridged(); |
|||
break; |
|||
case PushSubSize: |
|||
_out << " PushSubSize " << hex << h256(_item.data()).abridged(); |
|||
break; |
|||
case PushProgramSize: |
|||
_out << " PushProgramSize"; |
|||
break; |
|||
case UndefinedItem: |
|||
_out << " ???"; |
|||
break; |
|||
default: |
|||
BOOST_THROW_EXCEPTION(InvalidOpcode()); |
|||
} |
|||
return _out; |
|||
} |
|||
|
|||
ostream& dev::eth::operator<<(ostream& _out, AssemblyItemsConstRef _i) |
|||
{ |
|||
for (AssemblyItem const& i: _i) |
|||
_out << i; |
|||
return _out; |
|||
} |
@ -0,0 +1,92 @@ |
|||
/*
|
|||
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 Assembly.h
|
|||
* @author Gav Wood <i@gavwood.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <iostream> |
|||
#include <sstream> |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcore/Assertions.h> |
|||
#include <libevmcore/SourceLocation.h> |
|||
#include <libevmcore/Instruction.h> |
|||
#include "Exceptions.h" |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth |
|||
{ |
|||
|
|||
enum AssemblyItemType { UndefinedItem, Operation, Push, PushString, PushTag, PushSub, PushSubSize, PushProgramSize, Tag, PushData }; |
|||
|
|||
class Assembly; |
|||
|
|||
class AssemblyItem |
|||
{ |
|||
friend class Assembly; |
|||
|
|||
public: |
|||
enum class JumpType { Ordinary, IntoFunction, OutOfFunction }; |
|||
|
|||
AssemblyItem(u256 _push): m_type(Push), m_data(_push) {} |
|||
AssemblyItem(Instruction _i): m_type(Operation), m_data((byte)_i) {} |
|||
AssemblyItem(AssemblyItemType _type, u256 _data = 0): m_type(_type), m_data(_data) {} |
|||
|
|||
AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(Tag, m_data); } |
|||
AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(PushTag, m_data); } |
|||
|
|||
AssemblyItemType type() const { return m_type; } |
|||
u256 const& data() const { return m_data; } |
|||
/// @returns the instruction of this item (only valid if type() == Operation)
|
|||
Instruction instruction() const { return Instruction(byte(m_data)); } |
|||
|
|||
/// @returns true iff the type and data of the items are equal.
|
|||
bool operator==(AssemblyItem const& _other) const { return m_type == _other.m_type && m_data == _other.m_data; } |
|||
bool operator!=(AssemblyItem const& _other) const { return !operator==(_other); } |
|||
|
|||
/// @returns an upper bound for the number of bytes required by this item, assuming that
|
|||
/// the value of a jump tag takes @a _addressLength bytes.
|
|||
unsigned bytesRequired(unsigned _addressLength) const; |
|||
int deposit() const; |
|||
|
|||
bool match(AssemblyItem const& _i) const { return _i.m_type == UndefinedItem || (m_type == _i.m_type && (m_type != Operation || m_data == _i.m_data)); } |
|||
void setLocation(SourceLocation const& _location) { m_location = _location; } |
|||
SourceLocation const& getLocation() const { return m_location; } |
|||
|
|||
void setJumpType(JumpType _jumpType) { m_jumpType = _jumpType; } |
|||
JumpType getJumpType() const { return m_jumpType; } |
|||
std::string getJumpTypeAsString() const; |
|||
|
|||
private: |
|||
AssemblyItemType m_type; |
|||
u256 m_data; |
|||
SourceLocation m_location; |
|||
JumpType m_jumpType = JumpType::Ordinary; |
|||
}; |
|||
|
|||
using AssemblyItems = std::vector<AssemblyItem>; |
|||
using AssemblyItemsConstRef = vector_ref<AssemblyItem const>; |
|||
|
|||
std::ostream& operator<<(std::ostream& _out, AssemblyItem const& _item); |
|||
std::ostream& operator<<(std::ostream& _out, AssemblyItemsConstRef _i); |
|||
inline std::ostream& operator<<(std::ostream& _out, AssemblyItems const& _i) { return operator<<(_out, AssemblyItemsConstRef(&_i)); } |
|||
|
|||
} |
|||
} |
@ -0,0 +1,440 @@ |
|||
/*
|
|||
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, ExpressionClasses::Id> initialStackContents; |
|||
map<int, ExpressionClasses::Id> targetStackContents; |
|||
int minHeight = m_stackHeight + 1; |
|||
if (!m_stackElements.empty()) |
|||
minHeight = min(minHeight, m_stackElements.begin()->first); |
|||
for (int height = minHeight; height <= 0; ++height) |
|||
initialStackContents[height] = initialStackElement(height); |
|||
for (int height = minHeight; height <= m_stackHeight; ++height) |
|||
targetStackContents[height] = stackElement(height); |
|||
|
|||
// Debug info:
|
|||
//stream(cout, initialStackContents, targetStackContents);
|
|||
|
|||
return CSECodeGenerator(m_expressionClasses).generateCode(initialStackContents, targetStackContents); |
|||
} |
|||
|
|||
ostream& CommonSubexpressionEliminator::stream( |
|||
ostream& _out, |
|||
map<int, ExpressionClasses::Id> _currentStack, |
|||
map<int, ExpressionClasses::Id> _targetStack |
|||
) const |
|||
{ |
|||
auto streamExpressionClass = [this](ostream& _out, ExpressionClasses::Id _id) |
|||
{ |
|||
auto const& expr = m_expressionClasses.representative(_id); |
|||
_out << " " << _id << ": " << *expr.item; |
|||
_out << "("; |
|||
for (ExpressionClasses::Id arg: expr.arguments) |
|||
_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 << " = "; |
|||
streamExpressionClass(_out, it.second); |
|||
} |
|||
_out << "Equivalence classes: " << endl; |
|||
for (ExpressionClasses::Id eqClass = 0; eqClass < m_expressionClasses.size(); ++eqClass) |
|||
streamExpressionClass(_out, eqClass); |
|||
|
|||
_out << "Current stack: " << endl; |
|||
for (auto const& it: _currentStack) |
|||
{ |
|||
_out << " " << dec << it.first << ": "; |
|||
streamExpressionClass(_out, it.second); |
|||
} |
|||
_out << "Target stack: " << endl; |
|||
for (auto const& it: _targetStack) |
|||
{ |
|||
_out << " " << dec << it.first << ": "; |
|||
streamExpressionClass(_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, m_expressionClasses.find(_item, {})); |
|||
} |
|||
else |
|||
{ |
|||
Instruction instruction = _item.instruction(); |
|||
InstructionInfo info = instructionInfo(instruction); |
|||
if (SemanticInformation::isDupInstruction(_item)) |
|||
setStackElement( |
|||
m_stackHeight + 1, |
|||
stackElement(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<ExpressionClasses::Id> arguments(info.args); |
|||
for (int i = 0; i < info.args; ++i) |
|||
arguments[i] = stackElement(m_stackHeight - i); |
|||
setStackElement(m_stackHeight + _item.deposit(), m_expressionClasses.find(_item, arguments)); |
|||
} |
|||
m_stackHeight += _item.deposit(); |
|||
} |
|||
} |
|||
|
|||
void CommonSubexpressionEliminator::setStackElement(int _stackHeight, ExpressionClasses::Id _class) |
|||
{ |
|||
m_stackElements[_stackHeight] = _class; |
|||
} |
|||
|
|||
void CommonSubexpressionEliminator::swapStackElements(int _stackHeightA, int _stackHeightB) |
|||
{ |
|||
if (_stackHeightA == _stackHeightB) |
|||
BOOST_THROW_EXCEPTION(OptimizerException() << errinfo_comment("Swap on same stack elements.")); |
|||
// ensure they are created
|
|||
stackElement(_stackHeightA); |
|||
stackElement(_stackHeightB); |
|||
|
|||
swap(m_stackElements[_stackHeightA], m_stackElements[_stackHeightB]); |
|||
} |
|||
|
|||
ExpressionClasses::Id CommonSubexpressionEliminator::stackElement(int _stackHeight) |
|||
{ |
|||
if (m_stackElements.count(_stackHeight)) |
|||
return m_stackElements.at(_stackHeight); |
|||
// Stack element not found (not assigned yet), create new equivalence class.
|
|||
return m_stackElements[_stackHeight] = initialStackElement(_stackHeight); |
|||
} |
|||
|
|||
ExpressionClasses::Id CommonSubexpressionEliminator::initialStackElement(int _stackHeight) |
|||
{ |
|||
assertThrow(_stackHeight <= 0, OptimizerException, "Initial stack element of positive height requested."); |
|||
assertThrow(_stackHeight > -16, StackTooDeepException, ""); |
|||
// This is a special assembly item that refers to elements pre-existing on the initial stack.
|
|||
return m_expressionClasses.find(AssemblyItem(dupInstruction(1 - _stackHeight))); |
|||
} |
|||
|
|||
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, ExpressionClasses::Id> const& _initialStack, |
|||
map<int, ExpressionClasses::Id> const& _targetStackContents |
|||
) |
|||
{ |
|||
m_stack = _initialStack; |
|||
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(ExpressionClasses::Id _c) |
|||
{ |
|||
if (m_neededBy.count(_c)) |
|||
return; |
|||
for (ExpressionClasses::Id argument: m_expressionClasses.representative(_c).arguments) |
|||
{ |
|||
addDependencies(argument); |
|||
m_neededBy.insert(make_pair(argument, _c)); |
|||
} |
|||
} |
|||
|
|||
int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c) |
|||
{ |
|||
if (m_classPositions.count(_c)) |
|||
{ |
|||
assertThrow( |
|||
m_classPositions[_c] != c_invalidPosition, |
|||
OptimizerException, |
|||
"Element already removed but still needed." |
|||
); |
|||
return m_classPositions[_c]; |
|||
} |
|||
ExpressionClasses::Ids const& arguments = m_expressionClasses.representative(_c).arguments; |
|||
for (ExpressionClasses::Id 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_expressionClasses.representative(_c).item; |
|||
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_expressionClasses.representative(_c).item); |
|||
m_stack[m_stackHeight] = _c; |
|||
return m_classPositions[_c] = m_stackHeight; |
|||
} |
|||
|
|||
bool CSECodeGenerator::canBeRemoved(ExpressionClasses::Id _element, ExpressionClasses::Id _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, ""); |
|||
ExpressionClasses::Id 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(nr <= 16, StackTooDeepException, "Stack too deep."); |
|||
assertThrow(1 <= nr, OptimizerException, "Invalid stack access."); |
|||
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(nr <= 16, StackTooDeepException, "Stack too deep."); |
|||
assertThrow(1 <= nr, OptimizerException, "Invalid stack access."); |
|||
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,183 @@ |
|||
/*
|
|||
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> |
|||
#include <libevmcore/ExpressionClasses.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth |
|||
{ |
|||
|
|||
class AssemblyItem; |
|||
using AssemblyItems = std::vector<AssemblyItem>; |
|||
|
|||
/**
|
|||
* 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, ExpressionClasses::Id> _currentStack = std::map<int, ExpressionClasses::Id>(), |
|||
std::map<int, ExpressionClasses::Id> _targetStack = std::map<int, ExpressionClasses::Id>() |
|||
) const; |
|||
|
|||
private: |
|||
/// Feeds the item into the system for analysis.
|
|||
void feedItem(AssemblyItem const& _item); |
|||
|
|||
/// Simplifies the given item using
|
|||
/// Assigns a new equivalence class to the next sequence number of the given stack element.
|
|||
void setStackElement(int _stackHeight, ExpressionClasses::Id _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).
|
|||
ExpressionClasses::Id stackElement(int _stackHeight); |
|||
/// @returns the equivalence class id of the special initial stack element at the given height
|
|||
/// (must not be positive).
|
|||
ExpressionClasses::Id initialStackElement(int _stackHeight); |
|||
|
|||
/// Current stack height, can be negative.
|
|||
int m_stackHeight = 0; |
|||
/// Current stack layout, mapping stack height -> equivalence class
|
|||
std::map<int, ExpressionClasses::Id> m_stackElements; |
|||
/// Structure containing the classes of equivalent expressions.
|
|||
ExpressionClasses m_expressionClasses; |
|||
}; |
|||
|
|||
/**
|
|||
* 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: |
|||
CSECodeGenerator(ExpressionClasses const& _expressionClasses): |
|||
m_expressionClasses(_expressionClasses) |
|||
{} |
|||
|
|||
/// @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 should only be called once on each object.
|
|||
AssemblyItems generateCode( |
|||
std::map<int, ExpressionClasses::Id> const& _initialStack, |
|||
std::map<int, ExpressionClasses::Id> const& _targetStackContents |
|||
); |
|||
|
|||
private: |
|||
/// Recursively discovers all dependencies to @a m_requests.
|
|||
void addDependencies(ExpressionClasses::Id _c); |
|||
|
|||
/// Produce code that generates the given element if it is not yet present.
|
|||
/// @returns the stack position of the element.
|
|||
int generateClassElement(ExpressionClasses::Id _c); |
|||
|
|||
/// @returns true if @a _element can be removed - in general or, if given, while computing @a _result.
|
|||
bool canBeRemoved(ExpressionClasses::Id _element, ExpressionClasses::Id _result = ExpressionClasses::Id(-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<ExpressionClasses::Id, ExpressionClasses::Id> m_neededBy; |
|||
/// Current content of the stack.
|
|||
std::map<int, ExpressionClasses::Id> m_stack; |
|||
/// Current positions of equivalence classes, equal to c_invalidPosition if already deleted.
|
|||
std::map<ExpressionClasses::Id, int> m_classPositions; |
|||
|
|||
/// The actual eqivalence class items and how to compute them.
|
|||
ExpressionClasses const& m_expressionClasses; |
|||
/// The set of equivalence classes that should be present on the stack at the end.
|
|||
std::set<ExpressionClasses::Id> 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,371 @@ |
|||
/*
|
|||
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 ExpressionClasses.cpp |
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Container for equivalence classes of expressions for use in common subexpression elimination. |
|||
*/ |
|||
|
|||
#include <libevmcore/ExpressionClasses.h> |
|||
#include <utility> |
|||
#include <tuple> |
|||
#include <functional> |
|||
#include <boost/range/adaptor/reversed.hpp> |
|||
#include <boost/noncopyable.hpp> |
|||
#include <libevmcore/Assembly.h> |
|||
#include <libevmcore/CommonSubexpressionEliminator.h> |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
|||
|
|||
bool ExpressionClasses::Expression::operator<(ExpressionClasses::Expression const& _other) const |
|||
{ |
|||
auto type = item->type(); |
|||
auto otherType = _other.item->type(); |
|||
return std::tie(type, item->data(), arguments) < |
|||
std::tie(otherType, _other.item->data(), _other.arguments); |
|||
} |
|||
|
|||
ExpressionClasses::Id ExpressionClasses::find(AssemblyItem const& _item, Ids const& _arguments) |
|||
{ |
|||
Expression exp; |
|||
exp.id = Id(-1); |
|||
exp.item = &_item; |
|||
exp.arguments = _arguments; |
|||
|
|||
if (SemanticInformation::isCommutativeOperation(_item)) |
|||
sort(exp.arguments.begin(), exp.arguments.end()); |
|||
|
|||
//@todo store all class members (not only the representatives) in an efficient data structure to search here
|
|||
for (Expression const& e: m_representatives) |
|||
if (!(e < exp || exp < e)) |
|||
return e.id; |
|||
|
|||
if (SemanticInformation::isDupInstruction(_item)) |
|||
{ |
|||
// Special item that refers to values pre-existing on the stack
|
|||
m_spareAssemblyItem.push_back(make_shared<AssemblyItem>(_item)); |
|||
exp.item = m_spareAssemblyItem.back().get(); |
|||
} |
|||
|
|||
ExpressionClasses::Id id = tryToSimplify(exp); |
|||
if (id < m_representatives.size()) |
|||
return id; |
|||
|
|||
exp.id = m_representatives.size(); |
|||
m_representatives.push_back(exp); |
|||
return exp.id; |
|||
} |
|||
|
|||
string ExpressionClasses::fullDAGToString(ExpressionClasses::Id _id) const |
|||
{ |
|||
Expression const& expr = representative(_id); |
|||
stringstream str; |
|||
str << dec << expr.id << ":" << *expr.item << "("; |
|||
for (Id arg: expr.arguments) |
|||
str << fullDAGToString(arg) << ","; |
|||
str << ")"; |
|||
return str.str(); |
|||
} |
|||
|
|||
class Rules: public boost::noncopyable |
|||
{ |
|||
public: |
|||
Rules(); |
|||
void resetMatchGroups() { m_matchGroups.clear(); } |
|||
vector<pair<Pattern, function<Pattern()>>> rules() const { return m_rules; } |
|||
|
|||
private: |
|||
using Expression = ExpressionClasses::Expression; |
|||
map<unsigned, Expression const*> m_matchGroups; |
|||
vector<pair<Pattern, function<Pattern()>>> m_rules; |
|||
}; |
|||
|
|||
Rules::Rules() |
|||
{ |
|||
// Multiple occurences of one of these inside one rule must match the same equivalence class.
|
|||
// Constants.
|
|||
Pattern A(Push); |
|||
Pattern B(Push); |
|||
Pattern C(Push); |
|||
// Anything.
|
|||
Pattern X; |
|||
Pattern Y; |
|||
Pattern Z; |
|||
A.setMatchGroup(1, m_matchGroups); |
|||
B.setMatchGroup(2, m_matchGroups); |
|||
C.setMatchGroup(3, m_matchGroups); |
|||
X.setMatchGroup(4, m_matchGroups); |
|||
Y.setMatchGroup(5, m_matchGroups); |
|||
Z.setMatchGroup(6, m_matchGroups); |
|||
|
|||
m_rules = vector<pair<Pattern, function<Pattern()>>>{ |
|||
// arithmetics on constants
|
|||
{{Instruction::ADD, {A, B}}, [=]{ return A.d() + B.d(); }}, |
|||
{{Instruction::MUL, {A, B}}, [=]{ return A.d() * B.d(); }}, |
|||
{{Instruction::SUB, {A, B}}, [=]{ return A.d() - B.d(); }}, |
|||
{{Instruction::DIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : A.d() / B.d(); }}, |
|||
{{Instruction::SDIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(u2s(A.d()) / u2s(B.d())); }}, |
|||
{{Instruction::MOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : A.d() % B.d(); }}, |
|||
{{Instruction::SMOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(u2s(A.d()) % u2s(B.d())); }}, |
|||
{{Instruction::EXP, {A, B}}, [=]{ return u256(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << 256)); }}, |
|||
{{Instruction::NOT, {A}}, [=]{ return ~A.d(); }}, |
|||
{{Instruction::LT, {A, B}}, [=]() { return A.d() < B.d() ? u256(1) : 0; }}, |
|||
{{Instruction::GT, {A, B}}, [=]() -> u256 { return A.d() > B.d() ? 1 : 0; }}, |
|||
{{Instruction::SLT, {A, B}}, [=]() -> u256 { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }}, |
|||
{{Instruction::SGT, {A, B}}, [=]() -> u256 { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }}, |
|||
{{Instruction::EQ, {A, B}}, [=]() -> u256 { return A.d() == B.d() ? 1 : 0; }}, |
|||
{{Instruction::ISZERO, {A}}, [=]() -> u256 { return A.d() == 0 ? 1 : 0; }}, |
|||
{{Instruction::AND, {A, B}}, [=]{ return A.d() & B.d(); }}, |
|||
{{Instruction::OR, {A, B}}, [=]{ return A.d() | B.d(); }}, |
|||
{{Instruction::XOR, {A, B}}, [=]{ return A.d() ^ B.d(); }}, |
|||
{{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }}, |
|||
{{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }}, |
|||
{{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }}, |
|||
{{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }}, |
|||
{{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 { |
|||
if (A.d() >= 31) |
|||
return B.d(); |
|||
unsigned testBit = unsigned(A.d()) * 8 + 7; |
|||
u256 mask = (u256(1) << testBit) - 1; |
|||
return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask); |
|||
}}, |
|||
|
|||
// invariants involving known constants
|
|||
{{Instruction::ADD, {X, 0}}, [=]{ return X; }}, |
|||
{{Instruction::MUL, {X, 1}}, [=]{ return X; }}, |
|||
{{Instruction::DIV, {X, 1}}, [=]{ return X; }}, |
|||
{{Instruction::SDIV, {X, 1}}, [=]{ return X; }}, |
|||
{{Instruction::OR, {X, 0}}, [=]{ return X; }}, |
|||
{{Instruction::XOR, {X, 0}}, [=]{ return X; }}, |
|||
{{Instruction::AND, {X, ~u256(0)}}, [=]{ return X; }}, |
|||
{{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }}, |
|||
{{Instruction::DIV, {X, 0}}, [=]{ return u256(0); }}, |
|||
{{Instruction::MOD, {X, 0}}, [=]{ return u256(0); }}, |
|||
{{Instruction::MOD, {0, X}}, [=]{ return u256(0); }}, |
|||
{{Instruction::AND, {X, 0}}, [=]{ return u256(0); }}, |
|||
{{Instruction::OR, {X, ~u256(0)}}, [=]{ return ~u256(0); }}, |
|||
// operations involving an expression and itself
|
|||
{{Instruction::AND, {X, X}}, [=]{ return X; }}, |
|||
{{Instruction::OR, {X, X}}, [=]{ return X; }}, |
|||
{{Instruction::SUB, {X, X}}, [=]{ return u256(0); }}, |
|||
{{Instruction::EQ, {X, X}}, [=]{ return u256(1); }}, |
|||
{{Instruction::LT, {X, X}}, [=]{ return u256(0); }}, |
|||
{{Instruction::SLT, {X, X}}, [=]{ return u256(0); }}, |
|||
{{Instruction::GT, {X, X}}, [=]{ return u256(0); }}, |
|||
{{Instruction::SGT, {X, X}}, [=]{ return u256(0); }}, |
|||
{{Instruction::MOD, {X, X}}, [=]{ return u256(0); }}, |
|||
|
|||
{{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }}, |
|||
}; |
|||
// Associative operations
|
|||
for (auto const& opFun: vector<pair<Instruction,function<u256(u256 const&,u256 const&)>>>{ |
|||
{Instruction::ADD, plus<u256>()}, |
|||
{Instruction::MUL, multiplies<u256>()}, |
|||
{Instruction::AND, bit_and<u256>()}, |
|||
{Instruction::OR, bit_or<u256>()}, |
|||
{Instruction::XOR, bit_xor<u256>()} |
|||
}) |
|||
{ |
|||
auto op = opFun.first; |
|||
auto fun = opFun.second; |
|||
// Moving constants to the outside, order matters here!
|
|||
// we need actions that return expressions (or patterns?) here, and we need also reversed rules
|
|||
// (X+A)+B -> X+(A+B)
|
|||
m_rules.push_back({ |
|||
{op, {{op, {X, A}}, B}}, |
|||
[=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } |
|||
}); |
|||
// X+(Y+A) -> (X+Y)+A
|
|||
m_rules.push_back({ |
|||
{op, {{op, {X, A}}, Y}}, |
|||
[=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } |
|||
}); |
|||
// For now, we still need explicit commutativity for the inner pattern
|
|||
m_rules.push_back({ |
|||
{op, {{op, {A, X}}, B}}, |
|||
[=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } |
|||
}); |
|||
m_rules.push_back({ |
|||
{op, {{op, {A, X}}, Y}}, |
|||
[=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } |
|||
}); |
|||
}; |
|||
|
|||
//@todo: (x+8)-3 and other things
|
|||
} |
|||
|
|||
ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr, bool _secondRun) |
|||
{ |
|||
static Rules rules; |
|||
|
|||
if (_expr.item->type() != Operation) |
|||
return -1; |
|||
|
|||
for (auto const& rule: rules.rules()) |
|||
{ |
|||
rules.resetMatchGroups(); |
|||
if (rule.first.matches(_expr, *this)) |
|||
{ |
|||
// Debug info
|
|||
//cout << "Simplifying " << *_expr.item << "(";
|
|||
//for (Id arg: _expr.arguments)
|
|||
// cout << fullDAGToString(arg) << ", ";
|
|||
//cout << ")" << endl;
|
|||
//cout << "with rule " << rule.first.toString() << endl;
|
|||
//ExpressionTemplate t(rule.second());
|
|||
//cout << "to" << rule.second().toString() << endl;
|
|||
return rebuildExpression(ExpressionTemplate(rule.second())); |
|||
} |
|||
} |
|||
|
|||
if (!_secondRun && _expr.arguments.size() == 2 && SemanticInformation::isCommutativeOperation(*_expr.item)) |
|||
{ |
|||
Expression expr = _expr; |
|||
swap(expr.arguments[0], expr.arguments[1]); |
|||
return tryToSimplify(expr, true); |
|||
} |
|||
|
|||
return -1; |
|||
} |
|||
|
|||
ExpressionClasses::Id ExpressionClasses::rebuildExpression(ExpressionTemplate const& _template) |
|||
{ |
|||
if (_template.hasId) |
|||
return _template.id; |
|||
|
|||
Ids arguments; |
|||
for (ExpressionTemplate const& t: _template.arguments) |
|||
arguments.push_back(rebuildExpression(t)); |
|||
m_spareAssemblyItem.push_back(make_shared<AssemblyItem>(_template.item)); |
|||
return find(*m_spareAssemblyItem.back(), arguments); |
|||
} |
|||
|
|||
|
|||
Pattern::Pattern(Instruction _instruction, std::vector<Pattern> const& _arguments): |
|||
m_type(Operation), |
|||
m_requireDataMatch(true), |
|||
m_data(_instruction), |
|||
m_arguments(_arguments) |
|||
{ |
|||
} |
|||
|
|||
void Pattern::setMatchGroup(unsigned _group, map<unsigned, Expression const*>& _matchGroups) |
|||
{ |
|||
m_matchGroup = _group; |
|||
m_matchGroups = &_matchGroups; |
|||
} |
|||
|
|||
bool Pattern::matches(Expression const& _expr, ExpressionClasses const& _classes) const |
|||
{ |
|||
if (!matchesBaseItem(*_expr.item)) |
|||
return false; |
|||
if (m_matchGroup) |
|||
{ |
|||
if (!m_matchGroups->count(m_matchGroup)) |
|||
(*m_matchGroups)[m_matchGroup] = &_expr; |
|||
else if ((*m_matchGroups)[m_matchGroup]->id != _expr.id) |
|||
return false; |
|||
} |
|||
assertThrow(m_arguments.size() == 0 || _expr.arguments.size() == m_arguments.size(), OptimizerException, ""); |
|||
for (size_t i = 0; i < m_arguments.size(); ++i) |
|||
if (!m_arguments[i].matches(_classes.representative(_expr.arguments[i]), _classes)) |
|||
return false; |
|||
return true; |
|||
} |
|||
|
|||
string Pattern::toString() const |
|||
{ |
|||
stringstream s; |
|||
switch (m_type) |
|||
{ |
|||
case Operation: |
|||
s << instructionInfo(Instruction(unsigned(m_data))).name; |
|||
break; |
|||
case Push: |
|||
s << "PUSH " << hex << m_data; |
|||
break; |
|||
case UndefinedItem: |
|||
s << "ANY"; |
|||
break; |
|||
default: |
|||
s << "t=" << dec << m_type << " d=" << hex << m_data; |
|||
break; |
|||
} |
|||
if (!m_requireDataMatch) |
|||
s << " ~"; |
|||
if (m_matchGroup) |
|||
s << "[" << dec << m_matchGroup << "]"; |
|||
s << "("; |
|||
for (Pattern const& p: m_arguments) |
|||
s << p.toString() << ", "; |
|||
s << ")"; |
|||
return s.str(); |
|||
} |
|||
|
|||
bool Pattern::matchesBaseItem(AssemblyItem const& _item) const |
|||
{ |
|||
if (m_type == UndefinedItem) |
|||
return true; |
|||
if (m_type != _item.type()) |
|||
return false; |
|||
if (m_requireDataMatch && m_data != _item.data()) |
|||
return false; |
|||
return true; |
|||
} |
|||
|
|||
Pattern::Expression const& Pattern::matchGroupValue() const |
|||
{ |
|||
assertThrow(m_matchGroup > 0, OptimizerException, ""); |
|||
assertThrow(!!m_matchGroups, OptimizerException, ""); |
|||
assertThrow((*m_matchGroups)[m_matchGroup], OptimizerException, ""); |
|||
return *(*m_matchGroups)[m_matchGroup]; |
|||
} |
|||
|
|||
|
|||
ExpressionTemplate::ExpressionTemplate(Pattern const& _pattern) |
|||
{ |
|||
if (_pattern.matchGroup()) |
|||
{ |
|||
hasId = true; |
|||
id = _pattern.id(); |
|||
} |
|||
else |
|||
{ |
|||
hasId = false; |
|||
item = _pattern.toAssemblyItem(); |
|||
} |
|||
for (auto const& arg: _pattern.arguments()) |
|||
arguments.push_back(ExpressionTemplate(arg)); |
|||
} |
|||
|
|||
string ExpressionTemplate::toString() const |
|||
{ |
|||
stringstream s; |
|||
if (hasId) |
|||
s << id; |
|||
else |
|||
s << item; |
|||
s << "("; |
|||
for (auto const& arg: arguments) |
|||
s << arg.toString(); |
|||
s << ")"; |
|||
return s.str(); |
|||
} |
@ -0,0 +1,150 @@ |
|||
/*
|
|||
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 ExpressionClasses.h |
|||
* @author Christian <c@ethdev.com> |
|||
* @date 2015 |
|||
* Container for equivalence classes of expressions for use in common subexpression elimination. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
#include <map> |
|||
#include <memory> |
|||
#include <libdevcore/Common.h> |
|||
#include <libevmcore/AssemblyItem.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace eth |
|||
{ |
|||
|
|||
class Pattern; |
|||
struct ExpressionTemplate; |
|||
|
|||
/**
|
|||
* Collection of classes of equivalent expressions that can also determine the class of an expression. |
|||
* Identifiers are contiguously assigned to new classes starting from zero. |
|||
*/ |
|||
class ExpressionClasses |
|||
{ |
|||
public: |
|||
using Id = unsigned; |
|||
using Ids = std::vector<Id>; |
|||
|
|||
struct Expression |
|||
{ |
|||
Id id; |
|||
AssemblyItem const* item; |
|||
Ids arguments; |
|||
bool operator<(Expression const& _other) const; |
|||
}; |
|||
|
|||
/// Retrieves the id of the expression equivalence class resulting from the given item applied to the
|
|||
/// given classes, might also create a new one.
|
|||
Id find(AssemblyItem const& _item, Ids const& _arguments = {}); |
|||
/// @returns the canonical representative of an expression class.
|
|||
Expression const& representative(Id _id) const { return m_representatives.at(_id); } |
|||
/// @returns the number of classes.
|
|||
Id size() const { return m_representatives.size(); } |
|||
|
|||
std::string fullDAGToString(Id _id) const; |
|||
|
|||
private: |
|||
/// Tries to simplify the given expression.
|
|||
/// @returns its class if it possible or Id(-1) otherwise.
|
|||
/// @param _secondRun is set to true for the second run where arguments of commutative expressions are reversed
|
|||
Id tryToSimplify(Expression const& _expr, bool _secondRun = false); |
|||
|
|||
/// Rebuilds an expression from a (matched) pattern.
|
|||
Id rebuildExpression(ExpressionTemplate const& _template); |
|||
|
|||
std::vector<std::pair<Pattern, std::function<Pattern()>>> createRules() const; |
|||
|
|||
/// Expression equivalence class representatives - we only store one item of an equivalence.
|
|||
std::vector<Expression> m_representatives; |
|||
std::vector<std::shared_ptr<AssemblyItem>> m_spareAssemblyItem; |
|||
}; |
|||
|
|||
/**
|
|||
* Pattern to match against an expression. |
|||
* Also stores matched expressions to retrieve them later, for constructing new expressions using |
|||
* ExpressionTemplate. |
|||
*/ |
|||
class Pattern |
|||
{ |
|||
public: |
|||
using Expression = ExpressionClasses::Expression; |
|||
using Id = ExpressionClasses::Id; |
|||
|
|||
// Matches a specific constant value.
|
|||
Pattern(unsigned _value): Pattern(u256(_value)) {} |
|||
// Matches a specific constant value.
|
|||
Pattern(u256 const& _value): m_type(Push), m_requireDataMatch(true), m_data(_value) {} |
|||
// Matches a specific assembly item type or anything if not given.
|
|||
Pattern(AssemblyItemType _type = UndefinedItem): m_type(_type) {} |
|||
// Matches a given instruction with given arguments
|
|||
Pattern(Instruction _instruction, std::vector<Pattern> const& _arguments = {}); |
|||
/// Sets this pattern to be part of the match group with the identifier @a _group.
|
|||
/// Inside one rule, all patterns in the same match group have to match expressions from the
|
|||
/// same expression equivalence class.
|
|||
void setMatchGroup(unsigned _group, std::map<unsigned, Expression const*>& _matchGroups); |
|||
unsigned matchGroup() const { return m_matchGroup; } |
|||
bool matches(Expression const& _expr, ExpressionClasses const& _classes) const; |
|||
|
|||
AssemblyItem toAssemblyItem() const { return AssemblyItem(m_type, m_data); } |
|||
std::vector<Pattern> arguments() const { return m_arguments; } |
|||
|
|||
/// @returns the id of the matched expression if this pattern is part of a match group.
|
|||
Id id() const { return matchGroupValue().id; } |
|||
/// @returns the data of the matched expression if this pattern is part of a match group.
|
|||
u256 d() const { return matchGroupValue().item->data(); } |
|||
|
|||
std::string toString() const; |
|||
|
|||
private: |
|||
bool matchesBaseItem(AssemblyItem const& _item) const; |
|||
Expression const& matchGroupValue() const; |
|||
|
|||
AssemblyItemType m_type; |
|||
bool m_requireDataMatch = false; |
|||
u256 m_data = 0; |
|||
std::vector<Pattern> m_arguments; |
|||
unsigned m_matchGroup = 0; |
|||
std::map<unsigned, Expression const*>* m_matchGroups = nullptr; |
|||
}; |
|||
|
|||
/**
|
|||
* Template for a new expression that can be built from matched patterns. |
|||
*/ |
|||
struct ExpressionTemplate |
|||
{ |
|||
using Expression = ExpressionClasses::Expression; |
|||
using Id = ExpressionClasses::Id; |
|||
explicit ExpressionTemplate(Pattern const& _pattern); |
|||
std::string toString() const; |
|||
bool hasId = false; |
|||
/// Id of the matched expression, if available.
|
|||
Id id = Id(-1); |
|||
// Otherwise, assembly item.
|
|||
AssemblyItem item = UndefinedItem; |
|||
std::vector<ExpressionTemplate> arguments; |
|||
}; |
|||
|
|||
} |
|||
} |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue