Browse Source

Merge branch 'develop' of https://github.com/ethereum/cpp-ethereum into

mix_test

Conflicts:
	mix/qml/LogsPane.qml
cl-refactor
arkpar 10 years ago
parent
commit
8ab55442ce
  1. 3
      CMakeLists.txt
  2. 405
      abi/main.cpp
  3. 2
      alethzero/MainWin.cpp
  4. 10
      cmake/EthDependencies.cmake
  5. 29
      cmake/Findjson_rpc_cpp.cmake
  6. 27
      libethereum/ABI.cpp
  7. 64
      libethereum/ABI.h
  8. 14
      libethereum/Client.cpp
  9. 34
      libethereum/Client.h
  10. 15
      libethereum/ClientBase.cpp
  11. 7
      libethereum/ClientBase.h
  12. 184
      libevmcore/Assembly.cpp
  13. 55
      libevmcore/Assembly.h
  14. 135
      libevmcore/AssemblyItem.cpp
  15. 92
      libevmcore/AssemblyItem.h
  16. 213
      libevmcore/CommonSubexpressionEliminator.cpp
  17. 60
      libevmcore/CommonSubexpressionEliminator.h
  18. 1
      libevmcore/Exceptions.h
  19. 371
      libevmcore/ExpressionClasses.cpp
  20. 150
      libevmcore/ExpressionClasses.h
  21. 1
      libnatspec/NatspecExpressionEvaluator.h
  22. 26
      libp2p/Common.cpp
  23. 9
      libp2p/Common.h
  24. 8
      libp2p/Host.cpp
  25. 48
      libp2p/NodeTable.cpp
  26. 15
      libp2p/RLPxHandshake.cpp
  27. 22
      libsolidity/AST.cpp
  28. 4
      libsolidity/AST.h
  29. 2
      libsolidity/ExpressionCompiler.cpp
  30. 4
      libsolidity/InterfaceHandler.cpp
  31. 40
      libsolidity/Types.cpp
  32. 22
      libsolidity/Types.h
  33. 1
      libtestutils/FixedClient.h
  34. 21
      mix/ClientModel.cpp
  35. 16
      mix/ContractCallDataEncoder.cpp
  36. 6
      mix/DebuggingStateWrapper.h
  37. 10
      mix/MixClient.cpp
  38. 2
      mix/QFunctionDefinition.cpp
  39. 20
      mix/qml/CodeEditorView.qml
  40. 26
      mix/qml/LogsPane.qml
  41. 1
      mix/qml/QHashTypeView.qml
  42. 1
      mix/qml/QIntTypeView.qml
  43. 1
      mix/qml/QStringTypeView.qml
  44. 5
      mix/qml/StatusPane.qml
  45. 11
      mix/qml/StructView.qml
  46. 5
      mix/qml/WebCodeEditor.qml
  47. 6
      mix/qml/html/WebContainer.html
  48. 61
      mix/qml/html/cm/anyword-hint.js
  49. 6
      mix/qml/html/cm/codemirror.css
  50. 118
      mix/qml/html/cm/mark-selection.js
  51. 34
      mix/qml/html/cm/show-hint.css
  52. 15
      mix/qml/html/cm/solarized.css
  53. 12
      mix/qml/html/cm/solidityToken.js
  54. 1
      mix/qml/html/codeeditor.html
  55. 19
      mix/qml/html/codeeditor.js
  56. 1
      mix/web.qrc
  57. 6
      neth/main.cpp
  58. 39
      test/CMakeLists.txt
  59. 202
      test/ClientBase.cpp
  60. 102
      test/SolidityNameAndTypeResolution.cpp
  61. 115
      test/SolidityOptimizer.cpp
  62. 16
      test/TestHelper.cpp
  63. 2
      test/TestHelper.h
  64. 117
      test/TestUtils.cpp
  65. 82
      test/TestUtils.h
  66. 38
      test/net.cpp

3
CMakeLists.txt

@ -229,9 +229,6 @@ if (NOT JUSTTESTS)
endif()
enable_testing()
add_test(NAME alltests WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test COMMAND testeth)
#unset(TARGET_PLATFORM CACHE)
if (WIN32)

405
abi/main.cpp

@ -21,6 +21,7 @@
*/
#include <fstream>
#include <iostream>
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>
#include "../test/JsonSpiritHeaders.h"
#include <libdevcore/CommonIO.h>
@ -105,13 +106,45 @@ enum class Base
Fixed
};
static const map<Base, string> s_bases =
{
{ Base::Bytes, "bytes" },
{ Base::Address, "address" },
{ Base::Int, "int" },
{ Base::Uint, "uint" },
{ Base::Fixed, "fixed" }
};
struct ABIType
{
Base base = Base::Unknown;
unsigned size = 32;
unsigned ssize = 0;
vector<int> dims;
string name;
ABIType() = default;
ABIType(std::string const& _type, std::string const& _name):
name(_name)
{
string rest;
for (auto const& i: s_bases)
if (boost::algorithm::starts_with(_type, i.second))
{
base = i.first;
rest = _type.substr(i.second.size());
}
if (base == Base::Unknown)
throw InvalidFormat();
boost::regex r("(\\d*)(x(\\d+))?((\\[\\d*\\])*)");
boost::smatch res;
boost::regex_match(rest, res, r);
size = res[1].length() > 0 ? stoi(res[1]) : 0;
ssize = res[3].length() > 0 ? stoi(res[3]) : 0;
boost::regex r2("\\[(\\d*)\\](.*)");
for (rest = res[4]; boost::regex_match(rest, res, r2); rest = res[2])
dims.push_back(!res[1].length() ? -1 : stoi(res[1]));
}
ABIType(std::string const& _s)
{
if (_s.size() < 1)
@ -129,17 +162,27 @@ struct ABIType
{
if (base == Base::Fixed)
size = ssize = 16;
else if (base == Base::Address || base == Base::Bytes)
size = 0;
else
size = 32;
return;
}
if (_s.find_first_of('x') == string::npos)
size = stoi(_s.substr(1));
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));
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
@ -147,7 +190,7 @@ struct ABIType
string ret;
switch (base)
{
case Base::Bytes: ret = "bytes" + toString(size); break;
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;
@ -159,11 +202,42 @@ struct ABIType
return ret;
}
bool isBytes() const { return base == Base::Bytes && !size; }
string render(bytes const& _data) const
{
if (base == Base::Uint)
return toString(fromBigEndian<u256>(_data));
else if (base == Base::Int)
return toString((s256)fromBigEndian<u256>(_data));
else if (base == Base::Address)
return toString(Address(h256(_data)));
else
return toHex(_data);
}
void noteHexInput(unsigned _nibbles) { if (base == Base::Unknown) { if (_nibbles == 40) base = Base::Address; else { base = Base::Bytes; size = _nibbles / 2; } } }
void noteBinaryInput() { if (base == Base::Unknown) { base = Base::Bytes; size = 32; } }
void noteDecimalInput() { if (base == Base::Unknown) { base = Base::Uint; size = 32; } }
};
bytes aligned(bytes const& _b, ABIType _t, Format _f, unsigned _length)
{
(void)_t;
bytes ret = _b;
while (ret.size() < _length)
if (_f == Format::Binary)
ret.push_back(0);
else
ret.insert(ret.begin(), 0);
while (ret.size() > _length)
if (_f == Format::Binary)
ret.pop_back();
else
ret.erase(ret.begin());
return ret;
}
tuple<bytes, ABIType, Format> fromUser(std::string const& _arg, Tristate _prefix, Tristate _typing)
{
ABIType type;
@ -185,7 +259,7 @@ tuple<bytes, ABIType, Format> fromUser(std::string const& _arg, Tristate _prefix
type.noteHexInput(val.size() - 2);
return make_tuple(fromHex(val), type, Format::Hex);
}
if (val.substr(0, 1) == ".")
if (val.substr(0, 1) == "+")
{
type.noteDecimalInput();
return make_tuple(toCompactBigEndian(bigint(val.substr(1))), type, Format::Decimal);
@ -214,6 +288,235 @@ tuple<bytes, ABIType, Format> fromUser(std::string const& _arg, Tristate _prefix
throw InvalidUserString();
}
struct ABIMethod
{
string name;
vector<ABIType> ins;
vector<ABIType> outs;
bool isConstant = false;
// isolation *IS* documentation.
ABIMethod() = default;
ABIMethod(js::mObject _o)
{
name = _o["name"].get_str();
isConstant = _o["constant"].get_bool();
if (_o.count("inputs"))
for (auto const& i: _o["inputs"].get_array())
{
js::mObject a = i.get_obj();
ins.push_back(ABIType(a["type"].get_str(), a["name"].get_str()));
}
if (_o.count("outputs"))
for (auto const& i: _o["outputs"].get_array())
{
js::mObject a = i.get_obj();
outs.push_back(ABIType(a["type"].get_str(), a["name"].get_str()));
}
}
ABIMethod(string const& _name, vector<ABIType> const& _args)
{
name = _name;
ins = _args;
}
string sig() const
{
string methodArgs;
for (auto const& arg: ins)
methodArgs += (methodArgs.empty() ? "" : ",") + arg.canon();
return name + "(" + methodArgs + ")";
}
FixedHash<4> id() const { return FixedHash<4>(sha3(sig())); }
std::string solidityDeclaration() const
{
ostringstream ss;
ss << "function " << name << "(";
int f = 0;
for (ABIType const& i: ins)
ss << (f++ ? ", " : "") << i.canon() << " " << i.name;
ss << ") ";
if (isConstant)
ss << "constant ";
if (!outs.empty())
{
ss << "returns (";
f = 0;
for (ABIType const& i: outs)
ss << (f ? ", " : "") << i.canon() << " " << i.name;
ss << ")";
}
return ss.str();
}
bytes encode(vector<pair<bytes, Format>> const& _params) const
{
// ALL WRONG!!!!
// INARITIES SHOULD BE HEIRARCHICAL!
bytes ret = name.empty() ? bytes() : id().asBytes();
unsigned pi = 0;
vector<unsigned> inArity;
for (ABIType const& i: ins)
{
unsigned arity = 1;
for (auto j: i.dims)
if (j == -1)
{
ret += aligned(_params[pi].first, ABIType(), Format::Decimal, 32);
arity *= fromBigEndian<unsigned>(_params[pi].first);
pi++;
}
else
arity *= j;
if (i.isBytes())
for (unsigned i = 0; i < arity; ++i)
inArity.push_back(arity);
}
unsigned ii = 0;
for (ABIType const& i: ins)
{
for (unsigned j = 0; j < inArity[ii]; ++j)
{
if (i.base == Base::Bytes && !i.size)
{
ret += _params[pi].first;
while (ret.size() % 32 != 0)
ret.push_back(0);
}
else
ret += aligned(_params[pi].first, i, _params[pi].second, 32);
++pi;
}
++ii;
}
return ret;
}
string decode(bytes const& _data, int _index = -1)
{
stringstream out;
if (_index == -1)
out << "[";
unsigned di = 0;
vector<ABIType> souts;
vector<unsigned> catDims;
for (ABIType a: outs)
{
unsigned q = 1;
for (auto& i: a.dims)
{
for (unsigned j = 0; j < q; ++j)
if (i == -1)
{
catDims.push_back(fromBigEndian<unsigned>(bytesConstRef(&_data).cropped(di, 32)));
di += 32;
}
q *= i;
}
if (a.isBytes())
souts.push_back(a);
}
for (ABIType const& a: souts)
{
auto put = [&]() {
out << a.render(bytesConstRef(&_data).cropped(di, 32).toBytes()) << ", ";
di += 32;
};
function<void(vector<int>)> putDim = [&](vector<int> addr) {
if (addr.size() == a.dims.size())
put();
else
{
out << "[";
auto d = addr;
addr.push_back(0);
for (addr.back() = 0; addr.back() < a.dims[addr.size() - 1]; ++addr.back())
{
if (addr.back())
out << ", ";
putDim(addr);
}
out << "]";
}
};
putDim(vector<int>());
if (_index == -1)
out << ", ";
}
(void)_data;
if (_index == -1)
out << "]";
return out.str();
}
};
string canonSig(string const& _name, vector<ABIType> const& _args)
{
string methodArgs;
for (auto const& arg: _args)
methodArgs += (methodArgs.empty() ? "" : ",") + arg.canon();
return _name + "(" + methodArgs + ")";
}
struct UnknownMethod: public Exception {};
struct OverloadedMethod: public Exception {};
class ABI
{
public:
ABI() = default;
ABI(std::string const& _json)
{
js::mValue v;
js::read_string(_json, v);
for (auto const& i: v.get_array())
{
js::mObject o = i.get_obj();
if (o["type"].get_str() != "function")
continue;
ABIMethod m(o);
m_methods[m.id()] = m;
}
}
ABIMethod method(string _nameOrSig, vector<ABIType> const& _args) const
{
auto id = FixedHash<4>(sha3(_nameOrSig));
if (!m_methods.count(id))
id = FixedHash<4>(sha3(canonSig(_nameOrSig, _args)));
if (!m_methods.count(id))
for (auto const& m: m_methods)
if (m.second.name == _nameOrSig)
{
if (m_methods.count(id))
throw OverloadedMethod();
id = m.first;
}
if (m_methods.count(id))
return m_methods.at(id);
throw UnknownMethod();
}
friend ostream& operator<<(ostream& _out, ABI const& _abi);
private:
map<FixedHash<4>, ABIMethod> m_methods;
};
ostream& operator<<(ostream& _out, ABI const& _abi)
{
_out << "contract {" << endl;
for (auto const& i: _abi.m_methods)
_out << " " << i.second.solidityDeclaration() << "; // " << i.first.abridged() << endl;
_out << "}" << endl;
return _out;
}
void userOutput(ostream& _out, bytes const& _data, Encoding _e)
{
switch (_e)
@ -226,20 +529,11 @@ void userOutput(ostream& _out, bytes const& _data, Encoding _e)
}
}
bytes aligned(bytes const& _b, ABIType _t, Format _f, unsigned _length)
template <unsigned n, class T> vector<typename std::remove_reference<decltype(get<n>(T()))>::type> retrieve(vector<T> const& _t)
{
(void)_t;
bytes ret = _b;
while (ret.size() < _length)
if (_f == Format::Binary)
ret.push_back(0);
else
ret.insert(ret.begin(), 0);
while (ret.size() > _length)
if (_f == Format::Binary)
ret.pop_back();
else
ret.erase(ret.begin());
vector<typename std::remove_reference<decltype(get<n>(T()))>::type> ret;
for (T const& i: _t)
ret.push_back(get<n>(i));
return ret;
}
@ -255,7 +549,8 @@ int main(int argc, char** argv)
bool clearNulls = false;
bool verbose = false;
int outputIndex = -1;
vector<tuple<bytes, ABIType, Format>> args;
vector<pair<bytes, Format>> params;
vector<ABIType> args;
for (int i = 1; i < argc; ++i)
{
@ -297,41 +592,65 @@ int main(int argc, char** argv)
else if (method.empty())
method = arg;
else
args.push_back(fromUser(arg, prefix, typePrefix));
{
auto u = fromUser(arg, prefix, typePrefix);
args.push_back(get<1>(u));
params.push_back(make_pair(get<0>(u), get<2>(u)));
}
}
string abi;
if (abiFile == "--")
for (int i = cin.get(); i != -1; i = cin.get())
abi.push_back((char)i);
else if (!abiFile.empty())
abi = contentsString(abiFile);
string abiData;
if (abiFile.empty())
abiData = contentsString(abiFile);
if (mode == Mode::Encode)
{
bytes ret;
if (abi.empty())
ABIMethod m;
if (abiData.empty())
m = ABIMethod(method, args);
else
{
if (!method.empty())
ABI abi(abiData);
if (verbose)
cerr << "ABI:" << endl << abi;
try {
m = abi.method(method, args);
}
catch(...)
{
string methodArgs;
for (auto const& arg: args)
methodArgs += (methodArgs.empty() ? "" : ",") + get<1>(arg).canon();
ret = FixedHash<4>(sha3(method + "(" + methodArgs + ")")).asBytes();
if (verbose)
cerr << "Method signature: " << (method + "(" + methodArgs + ")") << endl;
cerr << "Unknown method in ABI." << endl;
exit(-1);
}
for (tuple<bytes, ABIType, Format> const& arg: args)
ret += aligned(get<0>(arg), get<1>(arg), get<2>(arg), 32);
}
else
{
// TODO: read abi.
}
userOutput(cout, ret, encoding);
userOutput(cout, m.encode(params), encoding);
}
else if (mode == Mode::Decode)
{
if (abiData.empty())
{
cerr << "Please specify an ABI file." << endl;
exit(-1);
}
else
{
ABI abi(abiData);
ABIMethod m;
if (verbose)
cerr << "ABI:" << endl << abi;
try {
m = abi.method(method, args);
}
catch(...)
{
cerr << "Unknown method in ABI." << endl;
exit(-1);
}
string encoded;
for (int i = cin.get(); i != -1; i = cin.get())
encoded.push_back((char)i);
cout << m.decode(fromHex(encoded));
}
// TODO: read abi to determine output format.
(void)encoding;
(void)clearZeroes;

2
alethzero/MainWin.cpp

@ -263,6 +263,7 @@ unsigned Main::installWatch(LogFilter const& _tf, WatchHandler const& _f)
{
auto ret = ethereum()->installWatch(_tf);
m_handlers[ret] = _f;
_f(LocalisedLogEntries());
return ret;
}
@ -270,6 +271,7 @@ unsigned Main::installWatch(dev::h256 _tf, WatchHandler const& _f)
{
auto ret = ethereum()->installWatch(_tf, Reaping::Manual);
m_handlers[ret] = _f;
_f(LocalisedLogEntries());
return ret;
}

10
cmake/EthDependencies.cmake

@ -48,16 +48,10 @@ find_package (Jsoncpp 0.60 REQUIRED)
message(" - Jsoncpp header: ${JSONCPP_INCLUDE_DIRS}")
message(" - Jsoncpp lib : ${JSONCPP_LIBRARIES}")
# TODO the JsonRpcCpp package does not yet check for correct version number
# json-rpc-cpp support is currently not mandatory
# TODO make headless client optional
# TODO get rid of -DETH_JSONRPC
# TODO add EXACT once we commit ourselves to cmake 3.x
if (JSONRPC)
find_package (JsonRpcCpp 0.3.2)
if (NOT JSON_RPC_CPP_FOUND)
message (FATAL_ERROR "JSONRPC 0.3.2. not found")
endif()
find_package (json_rpc_cpp 0.4 REQUIRED)
message (" - json-rpc-cpp header: ${JSON_RPC_CPP_INCLUDE_DIRS}")
message (" - json-rpc-cpp lib : ${JSON_RPC_CPP_LIBRARIES}")
add_definitions(-DETH_JSONRPC)

29
cmake/FindJsonRpcCpp.cmake → cmake/Findjson_rpc_cpp.cmake

@ -10,6 +10,11 @@
# JSON_RPC_CPP_SERVER_LIBRARIES, the libraries needed to use json-rpc-cpp-server
# JSON_RPC_CPP_CLIENT_LIBRARIES, the libraries needed to use json-rpc-cpp-client
# JSON_RCP_CPP_FOUND, If false, do not try to use json-rpc-cpp.
# JSON_RPC_CPP_VERSION, version of library
# JSON_RPC_CPP_VERSION_MAJOR
# JSON_RPC_CPP_VERSION_MINOR
# JSON_RPC_CPP_VERSION_PATCH
# only look in default directories
find_path(
@ -90,10 +95,28 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
endif()
if (JSON_RPC_CPP_INCLUDE_DIR)
set (JSON_RPC_CPP_VERSION_HEADER "${JSON_RPC_CPP_INCLUDE_DIR}/jsonrpccpp/version.h")
if (EXISTS ${JSON_RPC_CPP_VERSION_HEADER})
file (STRINGS ${JSON_RPC_CPP_VERSION_HEADER} JSON_RPC_CPP_VERSION_MAJOR REGEX "^#define JSONRPC_CPP_MAJOR_VERSION[ \t]+[0-9]+$")
file (STRINGS ${JSON_RPC_CPP_VERSION_HEADER} JSON_RPC_CPP_VERSION_MINOR REGEX "^#define JSONRPC_CPP_MINOR_VERSION[ \t]+[0-9]+$")
file (STRINGS ${JSON_RPC_CPP_VERSION_HEADER} JSON_RPC_CPP_VERSION_PATCH REGEX "^#define JSONRPC_CPP_PATCH_VERSION[ \t]+[0-9]+$")
string (REGEX REPLACE "^#define JSONRPC_CPP_MAJOR_VERSION[ \t]+([0-9]+)" "\\1" JSON_RPC_CPP_VERSION_MAJOR ${JSON_RPC_CPP_VERSION_MAJOR})
string (REGEX REPLACE "^#define JSONRPC_CPP_MINOR_VERSION[ \t]+([0-9]+)" "\\1" JSON_RPC_CPP_VERSION_MINOR ${JSON_RPC_CPP_VERSION_MINOR})
string (REGEX REPLACE "^#define JSONRPC_CPP_PATCH_VERSION[ \t]+([0-9]+)" "\\1" JSON_RPC_CPP_VERSION_PATCH ${JSON_RPC_CPP_VERSION_PATCH})
set (JSON_RPC_CPP_VERSION ${JSON_RPC_CPP_VERSION_MAJOR}.${JSON_RPC_CPP_VERSION_MINOR}.${JSON_RPC_CPP_VERSION_PATCH})
endif()
endif()
# handle the QUIETLY and REQUIRED arguments and set JSON_RPC_CPP_FOUND to TRUE
# if all listed variables are TRUE, hide their existence from configuration view
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(json_rpc_cpp DEFAULT_MSG
JSON_RPC_CPP_COMMON_LIBRARY JSON_RPC_CPP_SERVER_LIBRARY JSON_RPC_CPP_CLIENT_LIBRARY JSON_RPC_CPP_INCLUDE_DIR)
mark_as_advanced (JSON_RPC_CPP_COMMON_LIBRARY JSON_RPC_CPP_SERVER_LIBRARY JSON_RPC_CPP_CLIENT_LIBRARY JSON_RPC_CPP_INCLUDE_DIR)
find_package_handle_standard_args(
json_rpc_cpp
REQUIRED_VARS JSON_RPC_CPP_INCLUDE_DIR JSON_RPC_CPP_COMMON_LIBRARY JSON_RPC_CPP_SERVER_LIBRARY JSON_RPC_CPP_CLIENT_LIBRARY
VERSION_VAR JSON_RPC_CPP_VERSION
)
mark_as_advanced (JSON_RPC_CPP_INCLUDE_DIR JSON_RPC_CPP_COMMON_LIBRARY JSON_RPC_CPP_SERVER_LIBRARY JSON_RPC_CPP_CLIENT_LIBRARY)

27
libethereum/ABI.cpp

@ -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;

64
libethereum/ABI.h

@ -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);
}
}
}

14
libethereum/Client.cpp

@ -250,14 +250,14 @@ void Client::noteChanged(h256Set const& _filters)
if (_filters.size())
cnote << "noteChanged(" << _filters << ")";
// accrue all changes left in each filter into the watches.
for (auto& i: m_watches)
if (_filters.count(i.second.id))
for (auto& w: m_watches)
if (_filters.count(w.second.id))
{
cwatch << "!!!" << i.first << i.second.id;
if (m_filters.count(i.second.id))
i.second.changes += m_filters.at(i.second.id).changes;
else
i.second.changes.push_back(LocalisedLogEntry(SpecialLogEntry, 0));
cwatch << "!!!" << w.first << w.second.id;
if (m_filters.count(w.second.id)) // Normal filtering watch
w.second.changes += m_filters.at(w.second.id).changes;
else // Special ('pending'/'latest') watch
w.second.changes.push_back(LocalisedLogEntry(SpecialLogEntry, 0));
}
// clear the filters now.
for (auto& i: m_filters)

34
libethereum/Client.h

@ -41,6 +41,7 @@
#include "State.h"
#include "CommonNet.h"
#include "Miner.h"
#include "ABI.h"
#include "ClientBase.h"
namespace dev
@ -71,37 +72,6 @@ private:
std::string m_path;
};
static const int GenesisBlock = INT_MIN;
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);
}
class RemoteMiner: public Miner
{
public:
@ -201,6 +171,8 @@ public:
// Mining stuff:
void setAddress(Address _us) { WriteGuard l(x_stateDB); m_preMine.setAddress(_us); }
/// Check block validity prior to mining.
bool miningParanoia() const { return m_paranoia; }
/// Change whether we check block validity prior to mining.

15
libethereum/ClientBase.cpp

@ -224,6 +224,7 @@ unsigned ClientBase::installWatch(h256 _h, Reaping _r)
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);
@ -231,6 +232,7 @@ unsigned ClientBase::installWatch(h256 _h, Reaping _r)
Guard l(x_filtersWatches);
swap(m_watches[ret].changes, ch);
}
#endif
return ret;
}
@ -260,9 +262,9 @@ LocalisedLogEntries ClientBase::peekWatch(unsigned _watchId) const
{
Guard l(x_filtersWatches);
cwatch << "peekWatch" << _watchId;
// 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();
// 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;
}
@ -272,9 +274,9 @@ LocalisedLogEntries ClientBase::checkWatch(unsigned _watchId)
Guard l(x_filtersWatches);
LocalisedLogEntries ret;
cwatch << "checkWatch" << _watchId;
// 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();
// 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();
@ -391,11 +393,6 @@ u256 ClientBase::gasLimitRemaining() const
return postMine().gasLimitRemaining();
}
void ClientBase::setAddress(Address _us)
{
preMine().setAddress(_us);
}
Address ClientBase::address() const
{
return preMine().address();

7
libethereum/ClientBase.h

@ -51,7 +51,11 @@ struct ClientWatch
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();
};
@ -127,7 +131,7 @@ public:
virtual u256 gasLimitRemaining() const override;
/// Set the coinbase address
virtual void setAddress(Address _us) override;
virtual void setAddress(Address _us) = 0;
/// Get the coinbase address
virtual Address address() const override;
@ -161,7 +165,6 @@ protected:
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.
};
}}

184
libevmcore/Assembly.cpp

@ -28,122 +28,6 @@ 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;
}
unsigned Assembly::bytesRequired() const
{
for (unsigned br = 1;; ++br)
{
unsigned ret = 1;
for (auto const& i: m_data)
ret += i.second.size();
for (AssemblyItem const& i: m_items)
ret += i.bytesRequired(br);
if (dev::bytesRequired(ret) <= br)
return ret;
}
}
void Assembly::append(Assembly const& _a)
{
auto newDeposit = m_deposit + _a.deposit();
@ -180,11 +64,19 @@ void Assembly::append(Assembly const& _a, int _deposit)
}
}
ostream& dev::eth::operator<<(ostream& _out, AssemblyItemsConstRef _i)
unsigned Assembly::bytesRequired() const
{
for (AssemblyItem const& i: _i)
_out << i;
return _out;
for (unsigned br = 1;; ++br)
{
unsigned ret = 1;
for (auto const& i: m_data)
ret += i.second.size();
for (AssemblyItem const& i: m_items)
ret += i.bytesRequired(br);
if (dev::bytesRequired(ret) <= br)
return ret;
}
}
string Assembly::getLocationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const
@ -288,18 +180,6 @@ inline bool matches(AssemblyItemsConstRef _a, AssemblyItemsConstRef _b)
return true;
}
//@todo this has to move to a special optimizer class soon
template<class Iterator>
unsigned bytesRequiredBySlice(Iterator _begin, Iterator _end)
{
// this is only used in the optimizer, so we can provide a guess for the address length
unsigned addressLength = 4;
unsigned size = 0;
for (; _begin != _end; ++_begin)
size += _begin->bytesRequired(addressLength);
return size;
}
struct OptimiserChannel: public LogChannel { static const char* name() { return "OPT"; } static const int verbosity = 12; };
#define copt dev::LogOutputStream<OptimiserChannel, true>()
@ -307,16 +187,6 @@ Assembly& Assembly::optimise(bool _enable)
{
if (!_enable)
return *this;
map<Instruction, function<u256(u256, u256)>> const c_associative =
{
{ Instruction::ADD, [](u256 a, u256 b)->u256{return a + b;} },
{ Instruction::MUL, [](u256 a, u256 b)->u256{return a * b;} },
{ Instruction::AND, [](u256 a, u256 b)->u256{return a & b;} },
{ Instruction::OR, [](u256 a, u256 b)->u256{return a | b;} },
{ Instruction::XOR, [](u256 a, u256 b)->u256{return a ^ b;} },
};
std::vector<pair<AssemblyItem, u256>> const c_identities =
{ { Instruction::ADD, 0}, { Instruction::MUL, 1}, { Instruction::MOD, 0}, { Instruction::OR, 0}, { Instruction::XOR, 0} };
std::vector<pair<AssemblyItems, function<AssemblyItems(AssemblyItemsConstRef)>>> rules =
{
{ { Push, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } },
@ -329,22 +199,13 @@ Assembly& Assembly::optimise(bool _enable)
{ { Instruction::ISZERO, Instruction::ISZERO }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } },
};
for (auto const& i: c_associative)
{
rules.push_back({ { Push, Push, i.first }, [&](AssemblyItemsConstRef m) -> AssemblyItems { return { i.second(m[1].data(), m[0].data()) }; } });
rules.push_back({ { Push, i.first, Push, i.first }, [&](AssemblyItemsConstRef m) -> AssemblyItems { return { i.second(m[2].data(), m[0].data()), i.first }; } });
}
for (auto const& i: c_identities)
rules.push_back({{Push, i.first}, [&](AssemblyItemsConstRef m) -> AssemblyItems
{ return m[0].data() == i.second ? AssemblyItems() : m.toVector(); }});
// jump to next instruction
rules.push_back({ { PushTag, Instruction::JUMP, Tag }, [](AssemblyItemsConstRef m) -> AssemblyItems { if (m[0].m_data == m[2].m_data) return {m[2]}; else return m.toVector(); }});
copt << *this;
unsigned total = 0;
for (unsigned count = 1; count > 0; total += count)
{
copt << *this;
count = 0;
copt << "Performing common subexpression elimination...";
@ -353,11 +214,22 @@ Assembly& Assembly::optimise(bool _enable)
CommonSubexpressionEliminator eliminator;
auto orig = iter;
iter = eliminator.feedItems(iter, m_items.end());
AssemblyItems optItems = eliminator.getOptimizedItems();
copt << "Old size: " << (iter - orig) << ", new size: " << optItems.size();
if (optItems.size() < size_t(iter - orig))
AssemblyItems optItems;
bool shouldReplace = false;
try
{
optItems = eliminator.getOptimizedItems();
shouldReplace = (optItems.size() < size_t(iter - orig));
}
catch (StackTooDeepException const&)
{
// This might happen if the opcode reconstruction is not as efficient
// as the hand-crafted code.
}
if (shouldReplace)
{
// replace items
copt << "Old size: " << (iter - orig) << ", new size: " << optItems.size();
count++;
for (auto moveIter = optItems.begin(); moveIter != optItems.end(); ++orig, ++moveIter)
*orig = move(*moveIter);

55
libevmcore/Assembly.h

@ -27,6 +27,7 @@
#include <libdevcore/Assertions.h>
#include <libevmcore/SourceLocation.h>
#include <libevmcore/Instruction.h>
#include <libevmcore/AssemblyItem.h>
#include "Exceptions.h"
namespace dev
@ -34,60 +35,6 @@ 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)); }
class Assembly
{
public:

135
libevmcore/AssemblyItem.cpp

@ -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;
}

92
libevmcore/AssemblyItem.h

@ -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)); }
}
}

213
libevmcore/CommonSubexpressionEliminator.cpp

@ -32,39 +32,34 @@ using namespace dev::eth;
vector<AssemblyItem> CommonSubexpressionEliminator::getOptimizedItems()
{
map<int, EquivalenceClassId> initialStackContents;
map<int, EquivalenceClassId> targetStackContents;
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.first);
for (int height = minHeight; height <= max(0, m_stackHeight); ++height)
{
// make sure it is created
EquivalenceClassId c = getStackElement(height);
if (height <= 0)
initialStackContents[height] = getClass(AssemblyItem(dupInstruction(1 - height)));
if (height <= m_stackHeight)
targetStackContents[height] = c;
}
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, currentStackContents, targetStackContents);
//stream(cout, initialStackContents, targetStackContents);
return CSECodeGenerator().generateCode(initialStackContents, targetStackContents, m_equivalenceClasses);
return CSECodeGenerator(m_expressionClasses).generateCode(initialStackContents, targetStackContents);
}
ostream& CommonSubexpressionEliminator::stream(
ostream& _out,
map<int, EquivalenceClassId> _currentStack,
map<int, EquivalenceClassId> _targetStack
map<int, ExpressionClasses::Id> _currentStack,
map<int, ExpressionClasses::Id> _targetStack
) const
{
auto streamEquivalenceClass = [this](ostream& _out, EquivalenceClassId _id)
auto streamExpressionClass = [this](ostream& _out, ExpressionClasses::Id _id)
{
auto const& eqClass = m_equivalenceClasses.at(_id);
_out << " " << _id << ": " << *eqClass.first;
auto const& expr = m_expressionClasses.representative(_id);
_out << " " << _id << ": " << *expr.item;
_out << "(";
for (EquivalenceClassId arg: eqClass.second)
for (ExpressionClasses::Id arg: expr.arguments)
_out << dec << arg << ",";
_out << ")" << endl;
};
@ -74,24 +69,24 @@ ostream& CommonSubexpressionEliminator::stream(
_out << "Stack elements: " << endl;
for (auto const& it: m_stackElements)
{
_out << " " << dec << it.first.first << "(" << it.first.second << ") = ";
streamEquivalenceClass(_out, it.second);
_out << " " << dec << it.first << " = ";
streamExpressionClass(_out, it.second);
}
_out << "Equivalence classes: " << endl;
for (EquivalenceClassId eqClass = 0; eqClass < m_equivalenceClasses.size(); ++eqClass)
streamEquivalenceClass(_out, eqClass);
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 << ": ";
streamEquivalenceClass(_out, it.second);
streamExpressionClass(_out, it.second);
}
_out << "Target stack: " << endl;
for (auto const& it: _targetStack)
{
_out << " " << dec << it.first << ": ";
streamEquivalenceClass(_out, it.second);
streamExpressionClass(_out, it.second);
}
return _out;
@ -103,7 +98,7 @@ void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item)
{
if (_item.deposit() != 1)
BOOST_THROW_EXCEPTION(InvalidDeposit());
setStackElement(++m_stackHeight, getClass(_item, {}));
setStackElement(++m_stackHeight, m_expressionClasses.find(_item, {}));
}
else
{
@ -112,7 +107,7 @@ void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item)
if (SemanticInformation::isDupInstruction(_item))
setStackElement(
m_stackHeight + 1,
getStackElement(m_stackHeight - int(instruction) + int(Instruction::DUP1))
stackElement(m_stackHeight - int(instruction) + int(Instruction::DUP1))
);
else if (SemanticInformation::isSwapInstruction(_item))
swapStackElements(
@ -121,139 +116,45 @@ void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item)
);
else if (instruction != Instruction::POP)
{
vector<EquivalenceClassId> arguments(info.args);
vector<ExpressionClasses::Id> arguments(info.args);
for (int i = 0; i < info.args; ++i)
arguments[i] = getStackElement(m_stackHeight - i);
setStackElement(m_stackHeight + _item.deposit(), getClass(_item, arguments));
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, EquivalenceClassId _class)
void CommonSubexpressionEliminator::setStackElement(int _stackHeight, ExpressionClasses::Id _class)
{
unsigned nextSequence = getNextStackElementSequence(_stackHeight);
m_stackElements[make_pair(_stackHeight, nextSequence)] = _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."));
EquivalenceClassId classA = getStackElement(_stackHeightA);
EquivalenceClassId classB = getStackElement(_stackHeightB);
// ensure they are created
stackElement(_stackHeightA);
stackElement(_stackHeightB);
unsigned nextSequenceA = getNextStackElementSequence(_stackHeightA);
unsigned nextSequenceB = getNextStackElementSequence(_stackHeightB);
m_stackElements[make_pair(_stackHeightA, nextSequenceA)] = classB;
m_stackElements[make_pair(_stackHeightB, nextSequenceB)] = classA;
swap(m_stackElements[_stackHeightA], m_stackElements[_stackHeightB]);
}
EquivalenceClassId CommonSubexpressionEliminator::getStackElement(int _stackHeight)
ExpressionClasses::Id CommonSubexpressionEliminator::stackElement(int _stackHeight)
{
// retrieve class by last sequence number
unsigned nextSequence = getNextStackElementSequence(_stackHeight);
if (nextSequence > 0)
return m_stackElements[make_pair(_stackHeight, nextSequence - 1)];
if (m_stackElements.count(_stackHeight))
return m_stackElements.at(_stackHeight);
// Stack element not found (not assigned yet), create new equivalence class.
if (_stackHeight > 0)
BOOST_THROW_EXCEPTION(OptimizerException() << errinfo_comment("Stack element accessed before assignment."));
if (_stackHeight <= -16)
BOOST_THROW_EXCEPTION(OptimizerException() << errinfo_comment("Stack too deep."));
// This is a special assembly item that refers to elements pre-existing on the initial stack.
m_spareAssemblyItem.push_back(make_shared<AssemblyItem>(dupInstruction(1 - _stackHeight)));
m_equivalenceClasses.push_back(make_pair(m_spareAssemblyItem.back().get(), EquivalenceClassIds()));
return m_stackElements[make_pair(_stackHeight, nextSequence)] = EquivalenceClassId(m_equivalenceClasses.size() - 1);
}
EquivalenceClassId CommonSubexpressionEliminator::getClass(
const AssemblyItem& _item,
EquivalenceClassIds const& _arguments
)
{
// TODO: do a clever search, i.e.
// - check for the presence of constants in the argument classes and do arithmetic
// - check whether the two items are equal for a SUB instruction
// - check whether 0 or 1 is in one of the classes for a MUL
EquivalenceClassIds args = _arguments;
if (SemanticInformation::isCommutativeOperation(_item))
sort(args.begin(), args.end());
//@todo use a better data structure for search here
for (EquivalenceClassId c = 0; c < m_equivalenceClasses.size(); ++c)
{
AssemblyItem const& classItem = *m_equivalenceClasses.at(c).first;
if (classItem != _item)
continue;
assertThrow(
args.size() == m_equivalenceClasses.at(c).second.size(),
OptimizerException,
"Equal assembly items with different number of arguments."
);
if (equal(args.begin(), args.end(), m_equivalenceClasses.at(c).second.begin()))
return c;
}
// constant folding
if (_item.type() == Operation && args.size() == 2 && all_of(
args.begin(),
args.end(),
[this](EquivalenceClassId eqc) { return m_equivalenceClasses.at(eqc).first->match(Push); }))
{
auto signextend = [](u256 const& _a, u256 const& _b) -> u256
{
if (_a >= 31)
return _b;
unsigned testBit = unsigned(_a) * 8 + 7;
u256 mask = (u256(1) << testBit) - 1;
return boost::multiprecision::bit_test(_b, testBit) ? _b | ~mask : _b & mask;
};
map<Instruction, function<u256(u256 const&, u256 const&)>> const arithmetics =
{
{ Instruction::SUB, [](u256 const& _a, u256 const& _b) -> u256 {return _a - _b; } },
{ Instruction::DIV, [](u256 const& _a, u256 const& _b) -> u256 {return _b == 0 ? 0 : _a / _b; } },
{ Instruction::SDIV, [](u256 const& _a, u256 const& _b) -> u256 { return _b == 0 ? 0 : s2u(u2s(_a) / u2s(_b)); } },
{ Instruction::MOD, [](u256 const& _a, u256 const& _b) -> u256 { return _b == 0 ? 0 : _a % _b; } },
{ Instruction::SMOD, [](u256 const& _a, u256 const& _b) -> u256 { return _b == 0 ? 0 : s2u(u2s(_a) % u2s(_b)); } },
{ Instruction::EXP, [](u256 const& _a, u256 const& _b) -> u256 { return (u256)boost::multiprecision::powm(bigint(_a), bigint(_b), bigint(1) << 256); } },
{ Instruction::SIGNEXTEND, signextend },
{ Instruction::LT, [](u256 const& _a, u256 const& _b) -> u256 { return _a < _b ? 1 : 0; } },
{ Instruction::GT, [](u256 const& _a, u256 const& _b) -> u256 { return _a > _b ? 1 : 0; } },
{ Instruction::SLT, [](u256 const& _a, u256 const& _b) -> u256 { return u2s(_a) < u2s(_b) ? 1 : 0; } },
{ Instruction::SGT, [](u256 const& _a, u256 const& _b) -> u256 { return u2s(_a) > u2s(_b) ? 1 : 0; } },
{ Instruction::EQ, [](u256 const& _a, u256 const& _b) -> u256 { return _a == _b ? 1 : 0; } },
{ Instruction::ADD, [](u256 const& _a, u256 const& _b) -> u256 { return _a + _b; } },
{ Instruction::MUL, [](u256 const& _a, u256 const& _b) -> u256 { return _a * _b; } },
{ Instruction::AND, [](u256 const& _a, u256 const& _b) -> u256 { return _a & _b; } },
{ Instruction::OR, [](u256 const& _a, u256 const& _b) -> u256 { return _a | _b; } },
{ Instruction::XOR, [](u256 const& _a, u256 const& _b) -> u256 { return _a ^ _b; } },
};
if (arithmetics.count(_item.instruction()))
{
u256 result = arithmetics.at(_item.instruction())(
m_equivalenceClasses.at(args[0]).first->data(),
m_equivalenceClasses.at(args[1]).first->data()
);
m_spareAssemblyItem.push_back(make_shared<AssemblyItem>(result));
return getClass(*m_spareAssemblyItem.back());
}
}
m_equivalenceClasses.push_back(make_pair(&_item, args));
return m_equivalenceClasses.size() - 1;
return m_stackElements[_stackHeight] = initialStackElement(_stackHeight);
}
unsigned CommonSubexpressionEliminator::getNextStackElementSequence(int _stackHeight)
ExpressionClasses::Id CommonSubexpressionEliminator::initialStackElement(int _stackHeight)
{
auto it = m_stackElements.upper_bound(make_pair(_stackHeight, unsigned(-1)));
if (it == m_stackElements.begin())
return 0;
--it;
if (it->first.first == _stackHeight)
return it->first.second + 1;
else
return 0;
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)
@ -318,15 +219,11 @@ bool SemanticInformation::isSwapInstruction(AssemblyItem const& _item)
}
AssemblyItems CSECodeGenerator::generateCode(
map<int, EquivalenceClassId> const& _initialStack,
map<int, EquivalenceClassId> const& _targetStackContents,
vector<pair<AssemblyItem const*, EquivalenceClassIds>> const& _equivalenceClasses
map<int, ExpressionClasses::Id> const& _initialStack,
map<int, ExpressionClasses::Id> const& _targetStackContents
)
{
// reset
*this = move(CSECodeGenerator());
m_stack = _initialStack;
m_equivalenceClasses = _equivalenceClasses;
for (auto const& item: m_stack)
if (!m_classPositions.count(item.second))
m_classPositions[item.second] = item.first;
@ -377,18 +274,18 @@ AssemblyItems CSECodeGenerator::generateCode(
return m_generatedItems;
}
void CSECodeGenerator::addDependencies(EquivalenceClassId _c)
void CSECodeGenerator::addDependencies(ExpressionClasses::Id _c)
{
if (m_neededBy.count(_c))
return;
for (EquivalenceClassId argument: m_equivalenceClasses.at(_c).second)
for (ExpressionClasses::Id argument: m_expressionClasses.representative(_c).arguments)
{
addDependencies(argument);
m_neededBy.insert(make_pair(argument, _c));
}
}
int CSECodeGenerator::generateClassElement(EquivalenceClassId _c)
int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c)
{
if (m_classPositions.count(_c))
{
@ -399,8 +296,8 @@ int CSECodeGenerator::generateClassElement(EquivalenceClassId _c)
);
return m_classPositions[_c];
}
EquivalenceClassIds const& arguments = m_equivalenceClasses.at(_c).second;
for (EquivalenceClassId arg: boost::adaptors::reverse(arguments))
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.
@ -458,7 +355,7 @@ int CSECodeGenerator::generateClassElement(EquivalenceClassId _c)
for (size_t i = 0; i < arguments.size(); ++i)
assertThrow(m_stack[m_stackHeight - i] == arguments[i], OptimizerException, "Expected arguments not present." );
AssemblyItem const& item = *m_equivalenceClasses.at(_c).first;
AssemblyItem const& item = *m_expressionClasses.representative(_c).item;
while (SemanticInformation::isCommutativeOperation(item) &&
!m_generatedItems.empty() &&
m_generatedItems.back() == AssemblyItem(Instruction::SWAP1))
@ -469,12 +366,12 @@ int CSECodeGenerator::generateClassElement(EquivalenceClassId _c)
m_classPositions[arg] = c_invalidPosition;
for (size_t i = 0; i < arguments.size(); ++i)
m_stack.erase(m_stackHeight - i);
appendItem(*m_equivalenceClasses.at(_c).first);
appendItem(*m_expressionClasses.representative(_c).item);
m_stack[m_stackHeight] = _c;
return m_classPositions[_c] = m_stackHeight;
}
bool CSECodeGenerator::canBeRemoved(EquivalenceClassId _element, EquivalenceClassId _result)
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.
@ -493,7 +390,7 @@ bool CSECodeGenerator::removeStackTopIfPossible()
if (m_stack.empty())
return false;
assertThrow(m_stack.count(m_stackHeight), OptimizerException, "");
EquivalenceClassId top = m_stack[m_stackHeight];
ExpressionClasses::Id top = m_stack[m_stackHeight];
if (!canBeRemoved(top))
return false;
m_generatedItems.push_back(AssemblyItem(Instruction::POP));
@ -505,7 +402,8 @@ bool CSECodeGenerator::removeStackTopIfPossible()
void CSECodeGenerator::appendDup(int _fromPosition)
{
int nr = 1 + m_stackHeight - _fromPosition;
assertThrow(1 <= nr && nr <= 16, OptimizerException, "Stack too deep.");
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];
@ -516,7 +414,8 @@ void CSECodeGenerator::appendSwapOrRemove(int _fromPosition)
if (_fromPosition == m_stackHeight)
return;
int nr = m_stackHeight - _fromPosition;
assertThrow(1 <= nr && nr <= 16, OptimizerException, "Stack too deep.");
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

60
libevmcore/CommonSubexpressionEliminator.h

@ -28,6 +28,7 @@
#include <ostream>
#include <libdevcore/CommonIO.h>
#include <libdevcore/Exceptions.h>
#include <libevmcore/ExpressionClasses.h>
namespace dev
{
@ -37,9 +38,6 @@ namespace eth
class AssemblyItem;
using AssemblyItems = std::vector<AssemblyItem>;
using EquivalenceClassId = unsigned;
using EquivalenceClassIds = std::vector<EquivalenceClassId>;
/**
* Optimizer step that performs common subexpression elimination and stack reorganisation,
* i.e. it tries to infer equality among expressions and compute the values of two expressions
@ -67,37 +65,32 @@ public:
/// Streams debugging information to @a _out.
std::ostream& stream(
std::ostream& _out,
std::map<int, EquivalenceClassId> _currentStack = std::map<int, EquivalenceClassId>(),
std::map<int, EquivalenceClassId> _targetStack = std::map<int, EquivalenceClassId>()
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, EquivalenceClassId _class);
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).
EquivalenceClassId getStackElement(int _stackHeight);
/// Retrieves the equivalence class resulting from the given item applied to the given classes,
/// might also create a new one.
EquivalenceClassId getClass(AssemblyItem const& _item, EquivalenceClassIds const& _arguments = {});
/// @returns the next sequence number of the given stack element.
unsigned getNextStackElementSequence(int _stackHeight);
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;
/// Mapping (stack height, sequence number) -> equivalence class
std::map<std::pair<int, unsigned>, EquivalenceClassId> m_stackElements;
/// Vector of equivalence class representatives - we only store one item of an equivalence
/// class and the index is used as identifier.
std::vector<std::pair<AssemblyItem const*, EquivalenceClassIds>> m_equivalenceClasses;
/// List of items generated during analysis.
std::vector<std::shared_ptr<AssemblyItem>> m_spareAssemblyItem;
/// 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;
};
/**
@ -121,27 +114,30 @@ struct SemanticInformation
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 resuts the state of the object for each call.
/// @note should only be called once on each object.
AssemblyItems generateCode(
std::map<int, EquivalenceClassId> const& _initialStack,
std::map<int, EquivalenceClassId> const& _targetStackContents,
std::vector<std::pair<AssemblyItem const*, EquivalenceClassIds>> const& _equivalenceClasses
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(EquivalenceClassId _c);
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(EquivalenceClassId _c);
int generateClassElement(ExpressionClasses::Id _c);
/// @returns true if @a _element can be removed - in general or, if given, while computing @a _result.
bool canBeRemoved(EquivalenceClassId _element, EquivalenceClassId _result = EquivalenceClassId(-1));
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();
@ -160,16 +156,16 @@ private:
/// Current height of the stack relative to the start.
int m_stackHeight = 0;
/// If (b, a) is in m_requests then b is needed to compute a.
std::multimap<EquivalenceClassId, EquivalenceClassId> m_neededBy;
std::multimap<ExpressionClasses::Id, ExpressionClasses::Id> m_neededBy;
/// Current content of the stack.
std::map<int, EquivalenceClassId> m_stack;
std::map<int, ExpressionClasses::Id> m_stack;
/// Current positions of equivalence classes, equal to c_invalidPosition if already deleted.
std::map<EquivalenceClassId, int> m_classPositions;
std::map<ExpressionClasses::Id, int> m_classPositions;
/// The actual eqivalence class items and how to compute them.
std::vector<std::pair<AssemblyItem const*, EquivalenceClassIds>> m_equivalenceClasses;
ExpressionClasses const& m_expressionClasses;
/// The set of equivalence classes that should be present on the stack at the end.
std::set<EquivalenceClassId> m_finalClasses;
std::set<ExpressionClasses::Id> m_finalClasses;
};
template <class _AssemblyItemIterator>

1
libevmcore/Exceptions.h

@ -32,6 +32,7 @@ struct AssemblyException: virtual Exception {};
struct InvalidDeposit: virtual AssemblyException {};
struct InvalidOpcode: virtual AssemblyException {};
struct OptimizerException: virtual AssemblyException {};
struct StackTooDeepException: virtual OptimizerException {};
}
}

371
libevmcore/ExpressionClasses.cpp

@ -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();
}

150
libevmcore/ExpressionClasses.h

@ -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;
};
}
}

1
libnatspec/NatspecExpressionEvaluator.h

@ -21,7 +21,6 @@
#pragma once
#include <QtCore/QObject>
#include <QtCore/QtCore>
#include <QtQml/QJSEngine>

26
libp2p/Common.cpp

@ -26,6 +26,16 @@ using namespace dev::p2p;
const unsigned dev::p2p::c_protocolVersion = 3;
bool p2p::isPublicAddress(std::string const& _addressToCheck)
{
return isPublicAddress(bi::address::from_string(_addressToCheck));
}
bool p2p::isPublicAddress(bi::address const& _addressToCheck)
{
return !(isPrivateAddress(_addressToCheck) || isLocalHostAddress(_addressToCheck));
}
// Helper function to determine if an address falls within one of the reserved ranges
// For V4:
// Class A "10.*", Class B "172.[16->31].*", Class C "192.168.*"
@ -55,6 +65,11 @@ bool p2p::isPrivateAddress(bi::address const& _addressToCheck)
return false;
}
bool p2p::isPrivateAddress(std::string const& _addressToCheck)
{
return isPrivateAddress(bi::address::from_string(_addressToCheck));
}
// Helper function to determine if an address is localhost
bool p2p::isLocalHostAddress(bi::address const& _addressToCheck)
{
@ -69,6 +84,11 @@ bool p2p::isLocalHostAddress(bi::address const& _addressToCheck)
return find(c_rejectAddresses.begin(), c_rejectAddresses.end(), _addressToCheck) != c_rejectAddresses.end();
}
bool p2p::isLocalHostAddress(std::string const& _addressToCheck)
{
return isLocalHostAddress(bi::address::from_string(_addressToCheck));
}
std::string p2p::reasonOf(DisconnectReason _r)
{
switch (_r)
@ -89,3 +109,9 @@ std::string p2p::reasonOf(DisconnectReason _r)
default: return "Unknown reason.";
}
}
void Node::cullEndpoint()
{
if (!isPublicAddress(endpoint.tcp.address()) && isPublicAddress(endpoint.udp.address()))
endpoint.tcp.address(endpoint.udp.address());
}

9
libp2p/Common.h

@ -54,7 +54,11 @@ extern const unsigned c_protocolVersion;
using NodeId = h512;
bool isPrivateAddress(bi::address const& _addressToCheck);
bool isPrivateAddress(std::string const& _addressToCheck);
bool isLocalHostAddress(bi::address const& _addressToCheck);
bool isLocalHostAddress(std::string const& _addressToCheck);
bool isPublicAddress(bi::address const& _addressToCheck);
bool isPublicAddress(std::string const& _addressToCheck);
class UPnP;
class Capability;
@ -62,6 +66,8 @@ class Host;
class Session;
struct NetworkStartRequired: virtual dev::Exception {};
struct InvalidPublicIPAddress: virtual dev::Exception {};
struct InvalidHostIPAddress: virtual dev::Exception {};
struct NetWarn: public LogChannel { static const char* name() { return "!N!"; } static const int verbosity = 0; };
struct NetNote: public LogChannel { static const char* name() { return "*N*"; } static const int verbosity = 1; };
@ -169,6 +175,9 @@ struct Node
virtual NodeId const& address() const { return id; }
virtual Public const& publicKey() const { return id; }
/// Adopt UDP address for TCP if TCP isn't public and UDP is. (to be removed when protocol is updated for nat)
void cullEndpoint();
NodeId id;
/// Endpoints by which we expect to reach node.

8
libp2p/Host.cpp

@ -592,12 +592,12 @@ void Host::startedWorking()
m_run = true;
}
// try to open acceptor (todo: ipv6)
m_listenPort = Network::tcp4Listen(m_tcp4Acceptor, m_netPrefs.listenPort);
// start capability threads
// start capability threads (ready for incoming connections)
for (auto const& h: m_capabilities)
h.second->onStarting();
// try to open acceptor (todo: ipv6)
m_listenPort = Network::tcp4Listen(m_tcp4Acceptor, m_netPrefs.listenPort);
// determine public IP, but only if we're able to listen for connections
// todo: GUI when listen is unavailable in UI

48
libp2p/NodeTable.cpp

@ -70,25 +70,22 @@ shared_ptr<NodeEntry> NodeTable::addNode(Public const& _pubk, bi::udp::endpoint
shared_ptr<NodeEntry> NodeTable::addNode(Node const& _node)
{
if (_node.endpoint.udp.address().to_string() == "0.0.0.0" || _node.endpoint.tcp.address().to_string() == "0.0.0.0")
// re-enable tcp checks when NAT hosts are handled by discover
// we handle when tcp endpoint is 0 below
if (_node.endpoint.udp.address().to_string() == "0.0.0.0")
{
string ptype;
if (_node.endpoint.udp.address().to_string() != "0.0.0.0")
ptype = "TCP";
else if (_node.endpoint.tcp.address().to_string() != "0.0.0.0")
ptype = "UDP";
else
ptype = "TCP,UDP";
clog(NodeTableWarn) << "addNode Failed. Invalid" << ptype << "address 0.0.0.0 for" << _node.id.abridged();
clog(NodeTableWarn) << "addNode Failed. Invalid UDP address 0.0.0.0 for" << _node.id.abridged();
return move(shared_ptr<NodeEntry>());
}
// ping address if nodeid is empty
// ping address to recover nodeid if nodeid is empty
if (!_node.id)
{
clog(NodeTableConnect) << "Sending public key discovery Ping to" << _node.endpoint.udp << "(Advertising:" << m_node.endpoint.udp << ")";
m_pubkDiscoverPings[_node.endpoint.udp.address()] = std::chrono::steady_clock::now();
{
Guard l(x_pubkDiscoverPings);
m_pubkDiscoverPings[_node.endpoint.udp.address()] = std::chrono::steady_clock::now();
}
PingNode p(_node.endpoint.udp, m_node.endpoint.udp.address().to_string(), m_node.endpoint.udp.port());
p.sign(m_secret);
m_socketPointer->send(p);
@ -103,6 +100,7 @@ shared_ptr<NodeEntry> NodeTable::addNode(Node const& _node)
shared_ptr<NodeEntry> ret(new NodeEntry(m_node, _node.id, NodeIPEndpoint(_node.endpoint.udp, _node.endpoint.tcp)));
m_nodes[_node.id] = ret;
ret->cullEndpoint();
PingNode p(_node.endpoint.udp, m_node.endpoint.udp.address().to_string(), m_node.endpoint.udp.port());
p.sign(m_secret);
m_socketPointer->send(p);
@ -314,10 +312,9 @@ void NodeTable::noteActiveNode(Public const& _pubk, bi::udp::endpoint const& _en
if (!!node && !node->pending)
{
clog(NodeTableConnect) << "Noting active node:" << _pubk.abridged() << _endpoint.address().to_string() << ":" << _endpoint.port();
// update udp endpoint
node->endpoint.udp.address(_endpoint.address());
node->endpoint.udp.port(_endpoint.port());
node->cullEndpoint();
shared_ptr<NodeEntry> contested;
{
@ -399,7 +396,7 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes
bytesConstRef signedBytes(hashedBytes.cropped(Signature::size, hashedBytes.size() - Signature::size));
// todo: verify sig via known-nodeid and MDC, or, do ping/pong auth if node/endpoint is unknown/untrusted
// todo: verify sig via known-nodeid and MDC
bytesConstRef sigBytes(_packet.cropped(h256::size, Signature::size));
Public nodeid(dev::recover(*(Signature const*)sigBytes.data(), sha3(signedBytes)));
@ -430,8 +427,7 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes
dropNode(n);
if (auto n = nodeEntry(it->first.first))
if (m_nodeEventHandler && n->pending)
n->pending = false;
n->pending = false;
it = m_evictions.erase(it);
}
@ -441,14 +437,18 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes
{
if (auto n = nodeEntry(nodeid))
n->pending = false;
else if (m_pubkDiscoverPings.count(_from.address()))
{
{
Guard l(x_pubkDiscoverPings);
m_pubkDiscoverPings.erase(_from.address());
}
if (!haveNode(nodeid))
addNode(nodeid, _from, bi::tcp::endpoint(_from.address(), _from.port()));
}
else
return; // unsolicited pong; don't note node as active
}
else if (m_pubkDiscoverPings.count(_from.address()))
{
m_pubkDiscoverPings.erase(_from.address());
addNode(nodeid, _from, bi::tcp::endpoint(_from.address(), _from.port()));
}
else
return; // unsolicited pong; don't note node as active
break;
}

15
libp2p/RLPxHandshake.cpp

@ -131,7 +131,14 @@ void RLPXHandshake::readAck()
void RLPXHandshake::error()
{
clog(NetConnect) << "Disconnecting " << m_socket->remoteEndpoint() << " (Handshake Failed)";
m_idleTimer.cancel();
auto connected = m_socket->isConnected();
if (connected && !m_socket->remoteEndpoint().address().is_unspecified())
clog(NetConnect) << "Disconnecting " << m_socket->remoteEndpoint() << " (Handshake Failed)";
else
clog(NetConnect) << "Handshake Failed (Connection reset by peer)";
m_socket->close();
if (m_io != nullptr)
delete m_io;
@ -140,7 +147,10 @@ void RLPXHandshake::error()
void RLPXHandshake::transition(boost::system::error_code _ech)
{
if (_ech || m_nextState == Error || m_cancel)
{
clog(NetConnect) << "Handshake Failed (I/O Error:" << _ech.message() << ")";
return error();
}
auto self(shared_from_this());
if (m_nextState == New)
@ -265,7 +275,8 @@ void RLPXHandshake::transition(boost::system::error_code _ech)
{
if (!_ec)
{
clog(NetWarn) << "Disconnecting " << m_socket->remoteEndpoint() << " (Handshake Timeout)";
if (!m_socket->remoteEndpoint().address().is_unspecified())
clog(NetWarn) << "Disconnecting " << m_socket->remoteEndpoint() << " (Handshake Timeout)";
cancel();
}
});

22
libsolidity/AST.cpp

@ -88,7 +88,7 @@ void ContractDefinition::checkTypeRequirements()
if (hashes.count(hash))
BOOST_THROW_EXCEPTION(createTypeError(
std::string("Function signature hash collision for ") +
it.second->getCanonicalSignature()));
it.second->externalSignature()));
hashes.insert(hash);
}
}
@ -192,7 +192,7 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::getIn
if (functionsSeen.count(f->getName()) == 0 && f->isPartOfExternalInterface())
{
functionsSeen.insert(f->getName());
FixedHash<4> hash(dev::sha3(f->getCanonicalSignature()));
FixedHash<4> hash(dev::sha3(f->externalSignature()));
m_interfaceFunctionList->push_back(make_pair(hash, make_shared<FunctionType>(*f, false)));
}
@ -200,8 +200,9 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::getIn
if (functionsSeen.count(v->getName()) == 0 && v->isPartOfExternalInterface())
{
FunctionType ftype(*v);
solAssert(v->getType().get(), "");
functionsSeen.insert(v->getName());
FixedHash<4> hash(dev::sha3(ftype.getCanonicalSignature(v->getName())));
FixedHash<4> hash(dev::sha3(ftype.externalSignature(v->getName())));
m_interfaceFunctionList->push_back(make_pair(hash, make_shared<FunctionType>(*v)));
}
}
@ -305,8 +306,12 @@ TypePointer FunctionDefinition::getType(ContractDefinition const*) const
void FunctionDefinition::checkTypeRequirements()
{
for (ASTPointer<VariableDeclaration> const& var: getParameters() + getReturnParameters())
{
if (!var->getType()->canLiveOutsideStorage())
BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage."));
if (!(var->getType()->externalType()) && getVisibility() >= Visibility::Public)
BOOST_THROW_EXCEPTION(var->createTypeError("Internal type is not allowed for function with external visibility"));
}
for (ASTPointer<ModifierInvocation> const& modifier: m_functionModifiers)
modifier->checkTypeRequirements(isConstructor() ?
dynamic_cast<ContractDefinition const&>(*getScope()).getBaseContracts() :
@ -315,9 +320,9 @@ void FunctionDefinition::checkTypeRequirements()
m_body->checkTypeRequirements();
}
string FunctionDefinition::getCanonicalSignature() const
string FunctionDefinition::externalSignature() const
{
return FunctionType(*this).getCanonicalSignature(getName());
return FunctionType(*this).externalSignature(getName());
}
bool VariableDeclaration::isLValue() const
@ -342,8 +347,11 @@ void VariableDeclaration::checkTypeRequirements()
if (!m_value)
return;
if (m_type)
{
m_value->expectType(*m_type);
else
if (m_isStateVariable && !m_type->externalType() && getVisibility() >= Visibility::Public)
BOOST_THROW_EXCEPTION(createTypeError("Internal type is not allowed for state variables."));
} else
{
// no type declared and no previous assignment, infer the type
m_value->checkTypeRequirements();
@ -422,6 +430,8 @@ void EventDefinition::checkTypeRequirements()
numIndexed++;
if (!var->getType()->canLiveOutsideStorage())
BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage."));
if (!var->getType()->externalType())
BOOST_THROW_EXCEPTION(var->createTypeError("Internal type is not allowed as event parameter type."));
}
if (numIndexed > 3)
BOOST_THROW_EXCEPTION(createTypeError("More than 3 indexed arguments for event."));

4
libsolidity/AST.h

@ -421,10 +421,10 @@ public:
/// Checks that all parameters have allowed types and calls checkTypeRequirements on the body.
void checkTypeRequirements();
/// @returns the canonical signature of the function
/// @returns the external signature of the function
/// That consists of the name of the function followed by the types of the
/// arguments separated by commas all enclosed in parentheses without any spaces.
std::string getCanonicalSignature() const;
std::string externalSignature() const;
private:
bool m_isConstructor;

2
libsolidity/ExpressionCompiler.cpp

@ -544,7 +544,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
}
if (!event.isAnonymous())
{
m_context << u256(h256::Arith(dev::sha3(function.getCanonicalSignature(event.getName()))));
m_context << u256(h256::Arith(dev::sha3(function.externalSignature(event.getName()))));
++numIndexed;
}
solAssert(numIndexed <= 4, "Too many indexed arguments.");

4
libsolidity/InterfaceHandler.cpp

@ -129,7 +129,7 @@ std::unique_ptr<std::string> InterfaceHandler::getUserDocumentation(ContractDefi
if (!m_notice.empty())
{// since @notice is the only user tag if missing function should not appear
user["notice"] = Json::Value(m_notice);
methods[it.second->getCanonicalSignature()] = user;
methods[it.second->externalSignature()] = user;
}
}
}
@ -185,7 +185,7 @@ std::unique_ptr<std::string> InterfaceHandler::getDevDocumentation(ContractDefin
method["return"] = m_return;
if (!method.empty()) // add the function, only if we have any documentation to add
methods[it.second->getCanonicalSignature()] = method;
methods[it.second->externalSignature()] = method;
}
}
doc["methods"] = methods;

40
libsolidity/Types.cpp

@ -742,6 +742,23 @@ string ArrayType::toString() const
return ret + "]";
}
TypePointer ArrayType::externalType() const
{
if (m_location != Location::CallData)
return TypePointer();
if (m_isByteArray)
return shared_from_this();
if (!m_baseType->externalType())
return TypePointer();
if (m_baseType->getCategory() == Category::Array && m_baseType->isDynamicallySized())
return TypePointer();
if (isDynamicallySized())
return std::make_shared<ArrayType>(Location::CallData, m_baseType->externalType());
else
return std::make_shared<ArrayType>(Location::CallData, m_baseType->externalType(), m_length);
}
shared_ptr<ArrayType> ArrayType::copyForLocation(ArrayType::Location _location) const
{
auto copy = make_shared<ArrayType>(_location);
@ -1081,6 +1098,19 @@ unsigned FunctionType::getSizeOnStack() const
return size;
}
TypePointer FunctionType::externalType() const
{
TypePointers paramTypes;
TypePointers retParamTypes;
for (auto it = m_parameterTypes.cbegin(); it != m_parameterTypes.cend(); ++it)
paramTypes.push_back((*it)->externalType());
for (auto it = m_returnParameterTypes.cbegin(); it != m_returnParameterTypes.cend(); ++it)
retParamTypes.push_back((*it)->externalType());
return make_shared<FunctionType>(paramTypes, retParamTypes, m_location, m_arbitraryParameters);
}
MemberList const& FunctionType::getMembers() const
{
switch (m_location)
@ -1110,7 +1140,7 @@ MemberList const& FunctionType::getMembers() const
}
}
string FunctionType::getCanonicalSignature(std::string const& _name) const
string FunctionType::externalSignature(std::string const& _name) const
{
std::string funcName = _name;
if (_name == "")
@ -1120,8 +1150,12 @@ string FunctionType::getCanonicalSignature(std::string const& _name) const
}
string ret = funcName + "(";
for (auto it = m_parameterTypes.cbegin(); it != m_parameterTypes.cend(); ++it)
ret += (*it)->toString() + (it + 1 == m_parameterTypes.cend() ? "" : ",");
TypePointers externalParameterTypes = dynamic_cast<FunctionType const&>(*externalType()).getParameterTypes();
for (auto it = externalParameterTypes.cbegin(); it != externalParameterTypes.cend(); ++it)
{
solAssert(!!(*it), "Parameter should have external type");
ret += (*it)->toString() + (it + 1 == externalParameterTypes.cend() ? "" : ",");
}
return ret + ")";
}

22
libsolidity/Types.h

@ -187,6 +187,10 @@ public:
"for type without literals."));
}
/// @returns a type suitable for outside of Solidity, i.e. for contract types it returns address.
/// If there is no such type, returns an empty shared pointer.
virtual TypePointer externalType() const { return TypePointer(); }
protected:
/// Convenience object used when returning an empty member list.
static const MemberList EmptyMemberList;
@ -217,10 +221,12 @@ public:
virtual unsigned getStorageBytes() const override { return m_bits / 8; }
virtual bool isValueType() const override { return true; }
virtual MemberList const& getMembers() const { return isAddress() ? AddressMemberList : EmptyMemberList; }
virtual MemberList const& getMembers() const override { return isAddress() ? AddressMemberList : EmptyMemberList; }
virtual std::string toString() const override;
virtual TypePointer externalType() const override { return shared_from_this(); }
int getNumBits() const { return m_bits; }
bool isAddress() const { return m_modifier == Modifier::Address; }
bool isSigned() const { return m_modifier == Modifier::Signed; }
@ -292,6 +298,7 @@ public:
virtual std::string toString() const override { return "bytes" + dev::toString(m_bytes); }
virtual u256 literalValue(Literal const* _literal) const override;
virtual TypePointer externalType() const override { return shared_from_this(); }
int getNumBytes() const { return m_bytes; }
@ -311,12 +318,13 @@ public:
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
virtual unsigned getCalldataEncodedSize(bool _padded) const { return _padded ? 32 : 1; }
virtual unsigned getCalldataEncodedSize(bool _padded) const override{ return _padded ? 32 : 1; }
virtual unsigned getStorageBytes() const override { return 1; }
virtual bool isValueType() const override { return true; }
virtual std::string toString() const override { return "bool"; }
virtual u256 literalValue(Literal const* _literal) const override;
virtual TypePointer externalType() const override { return shared_from_this(); }
};
/**
@ -361,6 +369,7 @@ public:
virtual unsigned getSizeOnStack() const override;
virtual std::string toString() const override;
virtual MemberList const& getMembers() const override { return s_arrayTypeMemberList; }
virtual TypePointer externalType() const override;
Location getLocation() const { return m_location; }
bool isByteArray() const { return m_isByteArray; }
@ -400,6 +409,7 @@ public:
virtual std::string toString() const override;
virtual MemberList const& getMembers() const override;
virtual TypePointer externalType() const override { return std::make_shared<IntegerType>(160, IntegerType::Modifier::Address); }
bool isSuper() const { return m_super; }
ContractDefinition const& getContractDefinition() const { return m_contract; }
@ -468,6 +478,7 @@ public:
virtual bool isValueType() const override { return true; }
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer externalType() const override { return std::make_shared<IntegerType>(8 * int(getStorageBytes())); }
EnumDefinition const& getEnumDefinition() const { return m_enum; }
/// @returns the value that the string has in the Enum
@ -500,6 +511,9 @@ public:
Bare };
virtual Category getCategory() const override { return Category::Function; }
virtual TypePointer externalType() const override;
explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true);
explicit FunctionType(VariableDeclaration const& _varDecl);
explicit FunctionType(EventDefinition const& _event);
@ -539,10 +553,10 @@ public:
virtual MemberList const& getMembers() const override;
Location const& getLocation() const { return m_location; }
/// @returns the canonical signature of this function type given the function name
/// @returns the external signature of this function type given the function name
/// If @a _name is not provided (empty string) then the @c m_declaration member of the
/// function type is used
std::string getCanonicalSignature(std::string const& _name = "") const;
std::string externalSignature(std::string const& _name = "") const;
Declaration const& getDeclaration() const
{
solAssert(m_declaration, "Requested declaration from a FunctionType that has none");

1
libtestutils/FixedClient.h

@ -47,6 +47,7 @@ public:
virtual eth::State asOf(h256 const& _h) const override;
virtual eth::State preMine() const override { ReadGuard l(x_stateDB); return m_state; }
virtual eth::State postMine() const override { ReadGuard l(x_stateDB); return m_state; }
virtual void setAddress(Address _us) { WriteGuard l(x_stateDB); m_state.setAddress(_us); }
virtual void prepareForTransaction() override {}
private:

21
mix/ClientModel.cpp

@ -243,7 +243,12 @@ void ClientModel::executeSequence(std::vector<TransactionSettings> const& _seque
break;
}
if (!f)
BOOST_THROW_EXCEPTION(FunctionNotFoundException() << FunctionName(transaction.functionId.toStdString()));
{
emit runFailed("Function '" + transaction.functionId + tr("' not found. Please check transactions or the contract code."));
m_running = false;
emit runStateChanged();
return;
}
if (!transaction.functionId.isEmpty())
encoder.encode(f);
for (QVariableDeclaration const* p: f->parametersList())
@ -270,7 +275,12 @@ void ClientModel::executeSequence(std::vector<TransactionSettings> const& _seque
{
auto contractAddressIter = m_contractAddresses.find(transaction.contractId);
if (contractAddressIter == m_contractAddresses.end())
BOOST_THROW_EXCEPTION(dev::Exception() << dev::errinfo_comment("Contract not deployed: " + transaction.contractId.toStdString()));
{
emit runFailed("Contract '" + transaction.contractId + tr(" not deployed.") + "' " + tr(" Cannot call ") + transaction.functionId);
m_running = false;
emit runStateChanged();
return;
}
callContract(contractAddressIter->second, encoder.encodedData(), transaction);
}
}
@ -284,7 +294,6 @@ void ClientModel::executeSequence(std::vector<TransactionSettings> const& _seque
std::cerr << boost::current_exception_diagnostic_information();
emit runFailed(QString::fromStdString(boost::current_exception_diagnostic_information()));
}
catch(std::exception const& e)
{
std::cerr << boost::current_exception_diagnostic_information();
@ -377,6 +386,8 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t)
for(auto l: solLocals)
if (l.first < (int)s.stack.size())
{
if (l.second->type()->name().startsWith("mapping"))
break; //mapping type not yet managed
localDeclarations.push_back(QVariant::fromValue(l.second));
localValues[l.second->name()] = formatValue(l.second->type()->type(), s.stack[l.first]);
}
@ -401,6 +412,8 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t)
storageDec = new QVariableDeclaration(debugData, storageIter.value().name.toStdString(), storageIter.value().type);
storageDeclarations[storageDec->name()] = storageDec;
}
if (storageDec->type()->name().startsWith("mapping"))
break; //mapping type not yet managed
storageDeclarationList.push_back(QVariant::fromValue(storageDec));
storageValues[storageDec->name()] = formatValue(storageDec->type()->type(), st.second);
}
@ -409,7 +422,7 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t)
storage["values"] = storageValues;
prevInstructionIndex = instructionIndex;
solState = new QSolState(debugData, std::move(storage), std::move(solCallStack), std::move(locals), instruction.getLocation().start, instruction.getLocation().end);
solState = new QSolState(debugData, std::move(storage), std::move(solCallStack), std::move(locals), instruction.getLocation().start, instruction.getLocation().end, QString::fromUtf8(instruction.getLocation().sourceName->c_str()));
}
states.append(QVariant::fromValue(new QMachineState(debugData, instructionIndex, s, codes[s.codeIndex], data[s.dataIndex], solState)));

16
mix/ContractCallDataEncoder.cpp

@ -76,18 +76,22 @@ unsigned ContractCallDataEncoder::encodeSingleItem(QVariant const& _data, Solidi
unsigned const alignSize = 32;
QString src = _data.toString();
bytes result;
if (src.length() >= 2 && ((src.startsWith("\"") && src.endsWith("\"")) || (src.startsWith("\'") && src.endsWith("\'"))))
{
if ((src.startsWith("\"") && src.endsWith("\"")) || (src.startsWith("\'") && src.endsWith("\'")))
src = src.remove(src.length() - 1, 1).remove(0, 1);
QByteArray bytesAr = src.toLocal8Bit();
result = bytes(bytesAr.begin(), bytesAr.end());
}
else if (src.startsWith("0x"))
QRegExp rx("[a-z]+");
if (src.startsWith("0x"))
{
result = fromHex(src.toStdString().substr(2));
if (_type.type != SolidityType::Type::Bytes)
result = padded(result, alignSize);
}
else if (rx.indexIn(src.toLower(), 0) != -1)
{
QByteArray bytesAr = src.toLocal8Bit();
result = bytes(bytesAr.begin(), bytesAr.end());
}
else
{
bigint i(src.toStdString());

6
mix/DebuggingStateWrapper.h

@ -65,10 +65,11 @@ class QSolState: public QObject
Q_PROPERTY(QVariantMap locals MEMBER m_locals CONSTANT)
Q_PROPERTY(int start MEMBER m_start CONSTANT)
Q_PROPERTY(int end MEMBER m_end CONSTANT)
Q_PROPERTY(QString sourceName MEMBER m_sourceName CONSTANT)
public:
QSolState(QObject* _parent, QVariantMap&& _storage, QVariantList&& _callStack, QVariantMap&& _locals, int _start, int _end):
QObject(_parent), m_storage(_storage), m_callStack(_callStack), m_locals(_locals), m_start(_start), m_end(_end)
QSolState(QObject* _parent, QVariantMap&& _storage, QVariantList&& _callStack, QVariantMap&& _locals, int _start, int _end, QString _sourceName):
QObject(_parent), m_storage(_storage), m_callStack(_callStack), m_locals(_locals), m_start(_start), m_end(_end), m_sourceName(_sourceName)
{ }
private:
@ -77,6 +78,7 @@ private:
QVariantMap m_locals;
int m_start;
int m_end;
QString m_sourceName;
};
/**

10
mix/MixClient.cpp

@ -44,7 +44,7 @@ namespace mix
const Secret c_defaultUserAccountSecret = Secret("cb73d9408c4720e230387d956eb0f829d8a4dd2c1055f96257167e14e7169074");
const u256 c_mixGenesisDifficulty = c_minimumDifficulty; //TODO: make it lower for Mix somehow
bytes MixBlockChain::createGenesisBlock(h256 _stateRoot)
{
RLPStream block(3);
@ -80,6 +80,7 @@ void MixClient::resetState(std::map<Secret, u256> _accounts)
SecureTrieDB<Address, MemoryDB> accountState(&m_stateDB);
accountState.init();
m_userAccounts.clear();
std::map<Address, Account> genesisState;
for (auto account: _accounts)
{
@ -260,8 +261,8 @@ Address MixClient::submitTransaction(Secret _secret, u256 _endowment, bytes cons
dev::eth::ExecutionResult MixClient::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice, BlockNumber _blockNumber)
{
State temp = asOf(_blockNumber);
(void)_blockNumber;
State temp = asOf(eth::PendingBlock);
u256 n = temp.transactionsFrom(toAddress(_secret));
Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret);
bytes rlp = t.rlp();
@ -272,11 +273,12 @@ dev::eth::ExecutionResult MixClient::call(Secret _secret, u256 _value, Address _
dev::eth::ExecutionResult MixClient::create(Secret _secret, u256 _value, bytes const& _data, u256 _gas, u256 _gasPrice, BlockNumber _blockNumber)
{
(void)_blockNumber;
u256 n;
State temp;
{
ReadGuard lr(x_state);
temp = asOf(_blockNumber);
temp = asOf(eth::PendingBlock);
n = temp.transactionsFrom(toAddress(_secret));
}
Transaction t(_value, _gasPrice, _gas, _data, n, _secret);

2
mix/QFunctionDefinition.cpp

@ -28,7 +28,7 @@
using namespace dev::solidity;
using namespace dev::mix;
QFunctionDefinition::QFunctionDefinition(QObject* _parent, dev::solidity::FunctionTypePointer const& _f): QBasicNodeDefinition(_parent, &_f->getDeclaration()), m_hash(dev::sha3(_f->getCanonicalSignature()))
QFunctionDefinition::QFunctionDefinition(QObject* _parent, dev::solidity::FunctionTypePointer const& _f): QBasicNodeDefinition(_parent, &_f->getDeclaration()), m_hash(dev::sha3(_f->externalSignature()))
{
auto paramNames = _f->getParameterNames();
auto paramTypes = _f->getParameterTypes();

20
mix/qml/CodeEditorView.qml

@ -71,10 +71,26 @@ Item {
return null;
}
function highlightExecution(documentId, location) {
function highlightExecution(documentId, location)
{
var editor = getEditor(documentId);
if (editor)
editor.highlightExecution(location);
{
if (documentId !== location.sourceName)
findAndHightlight(location.start, location.end, location.sourceName)
else
editor.highlightExecution(location);
}
}
// Execution is not in the current document. Try:
// Open targeted document and hightlight (TODO) or
// Warn user that file is not available
function findAndHightlight(start, end, sourceName)
{
var editor = getEditor(currentDocumentId);
if (editor)
editor.showWarning(qsTr("Currently debugging in " + sourceName + ". Source not available."));
}
function editingContract() {

26
mix/qml/LogsPane.qml

@ -156,32 +156,6 @@ Rectangle
}
}
}
ToolButton {
id: compilationButton
checkable: true
height: logStyle.generic.layout.headerButtonHeight
anchors.verticalCenter: parent.verticalCenter
checked: false
onCheckedChanged: {
proxyModel.toogleFilter("compilation")
}
tooltip: qsTr("Compilation")
style:
ButtonStyle {
label:
Item {
DefaultLabel {
font.family: logStyle.generic.layout.logLabelFont
font.pointSize: appStyle.absoluteSize(-3)
color: "#5391d8"
anchors.centerIn: parent
text: qsTr("Compilation")
}
}
}
}
DefaultTextField
{
id: searchBox

1
mix/qml/QHashTypeView.qml

@ -15,7 +15,6 @@ Item
Rectangle {
anchors.fill: parent
radius: 4
color: "#f7f7f7"
TextInput {
id: textinput
text: value

1
mix/qml/QIntTypeView.qml

@ -16,7 +16,6 @@ Item
Rectangle {
anchors.fill: parent
radius: 4
color: "#f7f7f7"
TextInput {
id: textinput
text: value

1
mix/qml/QStringTypeView.qml

@ -15,7 +15,6 @@ Item
Rectangle {
anchors.fill: parent
radius: 4
color: "#f7f7f7"
TextInput {
id: textinput
text: value

5
mix/qml/StatusPane.qml

@ -24,7 +24,6 @@ Rectangle {
var errorInfo = ErrorLocationFormater.extractErrorInfo(message, true);
status.text = errorInfo.errorLocation + " " + errorInfo.errorDetail;
debugImg.state = "";
errorMessage(status.text, "Compilation");
}
debugRunActionIcon.enabled = codeModel.hasContract;
}
@ -81,9 +80,9 @@ Rectangle {
function format(_message)
{
var formatted = _message.match(/(?:<dev::eth::)(.+)(?:>)/);
if (formatted === null)
if (formatted)
formatted = _message.match(/(?:<dev::)(.+)(?:>)/);
if (formatted.length > 1)
if (formatted && formatted.length > 1)
formatted = formatted[1];
else
return _message;

11
mix/qml/StructView.qml

@ -24,24 +24,24 @@ Column
height: 20
id: typeLabel
text: modelData.type.name
Layout.preferredWidth: 60
anchors.verticalCenter: parent.verticalCenter
}
DefaultLabel {
id: nameLabel
text: modelData.name
Layout.preferredWidth: 100
anchors.verticalCenter: parent.verticalCenter
}
DefaultLabel {
id: equalLabel
text: "="
Layout.preferredWidth: 15
anchors.verticalCenter: parent.verticalCenter
}
Loader
{
id: typeLoader
Layout.preferredWidth: 150
anchors.verticalCenter: parent.verticalCenter
sourceComponent:
{
var t = modelData.type.category;
@ -63,7 +63,8 @@ Column
var ptype = members[index].type;
var pname = members[index].name;
var vals = value;
if (ptype.category === QSolidityType.Struct && !item.members) {
if (ptype.category === QSolidityType.Struct && !item.members)
{
item.value = getValue();
item.members = ptype.members;
}

5
mix/qml/WebCodeEditor.qml

@ -47,6 +47,11 @@ Item {
editorBrowser.runJavaScript("highlightExecution(" + location.start + "," + location.end + ")");
}
function showWarning(content) {
if (initialized)
editorBrowser.runJavaScript("showWarning('" + content + "')");
}
function getBreakpoints() {
return currentBreakpoints;
}

6
mix/qml/html/WebContainer.html

@ -23,9 +23,9 @@ updateContracts = function(contracts) {
var contractProto = window.web3.eth.contract(contracts[c].interface);
var contract = new contractProto(contracts[c].address);
window.contracts[c] = {
address: c.address,
interface: c.interface,
contract: contract,
address: contracts[c].address,
interface: contracts[c].interface,
contract: contract
};
}
}

61
mix/qml/html/cm/anyword-hint.js

@ -13,6 +13,18 @@
var curWord = start != end && curLine.slice(start, end);
var list = [], seen = {};
if (editor.getMode().name === "solidity")
{
list = addSolToken(curWord, list, seen, solCurrency(), solCurrency);
list = addSolToken(curWord, list, seen, solKeywords(), solKeywords);
list = addSolToken(curWord, list, seen, solStdContract(), solStdContract);
list = addSolToken(curWord, list, seen, solTime(), solTime);
list = addSolToken(curWord, list, seen, solTypes(), solTypes);
list = addSolToken(curWord, list, seen, solMisc(), solMisc);
}
var previousWord = "";
var re = new RegExp(word.source, "g");
for (var dir = -1; dir <= 1; dir += 2) {
var line = cur.line, endLine = Math.min(Math.max(line + dir * range, editor.firstLine()), editor.lastLine()) + dir;
@ -22,41 +34,60 @@
if (line == cur.line && m[0] === curWord) continue;
if ((!curWord || m[0].lastIndexOf(curWord, 0) === 0) && !Object.prototype.hasOwnProperty.call(seen, m[0])) {
seen[m[0]] = true;
list.push({ text: m[0] });
var w = { text: m[0] };
checkDeclaration(previousWord, "Contract", w);
checkDeclaration(previousWord, "Function", w);
list.push(w);
}
previousWord = m[0];
}
}
}
if (editor.getMode().name === "solidity")
{
list = addSolToken(curWord, list, solCurrency(), solCurrency);
list = addSolToken(curWord, list, solKeywords(), solKeywords);
list = addSolToken(curWord, list, solStdContract(), solStdContract);
list = addSolToken(curWord, list, solTime(), solTime);
list = addSolToken(curWord, list, solTypes(), solTypes);
list = addSolToken(curWord, list, solMisc(), solMisc);
}
return {list: list, from: CodeMirror.Pos(cur.line, start), to: CodeMirror.Pos(cur.line, end)};
});
})();
function addSolToken(curWord, list, tokens, type)
function addSolToken(curWord, list, seen, tokens, type)
{
var keywordsTypeName = keywordsName();
for (var key in tokens)
{
seen[key] = true;
if (curWord === false || key.indexOf(curWord, 0) === 0)
{
var token = { text: key };
token.render = function(elt, data, cur)
{
elt.className = elt.className + " " + type.name.toLowerCase();
elt.appendChild(document.createTextNode(cur.displayText || cur.text));
render(elt, data, cur, type.name.toLowerCase(), keywordsTypeName[type.name.toLowerCase()]);
}
list.push(token);
}
}
return list;
}
function render(elt, data, cur, csstype, type)
{
var container = document.createElement("div");
var word = document.createElement("div");
word.className = csstype + " solToken";
word.appendChild(document.createTextNode(cur.displayText || cur.text));
var typeDiv = document.createElement("type");
typeDiv.appendChild(document.createTextNode(type));
typeDiv.className = "solTokenType";
container.appendChild(word);
container.appendChild(typeDiv);
elt.appendChild(container);
}
function checkDeclaration(previousToken, target, current)
{
if (previousToken.toLowerCase() === target.toLowerCase())
{
current.render = function(elt, data, cur)
{
render(elt, data, cur, "sol" + target, target);
}
}
}

6
mix/qml/html/cm/codemirror.css

@ -304,12 +304,12 @@ div.CodeMirror-cursors {
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }
/* Help users use markselection to safely style text background
span.CodeMirror-selectedtext { color: #586e75; } */

118
mix/qml/html/cm/mark-selection.js

@ -0,0 +1,118 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
// Because sometimes you need to mark the selected *text*.
//
// Adds an option 'styleSelectedText' which, when enabled, gives
// selected text the CSS class given as option value, or
// "CodeMirror-selectedtext" when the value is not a string.
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
CodeMirror.defineOption("styleSelectedText", false, function(cm, val, old) {
var prev = old && old != CodeMirror.Init;
if (val && !prev) {
cm.state.markedSelection = [];
cm.state.markedSelectionStyle = typeof val == "string" ? val : "CodeMirror-selectedtext";
reset(cm);
cm.on("cursorActivity", onCursorActivity);
cm.on("change", onChange);
} else if (!val && prev) {
cm.off("cursorActivity", onCursorActivity);
cm.off("change", onChange);
clear(cm);
cm.state.markedSelection = cm.state.markedSelectionStyle = null;
}
});
function onCursorActivity(cm) {
cm.operation(function() { update(cm); });
}
function onChange(cm) {
if (cm.state.markedSelection.length)
cm.operation(function() { clear(cm); });
}
var CHUNK_SIZE = 8;
var Pos = CodeMirror.Pos;
var cmp = CodeMirror.cmpPos;
function coverRange(cm, from, to, addAt) {
if (cmp(from, to) == 0) return;
var array = cm.state.markedSelection;
var cls = cm.state.markedSelectionStyle;
for (var line = from.line;;) {
var start = line == from.line ? from : Pos(line, 0);
var endLine = line + CHUNK_SIZE, atEnd = endLine >= to.line;
var end = atEnd ? to : Pos(endLine, 0);
var mark = cm.markText(start, end, {className: cls});
if (addAt == null) array.push(mark);
else array.splice(addAt++, 0, mark);
if (atEnd) break;
line = endLine;
}
}
function clear(cm) {
var array = cm.state.markedSelection;
for (var i = 0; i < array.length; ++i) array[i].clear();
array.length = 0;
}
function reset(cm) {
clear(cm);
var ranges = cm.listSelections();
for (var i = 0; i < ranges.length; i++)
coverRange(cm, ranges[i].from(), ranges[i].to());
}
function update(cm) {
if (!cm.somethingSelected()) return clear(cm);
if (cm.listSelections().length > 1) return reset(cm);
var from = cm.getCursor("start"), to = cm.getCursor("end");
var array = cm.state.markedSelection;
if (!array.length) return coverRange(cm, from, to);
var coverStart = array[0].find(), coverEnd = array[array.length - 1].find();
if (!coverStart || !coverEnd || to.line - from.line < CHUNK_SIZE ||
cmp(from, coverEnd.to) >= 0 || cmp(to, coverStart.from) <= 0)
return reset(cm);
while (cmp(from, coverStart.from) > 0) {
array.shift().clear();
coverStart = array[0].find();
}
if (cmp(from, coverStart.from) < 0) {
if (coverStart.to.line - from.line < CHUNK_SIZE) {
array.shift().clear();
coverRange(cm, from, coverStart.to, 0);
} else {
coverRange(cm, from, coverStart.from, 0);
}
}
while (cmp(to, coverEnd.to) < 0) {
array.pop().clear();
coverEnd = array[array.length - 1].find();
}
if (cmp(to, coverEnd.to) > 0) {
if (to.line - coverEnd.from.line < CHUNK_SIZE) {
array.pop().clear();
coverRange(cm, coverEnd.from, to);
} else {
coverRange(cm, coverEnd.to, to);
}
}
}
});

34
mix/qml/html/cm/show-hint.css

@ -31,33 +31,29 @@
white-space: pre;
color: black;
cursor: pointer;
padding-left: 20px;
padding-top: 3px;
padding-bottom: 3px;
}
.CodeMirror-hint-active {
background: #4a90e2;
color: white;
color: white !important;
}
.solcurrency {
color: red;
.CodeMirror-hint-active .solToken,
.CodeMirror-hint-active .solTokenType
{
color: white !important;
}
.solkeywords {
color: brown;
.solToken {
float: left;
}
.solstdcontract {
color: blue;
}
.soltime {
color: green;
}
.soltypes {
color: orange;
}
.solMisc {
color: grey;
.solTokenType
{
font-style: italic;
color: #808080;
float: right;
}

15
mix/qml/html/cm/solarized.css

@ -16,14 +16,14 @@ http://ethanschoonover.com/solarized/img/solarized-palette.png
.solarized.base1 { color: #93a1a1; }
.solarized.base2 { color: #eee8d5; }
.solarized.base3 { color: #fdf6e3; }
.solarized.solar-yellow { color: #b58900; }
.solarized.solar-orange { color: #cb4b16; }
.solarized.solar-yellow, .solcurrency { color: #b58900; }
.solarized.solar-orange, .solkeywords { color: #cb4b16; }
.solarized.solar-red { color: #dc322f; }
.solarized.solar-magenta { color: #d33682; }
.solarized.solar-magenta, .solstdcontract { color: #d33682; }
.solarized.solar-violet { color: #6c71c4; }
.solarized.solar-blue { color: #268bd2; }
.solarized.solar-cyan { color: #2aa198; }
.solarized.solar-green { color: #859900; }
.solarized.solar-blue, .soltime { color: #268bd2; }
.solarized.solar-cyan, .soltypes { color: #2aa198; }
.solarized.solar-green, .solMisc { color: #859900; }
/* Color scheme for code-mirror */
@ -166,7 +166,7 @@ view-port
/* Code execution */
.CodeMirror-exechighlight {
background: rgba(255, 255, 255, 0.10);
background: #eee8d5;
}
/* Error annotation */
@ -182,3 +182,4 @@ view-port
padding: 2px;
}
span.CodeMirror-selectedtext { color: #586e75 !important; }

12
mix/qml/html/cm/solidityToken.js

@ -27,3 +27,15 @@ function solMisc()
{
return { "true": true, "false": true, "null": true };
}
function keywordsName()
{
var keywords = {};
keywords[solCurrency.name.toLowerCase()] = "Currency";
keywords[solKeywords.name.toLowerCase()] = "Keyword";
keywords[solStdContract.name.toLowerCase()] = "Contract";
keywords[solTime.name.toLowerCase()] = "Time";
keywords[solTypes.name.toLowerCase()] = "Type";
keywords[solMisc.name.toLowerCase()] = "Misc";
return keywords;
}

1
mix/qml/html/codeeditor.html

@ -22,6 +22,7 @@
<script src="cm/javascript-hint.js"></script>
<script src="cm/anyword-hint.js"></script>
<script src="cm/closebrackets.js"></script>
<script src="cm/mark-selection.js"></script>
<script src="cm/errorannotation.js"></script>
<script src="cm/tern.js"></script>
<script src="cm/acorn.js"></script>

19
mix/qml/html/codeeditor.js

@ -4,7 +4,8 @@ var editor = CodeMirror(document.body, {
matchBrackets: true,
autofocus: true,
gutters: ["CodeMirror-linenumbers", "breakpoints"],
autoCloseBrackets: true
autoCloseBrackets: true,
styleSelectedText: true
});
var ternServer;
@ -134,6 +135,8 @@ highlightExecution = function(start, end) {
executionMark.clear();
if (start === 0 && end + 1 === editor.getValue().length)
return; // Do not hightlight the whole document.
if (debugWarning)
debugWarning.clear();
executionMark = editor.markText(editor.posFromIndex(start), editor.posFromIndex(end), { className: "CodeMirror-exechighlight" });
}
@ -148,6 +151,20 @@ isClean = function()
return editor.isClean(changeId);
}
var debugWarning = null;
showWarning = function(content)
{
if (executionMark)
executionMark.clear();
if (debugWarning)
debugWarning.clear();
var node = document.createElement("div");
node.id = "annotation"
node.innerHTML = content;
node.className = "CodeMirror-errorannotation-context";
debugWarning = editor.addLineWidget(0, node, { coverGutter: false, above: true });
}
var annotation = null;
var compilationCompleteBool = true;
compilationError = function(line, column, content)

1
mix/web.qrc

@ -39,5 +39,6 @@
<file>qml/html/cm/acorn.js</file>
<file>qml/html/cm/acorn_loose.js</file>
<file>qml/html/cm/walk.js</file>
<file>qml/html/cm/mark-selection.js</file>
</qresource>
</RCC>

6
neth/main.cpp

@ -1152,7 +1152,7 @@ int main(int argc, char** argv)
break;
}
#if ETH_FATDB
// Contracts and addresses
y = 1;
auto acs = c->addresses();
@ -1181,6 +1181,10 @@ int main(int argc, char** argv)
if (y > height * 3 / 5 - 4)
break;
}
#else
mvwaddnstr(contractswin, 1, x, "build with ETH_FATDB to list contracts", qwidth);
mvwaddnstr(addswin, 1, x, "build with ETH_FATDB to list addresses", width / 2 - 4);
#endif
// Peers
y = 1;

39
test/CMakeLists.txt

@ -16,6 +16,21 @@ include_directories(${Boost_INCLUDE_DIRS})
include_directories(${CRYPTOPP_INCLUDE_DIRS})
include_directories(${JSON_RPC_CPP_INCLUDE_DIRS})
# search for test names and create ctest tests
enable_testing()
foreach(file ${SRC_LIST})
file(STRINGS ${CMAKE_CURRENT_SOURCE_DIR}/${file} test_list_raw REGEX "BOOST_.*TEST_(SUITE|CASE)")
set(TestSuite "DEFAULT")
foreach(test_raw ${test_list_raw})
string(REGEX REPLACE ".*TEST_(SUITE|CASE)\\(([^ ,\\)]*).*" "\\1 \\2" test ${test_raw})
if(test MATCHES "^SUITE .*")
string(SUBSTRING ${test} 6 -1 TestSuite)
elseif(test MATCHES "^CASE .*")
string(SUBSTRING ${test} 5 -1 TestCase)
add_test(NAME ${TestSuite}/${TestCase} WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test COMMAND testeth -t ${TestSuite}/${TestCase})
endif(test MATCHES "^SUITE .*")
endforeach(test_raw)
endforeach(file)
file(GLOB HEADERS "*.h")
add_executable(testeth ${SRC_LIST} ${HEADERS})
@ -30,6 +45,7 @@ target_link_libraries(testeth ethereum)
target_link_libraries(testeth ethcore)
target_link_libraries(testeth secp256k1)
target_link_libraries(testeth solidity)
target_link_libraries(testeth testutils)
if (NOT HEADLESS AND NOT JUSTTESTS)
target_link_libraries(testeth webthree)
target_link_libraries(testeth natspec)
@ -42,13 +58,36 @@ endif()
target_link_libraries(createRandomVMTest ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
target_link_libraries(createRandomVMTest ethereum)
target_link_libraries(createRandomVMTest ethcore)
target_link_libraries(createRandomVMTest testutils)
target_link_libraries(createRandomStateTest ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
target_link_libraries(createRandomStateTest ethereum)
target_link_libraries(createRandomStateTest ethcore)
target_link_libraries(createRandomStateTest testutils)
target_link_libraries(checkRandomVMTest ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
target_link_libraries(checkRandomVMTest ethereum)
target_link_libraries(checkRandomVMTest ethcore)
target_link_libraries(checkRandomVMTest testutils)
target_link_libraries(checkRandomStateTest ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
target_link_libraries(checkRandomStateTest ethereum)
target_link_libraries(checkRandomStateTest ethcore)
target_link_libraries(checkRandomStateTest testutils)
enable_testing()
set(CTEST_OUTPUT_ON_FAILURE TRUE)
include(EthUtils)
eth_add_test(ClientBase
ARGS --eth_testfile=BlockTests/bcJS_API_Test --eth_threads=1
ARGS --eth_testfile=BlockTests/bcJS_API_Test --eth_threads=3
ARGS --eth_testfile=BlockTests/bcJS_API_Test --eth_threads=10
ARGS --eth_testfile=BlockTests/bcValidBlockTest --eth_threads=1
ARGS --eth_testfile=BlockTests/bcValidBlockTest --eth_threads=3
ARGS --eth_testfile=BlockTests/bcValidBlockTest --eth_threads=10
)
eth_add_test(JsonRpc
ARGS --eth_testfile=BlockTests/bcJS_API_Test
ARGS --eth_testfile=BlockTests/bcValidBlockTest
)

202
test/ClientBase.cpp

@ -0,0 +1,202 @@
/*
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 Marek Kotewicz <marek@ethdev.com>
* @date 2015
*/
#include <boost/test/unit_test.hpp>
#include <libdevcore/CommonJS.h>
#include "TestUtils.h"
using namespace std;
using namespace dev;
using namespace dev::eth;
using namespace dev::test;
BOOST_FIXTURE_TEST_SUITE(ClientBase, ParallelClientBaseFixture)
BOOST_AUTO_TEST_CASE(blocks)
{
enumerateClients([](Json::Value const& _json, dev::eth::ClientBase& _client) -> void
{
for (string const& name: _json["postState"].getMemberNames())
{
Json::Value o = _json["postState"][name];
Address address(name);
// balanceAt
u256 expectedBalance = u256(o["balance"].asString());
u256 balance = _client.balanceAt(address);
ETH_CHECK_EQUAL(expectedBalance, balance);
// countAt
u256 expectedCount = u256(o["nonce"].asString());
u256 count = _client.countAt(address);
ETH_CHECK_EQUAL(expectedCount, count);
// stateAt
for (string const& pos: o["storage"].getMemberNames())
{
u256 expectedState = u256(o["storage"][pos].asString());
u256 state = _client.stateAt(address, u256(pos));
ETH_CHECK_EQUAL(expectedState, state);
}
// codeAt
bytes expectedCode = fromHex(o["code"].asString());
bytes code = _client.codeAt(address);
ETH_CHECK_EQUAL_COLLECTIONS(expectedCode.begin(), expectedCode.end(),
code.begin(), code.end());
}
// number
unsigned expectedNumber = _json["blocks"].size();
unsigned number = _client.number();
ETH_CHECK_EQUAL(expectedNumber, number);
u256 totalDifficulty = u256(_json["genesisBlockHeader"]["difficulty"].asString());
for (Json::Value const& block: _json["blocks"])
{
Json::Value blockHeader = block["blockHeader"];
Json::Value uncles = block["uncleHeaders"];
Json::Value transactions = block["transactions"];
h256 blockHash = h256(fromHex(blockHeader["hash"].asString()));
// just update the difficulty
for (Json::Value const& uncle: uncles)
{
totalDifficulty += u256(uncle["difficulty"].asString());
}
// hashFromNumber
h256 expectedHashFromNumber = h256(fromHex(blockHeader["hash"].asString()));
h256 hashFromNumber = _client.hashFromNumber(jsToInt(blockHeader["number"].asString()));
ETH_CHECK_EQUAL(expectedHashFromNumber, hashFromNumber);
// blockInfo
auto compareBlockInfos = [](Json::Value const& _b, BlockInfo _blockInfo) -> void
{
LogBloom expectedBlockInfoBloom = LogBloom(fromHex(_b["bloom"].asString()));
Address expectedBlockInfoCoinbase = Address(fromHex(_b["coinbase"].asString()));
u256 expectedBlockInfoDifficulty = u256(_b["difficulty"].asString());
bytes expectedBlockInfoExtraData = fromHex(_b["extraData"].asString());
u256 expectedBlockInfoGasLimit = u256(_b["gasLimit"].asString());
u256 expectedBlockInfoGasUsed = u256(_b["gasUsed"].asString());
h256 expectedBlockInfoHash = h256(fromHex(_b["hash"].asString()));
h256 expectedBlockInfoMixHash = h256(fromHex(_b["mixHash"].asString()));
Nonce expectedBlockInfoNonce = Nonce(fromHex(_b["nonce"].asString()));
u256 expectedBlockInfoNumber = u256(_b["number"].asString());
h256 expectedBlockInfoParentHash = h256(fromHex(_b["parentHash"].asString()));
h256 expectedBlockInfoReceiptsRoot = h256(fromHex(_b["receiptTrie"].asString()));
u256 expectedBlockInfoTimestamp = u256(_b["timestamp"].asString());
h256 expectedBlockInfoTransactionsRoot = h256(fromHex(_b["transactionsTrie"].asString()));
h256 expectedBlockInfoUncldeHash = h256(fromHex(_b["uncleHash"].asString()));
ETH_CHECK_EQUAL(expectedBlockInfoBloom, _blockInfo.logBloom);
ETH_CHECK_EQUAL(expectedBlockInfoCoinbase, _blockInfo.coinbaseAddress);
ETH_CHECK_EQUAL(expectedBlockInfoDifficulty, _blockInfo.difficulty);
ETH_CHECK_EQUAL_COLLECTIONS(expectedBlockInfoExtraData.begin(), expectedBlockInfoExtraData.end(),
_blockInfo.extraData.begin(), _blockInfo.extraData.end());
ETH_CHECK_EQUAL(expectedBlockInfoGasLimit, _blockInfo.gasLimit);
ETH_CHECK_EQUAL(expectedBlockInfoGasUsed, _blockInfo.gasUsed);
ETH_CHECK_EQUAL(expectedBlockInfoHash, _blockInfo.hash);
ETH_CHECK_EQUAL(expectedBlockInfoMixHash, _blockInfo.mixHash);
ETH_CHECK_EQUAL(expectedBlockInfoNonce, _blockInfo.nonce);
ETH_CHECK_EQUAL(expectedBlockInfoNumber, _blockInfo.number);
ETH_CHECK_EQUAL(expectedBlockInfoParentHash, _blockInfo.parentHash);
ETH_CHECK_EQUAL(expectedBlockInfoReceiptsRoot, _blockInfo.receiptsRoot);
ETH_CHECK_EQUAL(expectedBlockInfoTimestamp, _blockInfo.timestamp);
ETH_CHECK_EQUAL(expectedBlockInfoTransactionsRoot, _blockInfo.transactionsRoot);
ETH_CHECK_EQUAL(expectedBlockInfoUncldeHash, _blockInfo.sha3Uncles);
};
BlockInfo blockInfo = _client.blockInfo(blockHash);
compareBlockInfos(blockHeader, blockInfo);
// blockDetails
unsigned expectedBlockDetailsNumber = jsToInt(blockHeader["number"].asString());
totalDifficulty += u256(blockHeader["difficulty"].asString());
BlockDetails blockDetails = _client.blockDetails(blockHash);
ETH_CHECK_EQUAL(expectedBlockDetailsNumber, blockDetails.number);
ETH_CHECK_EQUAL(totalDifficulty, blockDetails.totalDifficulty);
auto compareTransactions = [](Json::Value const& _t, Transaction _transaction) -> void
{
bytes expectedTransactionData = fromHex(_t["data"].asString());
u256 expectedTransactionGasLimit = u256(_t["gasLimit"].asString());
u256 expectedTransactionGasPrice = u256(_t["gasPrice"].asString());
u256 expectedTransactionNonce = u256(_t["nonce"].asString());
u256 expectedTransactionSignatureR = h256(fromHex(_t["r"].asString()));
u256 expectedTransactionSignatureS = h256(fromHex(_t["s"].asString()));
// unsigned expectedTransactionSignatureV = jsToInt(t["v"].asString());
ETH_CHECK_EQUAL_COLLECTIONS(expectedTransactionData.begin(), expectedTransactionData.end(),
_transaction.data().begin(), _transaction.data().end());
ETH_CHECK_EQUAL(expectedTransactionGasLimit, _transaction.gas());
ETH_CHECK_EQUAL(expectedTransactionGasPrice, _transaction.gasPrice());
ETH_CHECK_EQUAL(expectedTransactionNonce, _transaction.nonce());
ETH_CHECK_EQUAL(expectedTransactionSignatureR, _transaction.signature().r);
ETH_CHECK_EQUAL(expectedTransactionSignatureS, _transaction.signature().s);
// ETH_CHECK_EQUAL(expectedTransactionSignatureV, _transaction.signature().v); // 27 === 0x0, 28 === 0x1, not sure why
};
Transactions ts = _client.transactions(blockHash);
TransactionHashes tHashes = _client.transactionHashes(blockHash);
unsigned tsCount = _client.transactionCount(blockHash);
ETH_REQUIRE(transactions.size() == ts.size());
ETH_REQUIRE(transactions.size() == tHashes.size());
// transactionCount
ETH_CHECK_EQUAL(transactions.size(), tsCount);
for (unsigned i = 0; i < tsCount; i++)
{
Json::Value t = transactions[i];
// transaction (by block hash and transaction index)
Transaction transaction = _client.transaction(blockHash, i);
compareTransactions(t, transaction);
// transaction (by hash)
Transaction transactionByHash = _client.transaction(transaction.sha3());
compareTransactions(t, transactionByHash);
// transactions
compareTransactions(t, ts[i]);
// transactionHashes
ETH_CHECK_EQUAL(transaction.sha3(), tHashes[i]);
}
// uncleCount
unsigned usCount = _client.uncleCount(blockHash);
ETH_CHECK_EQUAL(uncles.size(), usCount);
for (unsigned i = 0; i < usCount; i++)
{
Json::Value u = uncles[i];
// uncle (by hash)
BlockInfo uncle = _client.uncle(blockHash, i);
compareBlockInfos(u, uncle);
}
}
});
}
BOOST_AUTO_TEST_SUITE_END()

102
test/SolidityNameAndTypeResolution.cpp

@ -28,6 +28,7 @@
#include <libsolidity/Parser.h>
#include <libsolidity/NameAndTypeResolver.h>
#include <libsolidity/Exceptions.h>
#include <libsolidity/GlobalContext.h>
#include "TestHelper.h"
using namespace std;
@ -48,16 +49,28 @@ ASTPointer<SourceUnit> parseTextAndResolveNames(std::string const& _source)
ASTPointer<SourceUnit> sourceUnit = parser.parse(std::make_shared<Scanner>(CharStream(_source)));
NameAndTypeResolver resolver({});
resolver.registerDeclarations(*sourceUnit);
std::shared_ptr<GlobalContext> globalContext = make_shared<GlobalContext>();
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
globalContext->setCurrentContract(*contract);
resolver.updateDeclaration(*globalContext->getCurrentThis());
resolver.updateDeclaration(*globalContext->getCurrentSuper());
resolver.resolveNamesAndTypes(*contract);
}
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
globalContext->setCurrentContract(*contract);
resolver.updateDeclaration(*globalContext->getCurrentThis());
resolver.checkTypeRequirements(*contract);
}
return sourceUnit;
}
static ContractDefinition const* retrieveContract(ASTPointer<SourceUnit> _source, unsigned index)
{
ContractDefinition* contract;
@ -359,7 +372,7 @@ BOOST_AUTO_TEST_CASE(function_canonical_signature)
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
auto functions = contract->getDefinedFunctions();
BOOST_CHECK_EQUAL("foo(uint256,uint64,bool)", functions[0]->getCanonicalSignature());
BOOST_CHECK_EQUAL("foo(uint256,uint64,bool)", functions[0]->externalSignature());
}
}
@ -376,8 +389,93 @@ BOOST_AUTO_TEST_CASE(function_canonical_signature_type_aliases)
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
auto functions = contract->getDefinedFunctions();
BOOST_CHECK_EQUAL("boo(uint256,bytes32,address)", functions[0]->getCanonicalSignature());
if (functions.empty())
continue;
BOOST_CHECK_EQUAL("boo(uint256,bytes32,address)", functions[0]->externalSignature());
}
}
BOOST_AUTO_TEST_CASE(function_external_types)
{
ASTPointer<SourceUnit> sourceUnit;
char const* text = R"(
contract C {
uint a;
}
contract Test {
function boo(uint arg2, bool arg3, bytes8 arg4, bool[2] pairs, uint[] dynamic, C carg) external returns (uint ret) {
ret = 5;
}
})";
ETH_TEST_REQUIRE_NO_THROW(sourceUnit = parseTextAndResolveNames(text), "Parsing and name Resolving failed");
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
auto functions = contract->getDefinedFunctions();
if (functions.empty())
continue;
BOOST_CHECK_EQUAL("boo(uint256,bool,bytes8,bool[2],uint256[],address)", functions[0]->externalSignature());
}
}
BOOST_AUTO_TEST_CASE(function_external_call_allowed_conversion)
{
char const* text = R"(
contract C {}
contract Test {
function externalCall() {
C arg;
this.g(arg);
}
function g (C c) external {}
})";
BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text));
}
BOOST_AUTO_TEST_CASE(function_external_call_not_allowed_conversion)
{
char const* text = R"(
contract C {}
contract Test {
function externalCall() {
address arg;
this.g(arg);
}
function g (C c) external {}
})";
BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError);
}
BOOST_AUTO_TEST_CASE(function_internal_allowed_conversion)
{
char const* text = R"(
contract C {
uint a;
}
contract Test {
C a;
function g (C c) {}
function internalCall() {
g(a);
}
})";
BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text));
}
BOOST_AUTO_TEST_CASE(function_internal_not_allowed_conversion)
{
char const* text = R"(
contract C {
uint a;
}
contract Test {
address a;
function g (C c) {}
function internalCall() {
g(a);
}
})";
BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError);
}
BOOST_AUTO_TEST_CASE(hash_collision_in_interface)

115
test/SolidityOptimizer.cpp

@ -74,6 +74,14 @@ public:
"\nOptimized: " + toHex(optimizedOutput));
}
void checkCSE(AssemblyItems const& _input, AssemblyItems const& _expectation)
{
eth::CommonSubexpressionEliminator cse;
BOOST_REQUIRE(cse.feedItems(_input.begin(), _input.end()) == _input.end());
AssemblyItems output = cse.getOptimizedItems();
BOOST_CHECK_EQUAL_COLLECTIONS(_expectation.begin(), _expectation.end(), output.begin(), output.end());
}
protected:
Address m_optimizedContract;
Address m_nonOptimizedContract;
@ -199,61 +207,100 @@ BOOST_AUTO_TEST_CASE(cse_intermediate_swap)
BOOST_AUTO_TEST_CASE(cse_negative_stack_access)
{
eth::CommonSubexpressionEliminator cse;
AssemblyItems input{AssemblyItem(Instruction::DUP2), AssemblyItem(u256(0))};
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end());
AssemblyItems output = cse.getOptimizedItems();
BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end());
AssemblyItems input{Instruction::DUP2, u256(0)};
checkCSE(input, input);
}
BOOST_AUTO_TEST_CASE(cse_negative_stack_end)
{
eth::CommonSubexpressionEliminator cse;
AssemblyItems input{Instruction::ADD};
checkCSE(input, input);
}
BOOST_AUTO_TEST_CASE(cse_intermediate_negative_stack)
{
AssemblyItems input{Instruction::ADD, u256(1), Instruction::DUP1};
checkCSE(input, input);
}
BOOST_AUTO_TEST_CASE(cse_pop)
{
checkCSE({Instruction::POP}, {Instruction::POP});
}
BOOST_AUTO_TEST_CASE(cse_unneeded_items)
{
AssemblyItems input{
AssemblyItem(Instruction::ADD)
Instruction::ADD,
Instruction::SWAP1,
Instruction::POP,
u256(7),
u256(8),
};
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end());
AssemblyItems output = cse.getOptimizedItems();
BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end());
checkCSE(input, input);
}
BOOST_AUTO_TEST_CASE(cse_intermediate_negative_stack)
BOOST_AUTO_TEST_CASE(cse_constant_addition)
{
AssemblyItems input{u256(7), u256(8), Instruction::ADD};
checkCSE(input, {u256(7 + 8)});
}
BOOST_AUTO_TEST_CASE(cse_invariants)
{
eth::CommonSubexpressionEliminator cse;
AssemblyItems input{
AssemblyItem(Instruction::ADD),
AssemblyItem(u256(1)),
AssemblyItem(Instruction::DUP2)
Instruction::DUP1,
Instruction::DUP1,
u256(0),
Instruction::OR,
Instruction::OR
};
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end());
AssemblyItems output = cse.getOptimizedItems();
BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end());
checkCSE(input, {Instruction::DUP1});
}
BOOST_AUTO_TEST_CASE(cse_pop)
BOOST_AUTO_TEST_CASE(cse_subself)
{
checkCSE({Instruction::DUP1, Instruction::SUB}, {Instruction::POP, u256(0)});
}
BOOST_AUTO_TEST_CASE(cse_subother)
{
checkCSE({Instruction::SUB}, {Instruction::SUB});
}
BOOST_AUTO_TEST_CASE(cse_double_negation)
{
checkCSE({Instruction::DUP5, Instruction::NOT, Instruction::NOT}, {Instruction::DUP5});
}
BOOST_AUTO_TEST_CASE(cse_associativity)
{
eth::CommonSubexpressionEliminator cse;
AssemblyItems input{
AssemblyItem(Instruction::POP)
Instruction::DUP1,
Instruction::DUP1,
u256(0),
Instruction::OR,
Instruction::OR
};
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end());
AssemblyItems output = cse.getOptimizedItems();
BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end());
checkCSE(input, {Instruction::DUP1});
}
BOOST_AUTO_TEST_CASE(cse_unneeded_items)
BOOST_AUTO_TEST_CASE(cse_associativity2)
{
eth::CommonSubexpressionEliminator cse;
AssemblyItems input{
AssemblyItem(Instruction::ADD),
AssemblyItem(Instruction::SWAP1),
AssemblyItem(Instruction::POP),
AssemblyItem(u256(7)),
AssemblyItem(u256(8)),
u256(0),
Instruction::DUP2,
u256(2),
u256(1),
Instruction::DUP6,
Instruction::ADD,
u256(2),
Instruction::ADD,
Instruction::ADD,
Instruction::ADD,
Instruction::ADD
};
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end()) == input.end());
AssemblyItems output = cse.getOptimizedItems();
BOOST_CHECK_EQUAL_COLLECTIONS(input.begin(), input.end(), output.begin(), output.end());
checkCSE(input, {Instruction::DUP2, Instruction::DUP2, Instruction::ADD, u256(5), Instruction::ADD});
}
BOOST_AUTO_TEST_SUITE_END()

16
test/TestHelper.cpp

@ -374,22 +374,6 @@ void checkCallCreates(eth::Transactions _resultCallCreates, eth::Transactions _e
}
}
std::string getTestPath()
{
string testPath;
const char* ptestPath = getenv("ETHEREUM_TEST_PATH");
if (ptestPath == NULL)
{
cnote << " could not find environment variable ETHEREUM_TEST_PATH \n";
testPath = "../../../tests";
}
else
testPath = ptestPath;
return testPath;
}
void userDefinedTest(string testTypeFlag, std::function<void(json_spirit::mValue&, bool)> doTests)
{
for (int i = 1; i < boost::unit_test::framework::master_test_suite().argc; ++i)

2
test/TestHelper.h

@ -28,6 +28,7 @@
#include "JsonSpiritHeaders.h"
#include <libethereum/State.h>
#include <libevm/ExtVMFace.h>
#include <libtestutils/Common.h>
namespace dev
{
@ -138,7 +139,6 @@ void checkLog(eth::LogEntries _resultLogs, eth::LogEntries _expectedLogs);
void checkCallCreates(eth::Transactions _resultCallCreates, eth::Transactions _expectedCallCreates);
void executeTests(const std::string& _name, const std::string& _testPathAppendix, std::function<void(json_spirit::mValue&, bool)> doTests);
std::string getTestPath();
void userDefinedTest(std::string testTypeFlag, std::function<void(json_spirit::mValue&, bool)> doTests);
RLPStream createRLPStreamFromTransactionFields(json_spirit::mObject& _tObj);
eth::LastHashes lastHashes(u256 _currentBlockNumber);

117
test/TestUtils.cpp

@ -0,0 +1,117 @@
/*
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 TestUtils.cpp
* @author Marek Kotewicz <marek@ethdev.com>
* @date 2015
*/
#include <thread>
#include <boost/test/unit_test.hpp>
#include <boost/filesystem.hpp>
#include <libtestutils/BlockChainLoader.h>
#include <libtestutils/FixedClient.h>
#include "TestUtils.h"
using namespace std;
using namespace dev;
using namespace dev::eth;
using namespace dev::test;
namespace dev
{
namespace test
{
bool getCommandLineOption(std::string const& _name);
std::string getCommandLineArgument(std::string const& _name, bool _require = false);
}
}
bool dev::test::getCommandLineOption(string const& _name)
{
auto argc = boost::unit_test::framework::master_test_suite().argc;
auto argv = boost::unit_test::framework::master_test_suite().argv;
bool result = false;
for (auto i = 0; !result && i < argc; ++i)
result = _name == argv[i];
return result;
}
std::string dev::test::getCommandLineArgument(string const& _name, bool _require)
{
auto argc = boost::unit_test::framework::master_test_suite().argc;
auto argv = boost::unit_test::framework::master_test_suite().argv;
for (auto i = 1; i < argc; ++i)
{
string str = argv[i];
if (_name == str.substr(0, _name.size()))
return str.substr(str.find("=") + 1);
}
if (_require)
BOOST_ERROR("Failed getting command line argument: " << _name << " from: " << argv);
return "";
}
LoadTestFileFixture::LoadTestFileFixture()
{
m_json = loadJsonFromFile(toTestFilePath(getCommandLineArgument("--eth_testfile")));
}
void ParallelFixture::enumerateThreads(std::function<void()> callback) const
{
size_t threadsCount = std::stoul(getCommandLineArgument("--eth_threads"), nullptr, 10);
vector<thread> workers;
for (size_t i = 0; i < threadsCount; i++)
workers.emplace_back(callback);
for_each(workers.begin(), workers.end(), [](thread &t)
{
t.join();
});
}
void BlockChainFixture::enumerateBlockchains(std::function<void(Json::Value const&, dev::eth::BlockChain const&, State state)> callback) const
{
for (string const& name: m_json.getMemberNames())
{
BlockChainLoader bcl(m_json[name]);
callback(m_json[name], bcl.bc(), bcl.state());
}
}
void ClientBaseFixture::enumerateClients(std::function<void(Json::Value const&, dev::eth::ClientBase&)> callback) const
{
enumerateBlockchains([&callback](Json::Value const& _json, BlockChain const& _bc, State _state) -> void
{
FixedClient client(_bc, _state);
callback(_json, client);
});
}
void ParallelClientBaseFixture::enumerateClients(std::function<void(Json::Value const&, dev::eth::ClientBase&)> callback) const
{
ClientBaseFixture::enumerateClients([this, &callback](Json::Value const& _json, dev::eth::ClientBase& _client) -> void
{
// json is being copied here
enumerateThreads([callback, _json, &_client]() -> void
{
callback(_json, _client);
});
});
}

82
test/TestUtils.h

@ -0,0 +1,82 @@
/*
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 TestUtils.h
* @author Marek Kotewicz <marek@ethdev.com>
* @date 2015
*/
#pragma once
#include <functional>
#include <string>
#include <json/json.h>
#include <libethereum/BlockChain.h>
#include <libethereum/ClientBase.h>
namespace dev
{
namespace test
{
// should be used for multithread tests
static SharedMutex x_boostTest;
#define ETH_CHECK_EQUAL(x, y) { dev::WriteGuard(x_boostTest); BOOST_CHECK_EQUAL(x, y); }
#define ETH_CHECK_EQUAL_COLLECTIONS(xb, xe, yb, ye) { dev::WriteGuard(x_boostTest); BOOST_CHECK_EQUAL_COLLECTIONS(xb, xe, yb, ye); }
#define ETH_REQUIRE(x) { dev::WriteGuard(x_boostTest); BOOST_REQUIRE(x); }
struct LoadTestFileFixture
{
LoadTestFileFixture();
protected:
Json::Value m_json;
};
struct ParallelFixture
{
void enumerateThreads(std::function<void()> callback) const;
};
struct BlockChainFixture: public LoadTestFileFixture
{
void enumerateBlockchains(std::function<void(Json::Value const&, dev::eth::BlockChain const&, dev::eth::State state)> callback) const;
};
struct ClientBaseFixture: public BlockChainFixture
{
void enumerateClients(std::function<void(Json::Value const&, dev::eth::ClientBase&)> callback) const;
};
// important BOOST TEST do have problems with thread safety!!!
// BOOST_CHECK is not thread safe
// BOOST_MESSAGE is not thread safe
// http://boost.2283326.n4.nabble.com/Is-boost-test-thread-safe-td3471644.html
// http://lists.boost.org/boost-users/2010/03/57691.php
// worth reading
// https://codecrafter.wordpress.com/2012/11/01/c-unit-test-framework-adapter-part-3/
struct ParallelClientBaseFixture: public ClientBaseFixture, public ParallelFixture
{
void enumerateClients(std::function<void(Json::Value const&, dev::eth::ClientBase&)> callback) const;
};
struct JsonRpcFixture: public ClientBaseFixture
{
};
}
}

38
test/net.cpp

@ -145,7 +145,41 @@ public:
bool success = false;
};
BOOST_AUTO_TEST_CASE(badPingNodePacket)
BOOST_AUTO_TEST_CASE(isIPAddressType)
{
string wildcard = "0.0.0.0";
BOOST_REQUIRE(bi::address::from_string(wildcard).is_unspecified());
string empty = "";
BOOST_REQUIRE_THROW(bi::address::from_string(empty).is_unspecified(), std::exception);
string publicAddress192 = "192.169.0.0";
BOOST_REQUIRE(isPublicAddress(publicAddress192));
BOOST_REQUIRE(!isPrivateAddress(publicAddress192));
BOOST_REQUIRE(!isLocalHostAddress(publicAddress192));
string publicAddress172 = "172.32.0.0";
BOOST_REQUIRE(isPublicAddress(publicAddress172));
BOOST_REQUIRE(!isPrivateAddress(publicAddress172));
BOOST_REQUIRE(!isLocalHostAddress(publicAddress172));
string privateAddress192 = "192.168.1.0";
BOOST_REQUIRE(isPrivateAddress(privateAddress192));
BOOST_REQUIRE(!isPublicAddress(privateAddress192));
BOOST_REQUIRE(!isLocalHostAddress(privateAddress192));
string privateAddress172 = "172.16.0.0";
BOOST_REQUIRE(isPrivateAddress(privateAddress172));
BOOST_REQUIRE(!isPublicAddress(privateAddress172));
BOOST_REQUIRE(!isLocalHostAddress(privateAddress172));
string privateAddress10 = "10.0.0.0";
BOOST_REQUIRE(isPrivateAddress(privateAddress10));
BOOST_REQUIRE(!isPublicAddress(privateAddress10));
BOOST_REQUIRE(!isLocalHostAddress(privateAddress10));
}
BOOST_AUTO_TEST_CASE(v2PingNodePacket)
{
// test old versino of pingNode packet w/new
RLPStream s;
@ -153,7 +187,7 @@ BOOST_AUTO_TEST_CASE(badPingNodePacket)
PingNode p((bi::udp::endpoint()));
BOOST_REQUIRE_NO_THROW(p = PingNode::fromBytesConstRef(bi::udp::endpoint(), bytesConstRef(&s.out())));
BOOST_REQUIRE(p.version == 0);
BOOST_REQUIRE(p.version == 2);
}
BOOST_AUTO_TEST_CASE(test_neighbours_packet)

Loading…
Cancel
Save