/* 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 . */ /** @file main.cpp * @author Gav Wood * @date 2014 * RLP tool. */ #include #include #include #include #include "../test/JsonSpiritHeaders.h" #include #include #include #include using namespace std; using namespace dev; namespace js = json_spirit; void help() { cout << "Usage abi enc (, (, ... ))" << endl << " abi enc -a (, (, ... ))" << endl << " abi dec -a [ | ]" << endl << "Options:" << endl << " -a,--abi-file Specify the JSON ABI file." << endl << " -h,--help Print this help message and exit." << endl << " -V,--version Show the version and exit." << endl << "Input options:" << endl << " -f,--format-prefix Require all input formats to be prefixed e.g. 0x for hex, . for decimal, @ for binary." << endl << " -F,--no-format-prefix Require no input format to be prefixed." << endl << " -t,--typing Require all arguments to be typed e.g. b32: (bytes32), u64: (uint64), b[]: (byte[]), i: (int256)." << endl << " -T,--no-typing Require no arguments to be typed." << endl << "Output options:" << endl << " -i,--index Output only the nth (counting from 0) return value." << endl << " -d,--decimal All data should be displayed as decimal." << endl << " -x,--hex Display all data as hex." << endl << " -b,--binary Display all data as binary." << endl << " -p,--prefix Prefix by a base identifier." << endl ; exit(0); } void version() { cout << "abi version " << dev::Version << endl; exit(0); } enum class Mode { Encode, Decode }; enum class Encoding { Auto, Decimal, Hex, Binary, }; enum class Tristate { False = false, True = true, Mu }; enum class Format { Binary, Hex, Decimal, Open, Close }; struct InvalidUserString: public Exception {}; struct InvalidFormat: public Exception {}; enum class Base { Unknown, Bytes, Address, Int, Uint, Fixed }; static const map s_bases = { { Base::Bytes, "bytes" }, { Base::Address, "address" }, { Base::Int, "int" }, { Base::Uint, "uint" }, { Base::Fixed, "fixed" } }; struct EncodingPrefs { Encoding e = Encoding::Auto; bool prefix = true; }; struct ABIType { Base base = Base::Unknown; unsigned size = 32; unsigned ssize = 0; vector dims; string name; ABIType() = default; ABIType(std::string const& _type, std::string const& _name): name(_name) { string rest; for (auto const& i: s_bases) if (boost::algorithm::starts_with(_type, i.second)) { base = i.first; rest = _type.substr(i.second.size()); } if (base == Base::Unknown) throw InvalidFormat(); boost::regex r("(\\d*)(x(\\d+))?((\\[\\d*\\])*)"); boost::smatch res; boost::regex_match(rest, res, r); size = res[1].length() > 0 ? stoi(res[1]) : 0; ssize = res[3].length() > 0 ? stoi(res[3]) : 0; boost::regex r2("\\[(\\d*)\\](.*)"); for (rest = res[4]; boost::regex_match(rest, res, r2); rest = res[2]) dims.push_back(!res[1].length() ? -1 : stoi(res[1])); } ABIType(std::string const& _s) { if (_s.size() < 1) return; switch (_s[0]) { case 'b': base = Base::Bytes; break; case 'a': base = Base::Address; break; case 'i': base = Base::Int; break; case 'u': base = Base::Uint; break; case 'f': base = Base::Fixed; break; default: throw InvalidFormat(); } if (_s.size() < 2) { if (base == Base::Fixed) size = ssize = 16; else if (base == Base::Address || base == Base::Bytes) size = 0; else size = 32; return; } strings d; boost::algorithm::split(d, _s, boost::is_any_of("*")); string s = d[0]; if (s.find_first_of('x') == string::npos) size = stoi(s.substr(1)); else { size = stoi(s.substr(1, s.find_first_of('x') - 1)); ssize = stoi(s.substr(s.find_first_of('x') + 1)); } for (unsigned i = 1; i < d.size(); ++i) if (d[i].empty()) dims.push_back(-1); else dims.push_back(stoi(d[i])); } string canon() const { string ret; switch (base) { case Base::Bytes: ret = "bytes" + (size > 0 ? toString(size) : ""); break; case Base::Address: ret = "address"; break; case Base::Int: ret = "int" + toString(size); break; case Base::Uint: ret = "uint" + toString(size); break; case Base::Fixed: ret = "fixed" + toString(size) + "x" + toString(ssize); break; default: throw InvalidFormat(); } for (int i: dims) ret += "[" + ((i > -1) ? toString(i) : "") + "]"; return ret; } bool isBytes() const { return base == Base::Bytes && !size; } string render(bytes const& _data, EncodingPrefs _e) const { if (base == Base::Uint || base == Base::Int) { if (_e.e == Encoding::Hex) return (_e.prefix ? "0x" : "") + toHex(toCompactBigEndian(fromBigEndian(bytesConstRef(&_data).cropped(32 - size / 8)))); else { bigint i = fromBigEndian(bytesConstRef(&_data).cropped(32 - size / 8)); if (base == Base::Int && i > (bigint(1) << (size - 1))) i -= (bigint(1) << size); return toString(i); } } else if (base == Base::Address) { Address a = Address(h256(_data), Address::AlignRight); return _e.e == Encoding::Binary ? asString(a.asBytes()) : ((_e.prefix ? "0x" : "") + toString(a)); } else if (isBytes()) { return _e.e == Encoding::Binary ? asString(_data) : ((_e.prefix ? "0x" : "") + toHex(_data)); } else if (base == Base::Bytes) { bytesConstRef b(&_data); b = b.cropped(0, size); return _e.e == Encoding::Binary ? asString(b) : ((_e.prefix ? "0x" : "") + toHex(b)); } else throw InvalidFormat(); } bytes unrender(bytes const& _data, Format _f) const { if (isBytes()) { auto ret = _data; while (ret.size() % 32 != 0) ret.push_back(0); return ret; } else return aligned(_data, _f, 32); } void noteHexInput(unsigned _nibbles) { if (base == Base::Unknown) { if (_nibbles == 40) base = Base::Address; else { base = Base::Bytes; size = _nibbles / 2; } } } void noteBinaryInput() { if (base == Base::Unknown) { base = Base::Bytes; size = 32; } } void noteDecimalInput() { if (base == Base::Unknown) { base = Base::Uint; size = 32; } } bytes aligned(bytes const& _b, Format _f, unsigned _length) const { bytes ret = _b; while (ret.size() < _length) if (base == Base::Bytes || (base == Base::Unknown && _f == Format::Binary)) ret.push_back(0); else ret.insert(ret.begin(), 0); while (ret.size() > _length) if (base == Base::Bytes || (base == Base::Unknown && _f == Format::Binary)) ret.pop_back(); else ret.erase(ret.begin()); return ret; } }; tuple fromUser(std::string const& _arg, Tristate _prefix, Tristate _typing) { ABIType type; string val; if (_typing == Tristate::True || (_typing == Tristate::Mu && _arg.find(':') != string::npos)) { if (_arg.find(':') == string::npos) throw InvalidUserString(); type = ABIType(_arg.substr(0, _arg.find(':'))); val = _arg.substr(_arg.find(':') + 1); } else val = _arg; if (_prefix != Tristate::False) { if (val.substr(0, 2) == "0x") { type.noteHexInput(val.size() - 2); return make_tuple(fromHex(val), type, Format::Hex); } if (val.substr(0, 1) == "+") { type.noteDecimalInput(); return make_tuple(toCompactBigEndian(bigint(val.substr(1))), type, Format::Decimal); } if (val.substr(0, 1) == "'") { type.noteBinaryInput(); return make_tuple(asBytes(val.substr(1)), type, Format::Binary); } if (val == "[") return make_tuple(bytes(), type, Format::Open); if (val == "]") return make_tuple(bytes(), type, Format::Close); } if (_prefix != Tristate::True) { if (val.find_first_not_of("0123456789") == string::npos) { type.noteDecimalInput(); return make_tuple(toCompactBigEndian(bigint(val)), type, Format::Decimal); } if (val.find_first_not_of("0123456789abcdefABCDEF") == string::npos) { type.noteHexInput(val.size()); return make_tuple(fromHex(val), type, Format::Hex); } if (val == "[") return make_tuple(bytes(), type, Format::Open); if (val == "]") return make_tuple(bytes(), type, Format::Close); type.noteBinaryInput(); return make_tuple(asBytes(val), type, Format::Binary); } throw InvalidUserString(); } struct ExpectedAdditionalParameter: public Exception {}; struct ExpectedOpen: public Exception {}; struct ExpectedClose: public Exception {}; struct ABIMethod { string name; vector ins; vector 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 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> const& _params) const { bytes ret = name.empty() ? bytes() : id().asBytes(); bytes suffix; // int int[] int // example: 42 [ 1 2 3 ] 69 // int[2][][3] // example: [ [ [ 1 2 3 ] [ 4 5 6 ] ] [ ] ] unsigned pi = 0; for (ABIType const& a: ins) { if (pi >= _params.size()) throw ExpectedAdditionalParameter(); auto put = [&]() { if (a.isBytes()) ret += h256(u256(_params[pi].first.size())).asBytes(); suffix += a.unrender(_params[pi].first, _params[pi].second); pi++; if (pi >= _params.size()) throw ExpectedAdditionalParameter(); }; function, unsigned)> putDim = [&](vector addr, unsigned q) { if (addr.size() == a.dims.size()) put(); else { if (_params[pi].second != Format::Open) throw ExpectedOpen(); ++pi; int l = a.dims[a.dims.size() - 1 - addr.size()]; if (l == -1) { // read ahead in params and discover the arity. unsigned depth = 0; l = 0; for (unsigned pi2 = pi; depth || _params[pi2].second != Format::Close;) { if (_params[pi2].second == Format::Open) ++depth; if (_params[pi2].second == Format::Close) --depth; if (!depth) ++l; if (++pi2 == _params.size()) throw ExpectedClose(); } ret += h256(u256(l)).asBytes(); } q *= l; for (addr.push_back(0); addr.back() < l; ++addr.back()) putDim(addr, q); if (_params[pi].second != Format::Close) throw ExpectedClose(); ++pi; } }; putDim(vector(), 1); } return ret + suffix; } string decode(bytes const& _data, int _index, EncodingPrefs _ep) { stringstream out; unsigned di = 0; vector catDims; for (ABIType const& a: outs) { auto put = [&]() { if (a.isBytes()) { catDims.push_back(fromBigEndian(bytesConstRef(&_data).cropped(di, 32))); di += 32; } }; function, unsigned)> putDim = [&](vector addr, unsigned q) { if (addr.size() == a.dims.size()) put(); else { int l = a.dims[a.dims.size() - 1 - addr.size()]; if (l == -1) { l = fromBigEndian(bytesConstRef(&_data).cropped(di, 32)); catDims.push_back(l); di += 32; } q *= l; for (addr.push_back(0); addr.back() < l; ++addr.back()) putDim(addr, q); } }; putDim(vector(), 1); } unsigned d = 0; for (ABIType const& a: outs) { if (_index == -1 && out.tellp() > 0) out << ", "; auto put = [&]() { if (a.isBytes()) { out << a.render(bytesConstRef(&_data).cropped(di, catDims[d]).toBytes(), _ep); di += ((catDims[d] + 31) / 32) * 32; d++; } else { out << a.render(bytesConstRef(&_data).cropped(di, 32).toBytes(), _ep); di += 32; } }; function)> putDim = [&](vector addr) { if (addr.size() == a.dims.size()) put(); else { out << "["; addr.push_back(0); int l = a.dims[a.dims.size() - 1 - (addr.size() - 1)]; if (l == -1) l = catDims[d++]; for (addr.back() = 0; addr.back() < l; ++addr.back()) { if (addr.back()) out << ", "; putDim(addr); } out << "]"; } }; putDim(vector()); } return out.str(); } }; string canonSig(string const& _name, vector const& _args) { try { string methodArgs; for (auto const& arg: _args) methodArgs += (methodArgs.empty() ? "" : ",") + arg.canon(); return _name + "(" + methodArgs + ")"; } catch (...) { return string(); } } struct UnknownMethod: public Exception {}; struct OverloadedMethod: public Exception {}; class ABI { public: ABI() = default; ABI(std::string const& _json) { js::mValue v; js::read_string(_json, v); for (auto const& i: v.get_array()) { js::mObject o = i.get_obj(); if (o["type"].get_str() != "function") continue; ABIMethod m(o); m_methods[m.id()] = m; } } ABIMethod method(string _nameOrSig, vector 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, ABIMethod> m_methods; }; ostream& operator<<(ostream& _out, ABI const& _abi) { _out << "contract {" << endl; for (auto const& i: _abi.m_methods) _out << " " << i.second.solidityDeclaration() << "; // " << i.first.abridged() << endl; _out << "}" << endl; return _out; } void userOutput(ostream& _out, bytes const& _data, Encoding _e) { switch (_e) { case Encoding::Binary: _out.write((char const*)_data.data(), _data.size()); break; default: _out << toHex(_data) << endl; } } template vector(T()))>::type> retrieve(vector const& _t) { vector(T()))>::type> ret; for (T const& i: _t) ret.push_back(get(i)); return ret; } int main(int argc, char** argv) { Encoding encoding = Encoding::Auto; Mode mode = Mode::Encode; string abiFile; string method; Tristate formatPrefix = Tristate::Mu; Tristate typePrefix = Tristate::Mu; EncodingPrefs prefs; bool verbose = false; int outputIndex = -1; vector> params; vector args; string incoming; for (int i = 1; i < argc; ++i) { string arg = argv[i]; if (arg == "-h" || arg == "--help") help(); else if (arg == "enc" && i == 1) mode = Mode::Encode; else if (arg == "dec" && i == 1) mode = Mode::Decode; else if ((arg == "-a" || arg == "--abi") && argc > i) abiFile = argv[++i]; else if ((arg == "-i" || arg == "--index") && argc > i) outputIndex = atoi(argv[++i]); else if (arg == "-p" || arg == "--prefix") prefs.prefix = true; else if (arg == "-f" || arg == "--format-prefix") formatPrefix = Tristate::True; else if (arg == "-F" || arg == "--no-format-prefix") formatPrefix = Tristate::False; else if (arg == "-t" || arg == "--typing") typePrefix = Tristate::True; else if (arg == "-T" || arg == "--no-typing") typePrefix = Tristate::False; else if (arg == "-x" || arg == "--hex") prefs.e = Encoding::Hex; else if (arg == "-d" || arg == "--decimal" || arg == "--dec") prefs.e = Encoding::Decimal; else if (arg == "-b" || arg == "--binary" || arg == "--bin") prefs.e = Encoding::Binary; else if (arg == "-v" || arg == "--verbose") verbose = true; else if (arg == "-V" || arg == "--version") version(); else if (method.empty()) method = arg; else if (mode == Mode::Encode) { auto u = fromUser(arg, formatPrefix, typePrefix); args.push_back(get<1>(u)); params.push_back(make_pair(get<0>(u), get<2>(u))); } else if (mode == Mode::Decode) incoming += arg; } string abiData; if (!abiFile.empty()) abiData = contentsString(abiFile); if (mode == Mode::Encode) { ABIMethod m; if (abiData.empty()) m = ABIMethod(method, args); else { ABI abi(abiData); if (verbose) cerr << "ABI:" << endl << abi; try { m = abi.method(method, args); } catch (...) { cerr << "Unknown method in ABI." << endl; exit(-1); } } try { userOutput(cout, m.encode(params), encoding); } catch (ExpectedAdditionalParameter const&) { cerr << "Expected additional parameter in input." << endl; exit(-1); } catch (ExpectedOpen const&) { cerr << "Expected open-bracket '[' in input." << endl; exit(-1); } catch (ExpectedClose const&) { cerr << "Expected close-bracket ']' in input." << endl; exit(-1); } } else if (mode == Mode::Decode) { if (abiData.empty()) { cerr << "Please specify an ABI file." << endl; exit(-1); } else { ABI abi(abiData); ABIMethod m; if (verbose) cerr << "ABI:" << endl << abi; try { m = abi.method(method, args); } catch(...) { cerr << "Unknown method in ABI." << endl; exit(-1); } string encoded; if (incoming == "--" || incoming.empty()) for (int i = cin.get(); i != -1; i = cin.get()) encoded.push_back((char)i); else { encoded = contentsString(incoming); } cout << m.decode(fromHex(boost::trim_copy(encoded)), outputIndex, prefs) << endl; } } return 0; }