diff --git a/README.md b/README.md index 7b9a78e6c..14e37b879 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ By Gav Wood, 2014. ----------|---------|-----|-------- develop | [![Build+Status](http://build.ethdev.com/buildstatusimage?builder=Linux%20C%2B%2B%20develop%20branch)](http://build.ethdev.com/builders/Linux%20C%2B%2B%20develop%20branch/builds/-1) | [![Build+Status](http://build.ethdev.com/buildstatusimage?builder=OSX%20C%2B%2B%20develop%20branch)](http://build.ethdev.com/builders/OSX%20C%2B%2B%20develop%20branch/builds/-1) | [![Build+Status](http://build.ethdev.com/buildstatusimage?builder=Windows%20C%2B%2B%20develop%20branch)](http://build.ethdev.com/builders/Windows%20C%2B%2B%20develop%20branch/builds/-1) master | [![Build+Status](http://build.ethdev.com/buildstatusimage?builder=Linux%20C%2B%2B%20master%20branch)](http://build.ethdev.com/builders/Linux%20C%2B%2B%20master%20branch/builds/-1) | [![Build+Status](http://build.ethdev.com/buildstatusimage?builder=OSX%20C%2B%2B%20master%20branch)](http://build.ethdev.com/builders/OSX%20C%2B%2B%20master%20branch/builds/-1) | [![Build+Status](http://build.ethdev.com/buildstatusimage?builder=Windows%20C%2B%2B%20master%20branch)](http://build.ethdev.com/builders/Windows%20C%2B%2B%20master%20branch/builds/-1) +evmjit | [![Build+Status](http://build.ethdev.com/buildstatusimage?builder=Linux%20C%2B%2B%20develop%20evmjit)](http://build.ethdev.com/builders/Linux%20C%2B%2B%20develop%20evmjit/builds/-1) | [![Build+Status](http://build.ethdev.com/buildstatusimage?builder=OSX%20C%2B%2B%20develop%20evmjit)](http://build.ethdev.com/builders/OSX%20C%2B%2B%20develop%20evmjit/builds/-1) | N/A [![Stories in Ready](https://badge.waffle.io/ethereum/cpp-ethereum.png?label=ready&title=Ready)](http://waffle.io/ethereum/cpp-ethereum) diff --git a/alethzero/Main.ui b/alethzero/Main.ui index 97ed31aa9..328b4acba 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -148,6 +148,7 @@ + @@ -2067,6 +2068,11 @@ font-size: 14pt Use &LLVM-EVM + + + &Kill Account + + diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index cf3411fdc..3c87f649a 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -102,7 +102,7 @@ static vector keysAsVector(QList const& keys) return {begin(list), end(list)}; } -static QString contentsOfQResource(string const& res) +QString contentsOfQResource(string const& res) { QFile file(QString::fromStdString(res)); if (!file.open(QFile::ReadOnly)) @@ -112,7 +112,7 @@ static QString contentsOfQResource(string const& res) } //Address c_config = Address("661005d2720d855f1d9976f88bb10c1a3398c77f"); -Address c_newConfig = Address("661005d2720d855f1d9976f88bb10c1a3398c77f"); +Address c_newConfig = Address("c6d9d2cd449a754c494264e1809c50e34d64562b"); //Address c_nameReg = Address("ddd1cea741d548f90d86fb87a3ae6492e18c03a1"); Main::Main(QWidget *parent) : @@ -178,13 +178,13 @@ Main::Main(QWidget *parent) : QWebSettings::globalSettings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); QWebFrame* f = ui->webView->page()->mainFrame(); f->disconnect(SIGNAL(javaScriptWindowObjectCleared())); + connect(f, &QWebFrame::javaScriptWindowObjectCleared, QETH_INSTALL_JS_NAMESPACE(f, this, qweb)); connect(m_qweb, SIGNAL(onNewId(QString)), this, SLOT(addNewId(QString))); }); connect(ui->webView, &QWebView::loadFinished, [=]() { - m_qweb->poll(); }); connect(ui->webView, &QWebView::titleChanged, [=]() @@ -311,7 +311,7 @@ void Main::installBalancesWatch() altCoins.push_back(right160(ethereum()->stateAt(coinsAddr, i + 1))); for (auto i: m_myKeys) for (auto c: altCoins) - tf.address(c).topic(h256(i.address(), h256::AlignRight)); + tf.address(c).topic(0, h256(i.address(), h256::AlignRight)); uninstallWatch(m_balancesFilter); m_balancesFilter = installWatch(tf, [=](LocalisedLogEntries const&){ onBalancesChange(); }); @@ -434,6 +434,11 @@ void Main::on_jsInput_returnPressed() ui->jsInput->setText(""); } +QVariant Main::evalRaw(QString const& _js) +{ + return ui->webView->page()->currentFrame()->evaluateJavaScript(_js); +} + void Main::eval(QString const& _js) { if (_js.trimmed().isEmpty()) @@ -721,7 +726,7 @@ void Main::readSettings(bool _skipGeometry) ui->enableOptimizer->setChecked(m_enableOptimizer); ui->clientName->setText(s.value("clientName", "").toString()); if (ui->clientName->text().isEmpty()) - ui->clientName->setText(QInputDialog::getText(this, "Enter identity", "Enter a name that will identify you on the peer network")); + ui->clientName->setText(QInputDialog::getText(nullptr, "Enter identity", "Enter a name that will identify you on the peer network")); ui->idealPeers->setValue(s.value("idealPeers", ui->idealPeers->value()).toInt()); ui->port->setValue(s.value("port", ui->port->value()).toInt()); ui->nameReg->setText(s.value("nameReg", "").toString()); @@ -783,6 +788,7 @@ void Main::on_importKeyFile_triggered() } } + cnote << k.address(); if (std::find(m_myKeys.begin(), m_myKeys.end(), k) == m_myKeys.end()) { if (m_myKeys.empty()) @@ -1179,9 +1185,6 @@ void Main::timerEvent(QTimerEvent*) else interval += 100; - if (m_qweb) - m_qweb->poll(); - for (auto const& i: m_handlers) { auto ls = ethereum()->checkWatch(i.first); @@ -1594,7 +1597,7 @@ void Main::on_destination_currentTextChanged() // updateFee(); } -static shh::Topic topicFromText(QString _s) +static shh::FullTopic topicFromText(QString _s) { shh::BuildTopic ret; while (_s.size()) @@ -1671,7 +1674,7 @@ string const Main::getFunctionHashes(dev::solidity::CompilerStack const &_compil { ret += it.first.abridged(); ret += " :"; - ret += it.second->getName() + "\n"; + ret += it.second->getDeclaration().getName() + "\n"; } return ret; } @@ -1697,7 +1700,7 @@ void Main::on_data_textChanged() // compiler.addSources(dev::solidity::StandardSources); m_data = compiler.compile(src, m_enableOptimizer); solidity = "

Solidity

"; - solidity += "
" + QString::fromStdString(compiler.getInterface()).replace(QRegExp("\\s"), "").toHtmlEscaped() + "
"; + solidity += "
var " + QString::fromStdString(compiler.getContractNames().front()) + " = web3.eth.contractFromAbi(" + QString::fromStdString(compiler.getInterface()).replace(QRegExp("\\s"), "").toHtmlEscaped() + ");
"; solidity += "
" + QString::fromStdString(compiler.getSolidityInterface()).toHtmlEscaped() + "
"; solidity += "
" + QString::fromStdString(getFunctionHashes(compiler)).toHtmlEscaped() + "
"; } @@ -1990,16 +1993,71 @@ bool beginsWith(Address _a, bytes const& _b) void Main::on_create_triggered() { bool ok = true; - QString s = QInputDialog::getText(this, "Special Beginning?", "If you want a special key, enter some hex digits that it should begin with.\nNOTE: The more you enter, the longer generation will take.", QLineEdit::Normal, QString(), &ok); + enum { NoVanity = 0, FirstTwo, FirstTwoNextTwo, FirstThree, FirstFour, StringMatch }; + QStringList items = {"No vanity (instant)", "Two pairs first (a few seconds)", "Two pairs first and second (a few minutes)", "Three pairs first (a few minutes)", "Four pairs first (several hours)", "Specific hex string"}; + unsigned v = items.QList::indexOf(QInputDialog::getItem(this, "Vanity Key?", "Would you a vanity key? This could take several hours.", items, 0, false, &ok)); if (!ok) return; + + bytes bs; + if (v == StringMatch) + { + QString s = QInputDialog::getText(this, "Vanity Beginning?", "Enter some hex digits that it should begin with.\nNOTE: The more you enter, the longer generation will take.", QLineEdit::Normal, QString(), &ok); + if (!ok) + return; + bs = fromHex(s.toStdString()); + } + KeyPair p; - while (!beginsWith(p.address(), asBytes(s.toStdString()))) - p = KeyPair::create(); + bool keepGoing = true; + unsigned done = 0; + function f = [&]() { + KeyPair lp; + while (keepGoing) + { + done++; + if (done % 1000 == 0) + cnote << "Tried" << done << "keys"; + lp = KeyPair::create(); + auto a = lp.address(); + if (v == NoVanity || + (v == FirstTwo && a[0] == a[1]) || + (v == FirstTwoNextTwo && a[0] == a[1] && a[2] == a[3]) || + (v == FirstThree && a[0] == a[1] && a[1] == a[2]) || + (v == FirstFour && a[0] == a[1] && a[1] == a[2] && a[2] == a[3]) || + (v == StringMatch && beginsWith(lp.address(), bs)) + ) + break; + } + if (keepGoing) + p = lp; + keepGoing = false; + }; + vector ts; + for (unsigned t = 0; t < std::thread::hardware_concurrency() - 1; ++t) + ts.push_back(new std::thread(f)); + f(); + for (std::thread* t: ts) + { + t->join(); + delete t; + } m_myKeys.append(p); keysChanged(); } +void Main::on_killAccount_triggered() +{ + if (ui->ourAccounts->currentRow() >= 0 && ui->ourAccounts->currentRow() < m_myKeys.size()) + { + auto k = m_myKeys[ui->ourAccounts->currentRow()]; + if (ethereum()->balanceAt(k.address()) != 0 && QMessageBox::critical(this, "Kill Account?!", "Account " + render(k.address()) + " has " + QString::fromStdString(formatBalance(ethereum()->balanceAt(k.address()))) + " in it. It, and any contract that this account can access, will be lost forever if you continue. Do NOT continue unless you know what you are doing.\nAre you sure you want to continue?", QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) + return; + m_myKeys.erase(m_myKeys.begin() + ui->ourAccounts->currentRow()); + keysChanged(); + } +} + void Main::on_debugStep_triggered() { if (ui->debugTimeline->value() < m_history.size()) { @@ -2356,10 +2414,10 @@ void Main::refreshWhispers() shh::Envelope const& e = w.second; shh::Message m; for (pair const& i: m_server->ids()) - if (!!(m = e.open(i.second))) + if (!!(m = e.open(shh::FullTopic(), i.second))) break; if (!m) - m = e.open(); + m = e.open(shh::FullTopic()); QString msg; if (m.from()) @@ -2372,7 +2430,7 @@ void Main::refreshWhispers() time_t ex = e.expiry(); QString t(ctime(&ex)); t.chop(1); - QString item = QString("[%1 - %2s] *%3 %5 %4").arg(t).arg(e.ttl()).arg(e.workProved()).arg(toString(e.topics()).c_str()).arg(msg); + QString item = QString("[%1 - %2s] *%3 %5 %4").arg(t).arg(e.ttl()).arg(e.workProved()).arg(toString(e.topic()).c_str()).arg(msg); ui->whispers->addItem(item); } } diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index bcfa8a0fc..b5639fc68 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -71,6 +71,8 @@ struct WorldState using WatchHandler = std::function; +QString contentsOfQResource(std::string const& res); + class Main : public QMainWindow { Q_OBJECT @@ -87,6 +89,8 @@ public: std::string lookupNatSpecUserNotice(dev::h256 const& _contractHash, dev::bytes const& _transactionData); QList owned() const { return m_myIdentities + m_myKeys; } + + QVariant evalRaw(QString const& _js); public slots: void load(QString _file); @@ -104,6 +108,7 @@ private slots: void on_mine_triggered(); void on_send_clicked(); void on_create_triggered(); + void on_killAccount_triggered(); void on_net_triggered(); void on_verbosity_valueChanged(); void on_ourAccounts_doubleClicked(); diff --git a/alethzero/OurWebThreeStubServer.cpp b/alethzero/OurWebThreeStubServer.cpp index ce1f28507..37d37bce1 100644 --- a/alethzero/OurWebThreeStubServer.cpp +++ b/alethzero/OurWebThreeStubServer.cpp @@ -24,25 +24,26 @@ #include #include #include + #include "MainWin.h" using namespace std; using namespace dev; using namespace dev::eth; -OurWebThreeStubServer::OurWebThreeStubServer(jsonrpc::AbstractServerConnector& _conn, dev::WebThreeDirect& _web3, - std::vector const& _accounts, Main* main): +OurWebThreeStubServer::OurWebThreeStubServer(jsonrpc::AbstractServerConnector& _conn, WebThreeDirect& _web3, + vector const& _accounts, Main* main): WebThreeStubServer(_conn, _web3, _accounts), m_web3(&_web3), m_main(main) {} -std::string OurWebThreeStubServer::shh_newIdentity() +string OurWebThreeStubServer::shh_newIdentity() { - dev::KeyPair kp = dev::KeyPair::create(); + KeyPair kp = dev::KeyPair::create(); emit onNewId(QString::fromStdString(toJS(kp.sec()))); return toJS(kp.pub()); } -bool OurWebThreeStubServer::showAuthenticationPopup(std::string const& _title, std::string const& _text) const +bool OurWebThreeStubServer::showAuthenticationPopup(string const& _title, string const& _text) const { QMessageBox userInput; userInput.setText(QString::fromStdString(_title)); @@ -54,19 +55,65 @@ bool OurWebThreeStubServer::showAuthenticationPopup(std::string const& _title, s return userInput.exec() == QMessageBox::Ok; } -bool OurWebThreeStubServer::authenticate(dev::TransactionSkeleton const& _t) const +void OurWebThreeStubServer::showBasicValueTransferNotice(u256 _value) const +{ + QMessageBox notice; + notice.setText("Basic Value Transfer Transaction"); + notice.setInformativeText(QString::fromStdString("Value is " + toString(_value))); + notice.setStandardButtons(QMessageBox::Ok); + notice.exec(); +} + +bool OurWebThreeStubServer::authenticate(TransactionSkeleton const& _t) { h256 contractCodeHash = m_web3->ethereum()->postState().codeHash(_t.to); if (contractCodeHash == EmptySHA3) - // recipient has no code - nothing special about this transaction. - // TODO: show basic message for value transfer. + // contract creation + return true; + + if (false) //TODO: When is is just a value transfer? + { + // recipient has no code - nothing special about this transaction, show basic value transfer info + showBasicValueTransferNotice(_t.value); return true; + } + + string userNotice = m_main->lookupNatSpecUserNotice(contractCodeHash, _t.data); - std::string userNotice = m_main->lookupNatSpecUserNotice(contractCodeHash, _t.data); if (userNotice.empty()) return showAuthenticationPopup("Unverified Pending Transaction", "An undocumented transaction is about to be executed."); + QNatspecExpressionEvaluator evaluator(this, m_main); + userNotice = evaluator.evalExpression(QString::fromStdString(userNotice)).toStdString(); + // otherwise it's a transaction to a contract for which we have the natspec return showAuthenticationPopup("Pending Transaction", userNotice); } + +QNatspecExpressionEvaluator::QNatspecExpressionEvaluator(OurWebThreeStubServer* _server, Main* _main) +: m_server(_server), m_main(_main) +{} + +QNatspecExpressionEvaluator::~QNatspecExpressionEvaluator() +{} + +QString QNatspecExpressionEvaluator::evalExpression(QString const& _expression) const +{ + // load natspec.js only when we need it for natspec evaluation + m_main->evalRaw(contentsOfQResource(":/js/natspec.js")); + QVariant result = m_main->evalRaw("evaluateExpression('" + _expression + "')"); + if (result.type() == QVariant::Invalid) + { + cerr << "Could not evaluate natspec expression: \"" << _expression.toStdString() << "\"" << endl; + // return the expression unevaluated + return _expression; + } + return result.toString(); +} + + + + + + diff --git a/alethzero/OurWebThreeStubServer.h b/alethzero/OurWebThreeStubServer.h index a67af0827..5223d79bd 100644 --- a/alethzero/OurWebThreeStubServer.h +++ b/alethzero/OurWebThreeStubServer.h @@ -35,14 +35,31 @@ public: std::vector const& _accounts, Main* main); virtual std::string shh_newIdentity() override; - virtual bool authenticate(dev::TransactionSkeleton const& _t) const; + virtual bool authenticate(dev::TransactionSkeleton const& _t); signals: void onNewId(QString _s); private: bool showAuthenticationPopup(std::string const& _title, std::string const& _text) const; + void showBasicValueTransferNotice(dev::u256 _value) const; dev::WebThreeDirect* m_web3; Main* m_main; }; + + +class QNatspecExpressionEvaluator: public QObject +{ + Q_OBJECT + +public: + QNatspecExpressionEvaluator(OurWebThreeStubServer* _server, Main* _main); + virtual ~QNatspecExpressionEvaluator(); + + QString evalExpression(QString const& _expression) const; + +private: + OurWebThreeStubServer* m_server; + Main* m_main; +}; diff --git a/eth/main.cpp b/eth/main.cpp index 515197ddc..7a128298a 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -39,7 +39,7 @@ #endif #if ETH_JSONRPC #include -#include +#include #endif #include "BuildInfo.h" using namespace std; diff --git a/evmjit/libevmjit/Compiler.cpp b/evmjit/libevmjit/Compiler.cpp index 1d00fd225..531ad5526 100644 --- a/evmjit/libevmjit/Compiler.cpp +++ b/evmjit/libevmjit/Compiler.cpp @@ -450,13 +450,17 @@ void Compiler::compileBasicBlock(BasicBlock& _basicBlock, bytes const& _bytecode auto word = stack.pop(); auto k32_ = m_builder.CreateTrunc(idx, m_builder.getIntNTy(5), "k_32"); - auto k32 = m_builder.CreateZExt(k32_, Type::Word); - auto k32x8 = m_builder.CreateMul(k32, Constant::get(8), "kx8"); + auto k32 = m_builder.CreateZExt(k32_, Type::lowPrecision); + auto k32x8 = m_builder.CreateMul(k32, m_builder.getInt64(8), "kx8"); // test for word >> (k * 8 + 7) - auto bitpos = m_builder.CreateAdd(k32x8, Constant::get(7), "bitpos"); - auto bitval = m_builder.CreateLShr(word, bitpos, "bitval"); - auto bittest = m_builder.CreateTrunc(bitval, Type::Bool, "bittest"); + auto bitpos = m_builder.CreateAdd(k32x8, m_builder.getInt64(7), "bitpos"); + auto bittester = m_builder.CreateShl(Constant::get(1), bitpos); + auto bitresult = m_builder.CreateAnd(word, bittester); + auto bittest = m_builder.CreateICmpUGT(bitresult, Constant::get(0)); + // FIXME: The following does not work - LLVM bug, report! + //auto bitval = m_builder.CreateLShr(word, bitpos, "bitval"); + //auto bittest = m_builder.CreateTrunc(bitval, Type::Bool, "bittest"); auto mask_ = m_builder.CreateShl(Constant::get(1), bitpos); auto mask = m_builder.CreateSub(mask_, Constant::get(1), "mask"); diff --git a/libdevcore/Common.h b/libdevcore/Common.h index 9fefea45a..9edeacccb 100644 --- a/libdevcore/Common.h +++ b/libdevcore/Common.h @@ -36,6 +36,7 @@ #include #include #include +#include #pragma warning(push) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" @@ -117,6 +118,15 @@ inline unsigned int toLog2(u256 _x) return ret; } +/// RAII utility class whose destructor calls a given function. +class ScopeGuard { +public: + ScopeGuard(std::function _f): m_f(_f) {} + ~ScopeGuard() { m_f(); } +private: + std::function m_f; +}; + // Assertions... #if defined(_MSC_VER) diff --git a/libdevcore/CommonJS.h b/libdevcore/CommonJS.h index dce6d9b60..8e6c5fe53 100644 --- a/libdevcore/CommonJS.h +++ b/libdevcore/CommonJS.h @@ -65,7 +65,7 @@ template FixedHash jsToFixed(std::string const& _s) { if (_s.substr(0, 2) == "0x") // Hex - return FixedHash(_s.substr(2 + std::max(40, _s.size() - 2) - 40)); + return FixedHash(_s.substr(2 + std::max(N * 2, _s.size() - 2) - N * 2)); else if (_s.find_first_not_of("0123456789") == std::string::npos) // Decimal return (typename FixedHash::Arith)(_s); diff --git a/libdevcore/FixedHash.h b/libdevcore/FixedHash.h index 6c42aa501..561f2f405 100644 --- a/libdevcore/FixedHash.h +++ b/libdevcore/FixedHash.h @@ -67,6 +67,9 @@ public: /// Explicitly construct, copying from a byte array. explicit FixedHash(bytes const& _b) { if (_b.size() == N) memcpy(m_data.data(), _b.data(), std::min(_b.size(), N)); } + /// Explicitly construct, copying from a byte array. + explicit FixedHash(bytesConstRef _b) { if (_b.size() == N) memcpy(m_data.data(), _b.data(), std::min(_b.size(), N)); } + /// Explicitly construct, copying from a bytes in memory with given pointer. explicit FixedHash(byte const* _bs, ConstructFromPointerType) { memcpy(m_data.data(), _bs, N); } diff --git a/libdevcrypto/Common.cpp b/libdevcrypto/Common.cpp index e1e9661c4..0a94662c8 100644 --- a/libdevcrypto/Common.cpp +++ b/libdevcrypto/Common.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include "SHA3.h" @@ -79,6 +80,18 @@ bool dev::decrypt(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext) return true; } +void dev::encryptSym(Secret const& _k, bytesConstRef _plain, bytes& o_cipher) +{ + // TOOD: @alex @subtly do this properly. + encrypt(KeyPair(_k).pub(), _plain, o_cipher); +} + +bool dev::decryptSym(Secret const& _k, bytesConstRef _cipher, bytes& o_plain) +{ + // TODO: @alex @subtly do this properly. + return decrypt(_k, _cipher, o_plain); +} + Public dev::recover(Signature const& _sig, h256 const& _message) { return s_secp256k1.recover(_sig, _message.ref()); @@ -96,12 +109,16 @@ bool dev::verify(Public const& _p, Signature const& _s, h256 const& _hash) KeyPair KeyPair::create() { - static mt19937_64 s_eng(time(0) + chrono::high_resolution_clock::now().time_since_epoch().count()); + static boost::thread_specific_ptr s_eng; + static unsigned s_id = 0; + if (!s_eng.get()) + s_eng.reset(new mt19937_64(time(0) + chrono::high_resolution_clock::now().time_since_epoch().count() + ++s_id)); + uniform_int_distribution d(0, 255); for (int i = 0; i < 100; ++i) { - KeyPair ret(FixedHash<32>::random(s_eng)); + KeyPair ret(FixedHash<32>::random(*s_eng.get())); if (ret.address()) return ret; } diff --git a/libdevcrypto/Common.h b/libdevcrypto/Common.h index 2eea2b83c..e91df2526 100644 --- a/libdevcrypto/Common.h +++ b/libdevcrypto/Common.h @@ -86,7 +86,13 @@ void encrypt(Public const& _k, bytesConstRef _plain, bytes& o_cipher); /// Decrypts cipher using Secret key. bool decrypt(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext); - + +/// Symmetric encryption. +void encryptSym(Secret const& _k, bytesConstRef _plain, bytes& o_cipher); + +/// Symmetric decryption. +bool decryptSym(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext); + /// Recovers Public key from signed message hash. Public recover(Signature const& _sig, h256 const& _hash); diff --git a/libdevcrypto/CryptoPP.h b/libdevcrypto/CryptoPP.h index 7ec95c552..fa9d92aa1 100644 --- a/libdevcrypto/CryptoPP.h +++ b/libdevcrypto/CryptoPP.h @@ -62,7 +62,7 @@ using namespace CryptoPP; inline ECP::Point publicToPoint(Public const& _p) { Integer x(_p.data(), 32); Integer y(_p.data() + 32, 32); return std::move(ECP::Point(x,y)); } inline Integer secretToExponent(Secret const& _s) { return std::move(Integer(_s.data(), Secret::size)); } - + /** * CryptoPP secp256k1 algorithms. */ diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index c70bc353b..8de20c21d 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -32,7 +32,7 @@ namespace dev namespace eth { -const unsigned c_protocolVersion = 51; +const unsigned c_protocolVersion = 52; const unsigned c_databaseVersion = 5; static const vector> g_units = diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 45d79701a..1addcbc14 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -60,7 +60,7 @@ std::map const& dev::eth::genesisState() { // Initialise. for (auto i: vector({ - "51ba59315b3a95761d0863b05ccc7a7f54703d99", + "dbdbdb2cbd23b783741e8d7fcf51e459b497e4a6", "e6716f9544a56c530d868e4bfbacb172315bdead", "b9c015918bdaba24b4ff057a92a3873d6eb201be", "1a26338f0d905e295fccb71fa9ea849ffa12aaf4", diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index fcc18c2d1..0f7fb64c6 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -226,11 +226,12 @@ void Client::uninstallWatch(unsigned _i) void Client::noteChanged(h256Set const& _filters) { Guard l(m_filterLock); + cnote << "noteChanged(" << _filters << ")"; // accrue all changes left in each filter into the watches. for (auto& i: m_watches) if (_filters.count(i.second.id)) { -// cwatch << "!!!" << i.first << i.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 @@ -642,14 +643,20 @@ Transaction Client::transaction(h256 _blockHash, unsigned _i) const { auto bl = m_bc.block(_blockHash); RLP b(bl); - return Transaction(b[1][_i].data(), CheckSignature::Range); + if (_i < b[1].itemCount()) + return Transaction(b[1][_i].data(), CheckSignature::Range); + else + return Transaction(); } BlockInfo Client::uncle(h256 _blockHash, unsigned _i) const { auto bl = m_bc.block(_blockHash); RLP b(bl); - return BlockInfo::fromHeader(b[2][_i].data()); + if (_i < b[2].itemCount()) + return BlockInfo::fromHeader(b[2][_i].data()); + else + return BlockInfo(); } LocalisedLogEntries Client::logs(LogFilter const& _f) const diff --git a/libethereum/Client.h b/libethereum/Client.h index 9d2396e46..d53781d90 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -277,17 +277,7 @@ public: /// Kills the blockchain. Just for debug use. void killChain(); -private: - /// Do some work. Handles blockchain maintenance and mining. - virtual void doWork(); - - virtual void doneWorking(); - - /// Overrides for being a mining host. - virtual void setupState(State& _s); - virtual bool turbo() const { return m_turboMining; } - virtual bool force() const { return m_forceMining; } - +protected: /// Collate the changed filters for the bloom filter of the given pending transaction. /// Insert any filters that are activated into @a o_changed. void appendFromNewPending(TransactionReceipt const& _receipt, h256Set& io_changed); @@ -300,6 +290,17 @@ private: /// This doesn't actually make any callbacks, but incrememnts some counters in m_watches. void noteChanged(h256Set const& _filters); +private: + /// Do some work. Handles blockchain maintenance and mining. + virtual void doWork(); + + virtual void doneWorking(); + + /// Overrides for being a mining host. + virtual void setupState(State& _s); + virtual bool turbo() const { return m_turboMining; } + virtual bool force() const { return m_forceMining; } + /// Return the actual block number of the block with the given int-number (positive is the same, INT_MIN is genesis block, < 0 is negative age, thus -1 is most recently mined, 0 is pending. unsigned numberOf(int _b) const; diff --git a/libethereum/Executive.cpp b/libethereum/Executive.cpp index dc2e62824..cea7d21f4 100644 --- a/libethereum/Executive.cpp +++ b/libethereum/Executive.cpp @@ -72,17 +72,17 @@ bool Executive::setup(bytesConstRef _rlp) BOOST_THROW_EXCEPTION(OutOfGas() << RequirementError((bigint)gasCost, (bigint)m_t.gas())); } - u256 cost = m_t.value() + m_t.gas() * m_t.gasPrice(); + bigint cost = m_t.value() + (bigint)m_t.gas() * m_t.gasPrice(); // Avoid unaffordable transactions. if (m_s.balance(m_t.sender()) < cost) { clog(StateDetail) << "Not enough cash: Require >" << cost << " Got" << m_s.balance(m_t.sender()); - BOOST_THROW_EXCEPTION(NotEnoughCash() << RequirementError((bigint)cost, (bigint)m_s.balance(m_t.sender()))); + BOOST_THROW_EXCEPTION(NotEnoughCash() << RequirementError(cost, (bigint)m_s.balance(m_t.sender()))); } u256 startGasUsed = m_s.gasUsed(); - if (startGasUsed + m_t.gas() > m_s.m_currentBlock.gasLimit) + if (startGasUsed + (bigint)m_t.gas() > m_s.m_currentBlock.gasLimit) { clog(StateDetail) << "Too much gas used in this block: Require <" << (m_s.m_currentBlock.gasLimit - startGasUsed) << " Got" << m_t.gas(); BOOST_THROW_EXCEPTION(BlockGasLimitReached() << RequirementError((bigint)(m_s.m_currentBlock.gasLimit - startGasUsed), (bigint)m_t.gas())); @@ -92,7 +92,7 @@ bool Executive::setup(bytesConstRef _rlp) m_s.noteSending(m_t.sender()); // Pay... - clog(StateDetail) << "Paying" << formatBalance(cost) << "from sender (includes" << m_t.gas() << "gas at" << formatBalance(m_t.gasPrice()) << ")"; + clog(StateDetail) << "Paying" << formatBalance(u256(cost)) << "from sender (includes" << m_t.gas() << "gas at" << formatBalance(m_t.gasPrice()) << ")"; m_s.subBalance(m_t.sender(), cost); if (m_t.isCreation()) diff --git a/libethereum/LogFilter.cpp b/libethereum/LogFilter.cpp index eca428cfa..79f43f2c6 100644 --- a/libethereum/LogFilter.cpp +++ b/libethereum/LogFilter.cpp @@ -43,20 +43,21 @@ bool LogFilter::matches(LogBloom _bloom) const { if (m_addresses.size()) { - for (auto i: m_addresses) + for (auto const& i: m_addresses) if (_bloom.containsBloom<3>(dev::sha3(i))) goto OK1; return false; } OK1: - if (m_topics.size()) - { - for (auto i: m_topics) - if (_bloom.containsBloom<3>(dev::sha3(i))) - goto OK2; - return false; - } - OK2: + for (auto const& t: m_topics) + if (t.size()) + { + for (auto const& i: t) + if (_bloom.containsBloom<3>(dev::sha3(i))) + goto OK2; + return false; + OK2:; + } return true; } @@ -72,11 +73,12 @@ LogEntries LogFilter::matches(TransactionReceipt const& _m) const for (LogEntry const& e: _m.log()) { if (!m_addresses.empty() && !m_addresses.count(e.address)) - continue; - for (auto const& t: m_topics) - if (!std::count(e.topics.begin(), e.topics.end(), t)) - continue; + goto continue2; + for (unsigned i = 0; i < 4; ++i) + if (!m_topics[i].empty() && (e.topics.size() < i || !m_topics[i].count(e.topics[i]))) + goto continue2; ret.push_back(e); + continue2:; } return ret; } diff --git a/libethereum/LogFilter.h b/libethereum/LogFilter.h index bda8e46a2..b99ba8ee0 100644 --- a/libethereum/LogFilter.h +++ b/libethereum/LogFilter.h @@ -50,7 +50,7 @@ public: LogEntries matches(TransactionReceipt const& _r) const; LogFilter address(Address _a) { m_addresses.insert(_a); return *this; } - LogFilter topic(h256 const& _t) { m_topics.insert(_t); return *this; } + LogFilter topic(unsigned _index, h256 const& _t) { if (_index < 4) m_topics[_index].insert(_t); return *this; } LogFilter withMax(unsigned _m) { m_max = _m; return *this; } LogFilter withSkip(unsigned _m) { m_skip = _m; return *this; } LogFilter withEarliest(int _e) { m_earliest = _e; return *this; } @@ -58,7 +58,7 @@ public: private: AddressSet m_addresses; - h256Set m_topics; + std::array m_topics; int m_earliest = 0; int m_latest = -1; unsigned m_max; diff --git a/libjsqrc/es6-promise-2.0.0.js b/libjsqrc/es6-promise-2.0.0.js deleted file mode 100644 index ba0e4d888..000000000 --- a/libjsqrc/es6-promise-2.0.0.js +++ /dev/null @@ -1,966 +0,0 @@ -/*! - * @overview es6-promise - a tiny implementation of Promises/A+. - * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) - * @license Licensed under MIT license - * See https://raw.githubusercontent.com/jakearchibald/es6-promise/master/LICENSE - * @version 2.0.0 - */ - -(function() { - "use strict"; - - function $$utils$$objectOrFunction(x) { - return typeof x === 'function' || (typeof x === 'object' && x !== null); - } - - function $$utils$$isFunction(x) { - return typeof x === 'function'; - } - - function $$utils$$isMaybeThenable(x) { - return typeof x === 'object' && x !== null; - } - - var $$utils$$_isArray; - - if (!Array.isArray) { - $$utils$$_isArray = function (x) { - return Object.prototype.toString.call(x) === '[object Array]'; - }; - } else { - $$utils$$_isArray = Array.isArray; - } - - var $$utils$$isArray = $$utils$$_isArray; - var $$utils$$now = Date.now || function() { return new Date().getTime(); }; - function $$utils$$F() { } - - var $$utils$$o_create = (Object.create || function (o) { - if (arguments.length > 1) { - throw new Error('Second argument not supported'); - } - if (typeof o !== 'object') { - throw new TypeError('Argument must be an object'); - } - $$utils$$F.prototype = o; - return new $$utils$$F(); - }); - - var $$asap$$len = 0; - - var $$asap$$default = function asap(callback, arg) { - $$asap$$queue[$$asap$$len] = callback; - $$asap$$queue[$$asap$$len + 1] = arg; - $$asap$$len += 2; - if ($$asap$$len === 2) { - // If len is 1, that means that we need to schedule an async flush. - // If additional callbacks are queued before the queue is flushed, they - // will be processed by this flush that we are scheduling. - $$asap$$scheduleFlush(); - } - }; - - var $$asap$$browserGlobal = (typeof window !== 'undefined') ? window : {}; - var $$asap$$BrowserMutationObserver = $$asap$$browserGlobal.MutationObserver || $$asap$$browserGlobal.WebKitMutationObserver; - - // test for web worker but not in IE10 - var $$asap$$isWorker = typeof Uint8ClampedArray !== 'undefined' && - typeof importScripts !== 'undefined' && - typeof MessageChannel !== 'undefined'; - - // node - function $$asap$$useNextTick() { - return function() { - process.nextTick($$asap$$flush); - }; - } - - function $$asap$$useMutationObserver() { - var iterations = 0; - var observer = new $$asap$$BrowserMutationObserver($$asap$$flush); - var node = document.createTextNode(''); - observer.observe(node, { characterData: true }); - - return function() { - node.data = (iterations = ++iterations % 2); - }; - } - - // web worker - function $$asap$$useMessageChannel() { - var channel = new MessageChannel(); - channel.port1.onmessage = $$asap$$flush; - return function () { - channel.port2.postMessage(0); - }; - } - - function $$asap$$useSetTimeout() { - return function() { - setTimeout($$asap$$flush, 1); - }; - } - - var $$asap$$queue = new Array(1000); - - function $$asap$$flush() { - for (var i = 0; i < $$asap$$len; i+=2) { - var callback = $$asap$$queue[i]; - var arg = $$asap$$queue[i+1]; - - callback(arg); - - $$asap$$queue[i] = undefined; - $$asap$$queue[i+1] = undefined; - } - - $$asap$$len = 0; - } - - var $$asap$$scheduleFlush; - - // Decide what async method to use to triggering processing of queued callbacks: - if (typeof process !== 'undefined' && {}.toString.call(process) === '[object process]') { - $$asap$$scheduleFlush = $$asap$$useNextTick(); - } else if ($$asap$$BrowserMutationObserver) { - $$asap$$scheduleFlush = $$asap$$useMutationObserver(); - } else if ($$asap$$isWorker) { - $$asap$$scheduleFlush = $$asap$$useMessageChannel(); - } else { - $$asap$$scheduleFlush = $$asap$$useSetTimeout(); - } - - function $$$internal$$noop() {} - var $$$internal$$PENDING = void 0; - var $$$internal$$FULFILLED = 1; - var $$$internal$$REJECTED = 2; - var $$$internal$$GET_THEN_ERROR = new $$$internal$$ErrorObject(); - - function $$$internal$$selfFullfillment() { - return new TypeError("You cannot resolve a promise with itself"); - } - - function $$$internal$$cannotReturnOwn() { - return new TypeError('A promises callback cannot return that same promise.') - } - - function $$$internal$$getThen(promise) { - try { - return promise.then; - } catch(error) { - $$$internal$$GET_THEN_ERROR.error = error; - return $$$internal$$GET_THEN_ERROR; - } - } - - function $$$internal$$tryThen(then, value, fulfillmentHandler, rejectionHandler) { - try { - then.call(value, fulfillmentHandler, rejectionHandler); - } catch(e) { - return e; - } - } - - function $$$internal$$handleForeignThenable(promise, thenable, then) { - $$asap$$default(function(promise) { - var sealed = false; - var error = $$$internal$$tryThen(then, thenable, function(value) { - if (sealed) { return; } - sealed = true; - if (thenable !== value) { - $$$internal$$resolve(promise, value); - } else { - $$$internal$$fulfill(promise, value); - } - }, function(reason) { - if (sealed) { return; } - sealed = true; - - $$$internal$$reject(promise, reason); - }, 'Settle: ' + (promise._label || ' unknown promise')); - - if (!sealed && error) { - sealed = true; - $$$internal$$reject(promise, error); - } - }, promise); - } - - function $$$internal$$handleOwnThenable(promise, thenable) { - if (thenable._state === $$$internal$$FULFILLED) { - $$$internal$$fulfill(promise, thenable._result); - } else if (promise._state === $$$internal$$REJECTED) { - $$$internal$$reject(promise, thenable._result); - } else { - $$$internal$$subscribe(thenable, undefined, function(value) { - $$$internal$$resolve(promise, value); - }, function(reason) { - $$$internal$$reject(promise, reason); - }); - } - } - - function $$$internal$$handleMaybeThenable(promise, maybeThenable) { - if (maybeThenable.constructor === promise.constructor) { - $$$internal$$handleOwnThenable(promise, maybeThenable); - } else { - var then = $$$internal$$getThen(maybeThenable); - - if (then === $$$internal$$GET_THEN_ERROR) { - $$$internal$$reject(promise, $$$internal$$GET_THEN_ERROR.error); - } else if (then === undefined) { - $$$internal$$fulfill(promise, maybeThenable); - } else if ($$utils$$isFunction(then)) { - $$$internal$$handleForeignThenable(promise, maybeThenable, then); - } else { - $$$internal$$fulfill(promise, maybeThenable); - } - } - } - - function $$$internal$$resolve(promise, value) { - if (promise === value) { - $$$internal$$reject(promise, $$$internal$$selfFullfillment()); - } else if ($$utils$$objectOrFunction(value)) { - $$$internal$$handleMaybeThenable(promise, value); - } else { - $$$internal$$fulfill(promise, value); - } - } - - function $$$internal$$publishRejection(promise) { - if (promise._onerror) { - promise._onerror(promise._result); - } - - $$$internal$$publish(promise); - } - - function $$$internal$$fulfill(promise, value) { - if (promise._state !== $$$internal$$PENDING) { return; } - - promise._result = value; - promise._state = $$$internal$$FULFILLED; - - if (promise._subscribers.length === 0) { - } else { - $$asap$$default($$$internal$$publish, promise); - } - } - - function $$$internal$$reject(promise, reason) { - if (promise._state !== $$$internal$$PENDING) { return; } - promise._state = $$$internal$$REJECTED; - promise._result = reason; - - $$asap$$default($$$internal$$publishRejection, promise); - } - - function $$$internal$$subscribe(parent, child, onFulfillment, onRejection) { - var subscribers = parent._subscribers; - var length = subscribers.length; - - parent._onerror = null; - - subscribers[length] = child; - subscribers[length + $$$internal$$FULFILLED] = onFulfillment; - subscribers[length + $$$internal$$REJECTED] = onRejection; - - if (length === 0 && parent._state) { - $$asap$$default($$$internal$$publish, parent); - } - } - - function $$$internal$$publish(promise) { - var subscribers = promise._subscribers; - var settled = promise._state; - - if (subscribers.length === 0) { return; } - - var child, callback, detail = promise._result; - - for (var i = 0; i < subscribers.length; i += 3) { - child = subscribers[i]; - callback = subscribers[i + settled]; - - if (child) { - $$$internal$$invokeCallback(settled, child, callback, detail); - } else { - callback(detail); - } - } - - promise._subscribers.length = 0; - } - - function $$$internal$$ErrorObject() { - this.error = null; - } - - var $$$internal$$TRY_CATCH_ERROR = new $$$internal$$ErrorObject(); - - function $$$internal$$tryCatch(callback, detail) { - try { - return callback(detail); - } catch(e) { - $$$internal$$TRY_CATCH_ERROR.error = e; - return $$$internal$$TRY_CATCH_ERROR; - } - } - - function $$$internal$$invokeCallback(settled, promise, callback, detail) { - var hasCallback = $$utils$$isFunction(callback), - value, error, succeeded, failed; - - if (hasCallback) { - value = $$$internal$$tryCatch(callback, detail); - - if (value === $$$internal$$TRY_CATCH_ERROR) { - failed = true; - error = value.error; - value = null; - } else { - succeeded = true; - } - - if (promise === value) { - $$$internal$$reject(promise, $$$internal$$cannotReturnOwn()); - return; - } - - } else { - value = detail; - succeeded = true; - } - - if (promise._state !== $$$internal$$PENDING) { - // noop - } else if (hasCallback && succeeded) { - $$$internal$$resolve(promise, value); - } else if (failed) { - $$$internal$$reject(promise, error); - } else if (settled === $$$internal$$FULFILLED) { - $$$internal$$fulfill(promise, value); - } else if (settled === $$$internal$$REJECTED) { - $$$internal$$reject(promise, value); - } - } - - function $$$internal$$initializePromise(promise, resolver) { - try { - resolver(function resolvePromise(value){ - $$$internal$$resolve(promise, value); - }, function rejectPromise(reason) { - $$$internal$$reject(promise, reason); - }); - } catch(e) { - $$$internal$$reject(promise, e); - } - } - - function $$$enumerator$$makeSettledResult(state, position, value) { - if (state === $$$internal$$FULFILLED) { - return { - state: 'fulfilled', - value: value - }; - } else { - return { - state: 'rejected', - reason: value - }; - } - } - - function $$$enumerator$$Enumerator(Constructor, input, abortOnReject, label) { - this._instanceConstructor = Constructor; - this.promise = new Constructor($$$internal$$noop, label); - this._abortOnReject = abortOnReject; - - if (this._validateInput(input)) { - this._input = input; - this.length = input.length; - this._remaining = input.length; - - this._init(); - - if (this.length === 0) { - $$$internal$$fulfill(this.promise, this._result); - } else { - this.length = this.length || 0; - this._enumerate(); - if (this._remaining === 0) { - $$$internal$$fulfill(this.promise, this._result); - } - } - } else { - $$$internal$$reject(this.promise, this._validationError()); - } - } - - $$$enumerator$$Enumerator.prototype._validateInput = function(input) { - return $$utils$$isArray(input); - }; - - $$$enumerator$$Enumerator.prototype._validationError = function() { - return new Error('Array Methods must be provided an Array'); - }; - - $$$enumerator$$Enumerator.prototype._init = function() { - this._result = new Array(this.length); - }; - - var $$$enumerator$$default = $$$enumerator$$Enumerator; - - $$$enumerator$$Enumerator.prototype._enumerate = function() { - var length = this.length; - var promise = this.promise; - var input = this._input; - - for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) { - this._eachEntry(input[i], i); - } - }; - - $$$enumerator$$Enumerator.prototype._eachEntry = function(entry, i) { - var c = this._instanceConstructor; - if ($$utils$$isMaybeThenable(entry)) { - if (entry.constructor === c && entry._state !== $$$internal$$PENDING) { - entry._onerror = null; - this._settledAt(entry._state, i, entry._result); - } else { - this._willSettleAt(c.resolve(entry), i); - } - } else { - this._remaining--; - this._result[i] = this._makeResult($$$internal$$FULFILLED, i, entry); - } - }; - - $$$enumerator$$Enumerator.prototype._settledAt = function(state, i, value) { - var promise = this.promise; - - if (promise._state === $$$internal$$PENDING) { - this._remaining--; - - if (this._abortOnReject && state === $$$internal$$REJECTED) { - $$$internal$$reject(promise, value); - } else { - this._result[i] = this._makeResult(state, i, value); - } - } - - if (this._remaining === 0) { - $$$internal$$fulfill(promise, this._result); - } - }; - - $$$enumerator$$Enumerator.prototype._makeResult = function(state, i, value) { - return value; - }; - - $$$enumerator$$Enumerator.prototype._willSettleAt = function(promise, i) { - var enumerator = this; - - $$$internal$$subscribe(promise, undefined, function(value) { - enumerator._settledAt($$$internal$$FULFILLED, i, value); - }, function(reason) { - enumerator._settledAt($$$internal$$REJECTED, i, reason); - }); - }; - - var $$promise$all$$default = function all(entries, label) { - return new $$$enumerator$$default(this, entries, true /* abort on reject */, label).promise; - }; - - var $$promise$race$$default = function race(entries, label) { - /*jshint validthis:true */ - var Constructor = this; - - var promise = new Constructor($$$internal$$noop, label); - - if (!$$utils$$isArray(entries)) { - $$$internal$$reject(promise, new TypeError('You must pass an array to race.')); - return promise; - } - - var length = entries.length; - - function onFulfillment(value) { - $$$internal$$resolve(promise, value); - } - - function onRejection(reason) { - $$$internal$$reject(promise, reason); - } - - for (var i = 0; promise._state === $$$internal$$PENDING && i < length; i++) { - $$$internal$$subscribe(Constructor.resolve(entries[i]), undefined, onFulfillment, onRejection); - } - - return promise; - }; - - var $$promise$resolve$$default = function resolve(object, label) { - /*jshint validthis:true */ - var Constructor = this; - - if (object && typeof object === 'object' && object.constructor === Constructor) { - return object; - } - - var promise = new Constructor($$$internal$$noop, label); - $$$internal$$resolve(promise, object); - return promise; - }; - - var $$promise$reject$$default = function reject(reason, label) { - /*jshint validthis:true */ - var Constructor = this; - var promise = new Constructor($$$internal$$noop, label); - $$$internal$$reject(promise, reason); - return promise; - }; - - var $$es6$promise$promise$$counter = 0; - - function $$es6$promise$promise$$needsResolver() { - throw new TypeError('You must pass a resolver function as the first argument to the promise constructor'); - } - - function $$es6$promise$promise$$needsNew() { - throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function."); - } - - var $$es6$promise$promise$$default = $$es6$promise$promise$$Promise; - - /** - Promise objects represent the eventual result of an asynchronous operation. The - primary way of interacting with a promise is through its `then` method, which - registers callbacks to receive either a promise’s eventual value or the reason - why the promise cannot be fulfilled. - - Terminology - ----------- - - - `promise` is an object or function with a `then` method whose behavior conforms to this specification. - - `thenable` is an object or function that defines a `then` method. - - `value` is any legal JavaScript value (including undefined, a thenable, or a promise). - - `exception` is a value that is thrown using the throw statement. - - `reason` is a value that indicates why a promise was rejected. - - `settled` the final resting state of a promise, fulfilled or rejected. - - A promise can be in one of three states: pending, fulfilled, or rejected. - - Promises that are fulfilled have a fulfillment value and are in the fulfilled - state. Promises that are rejected have a rejection reason and are in the - rejected state. A fulfillment value is never a thenable. - - Promises can also be said to *resolve* a value. If this value is also a - promise, then the original promise's settled state will match the value's - settled state. So a promise that *resolves* a promise that rejects will - itself reject, and a promise that *resolves* a promise that fulfills will - itself fulfill. - - - Basic Usage: - ------------ - - ```js - var promise = new Promise(function(resolve, reject) { - // on success - resolve(value); - - // on failure - reject(reason); - }); - - promise.then(function(value) { - // on fulfillment - }, function(reason) { - // on rejection - }); - ``` - - Advanced Usage: - --------------- - - Promises shine when abstracting away asynchronous interactions such as - `XMLHttpRequest`s. - - ```js - function getJSON(url) { - return new Promise(function(resolve, reject){ - var xhr = new XMLHttpRequest(); - - xhr.open('GET', url); - xhr.onreadystatechange = handler; - xhr.responseType = 'json'; - xhr.setRequestHeader('Accept', 'application/json'); - xhr.send(); - - function handler() { - if (this.readyState === this.DONE) { - if (this.status === 200) { - resolve(this.response); - } else { - reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']')); - } - } - }; - }); - } - - getJSON('/posts.json').then(function(json) { - // on fulfillment - }, function(reason) { - // on rejection - }); - ``` - - Unlike callbacks, promises are great composable primitives. - - ```js - Promise.all([ - getJSON('/posts'), - getJSON('/comments') - ]).then(function(values){ - values[0] // => postsJSON - values[1] // => commentsJSON - - return values; - }); - ``` - - @class Promise - @param {function} resolver - @param {String} label optional string for labeling the promise. - Useful for tooling. - @constructor - */ - function $$es6$promise$promise$$Promise(resolver, label) { - this._id = $$es6$promise$promise$$counter++; - this._label = label; - this._state = undefined; - this._result = undefined; - this._subscribers = []; - - if ($$$internal$$noop !== resolver) { - if (!$$utils$$isFunction(resolver)) { - $$es6$promise$promise$$needsResolver(); - } - - if (!(this instanceof $$es6$promise$promise$$Promise)) { - $$es6$promise$promise$$needsNew(); - } - - $$$internal$$initializePromise(this, resolver); - } - } - - $$es6$promise$promise$$Promise.all = $$promise$all$$default; - $$es6$promise$promise$$Promise.race = $$promise$race$$default; - $$es6$promise$promise$$Promise.resolve = $$promise$resolve$$default; - $$es6$promise$promise$$Promise.reject = $$promise$reject$$default; - - $$es6$promise$promise$$Promise.prototype = { - constructor: $$es6$promise$promise$$Promise, - - /** - The primary way of interacting with a promise is through its `then` method, - which registers callbacks to receive either a promise's eventual value or the - reason why the promise cannot be fulfilled. - - ```js - findUser().then(function(user){ - // user is available - }, function(reason){ - // user is unavailable, and you are given the reason why - }); - ``` - - Chaining - -------- - - The return value of `then` is itself a promise. This second, 'downstream' - promise is resolved with the return value of the first promise's fulfillment - or rejection handler, or rejected if the handler throws an exception. - - ```js - findUser().then(function (user) { - return user.name; - }, function (reason) { - return 'default name'; - }).then(function (userName) { - // If `findUser` fulfilled, `userName` will be the user's name, otherwise it - // will be `'default name'` - }); - - findUser().then(function (user) { - throw new Error('Found user, but still unhappy'); - }, function (reason) { - throw new Error('`findUser` rejected and we're unhappy'); - }).then(function (value) { - // never reached - }, function (reason) { - // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. - // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. - }); - ``` - If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. - - ```js - findUser().then(function (user) { - throw new PedagogicalException('Upstream error'); - }).then(function (value) { - // never reached - }).then(function (value) { - // never reached - }, function (reason) { - // The `PedgagocialException` is propagated all the way down to here - }); - ``` - - Assimilation - ------------ - - Sometimes the value you want to propagate to a downstream promise can only be - retrieved asynchronously. This can be achieved by returning a promise in the - fulfillment or rejection handler. The downstream promise will then be pending - until the returned promise is settled. This is called *assimilation*. - - ```js - findUser().then(function (user) { - return findCommentsByAuthor(user); - }).then(function (comments) { - // The user's comments are now available - }); - ``` - - If the assimliated promise rejects, then the downstream promise will also reject. - - ```js - findUser().then(function (user) { - return findCommentsByAuthor(user); - }).then(function (comments) { - // If `findCommentsByAuthor` fulfills, we'll have the value here - }, function (reason) { - // If `findCommentsByAuthor` rejects, we'll have the reason here - }); - ``` - - Simple Example - -------------- - - Synchronous Example - - ```javascript - var result; - - try { - result = findResult(); - // success - } catch(reason) { - // failure - } - ``` - - Errback Example - - ```js - findResult(function(result, err){ - if (err) { - // failure - } else { - // success - } - }); - ``` - - Promise Example; - - ```javascript - findResult().then(function(result){ - // success - }, function(reason){ - // failure - }); - ``` - - Advanced Example - -------------- - - Synchronous Example - - ```javascript - var author, books; - - try { - author = findAuthor(); - books = findBooksByAuthor(author); - // success - } catch(reason) { - // failure - } - ``` - - Errback Example - - ```js - - function foundBooks(books) { - - } - - function failure(reason) { - - } - - findAuthor(function(author, err){ - if (err) { - failure(err); - // failure - } else { - try { - findBoooksByAuthor(author, function(books, err) { - if (err) { - failure(err); - } else { - try { - foundBooks(books); - } catch(reason) { - failure(reason); - } - } - }); - } catch(error) { - failure(err); - } - // success - } - }); - ``` - - Promise Example; - - ```javascript - findAuthor(). - then(findBooksByAuthor). - then(function(books){ - // found books - }).catch(function(reason){ - // something went wrong - }); - ``` - - @method then - @param {Function} onFulfilled - @param {Function} onRejected - @param {String} label optional string for labeling the promise. - Useful for tooling. - @return {Promise} - */ - then: function(onFulfillment, onRejection, label) { - var parent = this; - var state = parent._state; - - if (state === $$$internal$$FULFILLED && !onFulfillment || state === $$$internal$$REJECTED && !onRejection) { - return this; - } - - parent._onerror = null; - - var child = new this.constructor($$$internal$$noop, label); - var result = parent._result; - - if (state) { - var callback = arguments[state - 1]; - $$asap$$default(function(){ - $$$internal$$invokeCallback(state, child, callback, result); - }); - } else { - $$$internal$$subscribe(parent, child, onFulfillment, onRejection); - } - - return child; - }, - - /** - `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same - as the catch block of a try/catch statement. - - ```js - function findAuthor(){ - throw new Error('couldn't find that author'); - } - - // synchronous - try { - findAuthor(); - } catch(reason) { - // something went wrong - } - - // async with promises - findAuthor().catch(function(reason){ - // something went wrong - }); - ``` - - @method catch - @param {Function} onRejection - @param {String} label optional string for labeling the promise. - Useful for tooling. - @return {Promise} - */ - 'catch': function(onRejection, label) { - return this.then(null, onRejection, label); - } - }; - - var $$es6$promise$polyfill$$default = function polyfill() { - var local; - - if (typeof global !== 'undefined') { - local = global; - } else if (typeof window !== 'undefined' && window.document) { - local = window; - } else { - local = self; - } - - var es6PromiseSupport = - "Promise" in local && - // Some of these methods are missing from - // Firefox/Chrome experimental implementations - "resolve" in local.Promise && - "reject" in local.Promise && - "all" in local.Promise && - "race" in local.Promise && - // Older version of the spec had a resolver object - // as the arg rather than a function - (function() { - var resolve; - new local.Promise(function(r) { resolve = r; }); - return $$utils$$isFunction(resolve); - }()); - - if (!es6PromiseSupport) { - local.Promise = $$es6$promise$promise$$default; - } - }; - - var es6$promise$umd$$ES6Promise = { - Promise: $$es6$promise$promise$$default, - polyfill: $$es6$promise$polyfill$$default - }; - - /* global define:true module:true window: true */ - if (typeof define === 'function' && define['amd']) { - define(function() { return es6$promise$umd$$ES6Promise; }); - } else if (typeof module !== 'undefined' && module['exports']) { - module['exports'] = es6$promise$umd$$ES6Promise; - } else if (typeof this !== 'undefined') { - this['ES6Promise'] = es6$promise$umd$$ES6Promise; - } -}).call(this); \ No newline at end of file diff --git a/libjsqrc/ethereumjs/README.md b/libjsqrc/ethereumjs/README.md index bb6967ce0..02988fe73 100644 --- a/libjsqrc/ethereumjs/README.md +++ b/libjsqrc/ethereumjs/README.md @@ -1,6 +1,6 @@ # Ethereum JavaScript API -This is the Ethereum compatible JavaScript API using `Promise`s +This is the Ethereum compatible [JavaScript API](https://github.com/ethereum/wiki/wiki/JavaScript-API) which implements the [Generic JSON RPC](https://github.com/ethereum/wiki/wiki/Generic-JSON-RPC) spec. It's available on npm as a node module and also for bower and component as an embeddable js [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![dependency status][dep-image]][dep-url] [![dev dependency status][dep-dev-image]][dep-dev-url] @@ -23,7 +23,7 @@ Component component install ethereum/ethereum.js * Include `ethereum.min.js` in your html file. -* Include [es6-promise](https://github.com/jakearchibald/es6-promise) or another ES6-Shim if your browser doesn't support ECMAScript 6. +* Include [bignumber.js](https://github.com/MikeMcl/bignumber.js/) ## Usage Require the library: @@ -37,14 +37,8 @@ Set a provider (QtProvider, WebSocketProvider, HttpRpcProvider) There you go, now you can use it: ``` -web3.eth.coinbase.then(function(result){ - console.log(result); - return web3.eth.balanceAt(result); -}).then(function(balance){ - console.log(web3.toDecimal(balance)); -}).catch(function(err){ - console.log(err); -}); +var coinbase = web3.eth.coinbase; +var balance = web3.eth.balanceAt(coinbase); ``` @@ -99,3 +93,4 @@ ethereum -ws -loglevel=4 [dep-url]: https://david-dm.org/ethereum/ethereum.js [dep-dev-image]: https://david-dm.org/ethereum/ethereum.js/dev-status.svg [dep-dev-url]: https://david-dm.org/ethereum/ethereum.js#info=devDependencies + diff --git a/libjsqrc/ethereumjs/bower.json b/libjsqrc/ethereumjs/bower.json index 3c5d2d33e..420618390 100644 --- a/libjsqrc/ethereumjs/bower.json +++ b/libjsqrc/ethereumjs/bower.json @@ -1,11 +1,10 @@ { "name": "ethereum.js", "namespace": "ethereum", - "version": "0.0.8", + "version": "0.0.11", "description": "Ethereum Compatible JavaScript API", "main": ["./dist/ethereum.js", "./dist/ethereum.min.js"], "dependencies": { - "es6-promise": "#master", "bignumber.js": ">=2.0.0" }, "repository": { diff --git a/libjsqrc/ethereumjs/dist/ethereum.js b/libjsqrc/ethereumjs/dist/ethereum.js index 1dbb42d89..817b55852 100644 --- a/libjsqrc/ethereumjs/dist/ethereum.js +++ b/libjsqrc/ethereumjs/dist/ethereum.js @@ -22,167 +22,57 @@ require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof requ * @date 2014 */ -// TODO: is these line is supposed to be here? -if ("build" !== 'build') {/* - var BigNumber = require('bignumber.js'); // jshint ignore:line -*/} - -var web3 = require('./web3'); // jshint ignore:line - -BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_DOWN }); +var web3 = require('./web3'); +var utils = require('./utils'); +var types = require('./types'); +var c = require('./const'); +var f = require('./formatters'); -var ETH_PADDING = 32; - -/// Finds first index of array element matching pattern -/// @param array -/// @param callback pattern -/// @returns index of element -var findIndex = function (array, callback) { - var end = false; - var i = 0; - for (; i < array.length && !end; i++) { - end = callback(array[i]); - } - return end ? i - 1 : -1; -}; - -/// @returns a function that is used as a pattern for 'findIndex' -var findMethodIndex = function (json, methodName) { - return findIndex(json, function (method) { - return method.name === methodName; - }); -}; - -/// @param string string to be padded -/// @param number of characters that result string should have -/// @param sign, by default 0 -/// @returns right aligned string -var padLeft = function (string, chars, sign) { - return new Array(chars - string.length + 1).join(sign ? sign : "0") + string; -}; - -/// @param expected type prefix (string) -/// @returns function which checks if type has matching prefix. if yes, returns true, otherwise false -var prefixedType = function (prefix) { - return function (type) { - return type.indexOf(prefix) === 0; - }; -}; - -/// @param expected type name (string) -/// @returns function which checks if type is matching expected one. if yes, returns true, otherwise false -var namedType = function (name) { - return function (type) { - return name === type; - }; +var displayTypeError = function (type) { + console.error('parser does not support type: ' + type); }; +/// This method should be called if we want to check if givent type is an array type +/// @returns true if it is, otherwise false var arrayType = function (type) { return type.slice(-2) === '[]'; }; -/// Formats input value to byte representation of int -/// If value is negative, return it's two's complement -/// If the value is floating point, round it down -/// @returns right-aligned byte representation of int -var formatInputInt = function (value) { - var padding = ETH_PADDING * 2; - if (value instanceof BigNumber || typeof value === 'number') { - if (typeof value === 'number') - value = new BigNumber(value); - value = value.round(); - - if (value.lessThan(0)) - value = new BigNumber("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16).plus(value).plus(1); - value = value.toString(16); - } - else if (value.indexOf('0x') === 0) - value = value.substr(2); - else if (typeof value === 'string') - value = formatInputInt(new BigNumber(value)); - else - value = (+value).toString(16); - return padLeft(value, padding); -}; - -/// Formats input value to byte representation of string -/// @returns left-algined byte representation of string -var formatInputString = function (value) { - return web3.fromAscii(value, ETH_PADDING).substr(2); -}; - -/// Formats input value to byte representation of bool -/// @returns right-aligned byte representation bool -var formatInputBool = function (value) { - return '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0'); -}; - -/// Formats input value to byte representation of real -/// Values are multiplied by 2^m and encoded as integers -/// @returns byte representation of real -var formatInputReal = function (value) { - return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))); -}; - var dynamicTypeBytes = function (type, value) { // TODO: decide what to do with array of strings - if (arrayType(type) || prefixedType('string')(type)) - return formatInputInt(value.length); + if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length. + return f.formatInputInt(value.length); return ""; }; -/// Setups input formatters for solidity types -/// @returns an array of input formatters -var setupInputTypes = function () { - - return [ - { type: prefixedType('uint'), format: formatInputInt }, - { type: prefixedType('int'), format: formatInputInt }, - { type: prefixedType('hash'), format: formatInputInt }, - { type: prefixedType('string'), format: formatInputString }, - { type: prefixedType('real'), format: formatInputReal }, - { type: prefixedType('ureal'), format: formatInputReal }, - { type: namedType('address'), format: formatInputInt }, - { type: namedType('bool'), format: formatInputBool } - ]; -}; - -var inputTypes = setupInputTypes(); +var inputTypes = types.inputTypes(); /// Formats input params to bytes -/// @param contract json abi -/// @param name of the method that we want to use +/// @param abi contract method inputs /// @param array of params that will be formatted to bytes /// @returns bytes representation of input params -var toAbiInput = function (json, methodName, params) { +var formatInput = function (inputs, params) { var bytes = ""; - var index = findMethodIndex(json, methodName); - - if (index === -1) { - return; - } - - var method = json[index]; - var padding = ETH_PADDING * 2; + var padding = c.ETH_PADDING * 2; /// first we iterate in search for dynamic - method.inputs.forEach(function (input, index) { + inputs.forEach(function (input, index) { bytes += dynamicTypeBytes(input.type, params[index]); }); - method.inputs.forEach(function (input, i) { + inputs.forEach(function (input, i) { var typeMatch = false; for (var j = 0; j < inputTypes.length && !typeMatch; j++) { - typeMatch = inputTypes[j].type(method.inputs[i].type, params[i]); + typeMatch = inputTypes[j].type(inputs[i].type, params[i]); } if (!typeMatch) { - console.error('input parser does not support type: ' + method.inputs[i].type); + displayTypeError(inputs[i].type); } var formatter = inputTypes[j - 1].format; var toAppend = ""; - if (arrayType(method.inputs[i].type)) + if (arrayType(inputs[i].type)) toAppend = params[i].reduce(function (acc, curr) { return acc + formatter(curr); }, ""); @@ -194,122 +84,44 @@ var toAbiInput = function (json, methodName, params) { return bytes; }; -/// Check if input value is negative -/// @param value is hex format -/// @returns true if it is negative, otherwise false -var signedIsNegative = function (value) { - return (new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1)) === '1'; -}; - -/// Formats input right-aligned input bytes to int -/// @returns right-aligned input bytes formatted to int -var formatOutputInt = function (value) { - // check if it's negative number - // it it is, return two's complement - if (signedIsNegative(value)) { - return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1); - } - return new BigNumber(value, 16); -}; - -/// Formats big right-aligned input bytes to uint -/// @returns right-aligned input bytes formatted to uint -var formatOutputUInt = function (value) { - return new BigNumber(value, 16); -}; - -/// @returns input bytes formatted to real -var formatOutputReal = function (value) { - return formatOutputInt(value).dividedBy(new BigNumber(2).pow(128)); -}; - -/// @returns input bytes formatted to ureal -var formatOutputUReal = function (value) { - return formatOutputUInt(value).dividedBy(new BigNumber(2).pow(128)); -}; - -/// @returns right-aligned input bytes formatted to hex -var formatOutputHash = function (value) { - return "0x" + value; -}; - -/// @returns right-aligned input bytes formatted to bool -var formatOutputBool = function (value) { - return value === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false; -}; - -/// @returns left-aligned input bytes formatted to ascii string -var formatOutputString = function (value) { - return web3.toAscii(value); -}; - -/// @returns right-aligned input bytes formatted to address -var formatOutputAddress = function (value) { - return "0x" + value.slice(value.length - 40, value.length); -}; - var dynamicBytesLength = function (type) { - if (arrayType(type) || prefixedType('string')(type)) - return ETH_PADDING * 2; + if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length. + return c.ETH_PADDING * 2; return 0; }; -/// Setups output formaters for solidity types -/// @returns an array of output formatters -var setupOutputTypes = function () { - - return [ - { type: prefixedType('uint'), format: formatOutputUInt }, - { type: prefixedType('int'), format: formatOutputInt }, - { type: prefixedType('hash'), format: formatOutputHash }, - { type: prefixedType('string'), format: formatOutputString }, - { type: prefixedType('real'), format: formatOutputReal }, - { type: prefixedType('ureal'), format: formatOutputUReal }, - { type: namedType('address'), format: formatOutputAddress }, - { type: namedType('bool'), format: formatOutputBool } - ]; -}; - -var outputTypes = setupOutputTypes(); +var outputTypes = types.outputTypes(); /// Formats output bytes back to param list -/// @param contract json abi -/// @param name of the method that we want to use +/// @param contract abi method outputs /// @param bytes representtion of output /// @returns array of output params -var fromAbiOutput = function (json, methodName, output) { - var index = findMethodIndex(json, methodName); - - if (index === -1) { - return; - } - +var formatOutput = function (outs, output) { + output = output.slice(2); - var result = []; - var method = json[index]; - var padding = ETH_PADDING * 2; + var padding = c.ETH_PADDING * 2; - var dynamicPartLength = method.outputs.reduce(function (acc, curr) { + var dynamicPartLength = outs.reduce(function (acc, curr) { return acc + dynamicBytesLength(curr.type); }, 0); var dynamicPart = output.slice(0, dynamicPartLength); output = output.slice(dynamicPartLength); - method.outputs.forEach(function (out, i) { + outs.forEach(function (out, i) { var typeMatch = false; for (var j = 0; j < outputTypes.length && !typeMatch; j++) { - typeMatch = outputTypes[j].type(method.outputs[i].type); + typeMatch = outputTypes[j].type(outs[i].type); } if (!typeMatch) { - console.error('output parser does not support type: ' + method.outputs[i].type); + displayTypeError(outs[i].type); } var formatter = outputTypes[j - 1].format; - if (arrayType(method.outputs[i].type)) { - var size = formatOutputUInt(dynamicPart.slice(0, padding)); + if (arrayType(outs[i].type)) { + var size = f.formatOutputUInt(dynamicPart.slice(0, padding)); dynamicPart = dynamicPart.slice(padding); var array = []; for (var k = 0; k < size; k++) { @@ -318,7 +130,7 @@ var fromAbiOutput = function (json, methodName, output) { } result.push(array); } - else if (prefixedType('string')(method.outputs[i].type)) { + else if (types.prefixedType('string')(outs[i].type)) { dynamicPart = dynamicPart.slice(padding); result.push(formatter(output.slice(0, padding))); output = output.slice(padding); @@ -333,13 +145,23 @@ var fromAbiOutput = function (json, methodName, output) { /// @param json abi for contract /// @returns input parser object for given json abi +/// TODO: refactor creating the parser, do not double logic from contract var inputParser = function (json) { var parser = {}; json.forEach(function (method) { - parser[method.name] = function () { + var displayName = utils.extractDisplayName(method.name); + var typeName = utils.extractTypeName(method.name); + + var impl = function () { var params = Array.prototype.slice.call(arguments); - return toAbiInput(json, method.name, params); + return formatInput(method.inputs, params); }; + + if (parser[displayName] === undefined) { + parser[displayName] = impl; + } + + parser[displayName][typeName] = impl; }); return parser; @@ -350,37 +172,45 @@ var inputParser = function (json) { var outputParser = function (json) { var parser = {}; json.forEach(function (method) { - parser[method.name] = function (output) { - return fromAbiOutput(json, method.name, output); + + var displayName = utils.extractDisplayName(method.name); + var typeName = utils.extractTypeName(method.name); + + var impl = function (output) { + return formatOutput(method.outputs, output); }; + + if (parser[displayName] === undefined) { + parser[displayName] = impl; + } + + parser[displayName][typeName] = impl; }); return parser; }; -/// @param json abi for contract -/// @param method name for which we want to get method signature -/// @returns (promise) contract method signature for method with given name -var methodSignature = function (json, name) { - var method = json[findMethodIndex(json, name)]; - var result = name + '('; - var inputTypes = method.inputs.map(function (inp) { - return inp.type; - }); - result += inputTypes.join(','); - result += ')'; +/// @param function/event name for which we want to get signature +/// @returns signature of function/event with given name +var signatureFromAscii = function (name) { + return web3.sha3(web3.fromAscii(name)).slice(0, 2 + c.ETH_SIGNATURE_LENGTH * 2); +}; - return web3.sha3(web3.fromAscii(result)); +var eventSignatureFromAscii = function (name) { + return web3.sha3(web3.fromAscii(name)); }; module.exports = { inputParser: inputParser, outputParser: outputParser, - methodSignature: methodSignature + formatInput: formatInput, + formatOutput: formatOutput, + signatureFromAscii: signatureFromAscii, + eventSignatureFromAscii: eventSignatureFromAscii }; -},{"./web3":8}],2:[function(require,module,exports){ +},{"./const":2,"./formatters":6,"./types":10,"./utils":11,"./web3":12}],2:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -397,106 +227,25 @@ module.exports = { You should have received a copy of the GNU Lesser General Public License along with ethereum.js. If not, see . */ -/** @file autoprovider.js +/** @file const.js * @authors: * Marek Kotewicz - * Marian Oancea - * @date 2014 - */ - -/* - * @brief if qt object is available, uses QtProvider, - * if not tries to connect over websockets - * if it fails, it uses HttpRpcProvider + * @date 2015 */ -var web3 = require('./web3'); // jshint ignore:line +/// required to define ETH_BIGNUMBER_ROUNDING_MODE if ("build" !== 'build') {/* - var WebSocket = require('ws'); // jshint ignore:line + var BigNumber = require('bignumber.js'); // jshint ignore:line */} -/** - * AutoProvider object prototype is implementing 'provider protocol' - * Automatically tries to setup correct provider(Qt, WebSockets or HttpRpc) - * First it checkes if we are ethereum browser (if navigator.qt object is available) - * if yes, we are using QtProvider - * if no, we check if it is possible to establish websockets connection with ethereum (ws://localhost:40404/eth is default) - * if it's not possible, we are using httprpc provider (http://localhost:8080) - * The constructor allows you to specify uris on which we are trying to connect over http or websockets - * You can do that by passing objects with fields httrpc and websockets - */ -var AutoProvider = function (userOptions) { - if (web3.haveProvider()) { - return; - } - - // before we determine what provider we are, we have to cache request - this.sendQueue = []; - this.onmessageQueue = []; - - if (navigator.qt) { - this.provider = new web3.providers.QtProvider(); - return; - } - - userOptions = userOptions || {}; - var options = { - httprpc: userOptions.httprpc || 'http://localhost:8080', - websockets: userOptions.websockets || 'ws://localhost:40404/eth' - }; - - var self = this; - var closeWithSuccess = function (success) { - ws.close(); - if (success) { - self.provider = new web3.providers.WebSocketProvider(options.websockets); - } else { - self.provider = new web3.providers.HttpRpcProvider(options.httprpc); - self.poll = self.provider.poll.bind(self.provider); - } - self.sendQueue.forEach(function (payload) { - self.provider.send(payload); - }); - self.onmessageQueue.forEach(function (handler) { - self.provider.onmessage = handler; - }); - }; - - var ws = new WebSocket(options.websockets); - - ws.onopen = function() { - closeWithSuccess(true); - }; - - ws.onerror = function() { - closeWithSuccess(false); - }; -}; - -/// Sends message forward to the provider, that is being used -/// if provider is not yet set, enqueues the message -AutoProvider.prototype.send = function (payload) { - if (this.provider) { - this.provider.send(payload); - return; - } - this.sendQueue.push(payload); +module.exports = { + ETH_PADDING: 32, + ETH_SIGNATURE_LENGTH: 4, + ETH_BIGNUMBER_ROUNDING_MODE: { ROUNDING_MODE: BigNumber.ROUND_DOWN } }; -/// On incoming message sends the message to the provider that is currently being used -Object.defineProperty(AutoProvider.prototype, 'onmessage', { - set: function (handler) { - if (this.provider) { - this.provider.onmessage = handler; - return; - } - this.onmessageQueue.push(handler); - } -}); - -module.exports = AutoProvider; -},{"./web3":8}],3:[function(require,module,exports){ +},{}],3:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -519,11 +268,144 @@ module.exports = AutoProvider; * @date 2014 */ -var web3 = require('./web3'); // jshint ignore:line +var web3 = require('./web3'); var abi = require('./abi'); +var utils = require('./utils'); +var eventImpl = require('./event'); + +var exportNatspecGlobals = function (vars) { + // it's used byt natspec.js + // TODO: figure out better way to solve this + web3._currentContractAbi = vars.abi; + web3._currentContractAddress = vars.address; + web3._currentContractMethodName = vars.method; + web3._currentContractMethodParams = vars.params; +}; + +var addFunctionRelatedPropertiesToContract = function (contract) { + + contract.call = function (options) { + contract._isTransact = false; + contract._options = options; + return contract; + }; + + contract.transact = function (options) { + contract._isTransact = true; + contract._options = options; + return contract; + }; + + contract._options = {}; + ['gas', 'gasPrice', 'value', 'from'].forEach(function(p) { + contract[p] = function (v) { + contract._options[p] = v; + return contract; + }; + }); + +}; + +var addFunctionsToContract = function (contract, desc, address) { + var inputParser = abi.inputParser(desc); + var outputParser = abi.outputParser(desc); + + // create contract functions + utils.filterFunctions(desc).forEach(function (method) { + + var displayName = utils.extractDisplayName(method.name); + var typeName = utils.extractTypeName(method.name); + + var impl = function () { + var params = Array.prototype.slice.call(arguments); + var signature = abi.signatureFromAscii(method.name); + var parsed = inputParser[displayName][typeName].apply(null, params); + + var options = contract._options || {}; + options.to = address; + options.data = signature + parsed; + + var isTransact = contract._isTransact === true || (contract._isTransact !== false && !method.constant); + var collapse = options.collapse !== false; + + // reset + contract._options = {}; + contract._isTransact = null; + + if (isTransact) { + + exportNatspecGlobals({ + abi: desc, + address: address, + method: method.name, + params: params + }); + + // transactions do not have any output, cause we do not know, when they will be processed + web3.eth.transact(options); + return; + } + + var output = web3.eth.call(options); + var ret = outputParser[displayName][typeName](output); + if (collapse) + { + if (ret.length === 1) + ret = ret[0]; + else if (ret.length === 0) + ret = null; + } + return ret; + }; + + if (contract[displayName] === undefined) { + contract[displayName] = impl; + } + + contract[displayName][typeName] = impl; + }); +}; + +var addEventRelatedPropertiesToContract = function (contract, desc, address) { + contract.address = address; + + Object.defineProperty(contract, 'topic', { + get: function() { + return utils.filterEvents(desc).map(function (e) { + return abi.eventSignatureFromAscii(e.name); + }); + } + }); + +}; + +var addEventsToContract = function (contract, desc, address) { + // create contract events + utils.filterEvents(desc).forEach(function (e) { + + var impl = function () { + var params = Array.prototype.slice.call(arguments); + var signature = abi.eventSignatureFromAscii(e.name); + var event = eventImpl(address, signature, e); + var o = event.apply(null, params); + return web3.eth.watch(o); + }; + + // this property should be used by eth.filter to check if object is an event + impl._isEvent = true; + + var displayName = utils.extractDisplayName(e.name); + var typeName = utils.extractTypeName(e.name); + + if (contract[displayName] === undefined) { + contract[displayName] = impl; + } + + contract[displayName][typeName] = impl; + + }); +}; -/// method signature length in bytes -var ETH_METHOD_SIGNATURE_LENGTH = 4; /** * This method should be called when we want to call / transact some solidity method from javascript @@ -538,56 +420,113 @@ var ETH_METHOD_SIGNATURE_LENGTH = 4; * * var myContract = web3.eth.contract('0x0123123121', abi); // creation of contract object * - * myContract.myMethod('this is test string param for call').call(); // myMethod call - * myContract.myMethod('this is test string param for transact').transact() // myMethod transact + * myContract.myMethod('this is test string param for call'); // myMethod call (implicit, default) + * myContract.call().myMethod('this is test string param for call'); // myMethod call (explicit) + * myContract.transact().myMethod('this is test string param for transact'); // myMethod transact * * @param address - address of the contract, which should be called * @param desc - abi json description of the contract, which is being created * @returns contract object */ -var contract = function (address, desc) { - var inputParser = abi.inputParser(desc); - var outputParser = abi.outputParser(desc); - var contract = {}; +var contract = function (address, desc) { + // workaround for invalid assumption that method.name is the full anonymous prototype of the method. + // it's not. it's just the name. the rest of the code assumes it's actually the anonymous + // prototype, so we make it so as a workaround. + // TODO: we may not want to modify input params, maybe use copy instead? desc.forEach(function (method) { - contract[method.name] = function () { - var params = Array.prototype.slice.call(arguments); - var parsed = inputParser[method.name].apply(null, params); + if (method.name.indexOf('(') === -1) { + var displayName = method.name; + var typeName = method.inputs.map(function(i){return i.type; }).join(); + method.name = displayName + '(' + typeName + ')'; + } + }); - var onSuccess = function (result) { - return outputParser[method.name](result); - }; + var result = {}; + addFunctionRelatedPropertiesToContract(result); + addFunctionsToContract(result, desc, address); + addEventRelatedPropertiesToContract(result, desc, address); + addEventsToContract(result, desc, address); - return { - call: function (extra) { - extra = extra || {}; - extra.to = address; - return abi.methodSignature(desc, method.name).then(function (signature) { - extra.data = signature.slice(0, 2 + ETH_METHOD_SIGNATURE_LENGTH * 2) + parsed; - return web3.eth.call(extra).then(onSuccess); - }); - }, - transact: function (extra) { - extra = extra || {}; - extra.to = address; - return abi.methodSignature(desc, method.name).then(function (signature) { - extra.data = signature.slice(0, 2 + ETH_METHOD_SIGNATURE_LENGTH * 2) + parsed; - return web3.eth.transact(extra).then(onSuccess); - }); - } - }; - }; + return result; +}; + +module.exports = contract; + + +},{"./abi":1,"./event":4,"./utils":11,"./web3":12}],4:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file event.js + * @authors: + * Marek Kotewicz + * @date 2014 + */ + +var abi = require('./abi'); +var utils = require('./utils'); + +var inputWithName = function (inputs, name) { + var index = utils.findIndex(inputs, function (input) { + return input.name === name; }); + + if (index === -1) { + console.error('indexed param with name ' + name + ' not found'); + return undefined; + } + return inputs[index]; +}; + +var indexedParamsToTopics = function (event, indexed) { + // sort keys? + return Object.keys(indexed).map(function (key) { + var inputs = [inputWithName(event.inputs, key)]; - return contract; + var value = indexed[key]; + if (value instanceof Array) { + return value.map(function (v) { + return abi.formatInput(inputs, [v]); + }); + } + return abi.formatInput(inputs, [value]); + }); }; -module.exports = contract; +var implementationOfEvent = function (address, signature, event) { + + // valid options are 'earliest', 'latest', 'offset' and 'max', as defined for 'eth.watch' + return function (indexed, options) { + var o = options || {}; + o.address = address; + o.topic = []; + o.topic.push(signature); + if (indexed) { + o.topic = o.topic.concat(indexedParamsToTopics(event, indexed)); + } + return o; + }; +}; +module.exports = implementationOfEvent; -},{"./abi":1,"./web3":8}],4:[function(require,module,exports){ + +},{"./abi":1,"./utils":11}],5:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -617,17 +556,34 @@ var web3 = require('./web3'); // jshint ignore:line /// should be used when we want to watch something /// it's using inner polling mechanism and is notified about changes +/// TODO: change 'options' name cause it may be not the best matching one, since we have events var Filter = function(options, impl) { + + if (typeof options !== "string") { + + // topics property is deprecated, warn about it! + if (options.topics) { + console.warn('"topics" is deprecated, use "topic" instead'); + } + + // evaluate lazy properties + options = { + to: options.to, + topic: options.topic, + earliest: options.earliest, + latest: options.latest, + max: options.max, + skip: options.skip, + address: options.address + }; + + } + this.impl = impl; this.callbacks = []; - var self = this; - this.promise = impl.newFilter(options); - this.promise.then(function (id) { - self.id = id; - web3.on(impl.changed, id, self.trigger.bind(self)); - web3.provider.startPolling({call: impl.changed, args: [id]}, id); - }); + this.id = impl.newFilter(options); + web3.provider.startPolling({call: impl.changed, args: [this.id]}, this.id, this.trigger.bind(this)); }; /// alias for changed* @@ -637,35 +593,27 @@ Filter.prototype.arrived = function(callback) { /// gets called when there is new eth/shh message Filter.prototype.changed = function(callback) { - var self = this; - this.promise.then(function(id) { - self.callbacks.push(callback); - }); + this.callbacks.push(callback); }; /// trigger calling new message from people Filter.prototype.trigger = function(messages) { - for(var i = 0; i < this.callbacks.length; i++) { - this.callbacks[i].call(this, messages); + for (var i = 0; i < this.callbacks.length; i++) { + for (var j = 0; j < messages.length; j++) { + this.callbacks[i].call(this, messages[j]); + } } }; /// should be called to uninstall current filter Filter.prototype.uninstall = function() { - var self = this; - this.promise.then(function (id) { - self.impl.uninstallFilter(id); - web3.provider.stopPolling(id); - web3.off(impl.changed, id); - }); + this.impl.uninstallFilter(this.id); + web3.provider.stopPolling(this.id); }; /// should be called to manually trigger getting latest messages from the client Filter.prototype.messages = function() { - var self = this; - return this.promise.then(function (id) { - return self.impl.getMessages(id); - }); + return this.impl.getMessages(this.id); }; /// alias for messages @@ -675,7 +623,163 @@ Filter.prototype.logs = function () { module.exports = Filter; -},{"./web3":8}],5:[function(require,module,exports){ +},{"./web3":12}],6:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file formatters.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + +if ("build" !== 'build') {/* + var BigNumber = require('bignumber.js'); // jshint ignore:line +*/} + +var utils = require('./utils'); +var c = require('./const'); + +/// @param string string to be padded +/// @param number of characters that result string should have +/// @param sign, by default 0 +/// @returns right aligned string +var padLeft = function (string, chars, sign) { + return new Array(chars - string.length + 1).join(sign ? sign : "0") + string; +}; + +/// Formats input value to byte representation of int +/// If value is negative, return it's two's complement +/// If the value is floating point, round it down +/// @returns right-aligned byte representation of int +var formatInputInt = function (value) { + var padding = c.ETH_PADDING * 2; + if (value instanceof BigNumber || typeof value === 'number') { + if (typeof value === 'number') + value = new BigNumber(value); + BigNumber.config(c.ETH_BIGNUMBER_ROUNDING_MODE); + value = value.round(); + + if (value.lessThan(0)) + value = new BigNumber("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16).plus(value).plus(1); + value = value.toString(16); + } + else if (value.indexOf('0x') === 0) + value = value.substr(2); + else if (typeof value === 'string') + value = formatInputInt(new BigNumber(value)); + else + value = (+value).toString(16); + return padLeft(value, padding); +}; + +/// Formats input value to byte representation of string +/// @returns left-algined byte representation of string +var formatInputString = function (value) { + return utils.fromAscii(value, c.ETH_PADDING).substr(2); +}; + +/// Formats input value to byte representation of bool +/// @returns right-aligned byte representation bool +var formatInputBool = function (value) { + return '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0'); +}; + +/// Formats input value to byte representation of real +/// Values are multiplied by 2^m and encoded as integers +/// @returns byte representation of real +var formatInputReal = function (value) { + return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))); +}; + + +/// Check if input value is negative +/// @param value is hex format +/// @returns true if it is negative, otherwise false +var signedIsNegative = function (value) { + return (new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1)) === '1'; +}; + +/// Formats input right-aligned input bytes to int +/// @returns right-aligned input bytes formatted to int +var formatOutputInt = function (value) { + value = value || "0"; + // check if it's negative number + // it it is, return two's complement + if (signedIsNegative(value)) { + return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1); + } + return new BigNumber(value, 16); +}; + +/// Formats big right-aligned input bytes to uint +/// @returns right-aligned input bytes formatted to uint +var formatOutputUInt = function (value) { + value = value || "0"; + return new BigNumber(value, 16); +}; + +/// @returns input bytes formatted to real +var formatOutputReal = function (value) { + return formatOutputInt(value).dividedBy(new BigNumber(2).pow(128)); +}; + +/// @returns input bytes formatted to ureal +var formatOutputUReal = function (value) { + return formatOutputUInt(value).dividedBy(new BigNumber(2).pow(128)); +}; + +/// @returns right-aligned input bytes formatted to hex +var formatOutputHash = function (value) { + return "0x" + value; +}; + +/// @returns right-aligned input bytes formatted to bool +var formatOutputBool = function (value) { + return value === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false; +}; + +/// @returns left-aligned input bytes formatted to ascii string +var formatOutputString = function (value) { + return utils.toAscii(value); +}; + +/// @returns right-aligned input bytes formatted to address +var formatOutputAddress = function (value) { + return "0x" + value.slice(value.length - 40, value.length); +}; + + +module.exports = { + formatInputInt: formatInputInt, + formatInputString: formatInputString, + formatInputBool: formatInputBool, + formatInputReal: formatInputReal, + formatOutputInt: formatOutputInt, + formatOutputUInt: formatOutputUInt, + formatOutputReal: formatOutputReal, + formatOutputUReal: formatOutputUReal, + formatOutputHash: formatOutputHash, + formatOutputBool: formatOutputBool, + formatOutputString: formatOutputString, + formatOutputAddress: formatOutputAddress +}; + + +},{"./const":2,"./utils":11}],7:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -692,28 +796,20 @@ module.exports = Filter; You should have received a copy of the GNU Lesser General Public License along with ethereum.js. If not, see . */ -/** @file httprpc.js +/** @file httpsync.js * @authors: * Marek Kotewicz * Marian Oancea * @date 2014 */ -// TODO: is these line is supposed to be here? if ("build" !== 'build') {/* - var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; // jshint ignore:line + var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; // jshint ignore:line */} -/** - * HttpRpcProvider object prototype is implementing 'provider protocol' - * Should be used when we want to connect to ethereum backend over http && jsonrpc - * It's compatible with cpp client - * The contructor allows to specify host uri - * This provider is using in-browser polling mechanism - */ -var HttpRpcProvider = function (host) { +var HttpSyncProvider = function (host) { this.handlers = []; - this.host = host; + this.host = host || 'http://localhost:8080'; }; /// Transforms inner message to proper jsonrpc object @@ -741,69 +837,21 @@ function formatJsonRpcMessage(message) { }; } -/// Prototype object method -/// Asynchronously sends request to server -/// @param payload is inner message object -/// @param cb is callback which is being called when response is comes back -HttpRpcProvider.prototype.sendRequest = function (payload, cb) { +HttpSyncProvider.prototype.send = function (payload) { var data = formatJsonRpcObject(payload); - + var request = new XMLHttpRequest(); - request.open("POST", this.host, true); + request.open('POST', this.host, false); request.send(JSON.stringify(data)); - request.onreadystatechange = function () { - if (request.readyState === 4 && cb) { - cb(request); - } - }; -}; - -/// Prototype object method -/// Should be called when we want to send single api request to server -/// Asynchronous -/// On response it passes message to handlers -/// @param payload is inner message object -HttpRpcProvider.prototype.send = function (payload) { - var self = this; - this.sendRequest(payload, function (request) { - self.handlers.forEach(function (handler) { - handler.call(self, formatJsonRpcMessage(request.responseText)); - }); - }); -}; - -/// Prototype object method -/// Should be called only for polling requests -/// Asynchronous -/// On response it passege message to handlers, but only if message's result is true or not empty array -/// Otherwise response is being silently ignored -/// @param payload is inner message object -/// @id is id of poll that we are calling -HttpRpcProvider.prototype.poll = function (payload, id) { - var self = this; - this.sendRequest(payload, function (request) { - var parsed = JSON.parse(request.responseText); - if (parsed.error || (parsed.result instanceof Array ? parsed.result.length === 0 : !parsed.result)) { - return; - } - self.handlers.forEach(function (handler) { - handler.call(self, {_event: payload.call, _id: id, data: parsed.result}); - }); - }); + + // check request.status + return request.responseText; }; -/// Prototype object property -/// Should be used to set message handlers for this provider -Object.defineProperty(HttpRpcProvider.prototype, "onmessage", { - set: function (handler) { - this.handlers.push(handler); - } -}); - -module.exports = HttpRpcProvider; +module.exports = HttpSyncProvider; -},{}],6:[function(require,module,exports){ +},{}],8:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -841,74 +889,65 @@ var web3 = require('./web3'); // jshint ignore:line * and provider manager polling mechanism is not used */ var ProviderManager = function() { - this.queued = []; this.polls = []; - this.ready = false; this.provider = undefined; this.id = 1; var self = this; var poll = function () { - if (self.provider && self.provider.poll) { + if (self.provider) { self.polls.forEach(function (data) { data.data._id = self.id; self.id++; - self.provider.poll(data.data, data.id); + var result = self.provider.send(data.data); + + result = JSON.parse(result); + + // dont call the callback if result is not an array, or empty one + if (result.error || !(result.result instanceof Array) || result.result.length === 0) { + return; + } + + data.callback(result.result); }); } - setTimeout(poll, 12000); + setTimeout(poll, 1000); }; poll(); }; -/// sends outgoing requests, if provider is not available, enqueue the request -ProviderManager.prototype.send = function(data, cb) { - data._id = this.id; - if (cb) { - web3._callbacks[data._id] = cb; - } +/// sends outgoing requests +ProviderManager.prototype.send = function(data) { data.args = data.args || []; - this.id++; - - if(this.provider !== undefined) { - this.provider.send(data); - } else { - console.warn("provider is not set"); - this.queued.push(data); - } -}; + data._id = this.id++; -/// setups provider, which will be used for sending messages -ProviderManager.prototype.set = function(provider) { - if(this.provider !== undefined && this.provider.unload !== undefined) { - this.provider.unload(); + if (this.provider === undefined) { + console.error('provider is not set'); + return null; } - this.provider = provider; - this.ready = true; -}; + //TODO: handle error here? + var result = this.provider.send(data); + result = JSON.parse(result); -/// resends queued messages -ProviderManager.prototype.sendQueued = function() { - for(var i = 0; this.queued.length; i++) { - // Resend - this.send(this.queued[i]); + if (result.error) { + console.log(result.error); + return null; } + + return result.result; }; -/// @returns true if the provider i properly set -ProviderManager.prototype.installed = function() { - return this.provider !== undefined; +/// setups provider, which will be used for sending messages +ProviderManager.prototype.set = function(provider) { + this.provider = provider; }; /// this method is only used, when we do not have native qt bindings and have to do polling on our own /// should be callled, on start watching for eth/shh changes -ProviderManager.prototype.startPolling = function (data, pollId) { - if (!this.provider || !this.provider.poll) { - return; - } - this.polls.push({data: data, id: pollId}); +ProviderManager.prototype.startPolling = function (data, pollId, callback) { + this.polls.push({data: data, id: pollId, callback: callback}); }; /// should be called to stop polling for certain watch changes @@ -924,7 +963,7 @@ ProviderManager.prototype.stopPolling = function (pollId) { module.exports = ProviderManager; -},{"./web3":8}],7:[function(require,module,exports){ +},{"./web3":12}],9:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -941,49 +980,105 @@ module.exports = ProviderManager; You should have received a copy of the GNU Lesser General Public License along with ethereum.js. If not, see . */ -/** @file qt.js +/** @file qtsync.js * @authors: - * Jeffrey Wilcke * Marek Kotewicz + * Marian Oancea * @date 2014 */ -/** - * QtProvider object prototype is implementing 'provider protocol' - * Should be used inside ethereum browser. It's compatible with cpp and go clients. - * It uses navigator.qt object to pass the messages to native bindings +var QtSyncProvider = function () { +}; + +QtSyncProvider.prototype.send = function (payload) { + return navigator.qt.callMethod(JSON.stringify(payload)); +}; + +module.exports = QtSyncProvider; + + +},{}],10:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file types.js + * @authors: + * Marek Kotewicz + * @date 2015 */ -var QtProvider = function() { - this.handlers = []; - var self = this; - navigator.qt.onmessage = function (message) { - self.handlers.forEach(function (handler) { - handler.call(self, JSON.parse(message.data)); - }); +var f = require('./formatters'); + +/// @param expected type prefix (string) +/// @returns function which checks if type has matching prefix. if yes, returns true, otherwise false +var prefixedType = function (prefix) { + return function (type) { + return type.indexOf(prefix) === 0; }; }; -/// Prototype object method -/// Should be called when we want to send single api request to native bindings -/// Asynchronous -/// Response will be received by navigator.qt.onmessage method and passed to handlers -/// @param payload is inner message object -QtProvider.prototype.send = function(payload) { - navigator.qt.postMessage(JSON.stringify(payload)); +/// @param expected type name (string) +/// @returns function which checks if type is matching expected one. if yes, returns true, otherwise false +var namedType = function (name) { + return function (type) { + return name === type; + }; }; -/// Prototype object property -/// Should be used to set message handlers for this provider -Object.defineProperty(QtProvider.prototype, "onmessage", { - set: function(handler) { - this.handlers.push(handler); - } -}); +/// Setups input formatters for solidity types +/// @returns an array of input formatters +var inputTypes = function () { + + return [ + { type: prefixedType('uint'), format: f.formatInputInt }, + { type: prefixedType('int'), format: f.formatInputInt }, + { type: prefixedType('hash'), format: f.formatInputInt }, + { type: prefixedType('string'), format: f.formatInputString }, + { type: prefixedType('real'), format: f.formatInputReal }, + { type: prefixedType('ureal'), format: f.formatInputReal }, + { type: namedType('address'), format: f.formatInputInt }, + { type: namedType('bool'), format: f.formatInputBool } + ]; +}; -module.exports = QtProvider; +/// Setups output formaters for solidity types +/// @returns an array of output formatters +var outputTypes = function () { -},{}],8:[function(require,module,exports){ + return [ + { type: prefixedType('uint'), format: f.formatOutputUInt }, + { type: prefixedType('int'), format: f.formatOutputInt }, + { type: prefixedType('hash'), format: f.formatOutputHash }, + { type: prefixedType('string'), format: f.formatOutputString }, + { type: prefixedType('real'), format: f.formatOutputReal }, + { type: prefixedType('ureal'), format: f.formatOutputUReal }, + { type: namedType('address'), format: f.formatOutputAddress }, + { type: namedType('bool'), format: f.formatOutputBool } + ]; +}; + +module.exports = { + prefixedType: prefixedType, + namedType: namedType, + inputTypes: inputTypes, + outputTypes: outputTypes +}; + + +},{"./formatters":6}],11:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -1000,56 +1095,157 @@ module.exports = QtProvider; You should have received a copy of the GNU Lesser General Public License along with ethereum.js. If not, see . */ -/** @file web3.js +/** @file utils.js * @authors: - * Jeffrey Wilcke * Marek Kotewicz - * Marian Oancea - * Gav Wood - * @date 2014 + * @date 2015 */ -/// Recursively resolves all promises in given object and replaces the resolved values with promises -/// @param any object/array/promise/anything else.. -/// @returns (resolves) object with replaced promises with their result -function flattenPromise (obj) { - if (obj instanceof Promise) { - return Promise.resolve(obj); +/// Finds first index of array element matching pattern +/// @param array +/// @param callback pattern +/// @returns index of element +var findIndex = function (array, callback) { + var end = false; + var i = 0; + for (; i < array.length && !end; i++) { + end = callback(array[i]); } + return end ? i - 1 : -1; +}; - if (obj instanceof Array) { - return new Promise(function (resolve) { - var promises = obj.map(function (o) { - return flattenPromise(o); - }); - - return Promise.all(promises).then(function (res) { - for (var i = 0; i < obj.length; i++) { - obj[i] = res[i]; - } - resolve(obj); - }); - }); +/// @returns ascii string representation of hex value prefixed with 0x +var toAscii = function(hex) { +// Find termination + var str = ""; + var i = 0, l = hex.length; + if (hex.substring(0, 2) === '0x') { + i = 2; } + for (; i < l; i+=2) { + var code = parseInt(hex.substr(i, 2), 16); + if (code === 0) { + break; + } - if (obj instanceof Object) { - return new Promise(function (resolve) { - var keys = Object.keys(obj); - var promises = keys.map(function (key) { - return flattenPromise(obj[key]); - }); + str += String.fromCharCode(code); + } - return Promise.all(promises).then(function (res) { - for (var i = 0; i < keys.length; i++) { - obj[keys[i]] = res[i]; - } - resolve(obj); - }); - }); + return str; +}; + +var toHex = function(str) { + var hex = ""; + for(var i = 0; i < str.length; i++) { + var n = str.charCodeAt(i).toString(16); + hex += n.length < 2 ? '0' + n : n; } - return Promise.resolve(obj); -} + return hex; +}; + +/// @returns hex representation (prefixed by 0x) of ascii string +var fromAscii = function(str, pad) { + pad = pad === undefined ? 0 : pad; + var hex = toHex(str); + while (hex.length < pad*2) + hex += "00"; + return "0x" + hex; +}; + +/// @returns display name for function/event eg. multiply(uint256) -> multiply +var extractDisplayName = function (name) { + var length = name.indexOf('('); + return length !== -1 ? name.substr(0, length) : name; +}; + +/// @returns overloaded part of function/event name +var extractTypeName = function (name) { + /// TODO: make it invulnerable + var length = name.indexOf('('); + return length !== -1 ? name.substr(length + 1, name.length - 1 - (length + 1)) : ""; +}; + +/// Filters all function from input abi +/// @returns abi array with filtered objects of type 'function' +var filterFunctions = function (json) { + return json.filter(function (current) { + return current.type === 'function'; + }); +}; + +/// Filters all events form input abi +/// @returns abi array with filtered objects of type 'event' +var filterEvents = function (json) { + return json.filter(function (current) { + return current.type === 'event'; + }); +}; + +module.exports = { + findIndex: findIndex, + toAscii: toAscii, + fromAscii: fromAscii, + extractDisplayName: extractDisplayName, + extractTypeName: extractTypeName, + filterFunctions: filterFunctions, + filterEvents: filterEvents +}; + + +},{}],12:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file web3.js + * @authors: + * Jeffrey Wilcke + * Marek Kotewicz + * Marian Oancea + * Gav Wood + * @date 2014 + */ + +if ("build" !== 'build') {/* + var BigNumber = require('bignumber.js'); +*/} + +var utils = require('./utils'); + +var ETH_UNITS = [ + 'wei', + 'Kwei', + 'Mwei', + 'Gwei', + 'szabo', + 'finney', + 'ether', + 'grand', + 'Mether', + 'Gether', + 'Tether', + 'Pether', + 'Eether', + 'Zether', + 'Yether', + 'Nether', + 'Dether', + 'Vether', + 'Uether' +]; /// @returns an array of objects describing web3 api methods var web3Methods = function () { @@ -1084,6 +1280,7 @@ var ethMethods = function () { { name: 'transaction', call: transactionCall }, { name: 'uncle', call: uncleCall }, { name: 'compilers', call: 'eth_compilers' }, + { name: 'flush', call: 'eth_flush' }, { name: 'lll', call: 'eth_lll' }, { name: 'solidity', call: 'eth_solidity' }, { name: 'serpent', call: 'eth_serpent' }, @@ -1099,7 +1296,6 @@ var ethProperties = function () { { name: 'listening', getter: 'eth_listening', setter: 'eth_setListening' }, { name: 'mining', getter: 'eth_mining', setter: 'eth_setMining' }, { name: 'gasPrice', getter: 'eth_gasPrice' }, - { name: 'account', getter: 'eth_account' }, { name: 'accounts', getter: 'eth_accounts' }, { name: 'peerCount', getter: 'eth_peerCount' }, { name: 'defaultBlock', getter: 'eth_defaultBlock', setter: 'eth_setDefaultBlock' }, @@ -1146,7 +1342,7 @@ var shhWatchMethods = function () { return [ { name: 'newFilter', call: 'shh_newFilter' }, { name: 'uninstallFilter', call: 'shh_uninstallFilter' }, - { name: 'getMessage', call: 'shh_getMessages' } + { name: 'getMessages', call: 'shh_getMessages' } ]; }; @@ -1155,21 +1351,11 @@ var shhWatchMethods = function () { var setupMethods = function (obj, methods) { methods.forEach(function (method) { obj[method.name] = function () { - return flattenPromise(Array.prototype.slice.call(arguments)).then(function (args) { - var call = typeof method.call === "function" ? method.call(args) : method.call; - return {call: call, args: args}; - }).then(function (request) { - return new Promise(function (resolve, reject) { - web3.provider.send(request, function (err, result) { - if (!err) { - resolve(result); - return; - } - reject(err); - }); - }); - }).catch(function(err) { - console.error(err); + var args = Array.prototype.slice.call(arguments); + var call = typeof method.call === 'function' ? method.call(args) : method.call; + return web3.provider.send({ + call: call, + args: args }); }; }); @@ -1181,30 +1367,16 @@ var setupProperties = function (obj, properties) { properties.forEach(function (property) { var proto = {}; proto.get = function () { - return new Promise(function(resolve, reject) { - web3.provider.send({call: property.getter}, function(err, result) { - if (!err) { - resolve(result); - return; - } - reject(err); - }); + return web3.provider.send({ + call: property.getter }); }; + if (property.setter) { proto.set = function (val) { - return flattenPromise([val]).then(function (args) { - return new Promise(function (resolve) { - web3.provider.send({call: property.setter, args: args}, function (err, result) { - if (!err) { - resolve(result); - return; - } - reject(err); - }); - }); - }).catch(function (err) { - console.error(err); + return web3.provider.send({ + call: property.setter, + args: [val] }); }; } @@ -1212,74 +1384,36 @@ var setupProperties = function (obj, properties) { }); }; -// TODO: import from a dependency, don't duplicate. -var hexToDec = function (hex) { - return parseInt(hex, 16).toString(); -}; - -var decToHex = function (dec) { - return parseInt(dec).toString(16); -}; - /// setups web3 object, and it's in-browser executed methods var web3 = { _callbacks: {}, _events: {}, providers: {}, - toHex: function(str) { - var hex = ""; - for(var i = 0; i < str.length; i++) { - var n = str.charCodeAt(i).toString(16); - hex += n.length < 2 ? '0' + n : n; - } - - return hex; - }, - /// @returns ascii string representation of hex value prefixed with 0x - toAscii: function(hex) { - // Find termination - var str = ""; - var i = 0, l = hex.length; - if (hex.substring(0, 2) === '0x') - i = 2; - for(; i < l; i+=2) { - var code = parseInt(hex.substr(i, 2), 16); - if(code === 0) { - break; - } - - str += String.fromCharCode(code); - } - - return str; - }, + toAscii: utils.toAscii, /// @returns hex representation (prefixed by 0x) of ascii string - fromAscii: function(str, pad) { - pad = pad === undefined ? 0 : pad; - var hex = this.toHex(str); - while(hex.length < pad*2) - hex += "00"; - return "0x" + hex; - }, + fromAscii: utils.fromAscii, /// @returns decimal representaton of hex value prefixed by 0x toDecimal: function (val) { - return hexToDec(val.substring(2)); + // remove 0x and place 0, if it's required + val = val.length > 2 ? val.substring(2) : "0"; + return (new BigNumber(val, 16).toString(10)); }, /// @returns hex representation (prefixed by 0x) of decimal value fromDecimal: function (val) { - return "0x" + decToHex(val); + return "0x" + (new BigNumber(val).toString(16)); }, /// used to transform value/string to eth string + /// TODO: use BigNumber.js to parse int toEth: function(str) { var val = typeof str === "string" ? str.indexOf('0x') === 0 ? parseInt(str.substr(2), 16) : parseInt(str) : str; var unit = 0; - var units = [ 'wei', 'Kwei', 'Mwei', 'Gwei', 'szabo', 'finney', 'ether', 'grand', 'Mether', 'Gether', 'Tether', 'Pether', 'Eether', 'Zether', 'Yether', 'Nether', 'Dether', 'Vether', 'Uether' ]; + var units = ETH_UNITS; while (val > 3000 && unit < units.length - 1) { val /= 1000; @@ -1301,8 +1435,24 @@ var web3 = { /// eth object prototype eth: { - watch: function (params) { - return new web3.filter(params, ethWatch); + contractFromAbi: function (abi) { + return function(addr) { + // Default to address of Config. TODO: rremove prior to genesis. + addr = addr || '0xc6d9d2cd449a754c494264e1809c50e34d64562b'; + var ret = web3.eth.contract(addr, abi); + ret.address = addr; + return ret; + }; + }, + + /// @param filter may be a string, object or event + /// @param indexed is optional, this is an object with optional event indexed params + /// @param options is optional, this is an object with optional event options ('max'...) + watch: function (filter, indexed, options) { + if (filter._isEvent) { + return filter(indexed, options); + } + return new web3.filter(filter, ethWatch); } }, @@ -1311,38 +1461,11 @@ var web3 = { /// shh object prototype shh: { - watch: function (params) { - return new web3.filter(params, shhWatch); - } - }, - - /// used by filter to register callback with given id - on: function(event, id, cb) { - if(web3._events[event] === undefined) { - web3._events[event] = {}; - } - - web3._events[event][id] = cb; - return this; - }, - - /// used by filter to unregister callback with given id - off: function(event, id) { - if(web3._events[event] !== undefined) { - delete web3._events[event][id]; - } - - return this; - }, - - /// used to trigger callback registered by filter - trigger: function(event, id, data) { - var callbacks = web3._events[event]; - if (!callbacks || !callbacks[id]) { - return; + + /// @param filter may be a string, object or event + watch: function (filter, indexed) { + return new web3.filter(filter, shhWatch); } - var cb = callbacks[id]; - cb(data); }, /// @returns true if provider is installed @@ -1371,144 +1494,27 @@ var shhWatch = { setupMethods(shhWatch, shhWatchMethods()); web3.setProvider = function(provider) { - provider.onmessage = messageHandler; + //provider.onmessage = messageHandler; // there will be no async calls, to remove web3.provider.set(provider); - web3.provider.sendQueued(); }; -/// callled when there is new incoming message -function messageHandler(data) { - if(data._event !== undefined) { - web3.trigger(data._event, data._id, data.data); - return; - } - - if(data._id) { - var cb = web3._callbacks[data._id]; - if (cb) { - cb.call(this, data.error, data.data); - delete web3._callbacks[data._id]; - } - } -} - module.exports = web3; -},{}],9:[function(require,module,exports){ -/* - This file is part of ethereum.js. - - ethereum.js is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ethereum.js 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with ethereum.js. If not, see . -*/ -/** @file websocket.js - * @authors: - * Jeffrey Wilcke - * Marek Kotewicz - * Marian Oancea - * @date 2014 - */ - -// TODO: is these line is supposed to be here? -if ("build" !== 'build') {/* - var WebSocket = require('ws'); // jshint ignore:line -*/} - -/** - * WebSocketProvider object prototype is implementing 'provider protocol' - * Should be used when we want to connect to ethereum backend over websockets - * It's compatible with go client - * The constructor allows to specify host uri - */ -var WebSocketProvider = function(host) { - - // onmessage handlers - this.handlers = []; - - // queue will be filled with messages if send is invoked before the ws is ready - this.queued = []; - this.ready = false; - - this.ws = new WebSocket(host); - - var self = this; - this.ws.onmessage = function(event) { - for(var i = 0; i < self.handlers.length; i++) { - self.handlers[i].call(self, JSON.parse(event.data), event); - } - }; - - this.ws.onopen = function() { - self.ready = true; - - for (var i = 0; i < self.queued.length; i++) { - // Resend - self.send(self.queued[i]); - } - }; -}; - -/// Prototype object method -/// Should be called when we want to send single api request to server -/// Asynchronous, it's using websockets -/// Response for the call will be received by ws.onmessage -/// @param payload is inner message object -WebSocketProvider.prototype.send = function(payload) { - if (this.ready) { - var data = JSON.stringify(payload); - - this.ws.send(data); - } else { - this.queued.push(payload); - } -}; - -/// Prototype object method -/// Should be called to add handlers -WebSocketProvider.prototype.onMessage = function(handler) { - this.handlers.push(handler); -}; - -/// Prototype object method -/// Should be called to close websockets connection -WebSocketProvider.prototype.unload = function() { - this.ws.close(); -}; - -/// Prototype object property -/// Should be used to set message handlers for this provider -Object.defineProperty(WebSocketProvider.prototype, "onmessage", { - set: function(provider) { this.onMessage(provider); } -}); - -if (typeof(module) !== "undefined") - module.exports = WebSocketProvider; - -},{}],"web3":[function(require,module,exports){ +},{"./utils":11}],"web3":[function(require,module,exports){ var web3 = require('./lib/web3'); var ProviderManager = require('./lib/providermanager'); web3.provider = new ProviderManager(); web3.filter = require('./lib/filter'); -web3.providers.WebSocketProvider = require('./lib/websocket'); -web3.providers.HttpRpcProvider = require('./lib/httprpc'); -web3.providers.QtProvider = require('./lib/qt'); -web3.providers.AutoProvider = require('./lib/autoprovider'); +web3.providers.HttpSyncProvider = require('./lib/httpsync'); +web3.providers.QtSyncProvider = require('./lib/qtsync'); web3.eth.contract = require('./lib/contract'); +web3.abi = require('./lib/abi'); + module.exports = web3; -},{"./lib/autoprovider":2,"./lib/contract":3,"./lib/filter":4,"./lib/httprpc":5,"./lib/providermanager":6,"./lib/qt":7,"./lib/web3":8,"./lib/websocket":9}]},{},["web3"]) +},{"./lib/abi":1,"./lib/contract":3,"./lib/filter":5,"./lib/httpsync":7,"./lib/providermanager":8,"./lib/qtsync":9,"./lib/web3":12}]},{},["web3"]) //# sourceMappingURL=ethereum.js.map \ No newline at end of file diff --git a/libjsqrc/ethereumjs/dist/ethereum.js.map b/libjsqrc/ethereumjs/dist/ethereum.js.map index c850654d7..81a7e3747 100644 --- a/libjsqrc/ethereumjs/dist/ethereum.js.map +++ b/libjsqrc/ethereumjs/dist/ethereum.js.map @@ -3,31 +3,37 @@ "sources": [ "node_modules/browserify/node_modules/browser-pack/_prelude.js", "lib/abi.js", - "lib/autoprovider.js", + "lib/const.js", "lib/contract.js", + "lib/event.js", "lib/filter.js", - "lib/httprpc.js", + "lib/formatters.js", + "lib/httpsync.js", "lib/providermanager.js", - "lib/qt.js", + "lib/qtsync.js", + "lib/types.js", + "lib/utils.js", "lib/web3.js", - "lib/websocket.js", "index.js" ], "names": [], - "mappings": "AAAA;ACAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC5XA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AClHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACzFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACtFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC9HA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACvHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACzDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC1ZA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AClGA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA", + "mappings": "AAAA;ACAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AClNA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACjCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AChNA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACrEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC/FA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC1JA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACtEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC9GA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AChCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC/EA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACjHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AClTA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA", "file": "generated.js", "sourceRoot": "", "sourcesContent": [ "(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o.\n*/\n/** @file abi.js\n * @authors:\n * Marek Kotewicz \n * Gav Wood \n * @date 2014\n */\n\n// TODO: is these line is supposed to be here? \nif (\"build\" !== 'build') {/*\n var BigNumber = require('bignumber.js'); // jshint ignore:line\n*/}\n\nvar web3 = require('./web3'); // jshint ignore:line\n\nBigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_DOWN });\n\nvar ETH_PADDING = 32;\n\n/// Finds first index of array element matching pattern\n/// @param array\n/// @param callback pattern\n/// @returns index of element\nvar findIndex = function (array, callback) {\n var end = false;\n var i = 0;\n for (; i < array.length && !end; i++) {\n end = callback(array[i]);\n }\n return end ? i - 1 : -1;\n};\n\n/// @returns a function that is used as a pattern for 'findIndex'\nvar findMethodIndex = function (json, methodName) {\n return findIndex(json, function (method) {\n return method.name === methodName;\n });\n};\n\n/// @param string string to be padded\n/// @param number of characters that result string should have\n/// @param sign, by default 0\n/// @returns right aligned string\nvar padLeft = function (string, chars, sign) {\n return new Array(chars - string.length + 1).join(sign ? sign : \"0\") + string;\n};\n\n/// @param expected type prefix (string)\n/// @returns function which checks if type has matching prefix. if yes, returns true, otherwise false\nvar prefixedType = function (prefix) {\n return function (type) {\n return type.indexOf(prefix) === 0;\n };\n};\n\n/// @param expected type name (string)\n/// @returns function which checks if type is matching expected one. if yes, returns true, otherwise false\nvar namedType = function (name) {\n return function (type) {\n return name === type;\n };\n};\n\nvar arrayType = function (type) {\n return type.slice(-2) === '[]';\n};\n\n/// Formats input value to byte representation of int\n/// If value is negative, return it's two's complement\n/// If the value is floating point, round it down\n/// @returns right-aligned byte representation of int\nvar formatInputInt = function (value) {\n var padding = ETH_PADDING * 2;\n if (value instanceof BigNumber || typeof value === 'number') {\n if (typeof value === 'number')\n value = new BigNumber(value);\n value = value.round();\n\n if (value.lessThan(0)) \n value = new BigNumber(\"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 16).plus(value).plus(1);\n value = value.toString(16);\n }\n else if (value.indexOf('0x') === 0)\n value = value.substr(2);\n else if (typeof value === 'string')\n value = formatInputInt(new BigNumber(value));\n else\n value = (+value).toString(16);\n return padLeft(value, padding);\n};\n\n/// Formats input value to byte representation of string\n/// @returns left-algined byte representation of string\nvar formatInputString = function (value) {\n return web3.fromAscii(value, ETH_PADDING).substr(2);\n};\n\n/// Formats input value to byte representation of bool\n/// @returns right-aligned byte representation bool\nvar formatInputBool = function (value) {\n return '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0');\n};\n\n/// Formats input value to byte representation of real\n/// Values are multiplied by 2^m and encoded as integers\n/// @returns byte representation of real\nvar formatInputReal = function (value) {\n return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))); \n};\n\nvar dynamicTypeBytes = function (type, value) {\n // TODO: decide what to do with array of strings\n if (arrayType(type) || prefixedType('string')(type))\n return formatInputInt(value.length); \n return \"\";\n};\n\n/// Setups input formatters for solidity types\n/// @returns an array of input formatters \nvar setupInputTypes = function () {\n \n return [\n { type: prefixedType('uint'), format: formatInputInt },\n { type: prefixedType('int'), format: formatInputInt },\n { type: prefixedType('hash'), format: formatInputInt },\n { type: prefixedType('string'), format: formatInputString }, \n { type: prefixedType('real'), format: formatInputReal },\n { type: prefixedType('ureal'), format: formatInputReal },\n { type: namedType('address'), format: formatInputInt },\n { type: namedType('bool'), format: formatInputBool }\n ];\n};\n\nvar inputTypes = setupInputTypes();\n\n/// Formats input params to bytes\n/// @param contract json abi\n/// @param name of the method that we want to use\n/// @param array of params that will be formatted to bytes\n/// @returns bytes representation of input params\nvar toAbiInput = function (json, methodName, params) {\n var bytes = \"\";\n var index = findMethodIndex(json, methodName);\n\n if (index === -1) {\n return;\n }\n\n var method = json[index];\n var padding = ETH_PADDING * 2;\n\n /// first we iterate in search for dynamic \n method.inputs.forEach(function (input, index) {\n bytes += dynamicTypeBytes(input.type, params[index]);\n });\n\n method.inputs.forEach(function (input, i) {\n var typeMatch = false;\n for (var j = 0; j < inputTypes.length && !typeMatch; j++) {\n typeMatch = inputTypes[j].type(method.inputs[i].type, params[i]);\n }\n if (!typeMatch) {\n console.error('input parser does not support type: ' + method.inputs[i].type);\n }\n\n var formatter = inputTypes[j - 1].format;\n var toAppend = \"\";\n\n if (arrayType(method.inputs[i].type))\n toAppend = params[i].reduce(function (acc, curr) {\n return acc + formatter(curr);\n }, \"\");\n else\n toAppend = formatter(params[i]);\n\n bytes += toAppend; \n });\n return bytes;\n};\n\n/// Check if input value is negative\n/// @param value is hex format\n/// @returns true if it is negative, otherwise false\nvar signedIsNegative = function (value) {\n return (new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1)) === '1';\n};\n\n/// Formats input right-aligned input bytes to int\n/// @returns right-aligned input bytes formatted to int\nvar formatOutputInt = function (value) {\n // check if it's negative number\n // it it is, return two's complement\n if (signedIsNegative(value)) {\n return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1);\n }\n return new BigNumber(value, 16);\n};\n\n/// Formats big right-aligned input bytes to uint\n/// @returns right-aligned input bytes formatted to uint\nvar formatOutputUInt = function (value) {\n return new BigNumber(value, 16);\n};\n\n/// @returns input bytes formatted to real\nvar formatOutputReal = function (value) {\n return formatOutputInt(value).dividedBy(new BigNumber(2).pow(128)); \n};\n\n/// @returns input bytes formatted to ureal\nvar formatOutputUReal = function (value) {\n return formatOutputUInt(value).dividedBy(new BigNumber(2).pow(128)); \n};\n\n/// @returns right-aligned input bytes formatted to hex\nvar formatOutputHash = function (value) {\n return \"0x\" + value;\n};\n\n/// @returns right-aligned input bytes formatted to bool\nvar formatOutputBool = function (value) {\n return value === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false;\n};\n\n/// @returns left-aligned input bytes formatted to ascii string\nvar formatOutputString = function (value) {\n return web3.toAscii(value);\n};\n\n/// @returns right-aligned input bytes formatted to address\nvar formatOutputAddress = function (value) {\n return \"0x\" + value.slice(value.length - 40, value.length);\n};\n\nvar dynamicBytesLength = function (type) {\n if (arrayType(type) || prefixedType('string')(type))\n return ETH_PADDING * 2;\n return 0;\n};\n\n/// Setups output formaters for solidity types\n/// @returns an array of output formatters\nvar setupOutputTypes = function () {\n\n return [\n { type: prefixedType('uint'), format: formatOutputUInt },\n { type: prefixedType('int'), format: formatOutputInt },\n { type: prefixedType('hash'), format: formatOutputHash },\n { type: prefixedType('string'), format: formatOutputString },\n { type: prefixedType('real'), format: formatOutputReal },\n { type: prefixedType('ureal'), format: formatOutputUReal },\n { type: namedType('address'), format: formatOutputAddress },\n { type: namedType('bool'), format: formatOutputBool }\n ];\n};\n\nvar outputTypes = setupOutputTypes();\n\n/// Formats output bytes back to param list\n/// @param contract json abi\n/// @param name of the method that we want to use\n/// @param bytes representtion of output \n/// @returns array of output params \nvar fromAbiOutput = function (json, methodName, output) {\n var index = findMethodIndex(json, methodName);\n\n if (index === -1) {\n return;\n }\n\n output = output.slice(2);\n\n var result = [];\n var method = json[index];\n var padding = ETH_PADDING * 2;\n\n var dynamicPartLength = method.outputs.reduce(function (acc, curr) {\n return acc + dynamicBytesLength(curr.type);\n }, 0);\n \n var dynamicPart = output.slice(0, dynamicPartLength);\n output = output.slice(dynamicPartLength);\n\n method.outputs.forEach(function (out, i) {\n var typeMatch = false;\n for (var j = 0; j < outputTypes.length && !typeMatch; j++) {\n typeMatch = outputTypes[j].type(method.outputs[i].type);\n }\n\n if (!typeMatch) {\n console.error('output parser does not support type: ' + method.outputs[i].type);\n }\n\n var formatter = outputTypes[j - 1].format;\n if (arrayType(method.outputs[i].type)) {\n var size = formatOutputUInt(dynamicPart.slice(0, padding));\n dynamicPart = dynamicPart.slice(padding);\n var array = [];\n for (var k = 0; k < size; k++) {\n array.push(formatter(output.slice(0, padding))); \n output = output.slice(padding);\n }\n result.push(array);\n }\n else if (prefixedType('string')(method.outputs[i].type)) {\n dynamicPart = dynamicPart.slice(padding); \n result.push(formatter(output.slice(0, padding)));\n output = output.slice(padding);\n } else {\n result.push(formatter(output.slice(0, padding)));\n output = output.slice(padding);\n }\n });\n\n return result;\n};\n\n/// @param json abi for contract\n/// @returns input parser object for given json abi\nvar inputParser = function (json) {\n var parser = {};\n json.forEach(function (method) {\n parser[method.name] = function () {\n var params = Array.prototype.slice.call(arguments);\n return toAbiInput(json, method.name, params);\n };\n });\n\n return parser;\n};\n\n/// @param json abi for contract\n/// @returns output parser for given json abi\nvar outputParser = function (json) {\n var parser = {};\n json.forEach(function (method) {\n parser[method.name] = function (output) {\n return fromAbiOutput(json, method.name, output);\n };\n });\n\n return parser;\n};\n\n/// @param json abi for contract\n/// @param method name for which we want to get method signature\n/// @returns (promise) contract method signature for method with given name\nvar methodSignature = function (json, name) {\n var method = json[findMethodIndex(json, name)];\n var result = name + '(';\n var inputTypes = method.inputs.map(function (inp) {\n return inp.type;\n });\n result += inputTypes.join(',');\n result += ')';\n\n return web3.sha3(web3.fromAscii(result));\n};\n\nmodule.exports = {\n inputParser: inputParser,\n outputParser: outputParser,\n methodSignature: methodSignature\n};\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file autoprovider.js\n * @authors:\n * Marek Kotewicz \n * Marian Oancea \n * @date 2014\n */\n\n/*\n * @brief if qt object is available, uses QtProvider,\n * if not tries to connect over websockets\n * if it fails, it uses HttpRpcProvider\n */\n\nvar web3 = require('./web3'); // jshint ignore:line\nif (\"build\" !== 'build') {/*\n var WebSocket = require('ws'); // jshint ignore:line\n*/}\n\n/**\n * AutoProvider object prototype is implementing 'provider protocol'\n * Automatically tries to setup correct provider(Qt, WebSockets or HttpRpc)\n * First it checkes if we are ethereum browser (if navigator.qt object is available)\n * if yes, we are using QtProvider\n * if no, we check if it is possible to establish websockets connection with ethereum (ws://localhost:40404/eth is default)\n * if it's not possible, we are using httprpc provider (http://localhost:8080)\n * The constructor allows you to specify uris on which we are trying to connect over http or websockets\n * You can do that by passing objects with fields httrpc and websockets\n */\nvar AutoProvider = function (userOptions) {\n if (web3.haveProvider()) {\n return;\n }\n\n // before we determine what provider we are, we have to cache request\n this.sendQueue = [];\n this.onmessageQueue = [];\n\n if (navigator.qt) {\n this.provider = new web3.providers.QtProvider();\n return;\n }\n\n userOptions = userOptions || {};\n var options = {\n httprpc: userOptions.httprpc || 'http://localhost:8080',\n websockets: userOptions.websockets || 'ws://localhost:40404/eth'\n };\n\n var self = this;\n var closeWithSuccess = function (success) {\n ws.close();\n if (success) {\n self.provider = new web3.providers.WebSocketProvider(options.websockets);\n } else {\n self.provider = new web3.providers.HttpRpcProvider(options.httprpc);\n self.poll = self.provider.poll.bind(self.provider);\n }\n self.sendQueue.forEach(function (payload) {\n self.provider.send(payload);\n });\n self.onmessageQueue.forEach(function (handler) {\n self.provider.onmessage = handler;\n });\n };\n\n var ws = new WebSocket(options.websockets);\n\n ws.onopen = function() {\n closeWithSuccess(true);\n };\n\n ws.onerror = function() {\n closeWithSuccess(false);\n };\n};\n\n/// Sends message forward to the provider, that is being used\n/// if provider is not yet set, enqueues the message\nAutoProvider.prototype.send = function (payload) {\n if (this.provider) {\n this.provider.send(payload);\n return;\n }\n this.sendQueue.push(payload);\n};\n\n/// On incoming message sends the message to the provider that is currently being used\nObject.defineProperty(AutoProvider.prototype, 'onmessage', {\n set: function (handler) {\n if (this.provider) {\n this.provider.onmessage = handler;\n return;\n }\n this.onmessageQueue.push(handler);\n }\n});\n\nmodule.exports = AutoProvider;\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file contract.js\n * @authors:\n * Marek Kotewicz \n * @date 2014\n */\n\nvar web3 = require('./web3'); // jshint ignore:line\nvar abi = require('./abi');\n\n/// method signature length in bytes\nvar ETH_METHOD_SIGNATURE_LENGTH = 4;\n\n/**\n * This method should be called when we want to call / transact some solidity method from javascript\n * it returns an object which has same methods available as solidity contract description\n * usage example: \n *\n * var abi = [{\n * name: 'myMethod',\n * inputs: [{ name: 'a', type: 'string' }],\n * outputs: [{name: 'd', type: 'string' }]\n * }]; // contract abi\n *\n * var myContract = web3.eth.contract('0x0123123121', abi); // creation of contract object\n *\n * myContract.myMethod('this is test string param for call').call(); // myMethod call\n * myContract.myMethod('this is test string param for transact').transact() // myMethod transact\n *\n * @param address - address of the contract, which should be called\n * @param desc - abi json description of the contract, which is being created\n * @returns contract object\n */\nvar contract = function (address, desc) {\n var inputParser = abi.inputParser(desc);\n var outputParser = abi.outputParser(desc);\n\n var contract = {};\n\n desc.forEach(function (method) {\n contract[method.name] = function () {\n var params = Array.prototype.slice.call(arguments);\n var parsed = inputParser[method.name].apply(null, params);\n\n var onSuccess = function (result) {\n return outputParser[method.name](result);\n };\n\n return {\n call: function (extra) {\n extra = extra || {};\n extra.to = address;\n return abi.methodSignature(desc, method.name).then(function (signature) {\n extra.data = signature.slice(0, 2 + ETH_METHOD_SIGNATURE_LENGTH * 2) + parsed;\n return web3.eth.call(extra).then(onSuccess);\n });\n },\n transact: function (extra) {\n extra = extra || {};\n extra.to = address;\n return abi.methodSignature(desc, method.name).then(function (signature) {\n extra.data = signature.slice(0, 2 + ETH_METHOD_SIGNATURE_LENGTH * 2) + parsed;\n return web3.eth.transact(extra).then(onSuccess);\n });\n }\n };\n };\n });\n\n return contract;\n};\n\nmodule.exports = contract;\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file filter.js\n * @authors:\n * Jeffrey Wilcke \n * Marek Kotewicz \n * Marian Oancea \n * Gav Wood \n * @date 2014\n */\n\nvar web3 = require('./web3'); // jshint ignore:line\n\n/// should be used when we want to watch something\n/// it's using inner polling mechanism and is notified about changes\nvar Filter = function(options, impl) {\n this.impl = impl;\n this.callbacks = [];\n\n var self = this;\n this.promise = impl.newFilter(options);\n this.promise.then(function (id) {\n self.id = id;\n web3.on(impl.changed, id, self.trigger.bind(self));\n web3.provider.startPolling({call: impl.changed, args: [id]}, id);\n });\n};\n\n/// alias for changed*\nFilter.prototype.arrived = function(callback) {\n this.changed(callback);\n};\n\n/// gets called when there is new eth/shh message\nFilter.prototype.changed = function(callback) {\n var self = this;\n this.promise.then(function(id) {\n self.callbacks.push(callback);\n });\n};\n\n/// trigger calling new message from people\nFilter.prototype.trigger = function(messages) {\n for(var i = 0; i < this.callbacks.length; i++) {\n this.callbacks[i].call(this, messages);\n }\n};\n\n/// should be called to uninstall current filter\nFilter.prototype.uninstall = function() {\n var self = this;\n this.promise.then(function (id) {\n self.impl.uninstallFilter(id);\n web3.provider.stopPolling(id);\n web3.off(impl.changed, id);\n });\n};\n\n/// should be called to manually trigger getting latest messages from the client\nFilter.prototype.messages = function() {\n var self = this;\n return this.promise.then(function (id) {\n return self.impl.getMessages(id);\n });\n};\n\n/// alias for messages\nFilter.prototype.logs = function () {\n return this.messages();\n};\n\nmodule.exports = Filter;\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file httprpc.js\n * @authors:\n * Marek Kotewicz \n * Marian Oancea \n * @date 2014\n */\n\n// TODO: is these line is supposed to be here? \nif (\"build\" !== 'build') {/*\n var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; // jshint ignore:line\n*/}\n\n/**\n * HttpRpcProvider object prototype is implementing 'provider protocol'\n * Should be used when we want to connect to ethereum backend over http && jsonrpc\n * It's compatible with cpp client\n * The contructor allows to specify host uri\n * This provider is using in-browser polling mechanism\n */\nvar HttpRpcProvider = function (host) {\n this.handlers = [];\n this.host = host;\n};\n\n/// Transforms inner message to proper jsonrpc object\n/// @param inner message object\n/// @returns jsonrpc object\nfunction formatJsonRpcObject(object) {\n return {\n jsonrpc: '2.0',\n method: object.call,\n params: object.args,\n id: object._id\n };\n}\n\n/// Transforms jsonrpc object to inner message\n/// @param incoming jsonrpc message \n/// @returns inner message object\nfunction formatJsonRpcMessage(message) {\n var object = JSON.parse(message);\n\n return {\n _id: object.id,\n data: object.result,\n error: object.error\n };\n}\n\n/// Prototype object method \n/// Asynchronously sends request to server\n/// @param payload is inner message object\n/// @param cb is callback which is being called when response is comes back\nHttpRpcProvider.prototype.sendRequest = function (payload, cb) {\n var data = formatJsonRpcObject(payload);\n\n var request = new XMLHttpRequest();\n request.open(\"POST\", this.host, true);\n request.send(JSON.stringify(data));\n request.onreadystatechange = function () {\n if (request.readyState === 4 && cb) {\n cb(request);\n }\n };\n};\n\n/// Prototype object method\n/// Should be called when we want to send single api request to server\n/// Asynchronous\n/// On response it passes message to handlers\n/// @param payload is inner message object\nHttpRpcProvider.prototype.send = function (payload) {\n var self = this;\n this.sendRequest(payload, function (request) {\n self.handlers.forEach(function (handler) {\n handler.call(self, formatJsonRpcMessage(request.responseText));\n });\n });\n};\n\n/// Prototype object method\n/// Should be called only for polling requests\n/// Asynchronous\n/// On response it passege message to handlers, but only if message's result is true or not empty array\n/// Otherwise response is being silently ignored\n/// @param payload is inner message object\n/// @id is id of poll that we are calling\nHttpRpcProvider.prototype.poll = function (payload, id) {\n var self = this;\n this.sendRequest(payload, function (request) {\n var parsed = JSON.parse(request.responseText);\n if (parsed.error || (parsed.result instanceof Array ? parsed.result.length === 0 : !parsed.result)) {\n return;\n }\n self.handlers.forEach(function (handler) {\n handler.call(self, {_event: payload.call, _id: id, data: parsed.result});\n });\n });\n};\n\n/// Prototype object property\n/// Should be used to set message handlers for this provider\nObject.defineProperty(HttpRpcProvider.prototype, \"onmessage\", {\n set: function (handler) {\n this.handlers.push(handler);\n }\n});\n\nmodule.exports = HttpRpcProvider;\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file providermanager.js\n * @authors:\n * Jeffrey Wilcke \n * Marek Kotewicz \n * Marian Oancea \n * Gav Wood \n * @date 2014\n */\n\nvar web3 = require('./web3'); // jshint ignore:line\n\n/**\n * Provider manager object prototype\n * It's responsible for passing messages to providers\n * If no provider is set it's responsible for queuing requests\n * It's also responsible for polling the ethereum node for incoming messages\n * Default poll timeout is 12 seconds\n * If we are running ethereum.js inside ethereum browser, there are backend based tools responsible for polling,\n * and provider manager polling mechanism is not used\n */\nvar ProviderManager = function() {\n this.queued = [];\n this.polls = [];\n this.ready = false;\n this.provider = undefined;\n this.id = 1;\n\n var self = this;\n var poll = function () {\n if (self.provider && self.provider.poll) {\n self.polls.forEach(function (data) {\n data.data._id = self.id;\n self.id++;\n self.provider.poll(data.data, data.id);\n });\n }\n setTimeout(poll, 12000);\n };\n poll();\n};\n\n/// sends outgoing requests, if provider is not available, enqueue the request\nProviderManager.prototype.send = function(data, cb) {\n data._id = this.id;\n if (cb) {\n web3._callbacks[data._id] = cb;\n }\n\n data.args = data.args || [];\n this.id++;\n\n if(this.provider !== undefined) {\n this.provider.send(data);\n } else {\n console.warn(\"provider is not set\");\n this.queued.push(data);\n }\n};\n\n/// setups provider, which will be used for sending messages\nProviderManager.prototype.set = function(provider) {\n if(this.provider !== undefined && this.provider.unload !== undefined) {\n this.provider.unload();\n }\n\n this.provider = provider;\n this.ready = true;\n};\n\n/// resends queued messages\nProviderManager.prototype.sendQueued = function() {\n for(var i = 0; this.queued.length; i++) {\n // Resend\n this.send(this.queued[i]);\n }\n};\n\n/// @returns true if the provider i properly set\nProviderManager.prototype.installed = function() {\n return this.provider !== undefined;\n};\n\n/// this method is only used, when we do not have native qt bindings and have to do polling on our own\n/// should be callled, on start watching for eth/shh changes\nProviderManager.prototype.startPolling = function (data, pollId) {\n if (!this.provider || !this.provider.poll) {\n return;\n }\n this.polls.push({data: data, id: pollId});\n};\n\n/// should be called to stop polling for certain watch changes\nProviderManager.prototype.stopPolling = function (pollId) {\n for (var i = this.polls.length; i--;) {\n var poll = this.polls[i];\n if (poll.id === pollId) {\n this.polls.splice(i, 1);\n }\n }\n};\n\nmodule.exports = ProviderManager;\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file qt.js\n * @authors:\n * Jeffrey Wilcke \n * Marek Kotewicz \n * @date 2014\n */\n\n/**\n * QtProvider object prototype is implementing 'provider protocol'\n * Should be used inside ethereum browser. It's compatible with cpp and go clients.\n * It uses navigator.qt object to pass the messages to native bindings\n */\nvar QtProvider = function() {\n this.handlers = [];\n\n var self = this;\n navigator.qt.onmessage = function (message) {\n self.handlers.forEach(function (handler) {\n handler.call(self, JSON.parse(message.data));\n });\n };\n};\n\n/// Prototype object method\n/// Should be called when we want to send single api request to native bindings\n/// Asynchronous\n/// Response will be received by navigator.qt.onmessage method and passed to handlers\n/// @param payload is inner message object\nQtProvider.prototype.send = function(payload) {\n navigator.qt.postMessage(JSON.stringify(payload));\n};\n\n/// Prototype object property\n/// Should be used to set message handlers for this provider\nObject.defineProperty(QtProvider.prototype, \"onmessage\", {\n set: function(handler) {\n this.handlers.push(handler);\n }\n});\n\nmodule.exports = QtProvider;\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file web3.js\n * @authors:\n * Jeffrey Wilcke \n * Marek Kotewicz \n * Marian Oancea \n * Gav Wood \n * @date 2014\n */\n\n/// Recursively resolves all promises in given object and replaces the resolved values with promises\n/// @param any object/array/promise/anything else..\n/// @returns (resolves) object with replaced promises with their result \nfunction flattenPromise (obj) {\n if (obj instanceof Promise) {\n return Promise.resolve(obj);\n }\n\n if (obj instanceof Array) {\n return new Promise(function (resolve) {\n var promises = obj.map(function (o) {\n return flattenPromise(o);\n });\n\n return Promise.all(promises).then(function (res) {\n for (var i = 0; i < obj.length; i++) {\n obj[i] = res[i];\n }\n resolve(obj);\n });\n });\n }\n\n if (obj instanceof Object) {\n return new Promise(function (resolve) {\n var keys = Object.keys(obj);\n var promises = keys.map(function (key) {\n return flattenPromise(obj[key]);\n });\n\n return Promise.all(promises).then(function (res) {\n for (var i = 0; i < keys.length; i++) {\n obj[keys[i]] = res[i];\n }\n resolve(obj);\n });\n });\n }\n\n return Promise.resolve(obj);\n}\n\n/// @returns an array of objects describing web3 api methods\nvar web3Methods = function () {\n return [\n { name: 'sha3', call: 'web3_sha3' }\n ];\n};\n\n/// @returns an array of objects describing web3.eth api methods\nvar ethMethods = function () {\n var blockCall = function (args) {\n return typeof args[0] === \"string\" ? \"eth_blockByHash\" : \"eth_blockByNumber\";\n };\n\n var transactionCall = function (args) {\n return typeof args[0] === \"string\" ? 'eth_transactionByHash' : 'eth_transactionByNumber';\n };\n\n var uncleCall = function (args) {\n return typeof args[0] === \"string\" ? 'eth_uncleByHash' : 'eth_uncleByNumber';\n };\n\n var methods = [\n { name: 'balanceAt', call: 'eth_balanceAt' },\n { name: 'stateAt', call: 'eth_stateAt' },\n { name: 'storageAt', call: 'eth_storageAt' },\n { name: 'countAt', call: 'eth_countAt'},\n { name: 'codeAt', call: 'eth_codeAt' },\n { name: 'transact', call: 'eth_transact' },\n { name: 'call', call: 'eth_call' },\n { name: 'block', call: blockCall },\n { name: 'transaction', call: transactionCall },\n { name: 'uncle', call: uncleCall },\n { name: 'compilers', call: 'eth_compilers' },\n { name: 'lll', call: 'eth_lll' },\n { name: 'solidity', call: 'eth_solidity' },\n { name: 'serpent', call: 'eth_serpent' },\n { name: 'logs', call: 'eth_logs' }\n ];\n return methods;\n};\n\n/// @returns an array of objects describing web3.eth api properties\nvar ethProperties = function () {\n return [\n { name: 'coinbase', getter: 'eth_coinbase', setter: 'eth_setCoinbase' },\n { name: 'listening', getter: 'eth_listening', setter: 'eth_setListening' },\n { name: 'mining', getter: 'eth_mining', setter: 'eth_setMining' },\n { name: 'gasPrice', getter: 'eth_gasPrice' },\n { name: 'account', getter: 'eth_account' },\n { name: 'accounts', getter: 'eth_accounts' },\n { name: 'peerCount', getter: 'eth_peerCount' },\n { name: 'defaultBlock', getter: 'eth_defaultBlock', setter: 'eth_setDefaultBlock' },\n { name: 'number', getter: 'eth_number'}\n ];\n};\n\n/// @returns an array of objects describing web3.db api methods\nvar dbMethods = function () {\n return [\n { name: 'put', call: 'db_put' },\n { name: 'get', call: 'db_get' },\n { name: 'putString', call: 'db_putString' },\n { name: 'getString', call: 'db_getString' }\n ];\n};\n\n/// @returns an array of objects describing web3.shh api methods\nvar shhMethods = function () {\n return [\n { name: 'post', call: 'shh_post' },\n { name: 'newIdentity', call: 'shh_newIdentity' },\n { name: 'haveIdentity', call: 'shh_haveIdentity' },\n { name: 'newGroup', call: 'shh_newGroup' },\n { name: 'addToGroup', call: 'shh_addToGroup' }\n ];\n};\n\n/// @returns an array of objects describing web3.eth.watch api methods\nvar ethWatchMethods = function () {\n var newFilter = function (args) {\n return typeof args[0] === 'string' ? 'eth_newFilterString' : 'eth_newFilter';\n };\n\n return [\n { name: 'newFilter', call: newFilter },\n { name: 'uninstallFilter', call: 'eth_uninstallFilter' },\n { name: 'getMessages', call: 'eth_filterLogs' }\n ];\n};\n\n/// @returns an array of objects describing web3.shh.watch api methods\nvar shhWatchMethods = function () {\n return [\n { name: 'newFilter', call: 'shh_newFilter' },\n { name: 'uninstallFilter', call: 'shh_uninstallFilter' },\n { name: 'getMessage', call: 'shh_getMessages' }\n ];\n};\n\n/// creates methods in a given object based on method description on input\n/// setups api calls for these methods\nvar setupMethods = function (obj, methods) {\n methods.forEach(function (method) {\n obj[method.name] = function () {\n return flattenPromise(Array.prototype.slice.call(arguments)).then(function (args) {\n var call = typeof method.call === \"function\" ? method.call(args) : method.call;\n return {call: call, args: args};\n }).then(function (request) {\n return new Promise(function (resolve, reject) {\n web3.provider.send(request, function (err, result) {\n if (!err) {\n resolve(result);\n return;\n }\n reject(err);\n });\n });\n }).catch(function(err) {\n console.error(err);\n });\n };\n });\n};\n\n/// creates properties in a given object based on properties description on input\n/// setups api calls for these properties\nvar setupProperties = function (obj, properties) {\n properties.forEach(function (property) {\n var proto = {};\n proto.get = function () {\n return new Promise(function(resolve, reject) {\n web3.provider.send({call: property.getter}, function(err, result) {\n if (!err) {\n resolve(result);\n return;\n }\n reject(err);\n });\n });\n };\n if (property.setter) {\n proto.set = function (val) {\n return flattenPromise([val]).then(function (args) {\n return new Promise(function (resolve) {\n web3.provider.send({call: property.setter, args: args}, function (err, result) {\n if (!err) {\n resolve(result);\n return;\n }\n reject(err);\n });\n });\n }).catch(function (err) {\n console.error(err);\n });\n };\n }\n Object.defineProperty(obj, property.name, proto);\n });\n};\n\n// TODO: import from a dependency, don't duplicate.\nvar hexToDec = function (hex) {\n return parseInt(hex, 16).toString();\n};\n\nvar decToHex = function (dec) {\n return parseInt(dec).toString(16);\n};\n\n/// setups web3 object, and it's in-browser executed methods\nvar web3 = {\n _callbacks: {},\n _events: {},\n providers: {},\n\n toHex: function(str) {\n var hex = \"\";\n for(var i = 0; i < str.length; i++) {\n var n = str.charCodeAt(i).toString(16);\n hex += n.length < 2 ? '0' + n : n;\n }\n\n return hex;\n },\n\n /// @returns ascii string representation of hex value prefixed with 0x\n toAscii: function(hex) {\n // Find termination\n var str = \"\";\n var i = 0, l = hex.length;\n if (hex.substring(0, 2) === '0x')\n i = 2;\n for(; i < l; i+=2) {\n var code = parseInt(hex.substr(i, 2), 16);\n if(code === 0) {\n break;\n }\n\n str += String.fromCharCode(code);\n }\n\n return str;\n },\n\n /// @returns hex representation (prefixed by 0x) of ascii string\n fromAscii: function(str, pad) {\n pad = pad === undefined ? 0 : pad;\n var hex = this.toHex(str);\n while(hex.length < pad*2)\n hex += \"00\";\n return \"0x\" + hex;\n },\n\n /// @returns decimal representaton of hex value prefixed by 0x\n toDecimal: function (val) {\n return hexToDec(val.substring(2));\n },\n\n /// @returns hex representation (prefixed by 0x) of decimal value\n fromDecimal: function (val) {\n return \"0x\" + decToHex(val);\n },\n\n /// used to transform value/string to eth string\n toEth: function(str) {\n var val = typeof str === \"string\" ? str.indexOf('0x') === 0 ? parseInt(str.substr(2), 16) : parseInt(str) : str;\n var unit = 0;\n var units = [ 'wei', 'Kwei', 'Mwei', 'Gwei', 'szabo', 'finney', 'ether', 'grand', 'Mether', 'Gether', 'Tether', 'Pether', 'Eether', 'Zether', 'Yether', 'Nether', 'Dether', 'Vether', 'Uether' ];\n while (val > 3000 && unit < units.length - 1)\n {\n val /= 1000;\n unit++;\n }\n var s = val.toString().length < val.toFixed(2).length ? val.toString() : val.toFixed(2);\n var replaceFunction = function($0, $1, $2) {\n return $1 + ',' + $2;\n };\n\n while (true) {\n var o = s;\n s = s.replace(/(\\d)(\\d\\d\\d[\\.\\,])/, replaceFunction);\n if (o === s)\n break;\n }\n return s + ' ' + units[unit];\n },\n\n /// eth object prototype\n eth: {\n watch: function (params) {\n return new web3.filter(params, ethWatch);\n }\n },\n\n /// db object prototype\n db: {},\n\n /// shh object prototype\n shh: {\n watch: function (params) {\n return new web3.filter(params, shhWatch);\n }\n },\n\n /// used by filter to register callback with given id\n on: function(event, id, cb) {\n if(web3._events[event] === undefined) {\n web3._events[event] = {};\n }\n\n web3._events[event][id] = cb;\n return this;\n },\n\n /// used by filter to unregister callback with given id\n off: function(event, id) {\n if(web3._events[event] !== undefined) {\n delete web3._events[event][id];\n }\n\n return this;\n },\n\n /// used to trigger callback registered by filter\n trigger: function(event, id, data) {\n var callbacks = web3._events[event];\n if (!callbacks || !callbacks[id]) {\n return;\n }\n var cb = callbacks[id];\n cb(data);\n },\n\n /// @returns true if provider is installed\n haveProvider: function() {\n return !!web3.provider.provider;\n }\n};\n\n/// setups all api methods\nsetupMethods(web3, web3Methods());\nsetupMethods(web3.eth, ethMethods());\nsetupProperties(web3.eth, ethProperties());\nsetupMethods(web3.db, dbMethods());\nsetupMethods(web3.shh, shhMethods());\n\nvar ethWatch = {\n changed: 'eth_changed'\n};\n\nsetupMethods(ethWatch, ethWatchMethods());\n\nvar shhWatch = {\n changed: 'shh_changed'\n};\n\nsetupMethods(shhWatch, shhWatchMethods());\n\nweb3.setProvider = function(provider) {\n provider.onmessage = messageHandler;\n web3.provider.set(provider);\n web3.provider.sendQueued();\n};\n\n/// callled when there is new incoming message\nfunction messageHandler(data) {\n if(data._event !== undefined) {\n web3.trigger(data._event, data._id, data.data);\n return;\n }\n\n if(data._id) {\n var cb = web3._callbacks[data._id];\n if (cb) {\n cb.call(this, data.error, data.data);\n delete web3._callbacks[data._id];\n }\n }\n}\n\nmodule.exports = web3;\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file websocket.js\n * @authors:\n * Jeffrey Wilcke \n * Marek Kotewicz \n * Marian Oancea \n * @date 2014\n */\n\n// TODO: is these line is supposed to be here? \nif (\"build\" !== 'build') {/*\n var WebSocket = require('ws'); // jshint ignore:line\n*/}\n\n/**\n * WebSocketProvider object prototype is implementing 'provider protocol'\n * Should be used when we want to connect to ethereum backend over websockets\n * It's compatible with go client\n * The constructor allows to specify host uri\n */\nvar WebSocketProvider = function(host) {\n\n // onmessage handlers\n this.handlers = [];\n\n // queue will be filled with messages if send is invoked before the ws is ready\n this.queued = [];\n this.ready = false;\n\n this.ws = new WebSocket(host);\n\n var self = this;\n this.ws.onmessage = function(event) {\n for(var i = 0; i < self.handlers.length; i++) {\n self.handlers[i].call(self, JSON.parse(event.data), event);\n }\n };\n\n this.ws.onopen = function() {\n self.ready = true;\n\n for (var i = 0; i < self.queued.length; i++) {\n // Resend\n self.send(self.queued[i]);\n }\n };\n};\n\n/// Prototype object method\n/// Should be called when we want to send single api request to server\n/// Asynchronous, it's using websockets\n/// Response for the call will be received by ws.onmessage\n/// @param payload is inner message object\nWebSocketProvider.prototype.send = function(payload) {\n if (this.ready) {\n var data = JSON.stringify(payload);\n\n this.ws.send(data);\n } else {\n this.queued.push(payload);\n }\n};\n\n/// Prototype object method\n/// Should be called to add handlers\nWebSocketProvider.prototype.onMessage = function(handler) {\n this.handlers.push(handler);\n};\n\n/// Prototype object method\n/// Should be called to close websockets connection\nWebSocketProvider.prototype.unload = function() {\n this.ws.close();\n};\n\n/// Prototype object property\n/// Should be used to set message handlers for this provider\nObject.defineProperty(WebSocketProvider.prototype, \"onmessage\", {\n set: function(provider) { this.onMessage(provider); }\n});\n\nif (typeof(module) !== \"undefined\")\n module.exports = WebSocketProvider;\n", - "var web3 = require('./lib/web3');\nvar ProviderManager = require('./lib/providermanager');\nweb3.provider = new ProviderManager();\nweb3.filter = require('./lib/filter');\nweb3.providers.WebSocketProvider = require('./lib/websocket');\nweb3.providers.HttpRpcProvider = require('./lib/httprpc');\nweb3.providers.QtProvider = require('./lib/qt');\nweb3.providers.AutoProvider = require('./lib/autoprovider');\nweb3.eth.contract = require('./lib/contract');\n\nmodule.exports = web3;\n" + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file abi.js\n * @authors:\n * Marek Kotewicz \n * Gav Wood \n * @date 2014\n */\n\nvar web3 = require('./web3'); \nvar utils = require('./utils');\nvar types = require('./types');\nvar c = require('./const');\nvar f = require('./formatters');\n\nvar displayTypeError = function (type) {\n console.error('parser does not support type: ' + type);\n};\n\n/// This method should be called if we want to check if givent type is an array type\n/// @returns true if it is, otherwise false\nvar arrayType = function (type) {\n return type.slice(-2) === '[]';\n};\n\nvar dynamicTypeBytes = function (type, value) {\n // TODO: decide what to do with array of strings\n if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length.\n return f.formatInputInt(value.length); \n return \"\";\n};\n\nvar inputTypes = types.inputTypes(); \n\n/// Formats input params to bytes\n/// @param abi contract method inputs\n/// @param array of params that will be formatted to bytes\n/// @returns bytes representation of input params\nvar formatInput = function (inputs, params) {\n var bytes = \"\";\n var padding = c.ETH_PADDING * 2;\n\n /// first we iterate in search for dynamic \n inputs.forEach(function (input, index) {\n bytes += dynamicTypeBytes(input.type, params[index]);\n });\n\n inputs.forEach(function (input, i) {\n var typeMatch = false;\n for (var j = 0; j < inputTypes.length && !typeMatch; j++) {\n typeMatch = inputTypes[j].type(inputs[i].type, params[i]);\n }\n if (!typeMatch) {\n displayTypeError(inputs[i].type);\n }\n\n var formatter = inputTypes[j - 1].format;\n var toAppend = \"\";\n\n if (arrayType(inputs[i].type))\n toAppend = params[i].reduce(function (acc, curr) {\n return acc + formatter(curr);\n }, \"\");\n else\n toAppend = formatter(params[i]);\n\n bytes += toAppend; \n });\n return bytes;\n};\n\nvar dynamicBytesLength = function (type) {\n if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length.\n return c.ETH_PADDING * 2;\n return 0;\n};\n\nvar outputTypes = types.outputTypes(); \n\n/// Formats output bytes back to param list\n/// @param contract abi method outputs\n/// @param bytes representtion of output \n/// @returns array of output params \nvar formatOutput = function (outs, output) {\n \n output = output.slice(2);\n var result = [];\n var padding = c.ETH_PADDING * 2;\n\n var dynamicPartLength = outs.reduce(function (acc, curr) {\n return acc + dynamicBytesLength(curr.type);\n }, 0);\n \n var dynamicPart = output.slice(0, dynamicPartLength);\n output = output.slice(dynamicPartLength);\n\n outs.forEach(function (out, i) {\n var typeMatch = false;\n for (var j = 0; j < outputTypes.length && !typeMatch; j++) {\n typeMatch = outputTypes[j].type(outs[i].type);\n }\n\n if (!typeMatch) {\n displayTypeError(outs[i].type);\n }\n\n var formatter = outputTypes[j - 1].format;\n if (arrayType(outs[i].type)) {\n var size = f.formatOutputUInt(dynamicPart.slice(0, padding));\n dynamicPart = dynamicPart.slice(padding);\n var array = [];\n for (var k = 0; k < size; k++) {\n array.push(formatter(output.slice(0, padding))); \n output = output.slice(padding);\n }\n result.push(array);\n }\n else if (types.prefixedType('string')(outs[i].type)) {\n dynamicPart = dynamicPart.slice(padding); \n result.push(formatter(output.slice(0, padding)));\n output = output.slice(padding);\n } else {\n result.push(formatter(output.slice(0, padding)));\n output = output.slice(padding);\n }\n });\n\n return result;\n};\n\n/// @param json abi for contract\n/// @returns input parser object for given json abi\n/// TODO: refactor creating the parser, do not double logic from contract\nvar inputParser = function (json) {\n var parser = {};\n json.forEach(function (method) {\n var displayName = utils.extractDisplayName(method.name); \n var typeName = utils.extractTypeName(method.name);\n\n var impl = function () {\n var params = Array.prototype.slice.call(arguments);\n return formatInput(method.inputs, params);\n };\n \n if (parser[displayName] === undefined) {\n parser[displayName] = impl;\n }\n\n parser[displayName][typeName] = impl;\n });\n\n return parser;\n};\n\n/// @param json abi for contract\n/// @returns output parser for given json abi\nvar outputParser = function (json) {\n var parser = {};\n json.forEach(function (method) {\n\n var displayName = utils.extractDisplayName(method.name); \n var typeName = utils.extractTypeName(method.name);\n\n var impl = function (output) {\n return formatOutput(method.outputs, output);\n };\n\n if (parser[displayName] === undefined) {\n parser[displayName] = impl;\n }\n\n parser[displayName][typeName] = impl;\n });\n\n return parser;\n};\n\n/// @param function/event name for which we want to get signature\n/// @returns signature of function/event with given name\nvar signatureFromAscii = function (name) {\n return web3.sha3(web3.fromAscii(name)).slice(0, 2 + c.ETH_SIGNATURE_LENGTH * 2);\n};\n\nvar eventSignatureFromAscii = function (name) {\n return web3.sha3(web3.fromAscii(name));\n};\n\nmodule.exports = {\n inputParser: inputParser,\n outputParser: outputParser,\n formatInput: formatInput,\n formatOutput: formatOutput,\n signatureFromAscii: signatureFromAscii,\n eventSignatureFromAscii: eventSignatureFromAscii\n};\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file const.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\n/// required to define ETH_BIGNUMBER_ROUNDING_MODE\nif (\"build\" !== 'build') {/*\n var BigNumber = require('bignumber.js'); // jshint ignore:line\n*/}\n\nmodule.exports = {\n ETH_PADDING: 32,\n ETH_SIGNATURE_LENGTH: 4,\n ETH_BIGNUMBER_ROUNDING_MODE: { ROUNDING_MODE: BigNumber.ROUND_DOWN }\n};\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file contract.js\n * @authors:\n * Marek Kotewicz \n * @date 2014\n */\n\nvar web3 = require('./web3'); \nvar abi = require('./abi');\nvar utils = require('./utils');\nvar eventImpl = require('./event');\n\nvar exportNatspecGlobals = function (vars) {\n // it's used byt natspec.js\n // TODO: figure out better way to solve this\n web3._currentContractAbi = vars.abi;\n web3._currentContractAddress = vars.address;\n web3._currentContractMethodName = vars.method;\n web3._currentContractMethodParams = vars.params;\n};\n\nvar addFunctionRelatedPropertiesToContract = function (contract) {\n \n contract.call = function (options) {\n contract._isTransact = false;\n contract._options = options;\n return contract;\n };\n\n contract.transact = function (options) {\n contract._isTransact = true;\n contract._options = options;\n return contract;\n };\n\n contract._options = {};\n ['gas', 'gasPrice', 'value', 'from'].forEach(function(p) {\n contract[p] = function (v) {\n contract._options[p] = v;\n return contract;\n };\n });\n\n};\n\nvar addFunctionsToContract = function (contract, desc, address) {\n var inputParser = abi.inputParser(desc);\n var outputParser = abi.outputParser(desc);\n\n // create contract functions\n utils.filterFunctions(desc).forEach(function (method) {\n\n var displayName = utils.extractDisplayName(method.name);\n var typeName = utils.extractTypeName(method.name);\n\n var impl = function () {\n var params = Array.prototype.slice.call(arguments);\n var signature = abi.signatureFromAscii(method.name);\n var parsed = inputParser[displayName][typeName].apply(null, params);\n\n var options = contract._options || {};\n options.to = address;\n options.data = signature + parsed;\n \n var isTransact = contract._isTransact === true || (contract._isTransact !== false && !method.constant);\n var collapse = options.collapse !== false;\n \n // reset\n contract._options = {};\n contract._isTransact = null;\n\n if (isTransact) {\n \n exportNatspecGlobals({\n abi: desc,\n address: address,\n method: method.name,\n params: params\n });\n\n // transactions do not have any output, cause we do not know, when they will be processed\n web3.eth.transact(options);\n return;\n }\n \n var output = web3.eth.call(options);\n var ret = outputParser[displayName][typeName](output);\n if (collapse)\n {\n if (ret.length === 1)\n ret = ret[0];\n else if (ret.length === 0)\n ret = null;\n }\n return ret;\n };\n\n if (contract[displayName] === undefined) {\n contract[displayName] = impl;\n }\n\n contract[displayName][typeName] = impl;\n });\n};\n\nvar addEventRelatedPropertiesToContract = function (contract, desc, address) {\n contract.address = address;\n \n Object.defineProperty(contract, 'topic', {\n get: function() {\n return utils.filterEvents(desc).map(function (e) {\n return abi.eventSignatureFromAscii(e.name);\n });\n }\n });\n\n};\n\nvar addEventsToContract = function (contract, desc, address) {\n // create contract events\n utils.filterEvents(desc).forEach(function (e) {\n\n var impl = function () {\n var params = Array.prototype.slice.call(arguments);\n var signature = abi.eventSignatureFromAscii(e.name);\n var event = eventImpl(address, signature, e);\n var o = event.apply(null, params);\n return web3.eth.watch(o); \n };\n \n // this property should be used by eth.filter to check if object is an event\n impl._isEvent = true;\n\n var displayName = utils.extractDisplayName(e.name);\n var typeName = utils.extractTypeName(e.name);\n\n if (contract[displayName] === undefined) {\n contract[displayName] = impl;\n }\n\n contract[displayName][typeName] = impl;\n\n });\n};\n\n\n/**\n * This method should be called when we want to call / transact some solidity method from javascript\n * it returns an object which has same methods available as solidity contract description\n * usage example: \n *\n * var abi = [{\n * name: 'myMethod',\n * inputs: [{ name: 'a', type: 'string' }],\n * outputs: [{name: 'd', type: 'string' }]\n * }]; // contract abi\n *\n * var myContract = web3.eth.contract('0x0123123121', abi); // creation of contract object\n *\n * myContract.myMethod('this is test string param for call'); // myMethod call (implicit, default)\n * myContract.call().myMethod('this is test string param for call'); // myMethod call (explicit)\n * myContract.transact().myMethod('this is test string param for transact'); // myMethod transact\n *\n * @param address - address of the contract, which should be called\n * @param desc - abi json description of the contract, which is being created\n * @returns contract object\n */\n\nvar contract = function (address, desc) {\n\n // workaround for invalid assumption that method.name is the full anonymous prototype of the method.\n // it's not. it's just the name. the rest of the code assumes it's actually the anonymous\n // prototype, so we make it so as a workaround.\n // TODO: we may not want to modify input params, maybe use copy instead?\n desc.forEach(function (method) {\n if (method.name.indexOf('(') === -1) {\n var displayName = method.name;\n var typeName = method.inputs.map(function(i){return i.type; }).join();\n method.name = displayName + '(' + typeName + ')';\n }\n });\n\n var result = {};\n addFunctionRelatedPropertiesToContract(result);\n addFunctionsToContract(result, desc, address);\n addEventRelatedPropertiesToContract(result, desc, address);\n addEventsToContract(result, desc, address);\n\n return result;\n};\n\nmodule.exports = contract;\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file event.js\n * @authors:\n * Marek Kotewicz \n * @date 2014\n */\n\nvar abi = require('./abi');\nvar utils = require('./utils');\n\nvar inputWithName = function (inputs, name) {\n var index = utils.findIndex(inputs, function (input) {\n return input.name === name;\n });\n \n if (index === -1) {\n console.error('indexed param with name ' + name + ' not found');\n return undefined;\n }\n return inputs[index];\n};\n\nvar indexedParamsToTopics = function (event, indexed) {\n // sort keys?\n return Object.keys(indexed).map(function (key) {\n var inputs = [inputWithName(event.inputs, key)];\n\n var value = indexed[key];\n if (value instanceof Array) {\n return value.map(function (v) {\n return abi.formatInput(inputs, [v]);\n }); \n }\n return abi.formatInput(inputs, [value]);\n });\n};\n\nvar implementationOfEvent = function (address, signature, event) {\n \n // valid options are 'earliest', 'latest', 'offset' and 'max', as defined for 'eth.watch'\n return function (indexed, options) {\n var o = options || {};\n o.address = address;\n o.topic = [];\n o.topic.push(signature);\n if (indexed) {\n o.topic = o.topic.concat(indexedParamsToTopics(event, indexed));\n }\n return o;\n };\n};\n\nmodule.exports = implementationOfEvent;\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file filter.js\n * @authors:\n * Jeffrey Wilcke \n * Marek Kotewicz \n * Marian Oancea \n * Gav Wood \n * @date 2014\n */\n\nvar web3 = require('./web3'); // jshint ignore:line\n\n/// should be used when we want to watch something\n/// it's using inner polling mechanism and is notified about changes\n/// TODO: change 'options' name cause it may be not the best matching one, since we have events\nvar Filter = function(options, impl) {\n\n if (typeof options !== \"string\") {\n\n // topics property is deprecated, warn about it!\n if (options.topics) {\n console.warn('\"topics\" is deprecated, use \"topic\" instead');\n }\n\n // evaluate lazy properties\n options = {\n to: options.to,\n topic: options.topic,\n earliest: options.earliest,\n latest: options.latest,\n max: options.max,\n skip: options.skip,\n address: options.address\n };\n\n }\n \n this.impl = impl;\n this.callbacks = [];\n\n this.id = impl.newFilter(options);\n web3.provider.startPolling({call: impl.changed, args: [this.id]}, this.id, this.trigger.bind(this));\n};\n\n/// alias for changed*\nFilter.prototype.arrived = function(callback) {\n this.changed(callback);\n};\n\n/// gets called when there is new eth/shh message\nFilter.prototype.changed = function(callback) {\n this.callbacks.push(callback);\n};\n\n/// trigger calling new message from people\nFilter.prototype.trigger = function(messages) {\n for (var i = 0; i < this.callbacks.length; i++) {\n for (var j = 0; j < messages.length; j++) {\n this.callbacks[i].call(this, messages[j]);\n }\n }\n};\n\n/// should be called to uninstall current filter\nFilter.prototype.uninstall = function() {\n this.impl.uninstallFilter(this.id);\n web3.provider.stopPolling(this.id);\n};\n\n/// should be called to manually trigger getting latest messages from the client\nFilter.prototype.messages = function() {\n return this.impl.getMessages(this.id);\n};\n\n/// alias for messages\nFilter.prototype.logs = function () {\n return this.messages();\n};\n\nmodule.exports = Filter;\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file formatters.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\nif (\"build\" !== 'build') {/*\n var BigNumber = require('bignumber.js'); // jshint ignore:line\n*/}\n\nvar utils = require('./utils');\nvar c = require('./const');\n\n/// @param string string to be padded\n/// @param number of characters that result string should have\n/// @param sign, by default 0\n/// @returns right aligned string\nvar padLeft = function (string, chars, sign) {\n return new Array(chars - string.length + 1).join(sign ? sign : \"0\") + string;\n};\n\n/// Formats input value to byte representation of int\n/// If value is negative, return it's two's complement\n/// If the value is floating point, round it down\n/// @returns right-aligned byte representation of int\nvar formatInputInt = function (value) {\n var padding = c.ETH_PADDING * 2;\n if (value instanceof BigNumber || typeof value === 'number') {\n if (typeof value === 'number')\n value = new BigNumber(value);\n BigNumber.config(c.ETH_BIGNUMBER_ROUNDING_MODE);\n value = value.round();\n\n if (value.lessThan(0)) \n value = new BigNumber(\"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 16).plus(value).plus(1);\n value = value.toString(16);\n }\n else if (value.indexOf('0x') === 0)\n value = value.substr(2);\n else if (typeof value === 'string')\n value = formatInputInt(new BigNumber(value));\n else\n value = (+value).toString(16);\n return padLeft(value, padding);\n};\n\n/// Formats input value to byte representation of string\n/// @returns left-algined byte representation of string\nvar formatInputString = function (value) {\n return utils.fromAscii(value, c.ETH_PADDING).substr(2);\n};\n\n/// Formats input value to byte representation of bool\n/// @returns right-aligned byte representation bool\nvar formatInputBool = function (value) {\n return '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0');\n};\n\n/// Formats input value to byte representation of real\n/// Values are multiplied by 2^m and encoded as integers\n/// @returns byte representation of real\nvar formatInputReal = function (value) {\n return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))); \n};\n\n\n/// Check if input value is negative\n/// @param value is hex format\n/// @returns true if it is negative, otherwise false\nvar signedIsNegative = function (value) {\n return (new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1)) === '1';\n};\n\n/// Formats input right-aligned input bytes to int\n/// @returns right-aligned input bytes formatted to int\nvar formatOutputInt = function (value) {\n value = value || \"0\";\n // check if it's negative number\n // it it is, return two's complement\n if (signedIsNegative(value)) {\n return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1);\n }\n return new BigNumber(value, 16);\n};\n\n/// Formats big right-aligned input bytes to uint\n/// @returns right-aligned input bytes formatted to uint\nvar formatOutputUInt = function (value) {\n value = value || \"0\";\n return new BigNumber(value, 16);\n};\n\n/// @returns input bytes formatted to real\nvar formatOutputReal = function (value) {\n return formatOutputInt(value).dividedBy(new BigNumber(2).pow(128)); \n};\n\n/// @returns input bytes formatted to ureal\nvar formatOutputUReal = function (value) {\n return formatOutputUInt(value).dividedBy(new BigNumber(2).pow(128)); \n};\n\n/// @returns right-aligned input bytes formatted to hex\nvar formatOutputHash = function (value) {\n return \"0x\" + value;\n};\n\n/// @returns right-aligned input bytes formatted to bool\nvar formatOutputBool = function (value) {\n return value === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false;\n};\n\n/// @returns left-aligned input bytes formatted to ascii string\nvar formatOutputString = function (value) {\n return utils.toAscii(value);\n};\n\n/// @returns right-aligned input bytes formatted to address\nvar formatOutputAddress = function (value) {\n return \"0x\" + value.slice(value.length - 40, value.length);\n};\n\n\nmodule.exports = {\n formatInputInt: formatInputInt,\n formatInputString: formatInputString,\n formatInputBool: formatInputBool,\n formatInputReal: formatInputReal,\n formatOutputInt: formatOutputInt,\n formatOutputUInt: formatOutputUInt,\n formatOutputReal: formatOutputReal,\n formatOutputUReal: formatOutputUReal,\n formatOutputHash: formatOutputHash,\n formatOutputBool: formatOutputBool,\n formatOutputString: formatOutputString,\n formatOutputAddress: formatOutputAddress\n};\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file httpsync.js\n * @authors:\n * Marek Kotewicz \n * Marian Oancea \n * @date 2014\n */\n\nif (\"build\" !== 'build') {/*\n var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; // jshint ignore:line\n*/}\n\nvar HttpSyncProvider = function (host) {\n this.handlers = [];\n this.host = host || 'http://localhost:8080';\n};\n\n/// Transforms inner message to proper jsonrpc object\n/// @param inner message object\n/// @returns jsonrpc object\nfunction formatJsonRpcObject(object) {\n return {\n jsonrpc: '2.0',\n method: object.call,\n params: object.args,\n id: object._id\n };\n}\n\n/// Transforms jsonrpc object to inner message\n/// @param incoming jsonrpc message \n/// @returns inner message object\nfunction formatJsonRpcMessage(message) {\n var object = JSON.parse(message);\n\n return {\n _id: object.id,\n data: object.result,\n error: object.error\n };\n}\n\nHttpSyncProvider.prototype.send = function (payload) {\n var data = formatJsonRpcObject(payload);\n \n var request = new XMLHttpRequest();\n request.open('POST', this.host, false);\n request.send(JSON.stringify(data));\n \n // check request.status\n return request.responseText;\n};\n\nmodule.exports = HttpSyncProvider;\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file providermanager.js\n * @authors:\n * Jeffrey Wilcke \n * Marek Kotewicz \n * Marian Oancea \n * Gav Wood \n * @date 2014\n */\n\nvar web3 = require('./web3'); // jshint ignore:line\n\n/**\n * Provider manager object prototype\n * It's responsible for passing messages to providers\n * If no provider is set it's responsible for queuing requests\n * It's also responsible for polling the ethereum node for incoming messages\n * Default poll timeout is 12 seconds\n * If we are running ethereum.js inside ethereum browser, there are backend based tools responsible for polling,\n * and provider manager polling mechanism is not used\n */\nvar ProviderManager = function() {\n this.polls = [];\n this.provider = undefined;\n this.id = 1;\n\n var self = this;\n var poll = function () {\n if (self.provider) {\n self.polls.forEach(function (data) {\n data.data._id = self.id;\n self.id++;\n var result = self.provider.send(data.data);\n \n result = JSON.parse(result);\n \n // dont call the callback if result is not an array, or empty one\n if (result.error || !(result.result instanceof Array) || result.result.length === 0) {\n return;\n }\n\n data.callback(result.result);\n });\n }\n setTimeout(poll, 1000);\n };\n poll();\n};\n\n/// sends outgoing requests\nProviderManager.prototype.send = function(data) {\n\n data.args = data.args || [];\n data._id = this.id++;\n\n if (this.provider === undefined) {\n console.error('provider is not set');\n return null; \n }\n\n //TODO: handle error here? \n var result = this.provider.send(data);\n result = JSON.parse(result);\n\n if (result.error) {\n console.log(result.error);\n return null;\n }\n\n return result.result;\n};\n\n/// setups provider, which will be used for sending messages\nProviderManager.prototype.set = function(provider) {\n this.provider = provider;\n};\n\n/// this method is only used, when we do not have native qt bindings and have to do polling on our own\n/// should be callled, on start watching for eth/shh changes\nProviderManager.prototype.startPolling = function (data, pollId, callback) {\n this.polls.push({data: data, id: pollId, callback: callback});\n};\n\n/// should be called to stop polling for certain watch changes\nProviderManager.prototype.stopPolling = function (pollId) {\n for (var i = this.polls.length; i--;) {\n var poll = this.polls[i];\n if (poll.id === pollId) {\n this.polls.splice(i, 1);\n }\n }\n};\n\nmodule.exports = ProviderManager;\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file qtsync.js\n * @authors:\n * Marek Kotewicz \n * Marian Oancea \n * @date 2014\n */\n\nvar QtSyncProvider = function () {\n};\n\nQtSyncProvider.prototype.send = function (payload) {\n return navigator.qt.callMethod(JSON.stringify(payload));\n};\n\nmodule.exports = QtSyncProvider;\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file types.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\nvar f = require('./formatters');\n\n/// @param expected type prefix (string)\n/// @returns function which checks if type has matching prefix. if yes, returns true, otherwise false\nvar prefixedType = function (prefix) {\n return function (type) {\n return type.indexOf(prefix) === 0;\n };\n};\n\n/// @param expected type name (string)\n/// @returns function which checks if type is matching expected one. if yes, returns true, otherwise false\nvar namedType = function (name) {\n return function (type) {\n return name === type;\n };\n};\n\n/// Setups input formatters for solidity types\n/// @returns an array of input formatters \nvar inputTypes = function () {\n \n return [\n { type: prefixedType('uint'), format: f.formatInputInt },\n { type: prefixedType('int'), format: f.formatInputInt },\n { type: prefixedType('hash'), format: f.formatInputInt },\n { type: prefixedType('string'), format: f.formatInputString }, \n { type: prefixedType('real'), format: f.formatInputReal },\n { type: prefixedType('ureal'), format: f.formatInputReal },\n { type: namedType('address'), format: f.formatInputInt },\n { type: namedType('bool'), format: f.formatInputBool }\n ];\n};\n\n/// Setups output formaters for solidity types\n/// @returns an array of output formatters\nvar outputTypes = function () {\n\n return [\n { type: prefixedType('uint'), format: f.formatOutputUInt },\n { type: prefixedType('int'), format: f.formatOutputInt },\n { type: prefixedType('hash'), format: f.formatOutputHash },\n { type: prefixedType('string'), format: f.formatOutputString },\n { type: prefixedType('real'), format: f.formatOutputReal },\n { type: prefixedType('ureal'), format: f.formatOutputUReal },\n { type: namedType('address'), format: f.formatOutputAddress },\n { type: namedType('bool'), format: f.formatOutputBool }\n ];\n};\n\nmodule.exports = {\n prefixedType: prefixedType,\n namedType: namedType,\n inputTypes: inputTypes,\n outputTypes: outputTypes\n};\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file utils.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\n/// Finds first index of array element matching pattern\n/// @param array\n/// @param callback pattern\n/// @returns index of element\nvar findIndex = function (array, callback) {\n var end = false;\n var i = 0;\n for (; i < array.length && !end; i++) {\n end = callback(array[i]);\n }\n return end ? i - 1 : -1;\n};\n\n/// @returns ascii string representation of hex value prefixed with 0x\nvar toAscii = function(hex) {\n// Find termination\n var str = \"\";\n var i = 0, l = hex.length;\n if (hex.substring(0, 2) === '0x') {\n i = 2;\n }\n for (; i < l; i+=2) {\n var code = parseInt(hex.substr(i, 2), 16);\n if (code === 0) {\n break;\n }\n\n str += String.fromCharCode(code);\n }\n\n return str;\n};\n \nvar toHex = function(str) {\n var hex = \"\";\n for(var i = 0; i < str.length; i++) {\n var n = str.charCodeAt(i).toString(16);\n hex += n.length < 2 ? '0' + n : n;\n }\n\n return hex;\n};\n\n/// @returns hex representation (prefixed by 0x) of ascii string\nvar fromAscii = function(str, pad) {\n pad = pad === undefined ? 0 : pad;\n var hex = toHex(str);\n while (hex.length < pad*2)\n hex += \"00\";\n return \"0x\" + hex;\n};\n\n/// @returns display name for function/event eg. multiply(uint256) -> multiply\nvar extractDisplayName = function (name) {\n var length = name.indexOf('('); \n return length !== -1 ? name.substr(0, length) : name;\n};\n\n/// @returns overloaded part of function/event name\nvar extractTypeName = function (name) {\n /// TODO: make it invulnerable\n var length = name.indexOf('(');\n return length !== -1 ? name.substr(length + 1, name.length - 1 - (length + 1)) : \"\";\n};\n\n/// Filters all function from input abi\n/// @returns abi array with filtered objects of type 'function'\nvar filterFunctions = function (json) {\n return json.filter(function (current) {\n return current.type === 'function'; \n }); \n};\n\n/// Filters all events form input abi\n/// @returns abi array with filtered objects of type 'event'\nvar filterEvents = function (json) {\n return json.filter(function (current) {\n return current.type === 'event';\n });\n};\n\nmodule.exports = {\n findIndex: findIndex,\n toAscii: toAscii,\n fromAscii: fromAscii,\n extractDisplayName: extractDisplayName,\n extractTypeName: extractTypeName,\n filterFunctions: filterFunctions,\n filterEvents: filterEvents\n};\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file web3.js\n * @authors:\n * Jeffrey Wilcke \n * Marek Kotewicz \n * Marian Oancea \n * Gav Wood \n * @date 2014\n */\n\nif (\"build\" !== 'build') {/*\n var BigNumber = require('bignumber.js');\n*/}\n\nvar utils = require('./utils');\n\nvar ETH_UNITS = [ \n 'wei', \n 'Kwei', \n 'Mwei', \n 'Gwei', \n 'szabo', \n 'finney', \n 'ether', \n 'grand', \n 'Mether', \n 'Gether', \n 'Tether', \n 'Pether', \n 'Eether', \n 'Zether', \n 'Yether', \n 'Nether', \n 'Dether', \n 'Vether', \n 'Uether' \n];\n\n/// @returns an array of objects describing web3 api methods\nvar web3Methods = function () {\n return [\n { name: 'sha3', call: 'web3_sha3' }\n ];\n};\n\n/// @returns an array of objects describing web3.eth api methods\nvar ethMethods = function () {\n var blockCall = function (args) {\n return typeof args[0] === \"string\" ? \"eth_blockByHash\" : \"eth_blockByNumber\";\n };\n\n var transactionCall = function (args) {\n return typeof args[0] === \"string\" ? 'eth_transactionByHash' : 'eth_transactionByNumber';\n };\n\n var uncleCall = function (args) {\n return typeof args[0] === \"string\" ? 'eth_uncleByHash' : 'eth_uncleByNumber';\n };\n\n var methods = [\n { name: 'balanceAt', call: 'eth_balanceAt' },\n { name: 'stateAt', call: 'eth_stateAt' },\n { name: 'storageAt', call: 'eth_storageAt' },\n { name: 'countAt', call: 'eth_countAt'},\n { name: 'codeAt', call: 'eth_codeAt' },\n { name: 'transact', call: 'eth_transact' },\n { name: 'call', call: 'eth_call' },\n { name: 'block', call: blockCall },\n { name: 'transaction', call: transactionCall },\n { name: 'uncle', call: uncleCall },\n { name: 'compilers', call: 'eth_compilers' },\n { name: 'flush', call: 'eth_flush' },\n { name: 'lll', call: 'eth_lll' },\n { name: 'solidity', call: 'eth_solidity' },\n { name: 'serpent', call: 'eth_serpent' },\n { name: 'logs', call: 'eth_logs' }\n ];\n return methods;\n};\n\n/// @returns an array of objects describing web3.eth api properties\nvar ethProperties = function () {\n return [\n { name: 'coinbase', getter: 'eth_coinbase', setter: 'eth_setCoinbase' },\n { name: 'listening', getter: 'eth_listening', setter: 'eth_setListening' },\n { name: 'mining', getter: 'eth_mining', setter: 'eth_setMining' },\n { name: 'gasPrice', getter: 'eth_gasPrice' },\n { name: 'accounts', getter: 'eth_accounts' },\n { name: 'peerCount', getter: 'eth_peerCount' },\n { name: 'defaultBlock', getter: 'eth_defaultBlock', setter: 'eth_setDefaultBlock' },\n { name: 'number', getter: 'eth_number'}\n ];\n};\n\n/// @returns an array of objects describing web3.db api methods\nvar dbMethods = function () {\n return [\n { name: 'put', call: 'db_put' },\n { name: 'get', call: 'db_get' },\n { name: 'putString', call: 'db_putString' },\n { name: 'getString', call: 'db_getString' }\n ];\n};\n\n/// @returns an array of objects describing web3.shh api methods\nvar shhMethods = function () {\n return [\n { name: 'post', call: 'shh_post' },\n { name: 'newIdentity', call: 'shh_newIdentity' },\n { name: 'haveIdentity', call: 'shh_haveIdentity' },\n { name: 'newGroup', call: 'shh_newGroup' },\n { name: 'addToGroup', call: 'shh_addToGroup' }\n ];\n};\n\n/// @returns an array of objects describing web3.eth.watch api methods\nvar ethWatchMethods = function () {\n var newFilter = function (args) {\n return typeof args[0] === 'string' ? 'eth_newFilterString' : 'eth_newFilter';\n };\n\n return [\n { name: 'newFilter', call: newFilter },\n { name: 'uninstallFilter', call: 'eth_uninstallFilter' },\n { name: 'getMessages', call: 'eth_filterLogs' }\n ];\n};\n\n/// @returns an array of objects describing web3.shh.watch api methods\nvar shhWatchMethods = function () {\n return [\n { name: 'newFilter', call: 'shh_newFilter' },\n { name: 'uninstallFilter', call: 'shh_uninstallFilter' },\n { name: 'getMessages', call: 'shh_getMessages' }\n ];\n};\n\n/// creates methods in a given object based on method description on input\n/// setups api calls for these methods\nvar setupMethods = function (obj, methods) {\n methods.forEach(function (method) {\n obj[method.name] = function () {\n var args = Array.prototype.slice.call(arguments);\n var call = typeof method.call === 'function' ? method.call(args) : method.call;\n return web3.provider.send({\n call: call,\n args: args\n });\n };\n });\n};\n\n/// creates properties in a given object based on properties description on input\n/// setups api calls for these properties\nvar setupProperties = function (obj, properties) {\n properties.forEach(function (property) {\n var proto = {};\n proto.get = function () {\n return web3.provider.send({\n call: property.getter\n });\n };\n\n if (property.setter) {\n proto.set = function (val) {\n return web3.provider.send({\n call: property.setter,\n args: [val]\n });\n };\n }\n Object.defineProperty(obj, property.name, proto);\n });\n};\n\n/// setups web3 object, and it's in-browser executed methods\nvar web3 = {\n _callbacks: {},\n _events: {},\n providers: {},\n\n /// @returns ascii string representation of hex value prefixed with 0x\n toAscii: utils.toAscii,\n\n /// @returns hex representation (prefixed by 0x) of ascii string\n fromAscii: utils.fromAscii,\n\n /// @returns decimal representaton of hex value prefixed by 0x\n toDecimal: function (val) {\n // remove 0x and place 0, if it's required\n val = val.length > 2 ? val.substring(2) : \"0\";\n return (new BigNumber(val, 16).toString(10));\n },\n\n /// @returns hex representation (prefixed by 0x) of decimal value\n fromDecimal: function (val) {\n return \"0x\" + (new BigNumber(val).toString(16));\n },\n\n /// used to transform value/string to eth string\n /// TODO: use BigNumber.js to parse int\n toEth: function(str) {\n var val = typeof str === \"string\" ? str.indexOf('0x') === 0 ? parseInt(str.substr(2), 16) : parseInt(str) : str;\n var unit = 0;\n var units = ETH_UNITS;\n while (val > 3000 && unit < units.length - 1)\n {\n val /= 1000;\n unit++;\n }\n var s = val.toString().length < val.toFixed(2).length ? val.toString() : val.toFixed(2);\n var replaceFunction = function($0, $1, $2) {\n return $1 + ',' + $2;\n };\n\n while (true) {\n var o = s;\n s = s.replace(/(\\d)(\\d\\d\\d[\\.\\,])/, replaceFunction);\n if (o === s)\n break;\n }\n return s + ' ' + units[unit];\n },\n\n /// eth object prototype\n eth: {\n contractFromAbi: function (abi) {\n return function(addr) {\n // Default to address of Config. TODO: rremove prior to genesis.\n addr = addr || '0xc6d9d2cd449a754c494264e1809c50e34d64562b';\n var ret = web3.eth.contract(addr, abi);\n ret.address = addr;\n return ret;\n };\n },\n\n /// @param filter may be a string, object or event\n /// @param indexed is optional, this is an object with optional event indexed params\n /// @param options is optional, this is an object with optional event options ('max'...)\n watch: function (filter, indexed, options) {\n if (filter._isEvent) {\n return filter(indexed, options);\n }\n return new web3.filter(filter, ethWatch);\n }\n },\n\n /// db object prototype\n db: {},\n\n /// shh object prototype\n shh: {\n \n /// @param filter may be a string, object or event\n watch: function (filter, indexed) {\n return new web3.filter(filter, shhWatch);\n }\n },\n\n /// @returns true if provider is installed\n haveProvider: function() {\n return !!web3.provider.provider;\n }\n};\n\n/// setups all api methods\nsetupMethods(web3, web3Methods());\nsetupMethods(web3.eth, ethMethods());\nsetupProperties(web3.eth, ethProperties());\nsetupMethods(web3.db, dbMethods());\nsetupMethods(web3.shh, shhMethods());\n\nvar ethWatch = {\n changed: 'eth_changed'\n};\n\nsetupMethods(ethWatch, ethWatchMethods());\n\nvar shhWatch = {\n changed: 'shh_changed'\n};\n\nsetupMethods(shhWatch, shhWatchMethods());\n\nweb3.setProvider = function(provider) {\n //provider.onmessage = messageHandler; // there will be no async calls, to remove\n web3.provider.set(provider);\n};\n\nmodule.exports = web3;\n\n", + "var web3 = require('./lib/web3');\nvar ProviderManager = require('./lib/providermanager');\nweb3.provider = new ProviderManager();\nweb3.filter = require('./lib/filter');\nweb3.providers.HttpSyncProvider = require('./lib/httpsync');\nweb3.providers.QtSyncProvider = require('./lib/qtsync');\nweb3.eth.contract = require('./lib/contract');\nweb3.abi = require('./lib/abi');\n\n\nmodule.exports = web3;\n" ] } \ No newline at end of file diff --git a/libjsqrc/ethereumjs/dist/ethereum.min.js b/libjsqrc/ethereumjs/dist/ethereum.min.js index fba5e4db5..cc9b205d1 100644 --- a/libjsqrc/ethereumjs/dist/ethereum.min.js +++ b/libjsqrc/ethereumjs/dist/ethereum.min.js @@ -1 +1 @@ -require=function e(t,n,r){function i(s,a){if(!n[s]){if(!t[s]){var u="function"==typeof require&&require;if(!a&&u)return u(s,!0);if(o)return o(s,!0);var f=new Error("Cannot find module '"+s+"'");throw f.code="MODULE_NOT_FOUND",f}var c=n[s]={exports:{}};t[s][0].call(c.exports,function(e){var n=t[s][1][e];return i(n?n:e)},c,c.exports,e,t,n,r)}return n[s].exports}for(var o="function"==typeof require&&require,s=0;sd;d++)h.push(o(n.slice(0,c))),n=n.slice(c);s.push(h)}else a("string")(u.outputs[t].type)?(p=p.slice(c),s.push(o(n.slice(0,c))),n=n.slice(c)):(s.push(o(n.slice(0,c))),n=n.slice(c))}),s}},E=function(e){var t={};return e.forEach(function(n){t[n.name]=function(){var t=Array.prototype.slice.call(arguments);return m(e,n.name,t)}}),t},F=function(e){var t={};return e.forEach(function(n){t[n.name]=function(t){return q(e,n.name,t)}}),t},M=function(e,t){var r=e[o(e,t)],i=t+"(",s=r.inputs.map(function(e){return e.type});return i+=s.join(","),i+=")",n.sha3(n.fromAscii(i))};t.exports={inputParser:E,outputParser:F,methodSignature:M}},{"./web3":8}],2:[function(e,t){var n=e("./web3"),r=function(e){if(!n.haveProvider()){if(this.sendQueue=[],this.onmessageQueue=[],navigator.qt)return void(this.provider=new n.providers.QtProvider);e=e||{};var t={httprpc:e.httprpc||"http://localhost:8080",websockets:e.websockets||"ws://localhost:40404/eth"},r=this,i=function(e){o.close(),e?r.provider=new n.providers.WebSocketProvider(t.websockets):(r.provider=new n.providers.HttpRpcProvider(t.httprpc),r.poll=r.provider.poll.bind(r.provider)),r.sendQueue.forEach(function(e){r.provider.send(e)}),r.onmessageQueue.forEach(function(e){r.provider.onmessage=e})},o=new WebSocket(t.websockets);o.onopen=function(){i(!0)},o.onerror=function(){i(!1)}}};r.prototype.send=function(e){return this.provider?void this.provider.send(e):void this.sendQueue.push(e)},Object.defineProperty(r.prototype,"onmessage",{set:function(e){return this.provider?void(this.provider.onmessage=e):void this.onmessageQueue.push(e)}}),t.exports=r},{"./web3":8}],3:[function(e,t){var n=e("./web3"),r=e("./abi"),i=4,o=function(e,t){var o=r.inputParser(t),s=r.outputParser(t),a={};return t.forEach(function(u){a[u.name]=function(){var a=Array.prototype.slice.call(arguments),f=o[u.name].apply(null,a),c=function(e){return s[u.name](e)};return{call:function(o){return o=o||{},o.to=e,r.methodSignature(t,u.name).then(function(e){return o.data=e.slice(0,2+2*i)+f,n.eth.call(o).then(c)})},transact:function(o){return o=o||{},o.to=e,r.methodSignature(t,u.name).then(function(e){return o.data=e.slice(0,2+2*i)+f,n.eth.transact(o).then(c)})}}}}),a};t.exports=o},{"./abi":1,"./web3":8}],4:[function(e,t){var n=e("./web3"),r=function(e,t){this.impl=t,this.callbacks=[];var r=this;this.promise=t.newFilter(e),this.promise.then(function(e){r.id=e,n.on(t.changed,e,r.trigger.bind(r)),n.provider.startPolling({call:t.changed,args:[e]},e)})};r.prototype.arrived=function(e){this.changed(e)},r.prototype.changed=function(e){var t=this;this.promise.then(function(){t.callbacks.push(e)})},r.prototype.trigger=function(e){for(var t=0;tn;n+=2){var i=parseInt(e.substr(n,2),16);if(0===i)break;t+=String.fromCharCode(i)}return t},fromAscii:function(e,t){t=void 0===t?0:t;for(var n=this.toHex(e);n.length<2*t;)n+="00";return"0x"+n},toDecimal:function(e){return h(e.substring(2))},fromDecimal:function(e){return"0x"+d(e)},toEth:function(e){for(var t="string"==typeof e?0===e.indexOf("0x")?parseInt(e.substr(2),16):parseInt(e):e,n=0,r=["wei","Kwei","Mwei","Gwei","szabo","finney","ether","grand","Mether","Gether","Tether","Pether","Eether","Zether","Yether","Nether","Dether","Vether","Uether"];t>3e3&&nv;v++)g.push(h(e.slice(0,r))),e=e.slice(r);n.push(g)}else i.prefixedType("string")(t[s].type)?(c=c.slice(r),n.push(h(e.slice(0,r))),e=e.slice(r)):(n.push(h(e.slice(0,r))),e=e.slice(r))}),n},d=function(t){var e={};return t.forEach(function(t){var n=r.extractDisplayName(t.name),i=r.extractTypeName(t.name),o=function(){var e=Array.prototype.slice.call(arguments);return l(t.inputs,e)};void 0===e[n]&&(e[n]=o),e[n][i]=o}),e},g=function(t){var e={};return t.forEach(function(t){var n=r.extractDisplayName(t.name),i=r.extractTypeName(t.name),o=function(e){return h(t.outputs,e)};void 0===e[n]&&(e[n]=o),e[n][i]=o}),e},v=function(t){return n.sha3(n.fromAscii(t)).slice(0,2+2*o.ETH_SIGNATURE_LENGTH)},y=function(t){return n.sha3(n.fromAscii(t))};e.exports={inputParser:d,outputParser:g,formatInput:l,formatOutput:h,signatureFromAscii:v,eventSignatureFromAscii:y}},{"./const":2,"./formatters":6,"./types":10,"./utils":11,"./web3":12}],2:[function(t,e){e.exports={ETH_PADDING:32,ETH_SIGNATURE_LENGTH:4,ETH_BIGNUMBER_ROUNDING_MODE:{ROUNDING_MODE:BigNumber.ROUND_DOWN}}},{}],3:[function(t,e){var n=t("./web3"),r=t("./abi"),i=t("./utils"),o=t("./event"),a=function(t){n._currentContractAbi=t.abi,n._currentContractAddress=t.address,n._currentContractMethodName=t.method,n._currentContractMethodParams=t.params},u=function(t){t.call=function(e){return t._isTransact=!1,t._options=e,t},t.transact=function(e){return t._isTransact=!0,t._options=e,t},t._options={},["gas","gasPrice","value","from"].forEach(function(e){t[e]=function(n){return t._options[e]=n,t}})},f=function(t,e,o){var u=r.inputParser(e),f=r.outputParser(e);i.filterFunctions(e).forEach(function(s){var c=i.extractDisplayName(s.name),l=i.extractTypeName(s.name),p=function(){var i=Array.prototype.slice.call(arguments),p=r.signatureFromAscii(s.name),m=u[c][l].apply(null,i),h=t._options||{};h.to=o,h.data=p+m;var d=t._isTransact===!0||t._isTransact!==!1&&!s.constant,g=h.collapse!==!1;if(t._options={},t._isTransact=null,d)return a({abi:e,address:o,method:s.name,params:i}),void n.eth.transact(h);var v=n.eth.call(h),y=f[c][l](v);return g&&(1===y.length?y=y[0]:0===y.length&&(y=null)),y};void 0===t[c]&&(t[c]=p),t[c][l]=p})},s=function(t,e,n){t.address=n,Object.defineProperty(t,"topic",{get:function(){return i.filterEvents(e).map(function(t){return r.eventSignatureFromAscii(t.name)})}})},c=function(t,e,a){i.filterEvents(e).forEach(function(e){var u=function(){var t=Array.prototype.slice.call(arguments),i=r.eventSignatureFromAscii(e.name),u=o(a,i,e),f=u.apply(null,t);return n.eth.watch(f)};u._isEvent=!0;var f=i.extractDisplayName(e.name),s=i.extractTypeName(e.name);void 0===t[f]&&(t[f]=u),t[f][s]=u})},l=function(t,e){e.forEach(function(t){if(-1===t.name.indexOf("(")){var e=t.name,n=t.inputs.map(function(t){return t.type}).join();t.name=e+"("+n+")"}});var n={};return u(n),f(n,e,t),s(n,e,t),c(n,e,t),n};e.exports=l},{"./abi":1,"./event":4,"./utils":11,"./web3":12}],4:[function(t,e){var n=t("./abi"),r=t("./utils"),i=function(t,e){var n=r.findIndex(t,function(t){return t.name===e});return-1===n?void console.error("indexed param with name "+e+" not found"):t[n]},o=function(t,e){return Object.keys(e).map(function(r){var o=[i(t.inputs,r)],a=e[r];return a instanceof Array?a.map(function(t){return n.formatInput(o,[t])}):n.formatInput(o,[a])})},a=function(t,e,n){return function(r,i){var a=i||{};return a.address=t,a.topic=[],a.topic.push(e),r&&(a.topic=a.topic.concat(o(n,r))),a}};e.exports=a},{"./abi":1,"./utils":11}],5:[function(t,e){var n=t("./web3"),r=function(t,e){"string"!=typeof t&&(t.topics&&console.warn('"topics" is deprecated, use "topic" instead'),t={to:t.to,topic:t.topic,earliest:t.earliest,latest:t.latest,max:t.max,skip:t.skip,address:t.address}),this.impl=e,this.callbacks=[],this.id=e.newFilter(t),n.provider.startPolling({call:e.changed,args:[this.id]},this.id,this.trigger.bind(this))};r.prototype.arrived=function(t){this.changed(t)},r.prototype.changed=function(t){this.callbacks.push(t)},r.prototype.trigger=function(t){for(var e=0;en;n+=2){var i=parseInt(t.substr(n,2),16);if(0===i)break;e+=String.fromCharCode(i)}return e},i=function(t){for(var e="",n=0;n2?t.substring(2):"0",new BigNumber(t,16).toString(10)},fromDecimal:function(t){return"0x"+new BigNumber(t).toString(16)},toEth:function(t){for(var e="string"==typeof t?0===t.indexOf("0x")?parseInt(t.substr(2),16):parseInt(t):t,n=0,i=r;e>3e3&&n - diff --git a/libjsqrc/ethereumjs/example/contract_with_array.html b/libjsqrc/ethereumjs/example/contract_with_array.html new file mode 100644 index 000000000..a3dfc8a24 --- /dev/null +++ b/libjsqrc/ethereumjs/example/contract_with_array.html @@ -0,0 +1,76 @@ + + + + + + + + + +

contract

+
+
+ +
+ +
+ + + diff --git a/libjsqrc/ethereumjs/example/event.html b/libjsqrc/ethereumjs/example/event.html new file mode 100644 index 000000000..84d302437 --- /dev/null +++ b/libjsqrc/ethereumjs/example/event.html @@ -0,0 +1,120 @@ + + + + + + + + + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + diff --git a/libjsqrc/ethereumjs/example/natspec_contract.html b/libjsqrc/ethereumjs/example/natspec_contract.html index 8d12b4a81..212e582dc 100644 --- a/libjsqrc/ethereumjs/example/natspec_contract.html +++ b/libjsqrc/ethereumjs/example/natspec_contract.html @@ -2,17 +2,17 @@ - + diff --git a/libjsqrc/ethereumjs/example/node-app.js b/libjsqrc/ethereumjs/example/node-app.js index f63fa9115..8c2fc0ba3 100644 --- a/libjsqrc/ethereumjs/example/node-app.js +++ b/libjsqrc/ethereumjs/example/node-app.js @@ -1,16 +1,12 @@ #!/usr/bin/env node -require('es6-promise').polyfill(); - var web3 = require("../index.js"); -web3.setProvider(new web3.providers.HttpRpcProvider('http://localhost:8080')); +web3.setProvider(new web3.providers.HttpSyncProvider('http://localhost:8080')); + +var coinbase = web3.eth.coinbase; +console.log(coinbase); + +var balance = web3.eth.balanceAt(coinbase); +console.log(balance); -web3.eth.coinbase.then(function(result){ - console.log(result); - return web3.eth.balanceAt(result); -}).then(function(balance){ - console.log(web3.toDecimal(balance)); -}).catch(function(err){ - console.log(err); -}); \ No newline at end of file diff --git a/libjsqrc/ethereumjs/index.js b/libjsqrc/ethereumjs/index.js index e0382a8a4..76c923722 100644 --- a/libjsqrc/ethereumjs/index.js +++ b/libjsqrc/ethereumjs/index.js @@ -2,10 +2,10 @@ var web3 = require('./lib/web3'); var ProviderManager = require('./lib/providermanager'); web3.provider = new ProviderManager(); web3.filter = require('./lib/filter'); -web3.providers.WebSocketProvider = require('./lib/websocket'); -web3.providers.HttpRpcProvider = require('./lib/httprpc'); -web3.providers.QtProvider = require('./lib/qt'); -web3.providers.AutoProvider = require('./lib/autoprovider'); +web3.providers.HttpSyncProvider = require('./lib/httpsync'); +web3.providers.QtSyncProvider = require('./lib/qtsync'); web3.eth.contract = require('./lib/contract'); +web3.abi = require('./lib/abi'); + module.exports = web3; diff --git a/libjsqrc/ethereumjs/lib/abi.js b/libjsqrc/ethereumjs/lib/abi.js index 72001eaa0..1a92bf5e6 100644 --- a/libjsqrc/ethereumjs/lib/abi.js +++ b/libjsqrc/ethereumjs/lib/abi.js @@ -21,167 +21,57 @@ * @date 2014 */ -// TODO: is these line is supposed to be here? -if (process.env.NODE_ENV !== 'build') { - var BigNumber = require('bignumber.js'); // jshint ignore:line -} - -var web3 = require('./web3'); // jshint ignore:line - -BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_DOWN }); - -var ETH_PADDING = 32; - -/// Finds first index of array element matching pattern -/// @param array -/// @param callback pattern -/// @returns index of element -var findIndex = function (array, callback) { - var end = false; - var i = 0; - for (; i < array.length && !end; i++) { - end = callback(array[i]); - } - return end ? i - 1 : -1; -}; - -/// @returns a function that is used as a pattern for 'findIndex' -var findMethodIndex = function (json, methodName) { - return findIndex(json, function (method) { - return method.name === methodName; - }); -}; - -/// @param string string to be padded -/// @param number of characters that result string should have -/// @param sign, by default 0 -/// @returns right aligned string -var padLeft = function (string, chars, sign) { - return new Array(chars - string.length + 1).join(sign ? sign : "0") + string; -}; - -/// @param expected type prefix (string) -/// @returns function which checks if type has matching prefix. if yes, returns true, otherwise false -var prefixedType = function (prefix) { - return function (type) { - return type.indexOf(prefix) === 0; - }; -}; +var web3 = require('./web3'); +var utils = require('./utils'); +var types = require('./types'); +var c = require('./const'); +var f = require('./formatters'); -/// @param expected type name (string) -/// @returns function which checks if type is matching expected one. if yes, returns true, otherwise false -var namedType = function (name) { - return function (type) { - return name === type; - }; +var displayTypeError = function (type) { + console.error('parser does not support type: ' + type); }; +/// This method should be called if we want to check if givent type is an array type +/// @returns true if it is, otherwise false var arrayType = function (type) { return type.slice(-2) === '[]'; }; -/// Formats input value to byte representation of int -/// If value is negative, return it's two's complement -/// If the value is floating point, round it down -/// @returns right-aligned byte representation of int -var formatInputInt = function (value) { - var padding = ETH_PADDING * 2; - if (value instanceof BigNumber || typeof value === 'number') { - if (typeof value === 'number') - value = new BigNumber(value); - value = value.round(); - - if (value.lessThan(0)) - value = new BigNumber("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16).plus(value).plus(1); - value = value.toString(16); - } - else if (value.indexOf('0x') === 0) - value = value.substr(2); - else if (typeof value === 'string') - value = formatInputInt(new BigNumber(value)); - else - value = (+value).toString(16); - return padLeft(value, padding); -}; - -/// Formats input value to byte representation of string -/// @returns left-algined byte representation of string -var formatInputString = function (value) { - return web3.fromAscii(value, ETH_PADDING).substr(2); -}; - -/// Formats input value to byte representation of bool -/// @returns right-aligned byte representation bool -var formatInputBool = function (value) { - return '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0'); -}; - -/// Formats input value to byte representation of real -/// Values are multiplied by 2^m and encoded as integers -/// @returns byte representation of real -var formatInputReal = function (value) { - return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))); -}; - var dynamicTypeBytes = function (type, value) { // TODO: decide what to do with array of strings - if (arrayType(type) || prefixedType('string')(type)) - return formatInputInt(value.length); + if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length. + return f.formatInputInt(value.length); return ""; }; -/// Setups input formatters for solidity types -/// @returns an array of input formatters -var setupInputTypes = function () { - - return [ - { type: prefixedType('uint'), format: formatInputInt }, - { type: prefixedType('int'), format: formatInputInt }, - { type: prefixedType('hash'), format: formatInputInt }, - { type: prefixedType('string'), format: formatInputString }, - { type: prefixedType('real'), format: formatInputReal }, - { type: prefixedType('ureal'), format: formatInputReal }, - { type: namedType('address'), format: formatInputInt }, - { type: namedType('bool'), format: formatInputBool } - ]; -}; - -var inputTypes = setupInputTypes(); +var inputTypes = types.inputTypes(); /// Formats input params to bytes -/// @param contract json abi -/// @param name of the method that we want to use +/// @param abi contract method inputs /// @param array of params that will be formatted to bytes /// @returns bytes representation of input params -var toAbiInput = function (json, methodName, params) { +var formatInput = function (inputs, params) { var bytes = ""; - var index = findMethodIndex(json, methodName); - - if (index === -1) { - return; - } - - var method = json[index]; - var padding = ETH_PADDING * 2; + var padding = c.ETH_PADDING * 2; /// first we iterate in search for dynamic - method.inputs.forEach(function (input, index) { + inputs.forEach(function (input, index) { bytes += dynamicTypeBytes(input.type, params[index]); }); - method.inputs.forEach(function (input, i) { + inputs.forEach(function (input, i) { var typeMatch = false; for (var j = 0; j < inputTypes.length && !typeMatch; j++) { - typeMatch = inputTypes[j].type(method.inputs[i].type, params[i]); + typeMatch = inputTypes[j].type(inputs[i].type, params[i]); } if (!typeMatch) { - console.error('input parser does not support type: ' + method.inputs[i].type); + displayTypeError(inputs[i].type); } var formatter = inputTypes[j - 1].format; var toAppend = ""; - if (arrayType(method.inputs[i].type)) + if (arrayType(inputs[i].type)) toAppend = params[i].reduce(function (acc, curr) { return acc + formatter(curr); }, ""); @@ -193,122 +83,44 @@ var toAbiInput = function (json, methodName, params) { return bytes; }; -/// Check if input value is negative -/// @param value is hex format -/// @returns true if it is negative, otherwise false -var signedIsNegative = function (value) { - return (new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1)) === '1'; -}; - -/// Formats input right-aligned input bytes to int -/// @returns right-aligned input bytes formatted to int -var formatOutputInt = function (value) { - // check if it's negative number - // it it is, return two's complement - if (signedIsNegative(value)) { - return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1); - } - return new BigNumber(value, 16); -}; - -/// Formats big right-aligned input bytes to uint -/// @returns right-aligned input bytes formatted to uint -var formatOutputUInt = function (value) { - return new BigNumber(value, 16); -}; - -/// @returns input bytes formatted to real -var formatOutputReal = function (value) { - return formatOutputInt(value).dividedBy(new BigNumber(2).pow(128)); -}; - -/// @returns input bytes formatted to ureal -var formatOutputUReal = function (value) { - return formatOutputUInt(value).dividedBy(new BigNumber(2).pow(128)); -}; - -/// @returns right-aligned input bytes formatted to hex -var formatOutputHash = function (value) { - return "0x" + value; -}; - -/// @returns right-aligned input bytes formatted to bool -var formatOutputBool = function (value) { - return value === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false; -}; - -/// @returns left-aligned input bytes formatted to ascii string -var formatOutputString = function (value) { - return web3.toAscii(value); -}; - -/// @returns right-aligned input bytes formatted to address -var formatOutputAddress = function (value) { - return "0x" + value.slice(value.length - 40, value.length); -}; - var dynamicBytesLength = function (type) { - if (arrayType(type) || prefixedType('string')(type)) - return ETH_PADDING * 2; + if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length. + return c.ETH_PADDING * 2; return 0; }; -/// Setups output formaters for solidity types -/// @returns an array of output formatters -var setupOutputTypes = function () { - - return [ - { type: prefixedType('uint'), format: formatOutputUInt }, - { type: prefixedType('int'), format: formatOutputInt }, - { type: prefixedType('hash'), format: formatOutputHash }, - { type: prefixedType('string'), format: formatOutputString }, - { type: prefixedType('real'), format: formatOutputReal }, - { type: prefixedType('ureal'), format: formatOutputUReal }, - { type: namedType('address'), format: formatOutputAddress }, - { type: namedType('bool'), format: formatOutputBool } - ]; -}; - -var outputTypes = setupOutputTypes(); +var outputTypes = types.outputTypes(); /// Formats output bytes back to param list -/// @param contract json abi -/// @param name of the method that we want to use +/// @param contract abi method outputs /// @param bytes representtion of output /// @returns array of output params -var fromAbiOutput = function (json, methodName, output) { - var index = findMethodIndex(json, methodName); - - if (index === -1) { - return; - } - +var formatOutput = function (outs, output) { + output = output.slice(2); - var result = []; - var method = json[index]; - var padding = ETH_PADDING * 2; + var padding = c.ETH_PADDING * 2; - var dynamicPartLength = method.outputs.reduce(function (acc, curr) { + var dynamicPartLength = outs.reduce(function (acc, curr) { return acc + dynamicBytesLength(curr.type); }, 0); var dynamicPart = output.slice(0, dynamicPartLength); output = output.slice(dynamicPartLength); - method.outputs.forEach(function (out, i) { + outs.forEach(function (out, i) { var typeMatch = false; for (var j = 0; j < outputTypes.length && !typeMatch; j++) { - typeMatch = outputTypes[j].type(method.outputs[i].type); + typeMatch = outputTypes[j].type(outs[i].type); } if (!typeMatch) { - console.error('output parser does not support type: ' + method.outputs[i].type); + displayTypeError(outs[i].type); } var formatter = outputTypes[j - 1].format; - if (arrayType(method.outputs[i].type)) { - var size = formatOutputUInt(dynamicPart.slice(0, padding)); + if (arrayType(outs[i].type)) { + var size = f.formatOutputUInt(dynamicPart.slice(0, padding)); dynamicPart = dynamicPart.slice(padding); var array = []; for (var k = 0; k < size; k++) { @@ -317,7 +129,7 @@ var fromAbiOutput = function (json, methodName, output) { } result.push(array); } - else if (prefixedType('string')(method.outputs[i].type)) { + else if (types.prefixedType('string')(outs[i].type)) { dynamicPart = dynamicPart.slice(padding); result.push(formatter(output.slice(0, padding))); output = output.slice(padding); @@ -332,13 +144,23 @@ var fromAbiOutput = function (json, methodName, output) { /// @param json abi for contract /// @returns input parser object for given json abi +/// TODO: refactor creating the parser, do not double logic from contract var inputParser = function (json) { var parser = {}; json.forEach(function (method) { - parser[method.name] = function () { + var displayName = utils.extractDisplayName(method.name); + var typeName = utils.extractTypeName(method.name); + + var impl = function () { var params = Array.prototype.slice.call(arguments); - return toAbiInput(json, method.name, params); + return formatInput(method.inputs, params); }; + + if (parser[displayName] === undefined) { + parser[displayName] = impl; + } + + parser[displayName][typeName] = impl; }); return parser; @@ -349,32 +171,40 @@ var inputParser = function (json) { var outputParser = function (json) { var parser = {}; json.forEach(function (method) { - parser[method.name] = function (output) { - return fromAbiOutput(json, method.name, output); + + var displayName = utils.extractDisplayName(method.name); + var typeName = utils.extractTypeName(method.name); + + var impl = function (output) { + return formatOutput(method.outputs, output); }; + + if (parser[displayName] === undefined) { + parser[displayName] = impl; + } + + parser[displayName][typeName] = impl; }); return parser; }; -/// @param json abi for contract -/// @param method name for which we want to get method signature -/// @returns (promise) contract method signature for method with given name -var methodSignature = function (json, name) { - var method = json[findMethodIndex(json, name)]; - var result = name + '('; - var inputTypes = method.inputs.map(function (inp) { - return inp.type; - }); - result += inputTypes.join(','); - result += ')'; +/// @param function/event name for which we want to get signature +/// @returns signature of function/event with given name +var signatureFromAscii = function (name) { + return web3.sha3(web3.fromAscii(name)).slice(0, 2 + c.ETH_SIGNATURE_LENGTH * 2); +}; - return web3.sha3(web3.fromAscii(result)); +var eventSignatureFromAscii = function (name) { + return web3.sha3(web3.fromAscii(name)); }; module.exports = { inputParser: inputParser, outputParser: outputParser, - methodSignature: methodSignature + formatInput: formatInput, + formatOutput: formatOutput, + signatureFromAscii: signatureFromAscii, + eventSignatureFromAscii: eventSignatureFromAscii }; diff --git a/libjsqrc/ethereumjs/lib/autoprovider.js b/libjsqrc/ethereumjs/lib/autoprovider.js deleted file mode 100644 index 0501b93d8..000000000 --- a/libjsqrc/ethereumjs/lib/autoprovider.js +++ /dev/null @@ -1,114 +0,0 @@ -/* - This file is part of ethereum.js. - - ethereum.js is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ethereum.js 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with ethereum.js. If not, see . -*/ -/** @file autoprovider.js - * @authors: - * Marek Kotewicz - * Marian Oancea - * @date 2014 - */ - -/* - * @brief if qt object is available, uses QtProvider, - * if not tries to connect over websockets - * if it fails, it uses HttpRpcProvider - */ - -var web3 = require('./web3'); // jshint ignore:line -if (process.env.NODE_ENV !== 'build') { - var WebSocket = require('ws'); // jshint ignore:line -} - -/** - * AutoProvider object prototype is implementing 'provider protocol' - * Automatically tries to setup correct provider(Qt, WebSockets or HttpRpc) - * First it checkes if we are ethereum browser (if navigator.qt object is available) - * if yes, we are using QtProvider - * if no, we check if it is possible to establish websockets connection with ethereum (ws://localhost:40404/eth is default) - * if it's not possible, we are using httprpc provider (http://localhost:8080) - * The constructor allows you to specify uris on which we are trying to connect over http or websockets - * You can do that by passing objects with fields httrpc and websockets - */ -var AutoProvider = function (userOptions) { - if (web3.haveProvider()) { - return; - } - - // before we determine what provider we are, we have to cache request - this.sendQueue = []; - this.onmessageQueue = []; - - if (navigator.qt) { - this.provider = new web3.providers.QtProvider(); - return; - } - - userOptions = userOptions || {}; - var options = { - httprpc: userOptions.httprpc || 'http://localhost:8080', - websockets: userOptions.websockets || 'ws://localhost:40404/eth' - }; - - var self = this; - var closeWithSuccess = function (success) { - ws.close(); - if (success) { - self.provider = new web3.providers.WebSocketProvider(options.websockets); - } else { - self.provider = new web3.providers.HttpRpcProvider(options.httprpc); - self.poll = self.provider.poll.bind(self.provider); - } - self.sendQueue.forEach(function (payload) { - self.provider.send(payload); - }); - self.onmessageQueue.forEach(function (handler) { - self.provider.onmessage = handler; - }); - }; - - var ws = new WebSocket(options.websockets); - - ws.onopen = function() { - closeWithSuccess(true); - }; - - ws.onerror = function() { - closeWithSuccess(false); - }; -}; - -/// Sends message forward to the provider, that is being used -/// if provider is not yet set, enqueues the message -AutoProvider.prototype.send = function (payload) { - if (this.provider) { - this.provider.send(payload); - return; - } - this.sendQueue.push(payload); -}; - -/// On incoming message sends the message to the provider that is currently being used -Object.defineProperty(AutoProvider.prototype, 'onmessage', { - set: function (handler) { - if (this.provider) { - this.provider.onmessage = handler; - return; - } - this.onmessageQueue.push(handler); - } -}); - -module.exports = AutoProvider; diff --git a/libjsqrc/ethereumjs/lib/const.js b/libjsqrc/ethereumjs/lib/const.js new file mode 100644 index 000000000..22f6dc690 --- /dev/null +++ b/libjsqrc/ethereumjs/lib/const.js @@ -0,0 +1,33 @@ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file const.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + +/// required to define ETH_BIGNUMBER_ROUNDING_MODE +if (process.env.NODE_ENV !== 'build') { + var BigNumber = require('bignumber.js'); // jshint ignore:line +} + +module.exports = { + ETH_PADDING: 32, + ETH_SIGNATURE_LENGTH: 4, + ETH_BIGNUMBER_ROUNDING_MODE: { ROUNDING_MODE: BigNumber.ROUND_DOWN } +}; + diff --git a/libjsqrc/ethereumjs/lib/contract.js b/libjsqrc/ethereumjs/lib/contract.js index 744fc88a4..748844fec 100644 --- a/libjsqrc/ethereumjs/lib/contract.js +++ b/libjsqrc/ethereumjs/lib/contract.js @@ -20,11 +20,144 @@ * @date 2014 */ -var web3 = require('./web3'); // jshint ignore:line +var web3 = require('./web3'); var abi = require('./abi'); +var utils = require('./utils'); +var eventImpl = require('./event'); + +var exportNatspecGlobals = function (vars) { + // it's used byt natspec.js + // TODO: figure out better way to solve this + web3._currentContractAbi = vars.abi; + web3._currentContractAddress = vars.address; + web3._currentContractMethodName = vars.method; + web3._currentContractMethodParams = vars.params; +}; + +var addFunctionRelatedPropertiesToContract = function (contract) { + + contract.call = function (options) { + contract._isTransact = false; + contract._options = options; + return contract; + }; + + contract.transact = function (options) { + contract._isTransact = true; + contract._options = options; + return contract; + }; + + contract._options = {}; + ['gas', 'gasPrice', 'value', 'from'].forEach(function(p) { + contract[p] = function (v) { + contract._options[p] = v; + return contract; + }; + }); + +}; + +var addFunctionsToContract = function (contract, desc, address) { + var inputParser = abi.inputParser(desc); + var outputParser = abi.outputParser(desc); + + // create contract functions + utils.filterFunctions(desc).forEach(function (method) { + + var displayName = utils.extractDisplayName(method.name); + var typeName = utils.extractTypeName(method.name); + + var impl = function () { + var params = Array.prototype.slice.call(arguments); + var signature = abi.signatureFromAscii(method.name); + var parsed = inputParser[displayName][typeName].apply(null, params); + + var options = contract._options || {}; + options.to = address; + options.data = signature + parsed; + + var isTransact = contract._isTransact === true || (contract._isTransact !== false && !method.constant); + var collapse = options.collapse !== false; + + // reset + contract._options = {}; + contract._isTransact = null; + + if (isTransact) { + + exportNatspecGlobals({ + abi: desc, + address: address, + method: method.name, + params: params + }); + + // transactions do not have any output, cause we do not know, when they will be processed + web3.eth.transact(options); + return; + } + + var output = web3.eth.call(options); + var ret = outputParser[displayName][typeName](output); + if (collapse) + { + if (ret.length === 1) + ret = ret[0]; + else if (ret.length === 0) + ret = null; + } + return ret; + }; + + if (contract[displayName] === undefined) { + contract[displayName] = impl; + } + + contract[displayName][typeName] = impl; + }); +}; + +var addEventRelatedPropertiesToContract = function (contract, desc, address) { + contract.address = address; + + Object.defineProperty(contract, 'topic', { + get: function() { + return utils.filterEvents(desc).map(function (e) { + return abi.eventSignatureFromAscii(e.name); + }); + } + }); + +}; + +var addEventsToContract = function (contract, desc, address) { + // create contract events + utils.filterEvents(desc).forEach(function (e) { + + var impl = function () { + var params = Array.prototype.slice.call(arguments); + var signature = abi.eventSignatureFromAscii(e.name); + var event = eventImpl(address, signature, e); + var o = event.apply(null, params); + return web3.eth.watch(o); + }; + + // this property should be used by eth.filter to check if object is an event + impl._isEvent = true; + + var displayName = utils.extractDisplayName(e.name); + var typeName = utils.extractTypeName(e.name); + + if (contract[displayName] === undefined) { + contract[displayName] = impl; + } + + contract[displayName][typeName] = impl; + + }); +}; -/// method signature length in bytes -var ETH_METHOD_SIGNATURE_LENGTH = 4; /** * This method should be called when we want to call / transact some solidity method from javascript @@ -39,50 +172,36 @@ var ETH_METHOD_SIGNATURE_LENGTH = 4; * * var myContract = web3.eth.contract('0x0123123121', abi); // creation of contract object * - * myContract.myMethod('this is test string param for call').call(); // myMethod call - * myContract.myMethod('this is test string param for transact').transact() // myMethod transact + * myContract.myMethod('this is test string param for call'); // myMethod call (implicit, default) + * myContract.call().myMethod('this is test string param for call'); // myMethod call (explicit) + * myContract.transact().myMethod('this is test string param for transact'); // myMethod transact * * @param address - address of the contract, which should be called * @param desc - abi json description of the contract, which is being created * @returns contract object */ -var contract = function (address, desc) { - var inputParser = abi.inputParser(desc); - var outputParser = abi.outputParser(desc); - var contract = {}; +var contract = function (address, desc) { + // workaround for invalid assumption that method.name is the full anonymous prototype of the method. + // it's not. it's just the name. the rest of the code assumes it's actually the anonymous + // prototype, so we make it so as a workaround. + // TODO: we may not want to modify input params, maybe use copy instead? desc.forEach(function (method) { - contract[method.name] = function () { - var params = Array.prototype.slice.call(arguments); - var parsed = inputParser[method.name].apply(null, params); - - var onSuccess = function (result) { - return outputParser[method.name](result); - }; - - return { - call: function (extra) { - extra = extra || {}; - extra.to = address; - return abi.methodSignature(desc, method.name).then(function (signature) { - extra.data = signature.slice(0, 2 + ETH_METHOD_SIGNATURE_LENGTH * 2) + parsed; - return web3.eth.call(extra).then(onSuccess); - }); - }, - transact: function (extra) { - extra = extra || {}; - extra.to = address; - return abi.methodSignature(desc, method.name).then(function (signature) { - extra.data = signature.slice(0, 2 + ETH_METHOD_SIGNATURE_LENGTH * 2) + parsed; - return web3.eth.transact(extra).then(onSuccess); - }); - } - }; - }; + if (method.name.indexOf('(') === -1) { + var displayName = method.name; + var typeName = method.inputs.map(function(i){return i.type; }).join(); + method.name = displayName + '(' + typeName + ')'; + } }); - return contract; + var result = {}; + addFunctionRelatedPropertiesToContract(result); + addFunctionsToContract(result, desc, address); + addEventRelatedPropertiesToContract(result, desc, address); + addEventsToContract(result, desc, address); + + return result; }; module.exports = contract; diff --git a/libjsqrc/ethereumjs/lib/event.js b/libjsqrc/ethereumjs/lib/event.js new file mode 100644 index 000000000..812ef9115 --- /dev/null +++ b/libjsqrc/ethereumjs/lib/event.js @@ -0,0 +1,69 @@ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file event.js + * @authors: + * Marek Kotewicz + * @date 2014 + */ + +var abi = require('./abi'); +var utils = require('./utils'); + +var inputWithName = function (inputs, name) { + var index = utils.findIndex(inputs, function (input) { + return input.name === name; + }); + + if (index === -1) { + console.error('indexed param with name ' + name + ' not found'); + return undefined; + } + return inputs[index]; +}; + +var indexedParamsToTopics = function (event, indexed) { + // sort keys? + return Object.keys(indexed).map(function (key) { + var inputs = [inputWithName(event.inputs, key)]; + + var value = indexed[key]; + if (value instanceof Array) { + return value.map(function (v) { + return abi.formatInput(inputs, [v]); + }); + } + return abi.formatInput(inputs, [value]); + }); +}; + +var implementationOfEvent = function (address, signature, event) { + + // valid options are 'earliest', 'latest', 'offset' and 'max', as defined for 'eth.watch' + return function (indexed, options) { + var o = options || {}; + o.address = address; + o.topic = []; + o.topic.push(signature); + if (indexed) { + o.topic = o.topic.concat(indexedParamsToTopics(event, indexed)); + } + return o; + }; +}; + +module.exports = implementationOfEvent; + diff --git a/libjsqrc/ethereumjs/lib/filter.js b/libjsqrc/ethereumjs/lib/filter.js index 4a82babb9..4cb297f37 100644 --- a/libjsqrc/ethereumjs/lib/filter.js +++ b/libjsqrc/ethereumjs/lib/filter.js @@ -27,17 +27,34 @@ var web3 = require('./web3'); // jshint ignore:line /// should be used when we want to watch something /// it's using inner polling mechanism and is notified about changes +/// TODO: change 'options' name cause it may be not the best matching one, since we have events var Filter = function(options, impl) { + + if (typeof options !== "string") { + + // topics property is deprecated, warn about it! + if (options.topics) { + console.warn('"topics" is deprecated, use "topic" instead'); + } + + // evaluate lazy properties + options = { + to: options.to, + topic: options.topic, + earliest: options.earliest, + latest: options.latest, + max: options.max, + skip: options.skip, + address: options.address + }; + + } + this.impl = impl; this.callbacks = []; - var self = this; - this.promise = impl.newFilter(options); - this.promise.then(function (id) { - self.id = id; - web3.on(impl.changed, id, self.trigger.bind(self)); - web3.provider.startPolling({call: impl.changed, args: [id]}, id); - }); + this.id = impl.newFilter(options); + web3.provider.startPolling({call: impl.changed, args: [this.id]}, this.id, this.trigger.bind(this)); }; /// alias for changed* @@ -47,35 +64,27 @@ Filter.prototype.arrived = function(callback) { /// gets called when there is new eth/shh message Filter.prototype.changed = function(callback) { - var self = this; - this.promise.then(function(id) { - self.callbacks.push(callback); - }); + this.callbacks.push(callback); }; /// trigger calling new message from people Filter.prototype.trigger = function(messages) { - for(var i = 0; i < this.callbacks.length; i++) { - this.callbacks[i].call(this, messages); + for (var i = 0; i < this.callbacks.length; i++) { + for (var j = 0; j < messages.length; j++) { + this.callbacks[i].call(this, messages[j]); + } } }; /// should be called to uninstall current filter Filter.prototype.uninstall = function() { - var self = this; - this.promise.then(function (id) { - self.impl.uninstallFilter(id); - web3.provider.stopPolling(id); - web3.off(impl.changed, id); - }); + this.impl.uninstallFilter(this.id); + web3.provider.stopPolling(this.id); }; /// should be called to manually trigger getting latest messages from the client Filter.prototype.messages = function() { - var self = this; - return this.promise.then(function (id) { - return self.impl.getMessages(id); - }); + return this.impl.getMessages(this.id); }; /// alias for messages diff --git a/libjsqrc/ethereumjs/lib/formatters.js b/libjsqrc/ethereumjs/lib/formatters.js new file mode 100644 index 000000000..857a01a40 --- /dev/null +++ b/libjsqrc/ethereumjs/lib/formatters.js @@ -0,0 +1,154 @@ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file formatters.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + +if (process.env.NODE_ENV !== 'build') { + var BigNumber = require('bignumber.js'); // jshint ignore:line +} + +var utils = require('./utils'); +var c = require('./const'); + +/// @param string string to be padded +/// @param number of characters that result string should have +/// @param sign, by default 0 +/// @returns right aligned string +var padLeft = function (string, chars, sign) { + return new Array(chars - string.length + 1).join(sign ? sign : "0") + string; +}; + +/// Formats input value to byte representation of int +/// If value is negative, return it's two's complement +/// If the value is floating point, round it down +/// @returns right-aligned byte representation of int +var formatInputInt = function (value) { + var padding = c.ETH_PADDING * 2; + if (value instanceof BigNumber || typeof value === 'number') { + if (typeof value === 'number') + value = new BigNumber(value); + BigNumber.config(c.ETH_BIGNUMBER_ROUNDING_MODE); + value = value.round(); + + if (value.lessThan(0)) + value = new BigNumber("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16).plus(value).plus(1); + value = value.toString(16); + } + else if (value.indexOf('0x') === 0) + value = value.substr(2); + else if (typeof value === 'string') + value = formatInputInt(new BigNumber(value)); + else + value = (+value).toString(16); + return padLeft(value, padding); +}; + +/// Formats input value to byte representation of string +/// @returns left-algined byte representation of string +var formatInputString = function (value) { + return utils.fromAscii(value, c.ETH_PADDING).substr(2); +}; + +/// Formats input value to byte representation of bool +/// @returns right-aligned byte representation bool +var formatInputBool = function (value) { + return '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0'); +}; + +/// Formats input value to byte representation of real +/// Values are multiplied by 2^m and encoded as integers +/// @returns byte representation of real +var formatInputReal = function (value) { + return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))); +}; + + +/// Check if input value is negative +/// @param value is hex format +/// @returns true if it is negative, otherwise false +var signedIsNegative = function (value) { + return (new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1)) === '1'; +}; + +/// Formats input right-aligned input bytes to int +/// @returns right-aligned input bytes formatted to int +var formatOutputInt = function (value) { + value = value || "0"; + // check if it's negative number + // it it is, return two's complement + if (signedIsNegative(value)) { + return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1); + } + return new BigNumber(value, 16); +}; + +/// Formats big right-aligned input bytes to uint +/// @returns right-aligned input bytes formatted to uint +var formatOutputUInt = function (value) { + value = value || "0"; + return new BigNumber(value, 16); +}; + +/// @returns input bytes formatted to real +var formatOutputReal = function (value) { + return formatOutputInt(value).dividedBy(new BigNumber(2).pow(128)); +}; + +/// @returns input bytes formatted to ureal +var formatOutputUReal = function (value) { + return formatOutputUInt(value).dividedBy(new BigNumber(2).pow(128)); +}; + +/// @returns right-aligned input bytes formatted to hex +var formatOutputHash = function (value) { + return "0x" + value; +}; + +/// @returns right-aligned input bytes formatted to bool +var formatOutputBool = function (value) { + return value === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false; +}; + +/// @returns left-aligned input bytes formatted to ascii string +var formatOutputString = function (value) { + return utils.toAscii(value); +}; + +/// @returns right-aligned input bytes formatted to address +var formatOutputAddress = function (value) { + return "0x" + value.slice(value.length - 40, value.length); +}; + + +module.exports = { + formatInputInt: formatInputInt, + formatInputString: formatInputString, + formatInputBool: formatInputBool, + formatInputReal: formatInputReal, + formatOutputInt: formatOutputInt, + formatOutputUInt: formatOutputUInt, + formatOutputReal: formatOutputReal, + formatOutputUReal: formatOutputUReal, + formatOutputHash: formatOutputHash, + formatOutputBool: formatOutputBool, + formatOutputString: formatOutputString, + formatOutputAddress: formatOutputAddress +}; + diff --git a/libjsqrc/ethereumjs/lib/httprpc.js b/libjsqrc/ethereumjs/lib/httprpc.js deleted file mode 100644 index de3ae8478..000000000 --- a/libjsqrc/ethereumjs/lib/httprpc.js +++ /dev/null @@ -1,126 +0,0 @@ -/* - This file is part of ethereum.js. - - ethereum.js is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ethereum.js 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with ethereum.js. If not, see . -*/ -/** @file httprpc.js - * @authors: - * Marek Kotewicz - * Marian Oancea - * @date 2014 - */ - -// TODO: is these line is supposed to be here? -if (process.env.NODE_ENV !== 'build') { - var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; // jshint ignore:line -} - -/** - * HttpRpcProvider object prototype is implementing 'provider protocol' - * Should be used when we want to connect to ethereum backend over http && jsonrpc - * It's compatible with cpp client - * The contructor allows to specify host uri - * This provider is using in-browser polling mechanism - */ -var HttpRpcProvider = function (host) { - this.handlers = []; - this.host = host; -}; - -/// Transforms inner message to proper jsonrpc object -/// @param inner message object -/// @returns jsonrpc object -function formatJsonRpcObject(object) { - return { - jsonrpc: '2.0', - method: object.call, - params: object.args, - id: object._id - }; -} - -/// Transforms jsonrpc object to inner message -/// @param incoming jsonrpc message -/// @returns inner message object -function formatJsonRpcMessage(message) { - var object = JSON.parse(message); - - return { - _id: object.id, - data: object.result, - error: object.error - }; -} - -/// Prototype object method -/// Asynchronously sends request to server -/// @param payload is inner message object -/// @param cb is callback which is being called when response is comes back -HttpRpcProvider.prototype.sendRequest = function (payload, cb) { - var data = formatJsonRpcObject(payload); - - var request = new XMLHttpRequest(); - request.open("POST", this.host, true); - request.send(JSON.stringify(data)); - request.onreadystatechange = function () { - if (request.readyState === 4 && cb) { - cb(request); - } - }; -}; - -/// Prototype object method -/// Should be called when we want to send single api request to server -/// Asynchronous -/// On response it passes message to handlers -/// @param payload is inner message object -HttpRpcProvider.prototype.send = function (payload) { - var self = this; - this.sendRequest(payload, function (request) { - self.handlers.forEach(function (handler) { - handler.call(self, formatJsonRpcMessage(request.responseText)); - }); - }); -}; - -/// Prototype object method -/// Should be called only for polling requests -/// Asynchronous -/// On response it passege message to handlers, but only if message's result is true or not empty array -/// Otherwise response is being silently ignored -/// @param payload is inner message object -/// @id is id of poll that we are calling -HttpRpcProvider.prototype.poll = function (payload, id) { - var self = this; - this.sendRequest(payload, function (request) { - var parsed = JSON.parse(request.responseText); - if (parsed.error || (parsed.result instanceof Array ? parsed.result.length === 0 : !parsed.result)) { - return; - } - self.handlers.forEach(function (handler) { - handler.call(self, {_event: payload.call, _id: id, data: parsed.result}); - }); - }); -}; - -/// Prototype object property -/// Should be used to set message handlers for this provider -Object.defineProperty(HttpRpcProvider.prototype, "onmessage", { - set: function (handler) { - this.handlers.push(handler); - } -}); - -module.exports = HttpRpcProvider; - diff --git a/libjsqrc/ethereumjs/lib/httpsync.js b/libjsqrc/ethereumjs/lib/httpsync.js new file mode 100644 index 000000000..a638cfe94 --- /dev/null +++ b/libjsqrc/ethereumjs/lib/httpsync.js @@ -0,0 +1,70 @@ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file httpsync.js + * @authors: + * Marek Kotewicz + * Marian Oancea + * @date 2014 + */ + +if (process.env.NODE_ENV !== 'build') { + var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; // jshint ignore:line +} + +var HttpSyncProvider = function (host) { + this.handlers = []; + this.host = host || 'http://localhost:8080'; +}; + +/// Transforms inner message to proper jsonrpc object +/// @param inner message object +/// @returns jsonrpc object +function formatJsonRpcObject(object) { + return { + jsonrpc: '2.0', + method: object.call, + params: object.args, + id: object._id + }; +} + +/// Transforms jsonrpc object to inner message +/// @param incoming jsonrpc message +/// @returns inner message object +function formatJsonRpcMessage(message) { + var object = JSON.parse(message); + + return { + _id: object.id, + data: object.result, + error: object.error + }; +} + +HttpSyncProvider.prototype.send = function (payload) { + var data = formatJsonRpcObject(payload); + + var request = new XMLHttpRequest(); + request.open('POST', this.host, false); + request.send(JSON.stringify(data)); + + // check request.status + return request.responseText; +}; + +module.exports = HttpSyncProvider; + diff --git a/libjsqrc/ethereumjs/lib/local.js b/libjsqrc/ethereumjs/lib/local.js new file mode 100644 index 000000000..30cd14df2 --- /dev/null +++ b/libjsqrc/ethereumjs/lib/local.js @@ -0,0 +1,18 @@ +var addressName = {"0x12378912345789": "Gav", "0x57835893478594739854": "Jeff"}; +var nameAddress = {}; + +for (var prop in addressName) { + if (addressName.hasOwnProperty(prop)) { + nameAddress[addressName[prop]] = prop; + } +} + +var local = { + addressBook:{ + byName: addressName, + byAddress: nameAddress + } +}; + +if (typeof(module) !== "undefined") + module.exports = local; diff --git a/libjsqrc/ethereumjs/lib/providermanager.js b/libjsqrc/ethereumjs/lib/providermanager.js index f79a9b087..25cd14288 100644 --- a/libjsqrc/ethereumjs/lib/providermanager.js +++ b/libjsqrc/ethereumjs/lib/providermanager.js @@ -35,74 +35,65 @@ var web3 = require('./web3'); // jshint ignore:line * and provider manager polling mechanism is not used */ var ProviderManager = function() { - this.queued = []; this.polls = []; - this.ready = false; this.provider = undefined; this.id = 1; var self = this; var poll = function () { - if (self.provider && self.provider.poll) { + if (self.provider) { self.polls.forEach(function (data) { data.data._id = self.id; self.id++; - self.provider.poll(data.data, data.id); + var result = self.provider.send(data.data); + + result = JSON.parse(result); + + // dont call the callback if result is not an array, or empty one + if (result.error || !(result.result instanceof Array) || result.result.length === 0) { + return; + } + + data.callback(result.result); }); } - setTimeout(poll, 12000); + setTimeout(poll, 1000); }; poll(); }; -/// sends outgoing requests, if provider is not available, enqueue the request -ProviderManager.prototype.send = function(data, cb) { - data._id = this.id; - if (cb) { - web3._callbacks[data._id] = cb; - } +/// sends outgoing requests +ProviderManager.prototype.send = function(data) { data.args = data.args || []; - this.id++; + data._id = this.id++; - if(this.provider !== undefined) { - this.provider.send(data); - } else { - console.warn("provider is not set"); - this.queued.push(data); + if (this.provider === undefined) { + console.error('provider is not set'); + return null; } -}; -/// setups provider, which will be used for sending messages -ProviderManager.prototype.set = function(provider) { - if(this.provider !== undefined && this.provider.unload !== undefined) { - this.provider.unload(); - } + //TODO: handle error here? + var result = this.provider.send(data); + result = JSON.parse(result); - this.provider = provider; - this.ready = true; -}; - -/// resends queued messages -ProviderManager.prototype.sendQueued = function() { - for(var i = 0; this.queued.length; i++) { - // Resend - this.send(this.queued[i]); + if (result.error) { + console.log(result.error); + return null; } + + return result.result; }; -/// @returns true if the provider i properly set -ProviderManager.prototype.installed = function() { - return this.provider !== undefined; +/// setups provider, which will be used for sending messages +ProviderManager.prototype.set = function(provider) { + this.provider = provider; }; /// this method is only used, when we do not have native qt bindings and have to do polling on our own /// should be callled, on start watching for eth/shh changes -ProviderManager.prototype.startPolling = function (data, pollId) { - if (!this.provider || !this.provider.poll) { - return; - } - this.polls.push({data: data, id: pollId}); +ProviderManager.prototype.startPolling = function (data, pollId, callback) { + this.polls.push({data: data, id: pollId, callback: callback}); }; /// should be called to stop polling for certain watch changes diff --git a/libjsqrc/ethereumjs/lib/qt.js b/libjsqrc/ethereumjs/lib/qt.js deleted file mode 100644 index 1b3a9547b..000000000 --- a/libjsqrc/ethereumjs/lib/qt.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - This file is part of ethereum.js. - - ethereum.js is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ethereum.js 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with ethereum.js. If not, see . -*/ -/** @file qt.js - * @authors: - * Jeffrey Wilcke - * Marek Kotewicz - * @date 2014 - */ - -/** - * QtProvider object prototype is implementing 'provider protocol' - * Should be used inside ethereum browser. It's compatible with cpp and go clients. - * It uses navigator.qt object to pass the messages to native bindings - */ -var QtProvider = function() { - this.handlers = []; - - var self = this; - navigator.qt.onmessage = function (message) { - self.handlers.forEach(function (handler) { - handler.call(self, JSON.parse(message.data)); - }); - }; -}; - -/// Prototype object method -/// Should be called when we want to send single api request to native bindings -/// Asynchronous -/// Response will be received by navigator.qt.onmessage method and passed to handlers -/// @param payload is inner message object -QtProvider.prototype.send = function(payload) { - navigator.qt.postMessage(JSON.stringify(payload)); -}; - -/// Prototype object property -/// Should be used to set message handlers for this provider -Object.defineProperty(QtProvider.prototype, "onmessage", { - set: function(handler) { - this.handlers.push(handler); - } -}); - -module.exports = QtProvider; diff --git a/libjsqrc/ethereumjs/lib/qtsync.js b/libjsqrc/ethereumjs/lib/qtsync.js new file mode 100644 index 000000000..a287a7172 --- /dev/null +++ b/libjsqrc/ethereumjs/lib/qtsync.js @@ -0,0 +1,32 @@ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file qtsync.js + * @authors: + * Marek Kotewicz + * Marian Oancea + * @date 2014 + */ + +var QtSyncProvider = function () { +}; + +QtSyncProvider.prototype.send = function (payload) { + return navigator.qt.callMethod(JSON.stringify(payload)); +}; + +module.exports = QtSyncProvider; + diff --git a/libjsqrc/ethereumjs/lib/types.js b/libjsqrc/ethereumjs/lib/types.js new file mode 100644 index 000000000..a39f2f1fc --- /dev/null +++ b/libjsqrc/ethereumjs/lib/types.js @@ -0,0 +1,79 @@ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file types.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + +var f = require('./formatters'); + +/// @param expected type prefix (string) +/// @returns function which checks if type has matching prefix. if yes, returns true, otherwise false +var prefixedType = function (prefix) { + return function (type) { + return type.indexOf(prefix) === 0; + }; +}; + +/// @param expected type name (string) +/// @returns function which checks if type is matching expected one. if yes, returns true, otherwise false +var namedType = function (name) { + return function (type) { + return name === type; + }; +}; + +/// Setups input formatters for solidity types +/// @returns an array of input formatters +var inputTypes = function () { + + return [ + { type: prefixedType('uint'), format: f.formatInputInt }, + { type: prefixedType('int'), format: f.formatInputInt }, + { type: prefixedType('hash'), format: f.formatInputInt }, + { type: prefixedType('string'), format: f.formatInputString }, + { type: prefixedType('real'), format: f.formatInputReal }, + { type: prefixedType('ureal'), format: f.formatInputReal }, + { type: namedType('address'), format: f.formatInputInt }, + { type: namedType('bool'), format: f.formatInputBool } + ]; +}; + +/// Setups output formaters for solidity types +/// @returns an array of output formatters +var outputTypes = function () { + + return [ + { type: prefixedType('uint'), format: f.formatOutputUInt }, + { type: prefixedType('int'), format: f.formatOutputInt }, + { type: prefixedType('hash'), format: f.formatOutputHash }, + { type: prefixedType('string'), format: f.formatOutputString }, + { type: prefixedType('real'), format: f.formatOutputReal }, + { type: prefixedType('ureal'), format: f.formatOutputUReal }, + { type: namedType('address'), format: f.formatOutputAddress }, + { type: namedType('bool'), format: f.formatOutputBool } + ]; +}; + +module.exports = { + prefixedType: prefixedType, + namedType: namedType, + inputTypes: inputTypes, + outputTypes: outputTypes +}; + diff --git a/libjsqrc/ethereumjs/lib/utils.js b/libjsqrc/ethereumjs/lib/utils.js new file mode 100644 index 000000000..5cd6ec8d6 --- /dev/null +++ b/libjsqrc/ethereumjs/lib/utils.js @@ -0,0 +1,113 @@ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file utils.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + +/// Finds first index of array element matching pattern +/// @param array +/// @param callback pattern +/// @returns index of element +var findIndex = function (array, callback) { + var end = false; + var i = 0; + for (; i < array.length && !end; i++) { + end = callback(array[i]); + } + return end ? i - 1 : -1; +}; + +/// @returns ascii string representation of hex value prefixed with 0x +var toAscii = function(hex) { +// Find termination + var str = ""; + var i = 0, l = hex.length; + if (hex.substring(0, 2) === '0x') { + i = 2; + } + for (; i < l; i+=2) { + var code = parseInt(hex.substr(i, 2), 16); + if (code === 0) { + break; + } + + str += String.fromCharCode(code); + } + + return str; +}; + +var toHex = function(str) { + var hex = ""; + for(var i = 0; i < str.length; i++) { + var n = str.charCodeAt(i).toString(16); + hex += n.length < 2 ? '0' + n : n; + } + + return hex; +}; + +/// @returns hex representation (prefixed by 0x) of ascii string +var fromAscii = function(str, pad) { + pad = pad === undefined ? 0 : pad; + var hex = toHex(str); + while (hex.length < pad*2) + hex += "00"; + return "0x" + hex; +}; + +/// @returns display name for function/event eg. multiply(uint256) -> multiply +var extractDisplayName = function (name) { + var length = name.indexOf('('); + return length !== -1 ? name.substr(0, length) : name; +}; + +/// @returns overloaded part of function/event name +var extractTypeName = function (name) { + /// TODO: make it invulnerable + var length = name.indexOf('('); + return length !== -1 ? name.substr(length + 1, name.length - 1 - (length + 1)) : ""; +}; + +/// Filters all function from input abi +/// @returns abi array with filtered objects of type 'function' +var filterFunctions = function (json) { + return json.filter(function (current) { + return current.type === 'function'; + }); +}; + +/// Filters all events form input abi +/// @returns abi array with filtered objects of type 'event' +var filterEvents = function (json) { + return json.filter(function (current) { + return current.type === 'event'; + }); +}; + +module.exports = { + findIndex: findIndex, + toAscii: toAscii, + fromAscii: fromAscii, + extractDisplayName: extractDisplayName, + extractTypeName: extractTypeName, + filterFunctions: filterFunctions, + filterEvents: filterEvents +}; + diff --git a/libjsqrc/ethereumjs/lib/web3.js b/libjsqrc/ethereumjs/lib/web3.js index f071eea49..c3126afc4 100644 --- a/libjsqrc/ethereumjs/lib/web3.js +++ b/libjsqrc/ethereumjs/lib/web3.js @@ -23,48 +23,34 @@ * @date 2014 */ -/// Recursively resolves all promises in given object and replaces the resolved values with promises -/// @param any object/array/promise/anything else.. -/// @returns (resolves) object with replaced promises with their result -function flattenPromise (obj) { - if (obj instanceof Promise) { - return Promise.resolve(obj); - } - - if (obj instanceof Array) { - return new Promise(function (resolve) { - var promises = obj.map(function (o) { - return flattenPromise(o); - }); - - return Promise.all(promises).then(function (res) { - for (var i = 0; i < obj.length; i++) { - obj[i] = res[i]; - } - resolve(obj); - }); - }); - } - - if (obj instanceof Object) { - return new Promise(function (resolve) { - var keys = Object.keys(obj); - var promises = keys.map(function (key) { - return flattenPromise(obj[key]); - }); - - return Promise.all(promises).then(function (res) { - for (var i = 0; i < keys.length; i++) { - obj[keys[i]] = res[i]; - } - resolve(obj); - }); - }); - } - - return Promise.resolve(obj); +if (process.env.NODE_ENV !== 'build') { + var BigNumber = require('bignumber.js'); } +var utils = require('./utils'); + +var ETH_UNITS = [ + 'wei', + 'Kwei', + 'Mwei', + 'Gwei', + 'szabo', + 'finney', + 'ether', + 'grand', + 'Mether', + 'Gether', + 'Tether', + 'Pether', + 'Eether', + 'Zether', + 'Yether', + 'Nether', + 'Dether', + 'Vether', + 'Uether' +]; + /// @returns an array of objects describing web3 api methods var web3Methods = function () { return [ @@ -98,6 +84,7 @@ var ethMethods = function () { { name: 'transaction', call: transactionCall }, { name: 'uncle', call: uncleCall }, { name: 'compilers', call: 'eth_compilers' }, + { name: 'flush', call: 'eth_flush' }, { name: 'lll', call: 'eth_lll' }, { name: 'solidity', call: 'eth_solidity' }, { name: 'serpent', call: 'eth_serpent' }, @@ -113,7 +100,6 @@ var ethProperties = function () { { name: 'listening', getter: 'eth_listening', setter: 'eth_setListening' }, { name: 'mining', getter: 'eth_mining', setter: 'eth_setMining' }, { name: 'gasPrice', getter: 'eth_gasPrice' }, - { name: 'account', getter: 'eth_account' }, { name: 'accounts', getter: 'eth_accounts' }, { name: 'peerCount', getter: 'eth_peerCount' }, { name: 'defaultBlock', getter: 'eth_defaultBlock', setter: 'eth_setDefaultBlock' }, @@ -160,7 +146,7 @@ var shhWatchMethods = function () { return [ { name: 'newFilter', call: 'shh_newFilter' }, { name: 'uninstallFilter', call: 'shh_uninstallFilter' }, - { name: 'getMessage', call: 'shh_getMessages' } + { name: 'getMessages', call: 'shh_getMessages' } ]; }; @@ -169,21 +155,11 @@ var shhWatchMethods = function () { var setupMethods = function (obj, methods) { methods.forEach(function (method) { obj[method.name] = function () { - return flattenPromise(Array.prototype.slice.call(arguments)).then(function (args) { - var call = typeof method.call === "function" ? method.call(args) : method.call; - return {call: call, args: args}; - }).then(function (request) { - return new Promise(function (resolve, reject) { - web3.provider.send(request, function (err, result) { - if (!err) { - resolve(result); - return; - } - reject(err); - }); - }); - }).catch(function(err) { - console.error(err); + var args = Array.prototype.slice.call(arguments); + var call = typeof method.call === 'function' ? method.call(args) : method.call; + return web3.provider.send({ + call: call, + args: args }); }; }); @@ -195,30 +171,16 @@ var setupProperties = function (obj, properties) { properties.forEach(function (property) { var proto = {}; proto.get = function () { - return new Promise(function(resolve, reject) { - web3.provider.send({call: property.getter}, function(err, result) { - if (!err) { - resolve(result); - return; - } - reject(err); - }); + return web3.provider.send({ + call: property.getter }); }; + if (property.setter) { proto.set = function (val) { - return flattenPromise([val]).then(function (args) { - return new Promise(function (resolve) { - web3.provider.send({call: property.setter, args: args}, function (err, result) { - if (!err) { - resolve(result); - return; - } - reject(err); - }); - }); - }).catch(function (err) { - console.error(err); + return web3.provider.send({ + call: property.setter, + args: [val] }); }; } @@ -226,74 +188,36 @@ var setupProperties = function (obj, properties) { }); }; -// TODO: import from a dependency, don't duplicate. -var hexToDec = function (hex) { - return parseInt(hex, 16).toString(); -}; - -var decToHex = function (dec) { - return parseInt(dec).toString(16); -}; - /// setups web3 object, and it's in-browser executed methods var web3 = { _callbacks: {}, _events: {}, providers: {}, - toHex: function(str) { - var hex = ""; - for(var i = 0; i < str.length; i++) { - var n = str.charCodeAt(i).toString(16); - hex += n.length < 2 ? '0' + n : n; - } - - return hex; - }, - /// @returns ascii string representation of hex value prefixed with 0x - toAscii: function(hex) { - // Find termination - var str = ""; - var i = 0, l = hex.length; - if (hex.substring(0, 2) === '0x') - i = 2; - for(; i < l; i+=2) { - var code = parseInt(hex.substr(i, 2), 16); - if(code === 0) { - break; - } - - str += String.fromCharCode(code); - } - - return str; - }, + toAscii: utils.toAscii, /// @returns hex representation (prefixed by 0x) of ascii string - fromAscii: function(str, pad) { - pad = pad === undefined ? 0 : pad; - var hex = this.toHex(str); - while(hex.length < pad*2) - hex += "00"; - return "0x" + hex; - }, + fromAscii: utils.fromAscii, /// @returns decimal representaton of hex value prefixed by 0x toDecimal: function (val) { - return hexToDec(val.substring(2)); + // remove 0x and place 0, if it's required + val = val.length > 2 ? val.substring(2) : "0"; + return (new BigNumber(val, 16).toString(10)); }, /// @returns hex representation (prefixed by 0x) of decimal value fromDecimal: function (val) { - return "0x" + decToHex(val); + return "0x" + (new BigNumber(val).toString(16)); }, /// used to transform value/string to eth string + /// TODO: use BigNumber.js to parse int toEth: function(str) { var val = typeof str === "string" ? str.indexOf('0x') === 0 ? parseInt(str.substr(2), 16) : parseInt(str) : str; var unit = 0; - var units = [ 'wei', 'Kwei', 'Mwei', 'Gwei', 'szabo', 'finney', 'ether', 'grand', 'Mether', 'Gether', 'Tether', 'Pether', 'Eether', 'Zether', 'Yether', 'Nether', 'Dether', 'Vether', 'Uether' ]; + var units = ETH_UNITS; while (val > 3000 && unit < units.length - 1) { val /= 1000; @@ -315,8 +239,24 @@ var web3 = { /// eth object prototype eth: { - watch: function (params) { - return new web3.filter(params, ethWatch); + contractFromAbi: function (abi) { + return function(addr) { + // Default to address of Config. TODO: rremove prior to genesis. + addr = addr || '0xc6d9d2cd449a754c494264e1809c50e34d64562b'; + var ret = web3.eth.contract(addr, abi); + ret.address = addr; + return ret; + }; + }, + + /// @param filter may be a string, object or event + /// @param indexed is optional, this is an object with optional event indexed params + /// @param options is optional, this is an object with optional event options ('max'...) + watch: function (filter, indexed, options) { + if (filter._isEvent) { + return filter(indexed, options); + } + return new web3.filter(filter, ethWatch); } }, @@ -325,38 +265,11 @@ var web3 = { /// shh object prototype shh: { - watch: function (params) { - return new web3.filter(params, shhWatch); - } - }, - - /// used by filter to register callback with given id - on: function(event, id, cb) { - if(web3._events[event] === undefined) { - web3._events[event] = {}; + + /// @param filter may be a string, object or event + watch: function (filter, indexed) { + return new web3.filter(filter, shhWatch); } - - web3._events[event][id] = cb; - return this; - }, - - /// used by filter to unregister callback with given id - off: function(event, id) { - if(web3._events[event] !== undefined) { - delete web3._events[event][id]; - } - - return this; - }, - - /// used to trigger callback registered by filter - trigger: function(event, id, data) { - var callbacks = web3._events[event]; - if (!callbacks || !callbacks[id]) { - return; - } - var cb = callbacks[id]; - cb(data); }, /// @returns true if provider is installed @@ -385,26 +298,9 @@ var shhWatch = { setupMethods(shhWatch, shhWatchMethods()); web3.setProvider = function(provider) { - provider.onmessage = messageHandler; + //provider.onmessage = messageHandler; // there will be no async calls, to remove web3.provider.set(provider); - web3.provider.sendQueued(); }; -/// callled when there is new incoming message -function messageHandler(data) { - if(data._event !== undefined) { - web3.trigger(data._event, data._id, data.data); - return; - } - - if(data._id) { - var cb = web3._callbacks[data._id]; - if (cb) { - cb.call(this, data.error, data.data); - delete web3._callbacks[data._id]; - } - } -} - module.exports = web3; diff --git a/libjsqrc/ethereumjs/lib/websocket.js b/libjsqrc/ethereumjs/lib/websocket.js deleted file mode 100644 index e8663ecf5..000000000 --- a/libjsqrc/ethereumjs/lib/websocket.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - This file is part of ethereum.js. - - ethereum.js is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ethereum.js 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with ethereum.js. If not, see . -*/ -/** @file websocket.js - * @authors: - * Jeffrey Wilcke - * Marek Kotewicz - * Marian Oancea - * @date 2014 - */ - -// TODO: is these line is supposed to be here? -if (process.env.NODE_ENV !== 'build') { - var WebSocket = require('ws'); // jshint ignore:line -} - -/** - * WebSocketProvider object prototype is implementing 'provider protocol' - * Should be used when we want to connect to ethereum backend over websockets - * It's compatible with go client - * The constructor allows to specify host uri - */ -var WebSocketProvider = function(host) { - - // onmessage handlers - this.handlers = []; - - // queue will be filled with messages if send is invoked before the ws is ready - this.queued = []; - this.ready = false; - - this.ws = new WebSocket(host); - - var self = this; - this.ws.onmessage = function(event) { - for(var i = 0; i < self.handlers.length; i++) { - self.handlers[i].call(self, JSON.parse(event.data), event); - } - }; - - this.ws.onopen = function() { - self.ready = true; - - for (var i = 0; i < self.queued.length; i++) { - // Resend - self.send(self.queued[i]); - } - }; -}; - -/// Prototype object method -/// Should be called when we want to send single api request to server -/// Asynchronous, it's using websockets -/// Response for the call will be received by ws.onmessage -/// @param payload is inner message object -WebSocketProvider.prototype.send = function(payload) { - if (this.ready) { - var data = JSON.stringify(payload); - - this.ws.send(data); - } else { - this.queued.push(payload); - } -}; - -/// Prototype object method -/// Should be called to add handlers -WebSocketProvider.prototype.onMessage = function(handler) { - this.handlers.push(handler); -}; - -/// Prototype object method -/// Should be called to close websockets connection -WebSocketProvider.prototype.unload = function() { - this.ws.close(); -}; - -/// Prototype object property -/// Should be used to set message handlers for this provider -Object.defineProperty(WebSocketProvider.prototype, "onmessage", { - set: function(provider) { this.onMessage(provider); } -}); - -if (typeof(module) !== "undefined") - module.exports = WebSocketProvider; diff --git a/libjsqrc/ethereumjs/package.json b/libjsqrc/ethereumjs/package.json index 214dbb62e..a61ffc76a 100644 --- a/libjsqrc/ethereumjs/package.json +++ b/libjsqrc/ethereumjs/package.json @@ -1,14 +1,13 @@ { "name": "ethereum.js", "namespace": "ethereum", - "version": "0.0.8", + "version": "0.0.11", "description": "Ethereum Compatible JavaScript API", "main": "./index.js", "directories": { "lib": "./lib" }, "dependencies": { - "es6-promise": "*", "ws": "*", "xmlhttprequest": "*", "bignumber.js": ">=2.0.0" diff --git a/libjsqrc/ethereumjs/test/abi.parsers.js b/libjsqrc/ethereumjs/test/abi.parsers.js index e9613817b..12bccf5a5 100644 --- a/libjsqrc/ethereumjs/test/abi.parsers.js +++ b/libjsqrc/ethereumjs/test/abi.parsers.js @@ -5,6 +5,7 @@ var clone = function (object) { return JSON.parse(JSON.stringify(object)); }; var description = [{ "name": "test", + "type": "function", "inputs": [{ "name": "a", "type": "uint256" @@ -48,7 +49,6 @@ describe('abi', function() { assert.equal(parser.test('0.1'), "0000000000000000000000000000000000000000000000000000000000000000"); assert.equal(parser.test('3.9'), "0000000000000000000000000000000000000000000000000000000000000003"); - }); it('should parse input uint128', function() { @@ -321,7 +321,7 @@ describe('abi', function() { // given var d = clone(description); - d[0].name = 'helloworld'; + d[0].name = 'helloworld(int)'; d[0].inputs = [ { type: "int" } ]; @@ -331,6 +331,7 @@ describe('abi', function() { // then assert.equal(parser.helloworld(1), "0000000000000000000000000000000000000000000000000000000000000001"); + assert.equal(parser.helloworld['int'](1), "0000000000000000000000000000000000000000000000000000000000000001"); }); @@ -339,10 +340,12 @@ describe('abi', function() { // given var d = [{ name: "test", + type: "function", inputs: [{ type: "int" }], outputs: [{ type: "int" }] },{ name: "test2", + type: "function", inputs: [{ type: "string" }], outputs: [{ type: "string" }] }]; @@ -755,7 +758,7 @@ describe('abi', function() { // given var d = clone(description); - d[0].name = 'helloworld'; + d[0].name = 'helloworld(int)'; d[0].outputs = [ { type: "int" } ]; @@ -765,6 +768,7 @@ describe('abi', function() { // then assert.equal(parser.helloworld("0x0000000000000000000000000000000000000000000000000000000000000001")[0], 1); + assert.equal(parser.helloworld['int']("0x0000000000000000000000000000000000000000000000000000000000000001")[0], 1); }); @@ -774,10 +778,12 @@ describe('abi', function() { // given var d = [{ name: "test", + type: "function", inputs: [{ type: "int" }], outputs: [{ type: "int" }] },{ name: "test2", + type: "function", inputs: [{ type: "string" }], outputs: [{ type: "string" }] }]; @@ -822,6 +828,38 @@ describe('abi', function() { }); + it('should parse 0x value', function () { + + // given + var d = clone(description); + d[0].outputs = [ + { type: 'int' } + ]; + + // when + var parser = abi.outputParser(d); + + // then + assert.equal(parser.test("0x")[0], 0); + + }); + + it('should parse 0x value', function () { + + // given + var d = clone(description); + d[0].outputs = [ + { type: 'uint' } + ]; + + // when + var parser = abi.outputParser(d); + + // then + assert.equal(parser.test("0x")[0], 0); + + }); + }); }); diff --git a/libjsqrc/ethereumjs/test/db.methods.js b/libjsqrc/ethereumjs/test/db.methods.js index 662f4e7cc..8f8f5409f 100644 --- a/libjsqrc/ethereumjs/test/db.methods.js +++ b/libjsqrc/ethereumjs/test/db.methods.js @@ -1,9 +1,7 @@ -require('es6-promise').polyfill(); var assert = require('assert'); var web3 = require('../index.js'); var u = require('./utils.js'); -web3.setProvider(new web3.providers.WebSocketProvider('http://localhost:8080')); // TODO: create some mock provider describe('web3', function() { describe('db', function() { diff --git a/libjsqrc/ethereumjs/test/eth.contract.js b/libjsqrc/ethereumjs/test/eth.contract.js new file mode 100644 index 000000000..1a92ec88f --- /dev/null +++ b/libjsqrc/ethereumjs/test/eth.contract.js @@ -0,0 +1,201 @@ +var assert = require('assert'); +var contract = require('../lib/contract.js'); + +describe('contract', function() { + it('should create simple contract with one method from abi with explicit type name', function () { + + // given + var description = [{ + "name": "test(uint256)", + "type": "function", + "inputs": [{ + "name": "a", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "d", + "type": "uint256" + } + ] + }]; + + // when + var con = contract(null, description); + + // then + assert.equal('function', typeof con.test); + assert.equal('function', typeof con.test['uint256']); + }); + + it('should create simple contract with one method from abi with implicit type name', function () { + + // given + var description = [{ + "name": "test", + "type": "function", + "inputs": [{ + "name": "a", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "d", + "type": "uint256" + } + ] + }]; + + // when + var con = contract(null, description); + + // then + assert.equal('function', typeof con.test); + assert.equal('function', typeof con.test['uint256']); + }); + + it('should create contract with multiple methods', function () { + + // given + var description = [{ + "name": "test", + "type": "function", + "inputs": [{ + "name": "a", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "d", + "type": "uint256" + } + ], + }, { + "name": "test2", + "type": "function", + "inputs": [{ + "name": "a", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "d", + "type": "uint256" + } + ] + }]; + + // when + var con = contract(null, description); + + // then + assert.equal('function', typeof con.test); + assert.equal('function', typeof con.test['uint256']); + assert.equal('function', typeof con.test2); + assert.equal('function', typeof con.test2['uint256']); + }); + + it('should create contract with overloaded methods', function () { + + // given + var description = [{ + "name": "test", + "type": "function", + "inputs": [{ + "name": "a", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "d", + "type": "uint256" + } + ], + }, { + "name": "test", + "type": "function", + "inputs": [{ + "name": "a", + "type": "string" + } + ], + "outputs": [ + { + "name": "d", + "type": "uint256" + } + ] + }]; + + // when + var con = contract(null, description); + + // then + assert.equal('function', typeof con.test); + assert.equal('function', typeof con.test['uint256']); + assert.equal('function', typeof con.test['string']); + }); + + it('should create contract with no methods', function () { + + // given + var description = [{ + "name": "test(uint256)", + "inputs": [{ + "name": "a", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "d", + "type": "uint256" + } + ] + }]; + + + // when + var con = contract(null, description); + + // then + assert.equal('undefined', typeof con.test); + + }); + + it('should create contract with one event', function () { + + // given + var description = [{ + "name": "test", + "type": "event", + "inputs": [{ + "name": "a", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "d", + "type": "uint256" + } + ] + }]; + + + // when + var con = contract(null, description); + + // then + assert.equal('function', typeof con.test); + assert.equal('function', typeof con.test['uint256']); + + }); + +}); + diff --git a/libjsqrc/ethereumjs/test/eth.methods.js b/libjsqrc/ethereumjs/test/eth.methods.js index 892db0d8b..7a031c4c8 100644 --- a/libjsqrc/ethereumjs/test/eth.methods.js +++ b/libjsqrc/ethereumjs/test/eth.methods.js @@ -1,9 +1,6 @@ -require('es6-promise').polyfill(); - var assert = require('assert'); var web3 = require('../index.js'); var u = require('./utils.js'); -web3.setProvider(new web3.providers.WebSocketProvider('http://localhost:8080')); // TODO: create some mock provider describe('web3', function() { describe('eth', function() { @@ -27,7 +24,6 @@ describe('web3', function() { u.propertyExists(web3.eth, 'listening'); u.propertyExists(web3.eth, 'mining'); u.propertyExists(web3.eth, 'gasPrice'); - u.propertyExists(web3.eth, 'account'); u.propertyExists(web3.eth, 'accounts'); u.propertyExists(web3.eth, 'peerCount'); u.propertyExists(web3.eth, 'defaultBlock'); diff --git a/libjsqrc/ethereumjs/test/event.js b/libjsqrc/ethereumjs/test/event.js new file mode 100644 index 000000000..9edd93ae7 --- /dev/null +++ b/libjsqrc/ethereumjs/test/event.js @@ -0,0 +1,124 @@ +var assert = require('assert'); +var event = require('../lib/event.js'); +var f = require('../lib/formatters.js'); + +describe('event', function () { + it('should create basic filter input object', function () { + + // given + var address = '0x012345'; + var signature = '0x987654'; + var e = { + name: 'Event', + inputs: [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"hash256","indexed":false}] + }; + + // when + var impl = event(address, signature, e); + var result = impl(); + + // then + assert.equal(result.address, address); + assert.equal(result.topic.length, 1); + assert.equal(result.topic[0], signature); + + }); + + it('should create filter input object with options', function () { + + // given + var address = '0x012345'; + var signature = '0x987654'; + var options = { + earliest: 1, + latest: 2, + offset: 3, + max: 4 + }; + var e = { + name: 'Event', + inputs: [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"hash256","indexed":false}] + }; + + // when + var impl = event(address, signature, e); + var result = impl({}, options); + + // then + assert.equal(result.address, address); + assert.equal(result.topic.length, 1); + assert.equal(result.topic[0], signature); + assert.equal(result.earliest, options.earliest); + assert.equal(result.latest, options.latest); + assert.equal(result.offset, options.offset); + assert.equal(result.max, options.max); + + }); + + it('should create filter input object with indexed params', function () { + + // given + var address = '0x012345'; + var signature = '0x987654'; + var options = { + earliest: 1, + latest: 2, + offset: 3, + max: 4 + }; + var e = { + name: 'Event', + inputs: [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"hash256","indexed":false}] + }; + + // when + var impl = event(address, signature, e); + var result = impl({a: 4}, options); + + // then + assert.equal(result.address, address); + assert.equal(result.topic.length, 2); + assert.equal(result.topic[0], signature); + assert.equal(result.topic[1], f.formatInputInt(4)); + assert.equal(result.earliest, options.earliest); + assert.equal(result.latest, options.latest); + assert.equal(result.offset, options.offset); + assert.equal(result.max, options.max); + + }); + + it('should create filter input object with an array of indexed params', function () { + + // given + var address = '0x012345'; + var signature = '0x987654'; + var options = { + earliest: 1, + latest: 2, + offset: 3, + max: 4 + }; + var e = { + name: 'Event', + inputs: [{"name":"a","type":"uint256","indexed":true},{"name":"b","type":"hash256","indexed":false}] + }; + + // when + var impl = event(address, signature, e); + var result = impl({a: [4, 69]}, options); + + // then + assert.equal(result.address, address); + assert.equal(result.topic.length, 2); + assert.equal(result.topic[0], signature); + assert.equal(result.topic[1][0], f.formatInputInt(4)); + assert.equal(result.topic[1][1], f.formatInputInt(69)); + assert.equal(result.earliest, options.earliest); + assert.equal(result.latest, options.latest); + assert.equal(result.offset, options.offset); + assert.equal(result.max, options.max); + + }); + +}); + diff --git a/libjsqrc/ethereumjs/test/shh.methods.js b/libjsqrc/ethereumjs/test/shh.methods.js index f2f56edbc..fe2dae71d 100644 --- a/libjsqrc/ethereumjs/test/shh.methods.js +++ b/libjsqrc/ethereumjs/test/shh.methods.js @@ -1,9 +1,6 @@ -require('es6-promise').polyfill(); - var assert = require('assert'); var web3 = require('../index.js'); var u = require('./utils.js'); -web3.setProvider(new web3.providers.WebSocketProvider('http://localhost:8080')); // TODO: create some mock provider describe('web3', function() { describe('shh', function() { diff --git a/libjsqrc/ethereumjs/test/utils.filters.js b/libjsqrc/ethereumjs/test/utils.filters.js new file mode 100644 index 000000000..f2d2788b0 --- /dev/null +++ b/libjsqrc/ethereumjs/test/utils.filters.js @@ -0,0 +1,49 @@ +var assert = require('assert'); +var utils = require('../lib/utils.js'); + +describe('utils', function() { + it('should filter functions and events from input array properly', function () { + + // given + var description = [{ + "name": "test", + "type": "function", + "inputs": [{ + "name": "a", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "d", + "type": "uint256" + } + ], + }, { + "name": "test2", + "type": "event", + "inputs": [{ + "name": "a", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "d", + "type": "uint256" + } + ] + }]; + + // when + var events = utils.filterEvents(description); + var functions = utils.filterFunctions(description); + + // then + assert.equal(events.length, 1); + assert.equal(events[0].name, 'test2'); + assert.equal(functions.length, 1); + assert.equal(functions[0].name, 'test'); + + }); +}); diff --git a/libjsqrc/ethereumjs/test/utils.js b/libjsqrc/ethereumjs/test/utils.js index 8617348e4..8a1e9a0b6 100644 --- a/libjsqrc/ethereumjs/test/utils.js +++ b/libjsqrc/ethereumjs/test/utils.js @@ -8,7 +8,7 @@ var methodExists = function (object, method) { var propertyExists = function (object, property) { it('should have property ' + property + ' implemented', function() { - assert.equal('object', typeof object[property], 'property ' + property + ' is not implemented'); + assert.notEqual('undefined', typeof object[property], 'property ' + property + ' is not implemented'); }); }; diff --git a/libjsqrc/ethereumjs/test/web3.methods.js b/libjsqrc/ethereumjs/test/web3.methods.js index 5c30177e5..d08495dd9 100644 --- a/libjsqrc/ethereumjs/test/web3.methods.js +++ b/libjsqrc/ethereumjs/test/web3.methods.js @@ -1,16 +1,10 @@ -require('es6-promise').polyfill(); - var assert = require('assert'); var web3 = require('../index.js'); var u = require('./utils.js'); -web3.setProvider(new web3.providers.WebSocketProvider('http://localhost:8080')); // TODO: create some mock provider describe('web3', function() { u.methodExists(web3, 'sha3'); u.methodExists(web3, 'toAscii'); u.methodExists(web3, 'fromAscii'); - u.methodExists(web3, 'toFixed'); - u.methodExists(web3, 'fromFixed'); - u.methodExists(web3, 'offset'); }); diff --git a/libjsqrc/js.qrc b/libjsqrc/js.qrc index 329b3356a..50b5e098c 100644 --- a/libjsqrc/js.qrc +++ b/libjsqrc/js.qrc @@ -1,8 +1,8 @@ - es6-promise-2.0.0.js bignumber.min.js setup.js ethereumjs/dist/ethereum.js + natspec.js diff --git a/libjsqrc/natspec.js b/libjsqrc/natspec.js new file mode 100644 index 000000000..d0bbdff17 --- /dev/null +++ b/libjsqrc/natspec.js @@ -0,0 +1,83 @@ + +/** + * This plugin exposes 'evaluateExpression' method which should be used + * to evaluate natspec description + * It should be reloaded each time we want to evaluate set of expressions + * Just because of security reasons + * TODO: make use of sync api (once it's finished) and remove unnecessary + * code from 'getContractMethods' + * TODO: unify method signature creation with abi.js (and make a separate method from it) + */ + +/// Should be called to copy values from object to global context +var copyToContext = function (obj, context) { + var keys = Object.keys(obj); + keys.forEach(function (key) { + context[key] = obj[key]; + }); +} + +/// Function called to get all contract's storage values +/// @returns hashmap with contract properties which are used +var getContractProperties = function (address, abi) { + return {}; +}; + +/// Function called to get all contract's methods +/// @returns hashmap with used contract's methods +var getContractMethods = function (address, abi) { + return web3.eth.contract(address, abi); +}; + +/// Function called to get all contract method input variables +/// @returns hashmap with all contract's method input variables +var getContractInputParams = function (abi, methodName, params) { + var method = web3.abi.getMethodWithName(abi, methodName); + return method.inputs.reduce(function (acc, current, index) { + acc[current.name] = params[index]; + return acc; + }, {}); +}; + +/// Should be called to evaluate single expression +/// Is internally using javascript's 'eval' method +/// Should be checked if it is safe +var evaluateExpression = function (expression) { + + var self = this; + var abi = web3._currentContractAbi; + var address = web3._currentContractAddress; + var methodName = web3._currentContractMethodName; + var params = web3._currentContractMethodParams; + + var storage = getContractProperties(address, abi); + var methods = getContractMethods(address, abi); + var inputParams = getContractInputParams(abi, methodName, params); + + copyToContext(storage, self); + copyToContext(methods, self); + copyToContext(inputParams, self); + + // TODO: test if it is safe + var evaluatedExpression = ""; + + // match everything in `` quotes + var pattern = /\`(?:\\.|[^`\\])*\`/gim + var match; + var lastIndex = 0; + while ((match = pattern.exec(expression)) !== null) { + var startIndex = pattern.lastIndex - match[0].length; + + var toEval = match[0].slice(1, match[0].length - 1); + + evaluatedExpression += expression.slice(lastIndex, startIndex); + evaluatedExpression += eval(toEval).toString(); + + lastIndex = pattern.lastIndex; + } + + evaluatedExpression += expression.slice(lastIndex); + + return evaluatedExpression; +}; + diff --git a/libjsqrc/setup.js b/libjsqrc/setup.js index b83745c33..2aa54ed30 100644 --- a/libjsqrc/setup.js +++ b/libjsqrc/setup.js @@ -22,25 +22,6 @@ navigator.qt = _web3; -(function () { - navigator.qt.handlers = []; - Object.defineProperty(navigator.qt, 'onmessage', { - set: function (handler) { - navigator.qt.handlers.push(handler); - } - }); -})(); - -navigator.qt.response.connect(function (res) { - navigator.qt.handlers.forEach(function (handler) { - handler({data: res}); - }); -}); - -if (window.Promise === undefined) { - window.Promise = ES6Promise.Promise; -} - var web3 = require('web3'); -web3.setProvider(new web3.providers.QtProvider()); +web3.setProvider(new web3.providers.QtSyncProvider()); diff --git a/libqwebthree/QWebThree.cpp b/libqwebthree/QWebThree.cpp index 3f4b5c67d..804766563 100644 --- a/libqwebthree/QWebThree.cpp +++ b/libqwebthree/QWebThree.cpp @@ -36,58 +36,8 @@ QWebThree::~QWebThree() clientDieing(); } -static QString toJsonRpcBatch(std::vector const& _watches, QString _method) -{ - QJsonArray batch; - for (int w: _watches) - { - QJsonObject res; - res["jsonrpc"] = QString::fromStdString("2.0"); - res["method"] = _method; - - QJsonArray params; - params.append(w); - res["params"] = params; - res["id"] = w; - batch.append(res); - } - - return QString::fromUtf8(QJsonDocument(batch).toJson()); -} - -void QWebThree::poll() -{ - if (m_watches.size() > 0) - { - QString batch = toJsonRpcBatch(m_watches, "eth_changed"); - emit processData(batch, "eth_changed"); - } - if (m_shhWatches.size() > 0) - { - QString batch = toJsonRpcBatch(m_shhWatches, "shh_changed"); - emit processData(batch, "shh_changed"); - } -} - -void QWebThree::clearWatches() -{ - if (m_watches.size() > 0) - { - QString batch = toJsonRpcBatch(m_watches, "eth_uninstallFilter"); - m_watches.clear(); - emit processData(batch, "internal"); - } - if (m_shhWatches.size() > 0) - { - QString batch = toJsonRpcBatch(m_shhWatches, "shh_uninstallFilter"); - m_shhWatches.clear(); - emit processData(batch, "internal"); - } -} - void QWebThree::clientDieing() { - clearWatches(); this->disconnect(); } @@ -101,71 +51,22 @@ static QString formatInput(QJsonObject const& _object) return QString::fromUtf8(QJsonDocument(res).toJson()); } -void QWebThree::postMessage(QString _json) +QString QWebThree::callMethod(QString _json) { QJsonObject f = QJsonDocument::fromJson(_json.toUtf8()).object(); - - QString method = f["call"].toString(); - if (!method.compare("eth_uninstallFilter") && f["args"].isArray() && f["args"].toArray().size()) - { - int idToRemove = f["args"].toArray()[0].toInt(); - m_watches.erase(std::remove(m_watches.begin(), m_watches.end(), idToRemove), m_watches.end()); - } - else if (!method.compare("eth_uninstallFilter") && f["args"].isArray() && f["args"].toArray().size()) - { - int idToRemove = f["args"].toArray()[0].toInt(); - m_watches.erase(std::remove(m_watches.begin(), m_watches.end(), idToRemove), m_watches.end()); - } - - emit processData(formatInput(f), method); + emit processData(formatInput(f), ""); // it's synchronous + return m_response; } -static QString formatOutput(QJsonObject const& _object) +void QWebThree::onDataProcessed(QString _json, QString) { - QJsonObject res; - res["_id"] = _object["id"]; - res["data"] = _object["result"]; - res["error"] = _object["error"]; - return QString::fromUtf8(QJsonDocument(res).toJson()); + QJsonObject f = QJsonDocument::fromJson(_json.toUtf8()).object(); + syncResponse(QString::fromUtf8(QJsonDocument(f).toJson())); } -void QWebThree::onDataProcessed(QString _json, QString _addInfo) +void QWebThree::syncResponse(QString _json) { - if (!_addInfo.compare("internal")) - return; - - if (!_addInfo.compare("shh_changed") || !_addInfo.compare("eth_changed")) - { - QJsonArray resultsArray = QJsonDocument::fromJson(_json.toUtf8()).array(); - for (int i = 0; i < resultsArray.size(); i++) - { - QJsonObject elem = resultsArray[i].toObject(); - if (elem["result"].isArray() && elem["result"].toArray().size() > 0) - { - QJsonObject res; - res["_event"] = _addInfo; - - if (!_addInfo.compare("shh_changed")) - res["_id"] = (int)m_shhWatches[i]; // we can do that couse poll is synchronous - else - res["_id"] = (int)m_watches[i]; - - res["data"] = elem["result"].toArray(); - response(QString::fromUtf8(QJsonDocument(res).toJson())); - } - } - } - - QJsonObject f = QJsonDocument::fromJson(_json.toUtf8()).object(); - - if ((!_addInfo.compare("eth_newFilter") || !_addInfo.compare("eth_newFilterString")) && f.contains("result")) - m_watches.push_back(f["result"].toInt()); - else if (!_addInfo.compare("shh_newFilter") && f.contains("result")) - m_shhWatches.push_back(f["result"].toInt()); - else if (!_addInfo.compare("shh_newIdentity") && f.contains("result")) - emit onNewId(f["result"].toString()); - - response(formatOutput(f)); + m_response = _json; } QWebThreeConnector::QWebThreeConnector() diff --git a/libqwebthree/QWebThree.h b/libqwebthree/QWebThree.h index 48cae6135..72c434649 100644 --- a/libqwebthree/QWebThree.h +++ b/libqwebthree/QWebThree.h @@ -34,12 +34,10 @@ class QWebThree: public QObject public: QWebThree(QObject* _p); virtual ~QWebThree(); - - void poll(); - void clearWatches(); void clientDieing(); - Q_INVOKABLE void postMessage(QString _json); + Q_INVOKABLE QString callMethod(QString _json); + void syncResponse(QString _json); public slots: void onDataProcessed(QString _json, QString _addInfo); @@ -50,8 +48,7 @@ signals: void onNewId(QString _id); private: - std::vector m_watches; - std::vector m_shhWatches; + QString m_response; }; class QWebThreeConnector: public QObject, public jsonrpc::AbstractServerConnector @@ -84,7 +81,6 @@ private: _frame->disconnect(); \ _frame->addToJavaScriptWindowObject("_web3", qweb, QWebFrame::ScriptOwnership); \ _frame->addToJavaScriptWindowObject("env", _env, QWebFrame::QtOwnership); \ - _frame->evaluateJavaScript(contentsOfQResource(":/js/es6-promise-2.0.0.js")); \ _frame->evaluateJavaScript(contentsOfQResource(":/js/bignumber.min.js")); \ _frame->evaluateJavaScript(contentsOfQResource(":/js/webthree.js")); \ _frame->evaluateJavaScript(contentsOfQResource(":/js/setup.js")); \ diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 826673670..10464726e 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -41,38 +41,55 @@ TypeError ASTNode::createTypeError(string const& _description) const return TypeError() << errinfo_sourceLocation(getLocation()) << errinfo_comment(_description); } +TypePointer ContractDefinition::getType(ContractDefinition const* _currentContract) const +{ + return make_shared(make_shared(*this), _currentContract); +} + void ContractDefinition::checkTypeRequirements() { - for (ASTPointer const& base: getBaseContracts()) - base->checkTypeRequirements(); + for (ASTPointer const& baseSpecifier: getBaseContracts()) + baseSpecifier->checkTypeRequirements(); checkIllegalOverrides(); FunctionDefinition const* constructor = getConstructor(); if (constructor && !constructor->getReturnParameters().empty()) BOOST_THROW_EXCEPTION(constructor->getReturnParameterList()->createTypeError( - "Non-empty \"returns\" directive for constructor.")); + "Non-empty \"returns\" directive for constructor.")); + + FunctionDefinition const* fallbackFunction = getFallbackFunction(); + if (fallbackFunction && fallbackFunction->getScope() == this && !fallbackFunction->getParameters().empty()) + BOOST_THROW_EXCEPTION(fallbackFunction->getParameterList().createTypeError( + "Fallback function cannot take parameters.")); + + for (ASTPointer const& modifier: getFunctionModifiers()) + modifier->checkTypeRequirements(); for (ASTPointer const& function: getDefinedFunctions()) function->checkTypeRequirements(); // check for hash collisions in function signatures set> hashes; - for (auto const& hashAndFunction: getInterfaceFunctionList()) + for (auto const& it: getInterfaceFunctionList()) { - FixedHash<4> const& hash = hashAndFunction.first; + FixedHash<4> const& hash = it.first; if (hashes.count(hash)) - BOOST_THROW_EXCEPTION(createTypeError("Function signature hash collision for " + - hashAndFunction.second->getCanonicalSignature())); + BOOST_THROW_EXCEPTION(createTypeError( + std::string("Function signature hash collision for ") + + it.second->getCanonicalSignature())); hashes.insert(hash); } } -map, FunctionDefinition const*> ContractDefinition::getInterfaceFunctions() const +map, FunctionTypePointer> ContractDefinition::getInterfaceFunctions() const { - vector, FunctionDefinition const*>> exportedFunctionList = getInterfaceFunctionList(); - map, FunctionDefinition const*> exportedFunctions(exportedFunctionList.begin(), - exportedFunctionList.end()); + auto exportedFunctionList = getInterfaceFunctionList(); + + map, FunctionTypePointer> exportedFunctions; + for (auto const& it: exportedFunctionList) + exportedFunctions.insert(it); + solAssert(exportedFunctionList.size() == exportedFunctions.size(), "Hash collision at Function Definition Hash calculation"); @@ -87,17 +104,33 @@ FunctionDefinition const* ContractDefinition::getConstructor() const return nullptr; } +FunctionDefinition const* ContractDefinition::getFallbackFunction() const +{ + for (ContractDefinition const* contract: getLinearizedBaseContracts()) + for (ASTPointer const& f: contract->getDefinedFunctions()) + if (f->getName().empty()) + return f.get(); + return nullptr; +} + void ContractDefinition::checkIllegalOverrides() const { + // TODO unify this at a later point. for this we need to put the constness and the access specifier + // into the types map functions; + map modifiers; // We search from derived to base, so the stored item causes the error. for (ContractDefinition const* contract: getLinearizedBaseContracts()) + { for (ASTPointer const& function: contract->getDefinedFunctions()) { if (function->isConstructor()) - continue; // constructors can neither be overriden nor override anything - FunctionDefinition const*& override = functions[function->getName()]; + continue; // constructors can neither be overridden nor override anything + string const& name = function->getName(); + if (modifiers.count(name)) + BOOST_THROW_EXCEPTION(modifiers[name]->createTypeError("Override changes function to modifier.")); + FunctionDefinition const*& override = functions[name]; if (!override) override = function.get(); else if (override->isPublic() != function->isPublic() || @@ -105,22 +138,62 @@ void ContractDefinition::checkIllegalOverrides() const FunctionType(*override) != FunctionType(*function)) BOOST_THROW_EXCEPTION(override->createTypeError("Override changes extended function signature.")); } + for (ASTPointer const& modifier: contract->getFunctionModifiers()) + { + string const& name = modifier->getName(); + if (functions.count(name)) + BOOST_THROW_EXCEPTION(functions[name]->createTypeError("Override changes modifier to function.")); + ModifierDefinition const*& override = modifiers[name]; + if (!override) + override = modifier.get(); + else if (ModifierType(*override) != ModifierType(*modifier)) + BOOST_THROW_EXCEPTION(override->createTypeError("Override changes modifier signature.")); + } + } } -vector, FunctionDefinition const*>> const& ContractDefinition::getInterfaceFunctionList() const +std::vector> const& ContractDefinition::getInterfaceEvents() const +{ + if (!m_interfaceEvents) + { + set eventsSeen; + m_interfaceEvents.reset(new std::vector>()); + for (ContractDefinition const* contract: getLinearizedBaseContracts()) + for (ASTPointer const& e: contract->getEvents()) + if (eventsSeen.count(e->getName()) == 0) + { + eventsSeen.insert(e->getName()); + m_interfaceEvents->push_back(e); + } + } + return *m_interfaceEvents; +} + +vector, FunctionTypePointer>> const& ContractDefinition::getInterfaceFunctionList() const { if (!m_interfaceFunctionList) { set functionsSeen; - m_interfaceFunctionList.reset(new vector, FunctionDefinition const*>>()); + m_interfaceFunctionList.reset(new vector, FunctionTypePointer>>()); for (ContractDefinition const* contract: getLinearizedBaseContracts()) + { for (ASTPointer const& f: contract->getDefinedFunctions()) - if (f->isPublic() && !f->isConstructor() && functionsSeen.count(f->getName()) == 0) + if (f->isPublic() && !f->isConstructor() && !f->getName().empty() && functionsSeen.count(f->getName()) == 0) { functionsSeen.insert(f->getName()); FixedHash<4> hash(dev::sha3(f->getCanonicalSignature())); - m_interfaceFunctionList->push_back(make_pair(hash, f.get())); + m_interfaceFunctionList->push_back(make_pair(hash, make_shared(*f, false))); } + + for (ASTPointer const& v: contract->getStateVariables()) + if (v->isPublic() && functionsSeen.count(v->getName()) == 0) + { + FunctionType ftype(*v); + functionsSeen.insert(v->getName()); + FixedHash<4> hash(dev::sha3(ftype.getCanonicalSignature(v->getName()))); + m_interfaceFunctionList->push_back(make_pair(hash, make_shared(*v))); + } + } } return *m_interfaceFunctionList; } @@ -141,6 +214,11 @@ void InheritanceSpecifier::checkTypeRequirements() BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in constructer call.")); } +TypePointer StructDefinition::getType(ContractDefinition const*) const +{ + return make_shared(make_shared(*this)); +} + void StructDefinition::checkMemberTypes() const { for (ASTPointer const& member: getMembers()) @@ -169,18 +247,73 @@ void StructDefinition::checkRecursion() const } } +TypePointer FunctionDefinition::getType(ContractDefinition const*) const +{ + return make_shared(*this); +} + void FunctionDefinition::checkTypeRequirements() { for (ASTPointer const& var: getParameters() + getReturnParameters()) if (!var->getType()->canLiveOutsideStorage()) BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage.")); + for (ASTPointer const& modifier: m_functionModifiers) + modifier->checkTypeRequirements(); m_body->checkTypeRequirements(); } string FunctionDefinition::getCanonicalSignature() const { - return getName() + FunctionType(*this).getCanonicalSignature(); + return FunctionType(*this).getCanonicalSignature(getName()); +} + +Declaration::LValueType VariableDeclaration::getLValueType() const +{ + if (dynamic_cast(getScope()) || dynamic_cast(getScope())) + return Declaration::LValueType::LOCAL; + else + return Declaration::LValueType::STORAGE; +} + +TypePointer ModifierDefinition::getType(ContractDefinition const*) const +{ + return make_shared(*this); +} + +void ModifierDefinition::checkTypeRequirements() +{ + m_body->checkTypeRequirements(); +} + +void ModifierInvocation::checkTypeRequirements() +{ + m_modifierName->checkTypeRequirements(); + for (ASTPointer const& argument: m_arguments) + argument->checkTypeRequirements(); + + ModifierDefinition const* modifier = dynamic_cast(m_modifierName->getReferencedDeclaration()); + solAssert(modifier, "Function modifier not found."); + vector> const& parameters = modifier->getParameters(); + if (parameters.size() != m_arguments.size()) + BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for modifier invocation.")); + for (size_t i = 0; i < m_arguments.size(); ++i) + if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameters[i]->getType())) + BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in modifier invocation.")); +} + +void EventDefinition::checkTypeRequirements() +{ + int numIndexed = 0; + for (ASTPointer const& var: getParameters()) + { + if (var->isIndexed()) + numIndexed++; + if (!var->getType()->canLiveOutsideStorage()) + BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage.")); + } + if (numIndexed > 3) + BOOST_THROW_EXCEPTION(createTypeError("More than 3 indexed arguments for event.")); } void Block::checkTypeRequirements() @@ -218,7 +351,8 @@ void Return::checkTypeRequirements() { if (!m_expression) return; - solAssert(m_returnParameters, "Return parameters not assigned."); + if (!m_returnParameters) + BOOST_THROW_EXCEPTION(createTypeError("Return arguments not allowed.")); if (m_returnParameters->getParameters().size() != 1) BOOST_THROW_EXCEPTION(createTypeError("Different number of arguments in return statement " "than in returns declaration.")); @@ -340,8 +474,7 @@ void FunctionCall::checkTypeRequirements() //@todo for structs, we have to check the number of arguments to be equal to the // number of non-mapping members if (m_arguments.size() != 1) - BOOST_THROW_EXCEPTION(createTypeError("More than one argument for " - "explicit type conersion.")); + BOOST_THROW_EXCEPTION(createTypeError("More than one argument for explicit type conversion.")); if (!m_arguments.front()->getType()->isExplicitlyConvertibleTo(*type.getActualType())) BOOST_THROW_EXCEPTION(createTypeError("Explicit type conversion not allowed.")); m_type = type.getActualType(); @@ -394,7 +527,7 @@ void MemberAccess::checkTypeRequirements() BOOST_THROW_EXCEPTION(createTypeError("Member \"" + *m_memberName + "\" not found or not " "visible in " + type.toString())); //@todo later, this will not always be STORAGE - m_lvalue = type.getCategory() == Type::Category::STRUCT ? LValueType::STORAGE : LValueType::NONE; + m_lvalue = type.getCategory() == Type::Category::STRUCT ? Declaration::LValueType::STORAGE : Declaration::LValueType::NONE; } void IndexAccess::checkTypeRequirements() @@ -406,52 +539,17 @@ void IndexAccess::checkTypeRequirements() MappingType const& type = dynamic_cast(*m_base->getType()); m_index->expectType(*type.getKeyType()); m_type = type.getValueType(); - m_lvalue = LValueType::STORAGE; + m_lvalue = Declaration::LValueType::STORAGE; } void Identifier::checkTypeRequirements() { solAssert(m_referencedDeclaration, "Identifier not resolved."); - VariableDeclaration const* variable = dynamic_cast(m_referencedDeclaration); - if (variable) - { - if (!variable->getType()) - BOOST_THROW_EXCEPTION(createTypeError("Variable referenced before type could be determined.")); - m_type = variable->getType(); - m_lvalue = variable->isLocalVariable() ? LValueType::LOCAL : LValueType::STORAGE; - return; - } - //@todo can we unify these with TypeName::toType()? - StructDefinition const* structDef = dynamic_cast(m_referencedDeclaration); - if (structDef) - { - // note that we do not have a struct type here - m_type = make_shared(make_shared(*structDef)); - return; - } - FunctionDefinition const* functionDef = dynamic_cast(m_referencedDeclaration); - if (functionDef) - { - // a function reference is not a TypeType, because calling a TypeType converts to the type. - // Calling a function (e.g. function(12), otherContract.function(34)) does not do a type - // conversion. - m_type = make_shared(*functionDef); - return; - } - ContractDefinition const* contractDef = dynamic_cast(m_referencedDeclaration); - if (contractDef) - { - m_type = make_shared(make_shared(*contractDef), m_currentContract); - return; - } - MagicVariableDeclaration const* magicVariable = dynamic_cast(m_referencedDeclaration); - if (magicVariable) - { - m_type = magicVariable->getType(); - return; - } - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Declaration reference of unknown/forbidden type.")); + m_lvalue = m_referencedDeclaration->getLValueType(); + m_type = m_referencedDeclaration->getType(m_currentContract); + if (!m_type) + BOOST_THROW_EXCEPTION(createTypeError("Declaration referenced before type could be determined.")); } void ElementaryTypeNameExpression::checkTypeRequirements() diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 754e9254c..7bcdd99f8 100755 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -132,6 +132,8 @@ private: class Declaration: public ASTNode { public: + enum class LValueType { NONE, LOCAL, STORAGE }; + Declaration(Location const& _location, ASTPointer const& _name): ASTNode(_location), m_name(_name), m_scope(nullptr) {} @@ -142,17 +144,55 @@ public: Declaration const* getScope() const { return m_scope; } void setScope(Declaration const* _scope) { m_scope = _scope; } + /// @returns the type of expressions referencing this declaration. + /// The current contract has to be given since this context can change the type, especially of + /// contract types. + virtual TypePointer getType(ContractDefinition const* m_currentContract = nullptr) const = 0; + /// @returns the lvalue type of expressions referencing this declaration + virtual LValueType getLValueType() const { return LValueType::NONE; } + private: ASTPointer m_name; Declaration const* m_scope; }; +/** + * Abstract class that is added to each AST node that can store local variables. + */ +class VariableScope +{ +public: + void addLocalVariable(VariableDeclaration const& _localVariable) { m_localVariables.push_back(&_localVariable); } + std::vector const& getLocalVariables() const { return m_localVariables; } + +private: + std::vector m_localVariables; +}; + +/** + * Abstract class that is added to each AST node that can receive documentation. + */ +class Documented +{ +public: + explicit Documented(ASTPointer const& _documentation): m_documentation(_documentation) {} + + /// @return A shared pointer of an ASTString. + /// Can contain a nullptr in which case indicates absence of documentation + ASTPointer const& getDocumentation() const { return m_documentation; } + +protected: + ASTPointer m_documentation; +}; + +/// @} + /** * Definition of a contract. This is the only AST nodes where child nodes are not visited in * document order. It first visits all struct declarations, then all variable declarations and * finally all function declarations. */ -class ContractDefinition: public Declaration +class ContractDefinition: public Declaration, public Documented { public: ContractDefinition(Location const& _location, @@ -161,13 +201,16 @@ public: std::vector> const& _baseContracts, std::vector> const& _definedStructs, std::vector> const& _stateVariables, - std::vector> const& _definedFunctions): - Declaration(_location, _name), + std::vector> const& _definedFunctions, + std::vector> const& _functionModifiers, + std::vector> const& _events): + Declaration(_location, _name), Documented(_documentation), m_baseContracts(_baseContracts), m_definedStructs(_definedStructs), m_stateVariables(_stateVariables), m_definedFunctions(_definedFunctions), - m_documentation(_documentation) + m_functionModifiers(_functionModifiers), + m_events(_events) {} virtual void accept(ASTVisitor& _visitor) override; @@ -176,41 +219,46 @@ public: std::vector> const& getBaseContracts() const { return m_baseContracts; } std::vector> const& getDefinedStructs() const { return m_definedStructs; } std::vector> const& getStateVariables() const { return m_stateVariables; } + std::vector> const& getFunctionModifiers() const { return m_functionModifiers; } std::vector> const& getDefinedFunctions() const { return m_definedFunctions; } + std::vector> const& getEvents() const { return m_events; } + std::vector> const& getInterfaceEvents() const; + + virtual TypePointer getType(ContractDefinition const* m_currentContract) const override; /// Checks that there are no illegal overrides, that the constructor does not have a "returns" /// and calls checkTypeRequirements on all its functions. void checkTypeRequirements(); - /// @return A shared pointer of an ASTString. - /// Can contain a nullptr in which case indicates absence of documentation - ASTPointer const& getDocumentation() const { return m_documentation; } - /// @returns a map of canonical function signatures to FunctionDefinitions /// as intended for use by the ABI. - std::map, FunctionDefinition const*> getInterfaceFunctions() const; + std::map, FunctionTypePointer> getInterfaceFunctions() const; /// List of all (direct and indirect) base contracts in order from derived to base, including /// the contract itself. Available after name resolution std::vector const& getLinearizedBaseContracts() const { return m_linearizedBaseContracts; } void setLinearizedBaseContracts(std::vector const& _bases) { m_linearizedBaseContracts = _bases; } - /// Returns the constructor or nullptr if no constructor was specified + /// Returns the constructor or nullptr if no constructor was specified. FunctionDefinition const* getConstructor() const; + /// Returns the fallback function or nullptr if no constructor was specified. + FunctionDefinition const* getFallbackFunction() const; private: void checkIllegalOverrides() const; - std::vector, FunctionDefinition const*>> const& getInterfaceFunctionList() const; + std::vector, FunctionTypePointer>> const& getInterfaceFunctionList() const; std::vector> m_baseContracts; std::vector> m_definedStructs; std::vector> m_stateVariables; std::vector> m_definedFunctions; - ASTPointer m_documentation; + std::vector> m_functionModifiers; + std::vector> m_events; std::vector m_linearizedBaseContracts; - mutable std::unique_ptr, FunctionDefinition const*>>> m_interfaceFunctionList; + mutable std::unique_ptr, FunctionTypePointer>>> m_interfaceFunctionList; + mutable std::unique_ptr>> m_interfaceEvents; }; class InheritanceSpecifier: public ASTNode @@ -245,6 +293,8 @@ public: std::vector> const& getMembers() const { return m_members; } + virtual TypePointer getType(ContractDefinition const*) const override; + /// Checks that the members do not include any recursive structs and have valid types /// (e.g. no functions). void checkValidityOfMembers() const; @@ -276,7 +326,7 @@ private: std::vector> m_parameters; }; -class FunctionDefinition: public Declaration +class FunctionDefinition: public Declaration, public VariableScope, public Documented { public: FunctionDefinition(Location const& _location, ASTPointer const& _name, @@ -285,14 +335,16 @@ public: ASTPointer const& _documentation, ASTPointer const& _parameters, bool _isDeclaredConst, + std::vector> const& _modifiers, ASTPointer const& _returnParameters, ASTPointer const& _body): - Declaration(_location, _name), m_isPublic(_isPublic), m_isConstructor(_isConstructor), + Declaration(_location, _name), Documented(_documentation), + m_isPublic(_isPublic), m_isConstructor(_isConstructor), m_parameters(_parameters), m_isDeclaredConst(_isDeclaredConst), + m_functionModifiers(_modifiers), m_returnParameters(_returnParameters), - m_body(_body), - m_documentation(_documentation) + m_body(_body) {} virtual void accept(ASTVisitor& _visitor) override; @@ -301,17 +353,14 @@ public: bool isPublic() const { return m_isPublic; } bool isConstructor() const { return m_isConstructor; } bool isDeclaredConst() const { return m_isDeclaredConst; } + std::vector> const& getModifiers() const { return m_functionModifiers; } std::vector> const& getParameters() const { return m_parameters->getParameters(); } ParameterList const& getParameterList() const { return *m_parameters; } std::vector> const& getReturnParameters() const { return m_returnParameters->getParameters(); } ASTPointer const& getReturnParameterList() const { return m_returnParameters; } Block const& getBody() const { return *m_body; } - /// @return A shared pointer of an ASTString. - /// Can contain a nullptr in which case indicates absence of documentation - ASTPointer const& getDocumentation() const { return m_documentation; } - void addLocalVariable(VariableDeclaration const& _localVariable) { m_localVariables.push_back(&_localVariable); } - std::vector const& getLocalVariables() const { return m_localVariables; } + virtual TypePointer getType(ContractDefinition const*) const override; /// Checks that all parameters have allowed types and calls checkTypeRequirements on the body. void checkTypeRequirements(); @@ -326,11 +375,9 @@ private: bool m_isConstructor; ASTPointer m_parameters; bool m_isDeclaredConst; + std::vector> m_functionModifiers; ASTPointer m_returnParameters; ASTPointer m_body; - ASTPointer m_documentation; - - std::vector m_localVariables; }; /** @@ -341,8 +388,10 @@ class VariableDeclaration: public Declaration { public: VariableDeclaration(Location const& _location, ASTPointer const& _type, - ASTPointer const& _name): - Declaration(_location, _name), m_typeName(_type) {} + ASTPointer const& _name, bool _isPublic, bool _isStateVar = false, + bool _isIndexed = false): + Declaration(_location, _name), m_typeName(_type), + m_isPublic(_isPublic), m_isStateVariable(_isStateVar), m_isIndexed(_isIndexed) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; @@ -350,17 +399,108 @@ public: /// Returns the declared or inferred type. Can be an empty pointer if no type was explicitly /// declared and there is no assignment to the variable that fixes the type. - std::shared_ptr const& getType() const { return m_type; } + TypePointer getType(ContractDefinition const* = nullptr) const { return m_type; } void setType(std::shared_ptr const& _type) { m_type = _type; } + virtual LValueType getLValueType() const override; bool isLocalVariable() const { return !!dynamic_cast(getScope()); } + bool isPublic() const { return m_isPublic; } + bool isStateVariable() const { return m_isStateVariable; } + bool isIndexed() const { return m_isIndexed; } private: - ASTPointer m_typeName; ///< can be empty ("var") + ASTPointer m_typeName; ///< can be empty ("var") + bool m_isPublic; ///< Whether there is an accessor for it or not + bool m_isStateVariable; ///< Whether or not this is a contract state variable + bool m_isIndexed; ///< Whether this is an indexed variable (used by events). std::shared_ptr m_type; ///< derived type, initially empty }; +/** + * Definition of a function modifier. + */ +class ModifierDefinition: public Declaration, public VariableScope, public Documented +{ +public: + ModifierDefinition(Location const& _location, + ASTPointer const& _name, + ASTPointer const& _documentation, + ASTPointer const& _parameters, + ASTPointer const& _body): + Declaration(_location, _name), Documented(_documentation), + m_parameters(_parameters), m_body(_body) {} + + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + std::vector> const& getParameters() const { return m_parameters->getParameters(); } + ParameterList const& getParameterList() const { return *m_parameters; } + Block const& getBody() const { return *m_body; } + + virtual TypePointer getType(ContractDefinition const* = nullptr) const override; + + void checkTypeRequirements(); + +private: + ASTPointer m_parameters; + ASTPointer m_body; +}; + +/** + * Invocation/usage of a modifier in a function header. + */ +class ModifierInvocation: public ASTNode +{ +public: + ModifierInvocation(Location const& _location, ASTPointer const& _name, + std::vector> _arguments): + ASTNode(_location), m_modifierName(_name), m_arguments(_arguments) {} + + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + ASTPointer const& getName() const { return m_modifierName; } + std::vector> const& getArguments() const { return m_arguments; } + + void checkTypeRequirements(); + +private: + ASTPointer m_modifierName; + std::vector> m_arguments; +}; + +/** + * Definition of a (loggable) event. + */ +class EventDefinition: public Declaration, public VariableScope, public Documented +{ +public: + EventDefinition(Location const& _location, + ASTPointer const& _name, + ASTPointer const& _documentation, + ASTPointer const& _parameters): + Declaration(_location, _name), Documented(_documentation), m_parameters(_parameters) {} + + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + std::vector> const& getParameters() const { return m_parameters->getParameters(); } + ParameterList const& getParameterList() const { return *m_parameters; } + Block const& getBody() const { return *m_body; } + + virtual TypePointer getType(ContractDefinition const* = nullptr) const override + { + return std::make_shared(*this); + } + + void checkTypeRequirements(); + +private: + ASTPointer m_parameters; + ASTPointer m_body; +}; + /** * Pseudo AST node that is used as declaration for "this", "msg", "tx", "block" and the global * functions when such an identifier is encountered. Will never have a valid location in the source code. @@ -375,7 +515,7 @@ public: virtual void accept(ASTConstVisitor&) const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("MagicVariableDeclaration used inside real AST.")); } - std::shared_ptr const& getType() const { return m_type; } + virtual TypePointer getType(ContractDefinition const* = nullptr) const override { return m_type; } private: std::shared_ptr m_type; @@ -502,6 +642,21 @@ private: std::vector> m_statements; }; +/** + * Special placeholder statement denoted by "_" used in function modifiers. This is replaced by + * the original function when the modifier is applied. + */ +class PlaceholderStatement: public Statement +{ +public: + PlaceholderStatement(Location const& _location): Statement(_location) {} + + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + virtual void checkTypeRequirements() override { } +}; + /** * If-statement with an optional "else" part. Note that "else if" is modeled by having a new * if-statement as the false (else) body. @@ -618,12 +773,8 @@ public: virtual void accept(ASTConstVisitor& _visitor) const override; virtual void checkTypeRequirements() override; - void setFunctionReturnParameters(ParameterList const& _parameters) { m_returnParameters = &_parameters; } - ParameterList const& getFunctionReturnParameters() const - { - solAssert(m_returnParameters, ""); - return *m_returnParameters; - } + void setFunctionReturnParameters(ParameterList const* _parameters) { m_returnParameters = _parameters; } + ParameterList const* getFunctionReturnParameters() const { return m_returnParameters; } Expression const* getExpression() const { return m_expression.get(); } private: @@ -686,16 +837,13 @@ private: */ class Expression: public ASTNode { -protected: - enum class LValueType { NONE, LOCAL, STORAGE }; - public: - Expression(Location const& _location): ASTNode(_location), m_lvalue(LValueType::NONE), m_lvalueRequested(false) {} + Expression(Location const& _location): ASTNode(_location) {} virtual void checkTypeRequirements() = 0; std::shared_ptr const& getType() const { return m_type; } - bool isLValue() const { return m_lvalue != LValueType::NONE; } - bool isLocalLValue() const { return m_lvalue == LValueType::LOCAL; } + bool isLValue() const { return m_lvalue != Declaration::LValueType::NONE; } + bool isLocalLValue() const { return m_lvalue == Declaration::LValueType::LOCAL; } /// Helper function, infer the type via @ref checkTypeRequirements and then check that it /// is implicitly convertible to @a _expectedType. If not, throw exception. @@ -712,9 +860,9 @@ protected: std::shared_ptr m_type; //! If this expression is an lvalue (i.e. something that can be assigned to) and is stored //! locally or in storage. This is set during calls to @a checkTypeRequirements() - LValueType m_lvalue; + Declaration::LValueType m_lvalue = Declaration::LValueType::NONE; //! Whether the outer expression requested the address (true) or the value (false) of this expression. - bool m_lvalueRequested; + bool m_lvalueRequested = false; }; /// Assignment, can also be a compound assignment. @@ -979,5 +1127,6 @@ private: /// @} + } } diff --git a/libsolidity/ASTForward.h b/libsolidity/ASTForward.h index da0a88122..22015f26b 100644 --- a/libsolidity/ASTForward.h +++ b/libsolidity/ASTForward.h @@ -43,6 +43,9 @@ class StructDefinition; class ParameterList; class FunctionDefinition; class VariableDeclaration; +class ModifierDefinition; +class ModifierInvocation; +class EventDefinition; class MagicVariableDeclaration; class TypeName; class ElementaryTypeName; @@ -50,6 +53,7 @@ class UserDefinedTypeName; class Mapping; class Statement; class Block; +class PlaceholderStatement; class IfStatement; class BreakableStatement; class WhileStatement; @@ -72,6 +76,8 @@ class Identifier; class ElementaryTypeNameExpression; class Literal; +class VariableScope; + // Used as pointers to AST nodes, to be replaced by more clever pointers, e.g. pointers which do // not do reference counting but point to a special memory area that is completely released // explicitly. diff --git a/libsolidity/ASTJsonConverter.cpp b/libsolidity/ASTJsonConverter.cpp index 04ddee0a7..d9332990d 100644 --- a/libsolidity/ASTJsonConverter.cpp +++ b/libsolidity/ASTJsonConverter.cpp @@ -118,9 +118,10 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node) bool ASTJsonConverter::visit(VariableDeclaration const& _node) { + bool isLocalVariable = (_node.getLValueType() == VariableDeclaration::LValueType::LOCAL); addJsonNode("VariableDeclaration", { make_pair("name", _node.getName()), - make_pair("local", boost::lexical_cast(_node.isLocalVariable()))}, + make_pair("local", boost::lexical_cast(isLocalVariable))}, true); return true; } diff --git a/libsolidity/ASTPrinter.cpp b/libsolidity/ASTPrinter.cpp index 916fca1ef..949740e89 100644 --- a/libsolidity/ASTPrinter.cpp +++ b/libsolidity/ASTPrinter.cpp @@ -57,6 +57,13 @@ bool ASTPrinter::visit(ContractDefinition const& _node) return goDeeper(); } +bool ASTPrinter::visit(InheritanceSpecifier const& _node) +{ + writeLine("InheritanceSpecifier \"" + _node.getName()->getName() + "\""); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(StructDefinition const& _node) { writeLine("StructDefinition \"" + _node.getName() + "\""); @@ -87,6 +94,27 @@ bool ASTPrinter::visit(VariableDeclaration const& _node) return goDeeper(); } +bool ASTPrinter::visit(ModifierDefinition const& _node) +{ + writeLine("ModifierDefinition \"" + _node.getName() + "\""); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(ModifierInvocation const& _node) +{ + writeLine("ModifierInvocation \"" + _node.getName()->getName() + "\""); + printSourcePart(_node); + return goDeeper(); +} + +bool ASTPrinter::visit(EventDefinition const& _node) +{ + writeLine("EventDefinition \"" + _node.getName() + "\""); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(TypeName const& _node) { writeLine("TypeName"); @@ -129,6 +157,13 @@ bool ASTPrinter::visit(Block const& _node) return goDeeper(); } +bool ASTPrinter::visit(PlaceholderStatement const& _node) +{ + writeLine("PlaceholderStatement"); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(IfStatement const& _node) { writeLine("IfStatement"); @@ -302,6 +337,11 @@ void ASTPrinter::endVisit(ContractDefinition const&) m_indentation--; } +void ASTPrinter::endVisit(InheritanceSpecifier const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(StructDefinition const&) { m_indentation--; @@ -322,6 +362,21 @@ void ASTPrinter::endVisit(VariableDeclaration const&) m_indentation--; } +void ASTPrinter::endVisit(ModifierDefinition const&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(ModifierInvocation const&) +{ + m_indentation--; +} + +void ASTPrinter::endVisit(EventDefinition const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(TypeName const&) { m_indentation--; @@ -352,6 +407,11 @@ void ASTPrinter::endVisit(Block const&) m_indentation--; } +void ASTPrinter::endVisit(PlaceholderStatement const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(IfStatement const&) { m_indentation--; diff --git a/libsolidity/ASTPrinter.h b/libsolidity/ASTPrinter.h index fc5fb4acb..ebc163e31 100644 --- a/libsolidity/ASTPrinter.h +++ b/libsolidity/ASTPrinter.h @@ -44,16 +44,21 @@ public: bool visit(ImportDirective const& _node) override; bool visit(ContractDefinition const& _node) override; + bool visit(InheritanceSpecifier const& _node) override; bool visit(StructDefinition const& _node) override; bool visit(ParameterList const& _node) override; bool visit(FunctionDefinition const& _node) override; bool visit(VariableDeclaration const& _node) override; + bool visit(ModifierDefinition const& _node) override; + bool visit(ModifierInvocation const& _node) override; + bool visit(EventDefinition const& _node) override; bool visit(TypeName const& _node) override; bool visit(ElementaryTypeName const& _node) override; bool visit(UserDefinedTypeName const& _node) override; bool visit(Mapping const& _node) override; bool visit(Statement const& _node) override; bool visit(Block const& _node) override; + bool visit(PlaceholderStatement const& _node) override; bool visit(IfStatement const& _node) override; bool visit(BreakableStatement const& _node) override; bool visit(WhileStatement const& _node) override; @@ -78,16 +83,21 @@ public: void endVisit(ImportDirective const&) override; void endVisit(ContractDefinition const&) override; + void endVisit(InheritanceSpecifier const&) override; void endVisit(StructDefinition const&) override; void endVisit(ParameterList const&) override; void endVisit(FunctionDefinition const&) override; void endVisit(VariableDeclaration const&) override; + void endVisit(ModifierDefinition const&) override; + void endVisit(ModifierInvocation const&) override; + void endVisit(EventDefinition const&) override; void endVisit(TypeName const&) override; void endVisit(ElementaryTypeName const&) override; void endVisit(UserDefinedTypeName const&) override; void endVisit(Mapping const&) override; void endVisit(Statement const&) override; void endVisit(Block const&) override; + void endVisit(PlaceholderStatement const&) override; void endVisit(IfStatement const&) override; void endVisit(BreakableStatement const&) override; void endVisit(WhileStatement const&) override; diff --git a/libsolidity/ASTVisitor.h b/libsolidity/ASTVisitor.h index 33a8a3383..294902778 100644 --- a/libsolidity/ASTVisitor.h +++ b/libsolidity/ASTVisitor.h @@ -45,16 +45,21 @@ public: virtual bool visit(SourceUnit&) { return true; } virtual bool visit(ImportDirective&) { return true; } virtual bool visit(ContractDefinition&) { return true; } + virtual bool visit(InheritanceSpecifier&) { return true; } virtual bool visit(StructDefinition&) { return true; } virtual bool visit(ParameterList&) { return true; } virtual bool visit(FunctionDefinition&) { return true; } virtual bool visit(VariableDeclaration&) { return true; } + virtual bool visit(ModifierDefinition&) { return true; } + virtual bool visit(ModifierInvocation&) { return true; } + virtual bool visit(EventDefinition&) { return true; } virtual bool visit(TypeName&) { return true; } virtual bool visit(ElementaryTypeName&) { return true; } virtual bool visit(UserDefinedTypeName&) { return true; } virtual bool visit(Mapping&) { return true; } virtual bool visit(Statement&) { return true; } virtual bool visit(Block&) { return true; } + virtual bool visit(PlaceholderStatement&) { return true; } virtual bool visit(IfStatement&) { return true; } virtual bool visit(BreakableStatement&) { return true; } virtual bool visit(WhileStatement&) { return true; } @@ -81,16 +86,21 @@ public: virtual void endVisit(SourceUnit&) { } virtual void endVisit(ImportDirective&) { } virtual void endVisit(ContractDefinition&) { } + virtual void endVisit(InheritanceSpecifier&) { } virtual void endVisit(StructDefinition&) { } virtual void endVisit(ParameterList&) { } virtual void endVisit(FunctionDefinition&) { } virtual void endVisit(VariableDeclaration&) { } + virtual void endVisit(ModifierDefinition&) { } + virtual void endVisit(ModifierInvocation&) { } + virtual void endVisit(EventDefinition&) { } virtual void endVisit(TypeName&) { } virtual void endVisit(ElementaryTypeName&) { } virtual void endVisit(UserDefinedTypeName&) { } virtual void endVisit(Mapping&) { } virtual void endVisit(Statement&) { } virtual void endVisit(Block&) { } + virtual void endVisit(PlaceholderStatement&) { } virtual void endVisit(IfStatement&) { } virtual void endVisit(BreakableStatement&) { } virtual void endVisit(WhileStatement&) { } @@ -121,16 +131,21 @@ public: virtual bool visit(SourceUnit const&) { return true; } virtual bool visit(ImportDirective const&) { return true; } virtual bool visit(ContractDefinition const&) { return true; } + virtual bool visit(InheritanceSpecifier const&) { return true; } virtual bool visit(StructDefinition const&) { return true; } virtual bool visit(ParameterList const&) { return true; } virtual bool visit(FunctionDefinition const&) { return true; } virtual bool visit(VariableDeclaration const&) { return true; } + virtual bool visit(ModifierDefinition const&) { return true; } + virtual bool visit(ModifierInvocation const&) { return true; } + virtual bool visit(EventDefinition const&) { return true; } virtual bool visit(TypeName const&) { return true; } virtual bool visit(ElementaryTypeName const&) { return true; } virtual bool visit(UserDefinedTypeName const&) { return true; } virtual bool visit(Mapping const&) { return true; } virtual bool visit(Statement const&) { return true; } virtual bool visit(Block const&) { return true; } + virtual bool visit(PlaceholderStatement const&) { return true; } virtual bool visit(IfStatement const&) { return true; } virtual bool visit(BreakableStatement const&) { return true; } virtual bool visit(WhileStatement const&) { return true; } @@ -157,16 +172,21 @@ public: virtual void endVisit(SourceUnit const&) { } virtual void endVisit(ImportDirective const&) { } virtual void endVisit(ContractDefinition const&) { } + virtual void endVisit(InheritanceSpecifier const&) { } virtual void endVisit(StructDefinition const&) { } virtual void endVisit(ParameterList const&) { } virtual void endVisit(FunctionDefinition const&) { } virtual void endVisit(VariableDeclaration const&) { } + virtual void endVisit(ModifierDefinition const&) { } + virtual void endVisit(ModifierInvocation const&) { } + virtual void endVisit(EventDefinition const&) { } virtual void endVisit(TypeName const&) { } virtual void endVisit(ElementaryTypeName const&) { } virtual void endVisit(UserDefinedTypeName const&) { } virtual void endVisit(Mapping const&) { } virtual void endVisit(Statement const&) { } virtual void endVisit(Block const&) { } + virtual void endVisit(PlaceholderStatement const&) { } virtual void endVisit(IfStatement const&) { } virtual void endVisit(BreakableStatement const&) { } virtual void endVisit(WhileStatement const&) { } diff --git a/libsolidity/AST_accept.h b/libsolidity/AST_accept.h index b77cfe1c6..38108cd72 100644 --- a/libsolidity/AST_accept.h +++ b/libsolidity/AST_accept.h @@ -64,6 +64,8 @@ void ContractDefinition::accept(ASTVisitor& _visitor) listAccept(m_baseContracts, _visitor); listAccept(m_definedStructs, _visitor); listAccept(m_stateVariables, _visitor); + listAccept(m_events, _visitor); + listAccept(m_functionModifiers, _visitor); listAccept(m_definedFunctions, _visitor); } _visitor.endVisit(*this); @@ -76,6 +78,8 @@ void ContractDefinition::accept(ASTConstVisitor& _visitor) const listAccept(m_baseContracts, _visitor); listAccept(m_definedStructs, _visitor); listAccept(m_stateVariables, _visitor); + listAccept(m_events, _visitor); + listAccept(m_functionModifiers, _visitor); listAccept(m_definedFunctions, _visitor); } _visitor.endVisit(*this); @@ -142,6 +146,7 @@ void FunctionDefinition::accept(ASTVisitor& _visitor) m_parameters->accept(_visitor); if (m_returnParameters) m_returnParameters->accept(_visitor); + listAccept(m_functionModifiers, _visitor); m_body->accept(_visitor); } _visitor.endVisit(*this); @@ -154,6 +159,7 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const m_parameters->accept(_visitor); if (m_returnParameters) m_returnParameters->accept(_visitor); + listAccept(m_functionModifiers, _visitor); m_body->accept(_visitor); } _visitor.endVisit(*this); @@ -175,6 +181,60 @@ void VariableDeclaration::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void ModifierDefinition::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_parameters->accept(_visitor); + m_body->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void ModifierDefinition::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_parameters->accept(_visitor); + m_body->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void ModifierInvocation::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_modifierName->accept(_visitor); + listAccept(m_arguments, _visitor); + } + _visitor.endVisit(*this); +} + +void ModifierInvocation::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_modifierName->accept(_visitor); + listAccept(m_arguments, _visitor); + } + _visitor.endVisit(*this); +} + +void EventDefinition::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + m_parameters->accept(_visitor); + _visitor.endVisit(*this); +} + +void EventDefinition::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + m_parameters->accept(_visitor); + _visitor.endVisit(*this); +} + void TypeName::accept(ASTVisitor& _visitor) { _visitor.visit(*this); @@ -245,6 +305,18 @@ void Block::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void PlaceholderStatement::accept(ASTVisitor& _visitor) +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + +void PlaceholderStatement::accept(ASTConstVisitor& _visitor) const +{ + _visitor.visit(*this); + _visitor.endVisit(*this); +} + void IfStatement::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) diff --git a/libsolidity/BaseTypes.h b/libsolidity/BaseTypes.h index a8fd77c86..057289ef3 100644 --- a/libsolidity/BaseTypes.h +++ b/libsolidity/BaseTypes.h @@ -41,6 +41,8 @@ struct Location start(_start), end(_end), sourceName(_sourceName) { } Location(): start(-1), end(-1) { } + bool isEmpty() const { return start == -1 && end == -1; } + int start; int end; std::shared_ptr sourceName; @@ -49,6 +51,8 @@ struct Location /// Stream output for Location (used e.g. in boost exceptions). inline std::ostream& operator<<(std::ostream& _out, Location const& _location) { + if (_location.isEmpty()) + return _out << "NO_LOCATION_SPECIFIED"; return _out << *_location.sourceName << "[" << _location.start << "," << _location.end << ")"; } diff --git a/libsolidity/CallGraph.cpp b/libsolidity/CallGraph.cpp deleted file mode 100644 index a671796bd..000000000 --- a/libsolidity/CallGraph.cpp +++ /dev/null @@ -1,104 +0,0 @@ - -/* - 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 . -*/ -/** - * @author Christian - * @date 2014 - * Callgraph of functions inside a contract. - */ - -#include -#include - -using namespace std; - -namespace dev -{ -namespace solidity -{ - -void CallGraph::addNode(ASTNode const& _node) -{ - _node.accept(*this); -} - -set const& CallGraph::getCalls() -{ - computeCallGraph(); - return m_functionsSeen; -} - -void CallGraph::computeCallGraph() -{ - while (!m_workQueue.empty()) - { - m_workQueue.front()->accept(*this); - m_workQueue.pop(); - } -} - -bool CallGraph::visit(Identifier const& _identifier) -{ - FunctionDefinition const* fun = dynamic_cast(_identifier.getReferencedDeclaration()); - if (fun) - { - if (m_overrideResolver) - fun = (*m_overrideResolver)(fun->getName()); - solAssert(fun, "Error finding override for function " + fun->getName()); - addFunction(*fun); - } - return true; -} - -bool CallGraph::visit(FunctionDefinition const& _function) -{ - addFunction(_function); - return true; -} - -bool CallGraph::visit(MemberAccess const& _memberAccess) -{ - // used for "BaseContract.baseContractFunction" - if (_memberAccess.getExpression().getType()->getCategory() == Type::Category::TYPE) - { - TypeType const& type = dynamic_cast(*_memberAccess.getExpression().getType()); - if (type.getMembers().getMemberType(_memberAccess.getMemberName())) - { - ContractDefinition const& contract = dynamic_cast(*type.getActualType()) - .getContractDefinition(); - for (ASTPointer const& function: contract.getDefinedFunctions()) - if (function->getName() == _memberAccess.getMemberName()) - { - addFunction(*function); - return true; - } - } - } - return true; -} - -void CallGraph::addFunction(FunctionDefinition const& _function) -{ - if (!m_functionsSeen.count(&_function)) - { - m_functionsSeen.insert(&_function); - m_workQueue.push(&_function); - } -} - -} -} diff --git a/libsolidity/CallGraph.h b/libsolidity/CallGraph.h deleted file mode 100644 index 90176e7e6..000000000 --- a/libsolidity/CallGraph.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - 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 . -*/ -/** - * @author Christian - * @date 2014 - * Callgraph of functions inside a contract. - */ - -#include -#include -#include -#include -#include - -namespace dev -{ -namespace solidity -{ - -/** - * Can be used to compute the graph of calls (or rather references) between functions of the same - * contract. Current functionality is limited to computing all functions that are directly - * or indirectly called by some functions. - */ -class CallGraph: private ASTConstVisitor -{ -public: - using OverrideResolver = std::function; - - CallGraph(OverrideResolver const& _overrideResolver): m_overrideResolver(&_overrideResolver) {} - - void addNode(ASTNode const& _node); - - std::set const& getCalls(); - -private: - virtual bool visit(FunctionDefinition const& _function) override; - virtual bool visit(Identifier const& _identifier) override; - virtual bool visit(MemberAccess const& _memberAccess) override; - - void computeCallGraph(); - void addFunction(FunctionDefinition const& _function); - - OverrideResolver const* m_overrideResolver; - std::set m_functionsSeen; - std::queue m_workQueue; -}; - -} -} diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 5a434a71b..3c46d4552 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -28,7 +28,6 @@ #include #include #include -#include using namespace std; @@ -40,17 +39,14 @@ void Compiler::compileContract(ContractDefinition const& _contract, { m_context = CompilerContext(); // clear it just in case initializeContext(_contract, _contracts); - - for (ContractDefinition const* contract: _contract.getLinearizedBaseContracts()) - for (ASTPointer const& function: contract->getDefinedFunctions()) - if (!function->isConstructor()) - m_context.addFunction(*function); - appendFunctionSelector(_contract); - for (ContractDefinition const* contract: _contract.getLinearizedBaseContracts()) - for (ASTPointer const& function: contract->getDefinedFunctions()) - if (!function->isConstructor()) - function->accept(*this); + set functions = m_context.getFunctionsWithoutCode(); + while (!functions.empty()) + { + for (Declaration const* function: functions) + function->accept(*this); + functions = m_context.getFunctionsWithoutCode(); + } // Swap the runtime context with the creation-time context swap(m_context, m_runtimeContext); @@ -62,6 +58,7 @@ void Compiler::initializeContext(ContractDefinition const& _contract, map const& _contracts) { m_context.setCompiledContracts(_contracts); + m_context.setInheritanceHierarchy(_contract.getLinearizedBaseContracts()); registerStateVariables(_contract); } @@ -69,54 +66,18 @@ void Compiler::packIntoContractCreator(ContractDefinition const& _contract, Comp { // arguments for base constructors, filled in derived-to-base order map> const*> baseArguments; - set neededFunctions; - set nodesUsedInConstructors; - // Determine the arguments that are used for the base constructors and also which functions - // are needed at compile time. + // Determine the arguments that are used for the base constructors. std::vector const& bases = _contract.getLinearizedBaseContracts(); for (ContractDefinition const* contract: bases) - { - if (FunctionDefinition const* constructor = contract->getConstructor()) - nodesUsedInConstructors.insert(constructor); for (ASTPointer const& base: contract->getBaseContracts()) { ContractDefinition const* baseContract = dynamic_cast( base->getName()->getReferencedDeclaration()); solAssert(baseContract, ""); if (baseArguments.count(baseContract) == 0) - { baseArguments[baseContract] = &base->getArguments(); - for (ASTPointer const& arg: base->getArguments()) - nodesUsedInConstructors.insert(arg.get()); - } } - } - - auto overrideResolver = [&](string const& _name) -> FunctionDefinition const* - { - for (ContractDefinition const* contract: bases) - for (ASTPointer const& function: contract->getDefinedFunctions()) - if (!function->isConstructor() && function->getName() == _name) - return function.get(); - return nullptr; - }; - - neededFunctions = getFunctionsCalled(nodesUsedInConstructors, overrideResolver); - - // First add all overrides (or the functions themselves if there is no override) - for (FunctionDefinition const* fun: neededFunctions) - { - FunctionDefinition const* override = nullptr; - if (!fun->isConstructor()) - override = overrideResolver(fun->getName()); - if (!!override && neededFunctions.count(override)) - m_context.addFunction(*override); - } - // now add the rest - for (FunctionDefinition const* fun: neededFunctions) - if (fun->isConstructor() || overrideResolver(fun->getName()) != fun) - m_context.addFunction(*fun); // Call constructors in base-to-derived order. // The Constructor for the most derived contract is called later. @@ -138,10 +99,14 @@ void Compiler::packIntoContractCreator(ContractDefinition const& _contract, Comp m_context << eth::Instruction::DUP1 << sub << u256(0) << eth::Instruction::CODECOPY; m_context << u256(0) << eth::Instruction::RETURN; - // note that we have to explicitly include all used functions because of absolute jump - // labels - for (FunctionDefinition const* fun: neededFunctions) - fun->accept(*this); + // note that we have to include the functions again because of absolute jump labels + set functions = m_context.getFunctionsWithoutCode(); + while (!functions.empty()) + { + for (Declaration const* function: functions) + function->accept(*this); + functions = m_context.getFunctionsWithoutCode(); + } } void Compiler::appendBaseConstructorCall(FunctionDefinition const& _constructor, @@ -150,11 +115,7 @@ void Compiler::appendBaseConstructorCall(FunctionDefinition const& _constructor, FunctionType constructorType(_constructor); eth::AssemblyItem returnLabel = m_context.pushNewTag(); for (unsigned i = 0; i < _arguments.size(); ++i) - { - compileExpression(*_arguments[i]); - ExpressionCompiler::appendTypeConversion(m_context, *_arguments[i]->getType(), - *constructorType.getParameterTypes()[i]); - } + compileExpression(*_arguments[i], constructorType.getParameterTypes()[i]); m_context.appendJumpTo(m_context.getFunctionEntryLabel(_constructor)); m_context << returnLabel; } @@ -166,71 +127,69 @@ void Compiler::appendConstructorCall(FunctionDefinition const& _constructor) unsigned argumentSize = 0; for (ASTPointer const& var: _constructor.getParameters()) argumentSize += CompilerUtils::getPaddedSize(var->getType()->getCalldataEncodedSize()); + if (argumentSize > 0) { m_context << u256(argumentSize); m_context.appendProgramSize(); m_context << u256(CompilerUtils::dataStartOffset); // copy it to byte four as expected for ABI calls m_context << eth::Instruction::CODECOPY; - appendCalldataUnpacker(_constructor, true); + appendCalldataUnpacker(FunctionType(_constructor).getParameterTypes(), true); } m_context.appendJumpTo(m_context.getFunctionEntryLabel(_constructor)); m_context << returnTag; } -set Compiler::getFunctionsCalled(set const& _nodes, - function const& _resolveOverrides) -{ - CallGraph callgraph(_resolveOverrides); - for (ASTNode const* node: _nodes) - callgraph.addNode(*node); - return callgraph.getCalls(); -} - void Compiler::appendFunctionSelector(ContractDefinition const& _contract) { - map, FunctionDefinition const*> interfaceFunctions = _contract.getInterfaceFunctions(); + map, FunctionTypePointer> interfaceFunctions = _contract.getInterfaceFunctions(); map, const eth::AssemblyItem> callDataUnpackerEntryPoints; // retrieve the function signature hash from the calldata - m_context << u256(1) << u256(0); - CompilerUtils(m_context).loadFromMemory(0, 4, false, true); + if (!interfaceFunctions.empty()) + CompilerUtils(m_context).loadFromMemory(0, 4, false, true); // stack now is: 1 0 - // for (auto it = interfaceFunctions.cbegin(); it != interfaceFunctions.cend(); ++it) for (auto const& it: interfaceFunctions) { callDataUnpackerEntryPoints.insert(std::make_pair(it.first, m_context.newTag())); m_context << eth::dupInstruction(1) << u256(FixedHash<4>::Arith(it.first)) << eth::Instruction::EQ; m_context.appendConditionalJumpTo(callDataUnpackerEntryPoints.at(it.first)); } - m_context << eth::Instruction::STOP; // function not found + if (FunctionDefinition const* fallback = _contract.getFallbackFunction()) + { + eth::AssemblyItem returnTag = m_context.pushNewTag(); + fallback->accept(*this); + m_context << returnTag; + appendReturnValuePacker(FunctionType(*fallback).getReturnParameterTypes()); + } + else + m_context << eth::Instruction::STOP; // function not found for (auto const& it: interfaceFunctions) { - FunctionDefinition const& function = *it.second; + FunctionTypePointer const& functionType = it.second; m_context << callDataUnpackerEntryPoints.at(it.first); eth::AssemblyItem returnTag = m_context.pushNewTag(); - appendCalldataUnpacker(function); - m_context.appendJumpTo(m_context.getFunctionEntryLabel(function)); + appendCalldataUnpacker(functionType->getParameterTypes()); + m_context.appendJumpTo(m_context.getFunctionEntryLabel(it.second->getDeclaration())); m_context << returnTag; - appendReturnValuePacker(function); + appendReturnValuePacker(functionType->getReturnParameterTypes()); } } -unsigned Compiler::appendCalldataUnpacker(FunctionDefinition const& _function, bool _fromMemory) +unsigned Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory) { // We do not check the calldata size, everything is zero-padded. unsigned dataOffset = CompilerUtils::dataStartOffset; // the 4 bytes of the function hash signature //@todo this can be done more efficiently, saving some CALLDATALOAD calls - for (ASTPointer const& var: _function.getParameters()) + for (TypePointer const& type: _typeParameters) { - unsigned const c_numBytes = var->getType()->getCalldataEncodedSize(); + unsigned const c_numBytes = type->getCalldataEncodedSize(); if (c_numBytes > 32) BOOST_THROW_EXCEPTION(CompilerError() - << errinfo_sourceLocation(var->getLocation()) - << errinfo_comment("Type " + var->getType()->toString() + " not yet supported.")); - bool const c_leftAligned = var->getType()->getCategory() == Type::Category::STRING; + << errinfo_comment("Type " + type->toString() + " not yet supported.")); + bool const c_leftAligned = type->getCategory() == Type::Category::STRING; bool const c_padToWords = true; dataOffset += CompilerUtils(m_context).loadFromMemory(dataOffset, c_numBytes, c_leftAligned, !_fromMemory, c_padToWords); @@ -238,26 +197,26 @@ unsigned Compiler::appendCalldataUnpacker(FunctionDefinition const& _function, b return dataOffset; } -void Compiler::appendReturnValuePacker(FunctionDefinition const& _function) +void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters) { //@todo this can be also done more efficiently unsigned dataOffset = 0; - vector> const& parameters = _function.getReturnParameters(); - unsigned stackDepth = CompilerUtils(m_context).getSizeOnStack(parameters); - for (unsigned i = 0; i < parameters.size(); ++i) + unsigned stackDepth = 0; + for (TypePointer const& type: _typeParameters) + stackDepth += type->getSizeOnStack(); + + for (TypePointer const& type: _typeParameters) { - Type const& paramType = *parameters[i]->getType(); - unsigned numBytes = paramType.getCalldataEncodedSize(); + unsigned numBytes = type->getCalldataEncodedSize(); if (numBytes > 32) BOOST_THROW_EXCEPTION(CompilerError() - << errinfo_sourceLocation(parameters[i]->getLocation()) - << errinfo_comment("Type " + paramType.toString() + " not yet supported.")); - CompilerUtils(m_context).copyToStackTop(stackDepth, paramType); - ExpressionCompiler::appendTypeConversion(m_context, paramType, paramType, true); - bool const c_leftAligned = paramType.getCategory() == Type::Category::STRING; + << errinfo_comment("Type " + type->toString() + " not yet supported.")); + CompilerUtils(m_context).copyToStackTop(stackDepth, *type); + ExpressionCompiler::appendTypeConversion(m_context, *type, *type, true); + bool const c_leftAligned = type->getCategory() == Type::Category::STRING; bool const c_padToWords = true; dataOffset += CompilerUtils(m_context).storeInMemory(dataOffset, numBytes, c_leftAligned, c_padToWords); - stackDepth -= paramType.getSizeOnStack(); + stackDepth -= type->getSizeOnStack(); } // note that the stack is not cleaned up here m_context << u256(dataOffset) << u256(0) << eth::Instruction::RETURN; @@ -270,30 +229,54 @@ void Compiler::registerStateVariables(ContractDefinition const& _contract) m_context.addStateVariable(*variable); } +bool Compiler::visit(VariableDeclaration const& _variableDeclaration) +{ + solAssert(_variableDeclaration.isStateVariable(), "Compiler visit to non-state variable declaration."); + + m_context.startFunction(_variableDeclaration); + m_breakTags.clear(); + m_continueTags.clear(); + + m_context << m_context.getFunctionEntryLabel(_variableDeclaration); + ExpressionCompiler::appendStateVariableAccessor(m_context, _variableDeclaration); + + unsigned sizeOnStack = _variableDeclaration.getType()->getSizeOnStack(); + solAssert(sizeOnStack <= 15, "Stack too deep."); + m_context << eth::dupInstruction(sizeOnStack + 1) << eth::Instruction::JUMP; + + return false; +} + bool Compiler::visit(FunctionDefinition const& _function) { //@todo to simplify this, the calling convention could by changed such that // caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn] // although note that this reduces the size of the visible stack - m_context.startNewFunction(); + m_context.startFunction(_function); m_returnTag = m_context.newTag(); m_breakTags.clear(); m_continueTags.clear(); - - m_context << m_context.getFunctionEntryLabel(_function); + m_stackCleanupForReturn = 0; + m_currentFunction = &_function; + m_modifierDepth = 0; // stack upon entry: [return address] [arg0] [arg1] ... [argn] // reserve additional slots: [retarg0] ... [retargm] [localvar0] ... [localvarp] + unsigned parametersSize = CompilerUtils::getSizeOnStack(_function.getParameters()); + m_context.adjustStackOffset(parametersSize); for (ASTPointer const& variable: _function.getParameters()) - m_context.addVariable(*variable); + { + m_context.addVariable(*variable, parametersSize); + parametersSize -= variable->getType()->getSizeOnStack(); + } for (ASTPointer const& variable: _function.getReturnParameters()) m_context.addAndInitializeVariable(*variable); for (VariableDeclaration const* localVariable: _function.getLocalVariables()) m_context.addAndInitializeVariable(*localVariable); - _function.getBody().accept(*this); + appendModifierOrFunctionCode(); m_context << m_returnTag; @@ -420,13 +403,15 @@ bool Compiler::visit(Return const& _return) //@todo modifications are needed to make this work with functions returning multiple values if (Expression const* expression = _return.getExpression()) { - compileExpression(*expression); - VariableDeclaration const& firstVariable = *_return.getFunctionReturnParameters().getParameters().front(); - ExpressionCompiler::appendTypeConversion(m_context, *expression->getType(), *firstVariable.getType()); - + solAssert(_return.getFunctionReturnParameters(), "Invalid return parameters pointer."); + VariableDeclaration const& firstVariable = *_return.getFunctionReturnParameters()->getParameters().front(); + compileExpression(*expression, firstVariable.getType()); CompilerUtils(m_context).moveToStackVariable(firstVariable); } + for (unsigned i = 0; i < m_stackCleanupForReturn; ++i) + m_context << eth::Instruction::POP; m_context.appendJumpTo(m_returnTag); + m_context.adjustStackOffset(m_stackCleanupForReturn); return false; } @@ -434,10 +419,7 @@ bool Compiler::visit(VariableDefinition const& _variableDefinition) { if (Expression const* expression = _variableDefinition.getExpression()) { - compileExpression(*expression); - ExpressionCompiler::appendTypeConversion(m_context, - *expression->getType(), - *_variableDefinition.getDeclaration().getType()); + compileExpression(*expression, _variableDefinition.getDeclaration().getType()); CompilerUtils(m_context).moveToStackVariable(_variableDefinition.getDeclaration()); } return false; @@ -451,9 +433,51 @@ bool Compiler::visit(ExpressionStatement const& _expressionStatement) return false; } -void Compiler::compileExpression(Expression const& _expression) +bool Compiler::visit(PlaceholderStatement const&) +{ + ++m_modifierDepth; + appendModifierOrFunctionCode(); + --m_modifierDepth; + return true; +} + +void Compiler::appendModifierOrFunctionCode() +{ + solAssert(m_currentFunction, ""); + if (m_modifierDepth >= m_currentFunction->getModifiers().size()) + m_currentFunction->getBody().accept(*this); + else + { + ASTPointer const& modifierInvocation = m_currentFunction->getModifiers()[m_modifierDepth]; + + ModifierDefinition const& modifier = m_context.getFunctionModifier(modifierInvocation->getName()->getName()); + solAssert(modifier.getParameters().size() == modifierInvocation->getArguments().size(), ""); + for (unsigned i = 0; i < modifier.getParameters().size(); ++i) + { + m_context.addVariable(*modifier.getParameters()[i]); + compileExpression(*modifierInvocation->getArguments()[i], + modifier.getParameters()[i]->getType()); + } + for (VariableDeclaration const* localVariable: modifier.getLocalVariables()) + m_context.addAndInitializeVariable(*localVariable); + + unsigned const c_stackSurplus = CompilerUtils::getSizeOnStack(modifier.getParameters()) + + CompilerUtils::getSizeOnStack(modifier.getLocalVariables()); + m_stackCleanupForReturn += c_stackSurplus; + + modifier.getBody().accept(*this); + + for (unsigned i = 0; i < c_stackSurplus; ++i) + m_context << eth::Instruction::POP; + m_stackCleanupForReturn -= c_stackSurplus; + } +} + +void Compiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) { ExpressionCompiler::compileExpression(m_context, _expression, m_optimize); + if (_targetType) + ExpressionCompiler::appendTypeConversion(m_context, *_expression.getType(), *_targetType); } } diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index 2bae6b397..b3eae5b17 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -31,7 +31,8 @@ namespace solidity { class Compiler: private ASTConstVisitor { public: - explicit Compiler(bool _optimize = false): m_optimize(_optimize), m_context(), m_returnTag(m_context.newTag()) {} + explicit Compiler(bool _optimize = false): m_optimize(_optimize), m_context(), + m_returnTag(m_context.newTag()) {} void compileContract(ContractDefinition const& _contract, std::map const& _contracts); @@ -49,18 +50,15 @@ private: void appendBaseConstructorCall(FunctionDefinition const& _constructor, std::vector> const& _arguments); void appendConstructorCall(FunctionDefinition const& _constructor); - /// Recursively searches the call graph and returns all functions referenced inside _nodes. - /// _resolveOverride is called to resolve virtual function overrides. - std::set getFunctionsCalled(std::set const& _nodes, - std::function const& _resolveOverride); void appendFunctionSelector(ContractDefinition const& _contract); - /// Creates code that unpacks the arguments for the given function, from memory if - /// @a _fromMemory is true, otherwise from call data. @returns the size of the data in bytes. - unsigned appendCalldataUnpacker(FunctionDefinition const& _function, bool _fromMemory = false); - void appendReturnValuePacker(FunctionDefinition const& _function); + /// Creates code that unpacks the arguments for the given function represented by a vector of TypePointers. + /// From memory if @a _fromMemory is true, otherwise from call data. @returns the size of the data in bytes. + unsigned appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory = false); + void appendReturnValuePacker(TypePointers const& _typeParameters); void registerStateVariables(ContractDefinition const& _contract); + virtual bool visit(VariableDeclaration const& _variableDeclaration) override; virtual bool visit(FunctionDefinition const& _function) override; virtual bool visit(IfStatement const& _ifStatement) override; virtual bool visit(WhileStatement const& _whileStatement) override; @@ -70,8 +68,13 @@ private: virtual bool visit(Return const& _return) override; virtual bool visit(VariableDefinition const& _variableDefinition) override; virtual bool visit(ExpressionStatement const& _expressionStatement) override; + virtual bool visit(PlaceholderStatement const&) override; - void compileExpression(Expression const& _expression); + /// Appends one layer of function modifier code of the current function, or the function + /// body itself if the last modifier was reached. + void appendModifierOrFunctionCode(); + + void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); bool const m_optimize; CompilerContext m_context; @@ -79,6 +82,9 @@ private: std::vector m_breakTags; ///< tag to jump to for a "break" statement std::vector m_continueTags; ///< tag to jump to for a "continue" statement eth::AssemblyItem m_returnTag; ///< tag to jump to for a "return" statement + unsigned m_modifierDepth = 0; + FunctionDefinition const* m_currentFunction; + unsigned m_stackCleanupForReturn; ///< this number of stack elements need to be removed before jump to m_returnTag }; } diff --git a/libsolidity/CompilerContext.cpp b/libsolidity/CompilerContext.cpp index 27ec3efd5..52910a556 100644 --- a/libsolidity/CompilerContext.cpp +++ b/libsolidity/CompilerContext.cpp @@ -43,10 +43,19 @@ void CompilerContext::addStateVariable(VariableDeclaration const& _declaration) m_stateVariablesSize += _declaration.getType()->getStorageSize(); } -void CompilerContext::addVariable(VariableDeclaration const& _declaration) +void CompilerContext::startFunction(Declaration const& _function) { - m_localVariables[&_declaration] = m_localVariablesSize; - m_localVariablesSize += _declaration.getType()->getSizeOnStack(); + m_functionsWithCode.insert(&_function); + m_localVariables.clear(); + m_asm.setDeposit(0); + *this << getFunctionEntryLabel(_function); +} + +void CompilerContext::addVariable(VariableDeclaration const& _declaration, + unsigned _offsetToCurrent) +{ + solAssert(m_asm.deposit() >= 0 && unsigned(m_asm.deposit()) >= _offsetToCurrent, ""); + m_localVariables[&_declaration] = unsigned(m_asm.deposit()) - _offsetToCurrent; } void CompilerContext::addAndInitializeVariable(VariableDeclaration const& _declaration) @@ -56,14 +65,6 @@ void CompilerContext::addAndInitializeVariable(VariableDeclaration const& _decla int const size = _declaration.getType()->getSizeOnStack(); for (int i = 0; i < size; ++i) *this << u256(0); - m_asm.adjustDeposit(-size); -} - -void CompilerContext::addFunction(FunctionDefinition const& _function) -{ - eth::AssemblyItem tag(m_asm.newTag()); - m_functionEntryLabels.insert(make_pair(&_function, tag)); - m_virtualFunctionEntryLabels.insert(make_pair(_function.getName(), tag)); } bytes const& CompilerContext::getCompiledContract(const ContractDefinition& _contract) const @@ -75,38 +76,82 @@ bytes const& CompilerContext::getCompiledContract(const ContractDefinition& _con bool CompilerContext::isLocalVariable(Declaration const* _declaration) const { - return m_localVariables.count(_declaration) > 0; + return m_localVariables.count(_declaration); +} + +eth::AssemblyItem CompilerContext::getFunctionEntryLabel(Declaration const& _declaration) +{ + auto res = m_functionEntryLabels.find(&_declaration); + if (res == m_functionEntryLabels.end()) + { + eth::AssemblyItem tag(m_asm.newTag()); + m_functionEntryLabels.insert(make_pair(&_declaration, tag)); + return tag.tag(); + } + else + return res->second.tag(); +} + +eth::AssemblyItem CompilerContext::getVirtualFunctionEntryLabel(FunctionDefinition const& _function) +{ + solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); + for (ContractDefinition const* contract: m_inheritanceHierarchy) + for (ASTPointer const& function: contract->getDefinedFunctions()) + if (!function->isConstructor() && function->getName() == _function.getName()) + return getFunctionEntryLabel(*function); + solAssert(false, "Virtual function " + _function.getName() + " not found."); + return m_asm.newTag(); // not reached +} + +eth::AssemblyItem CompilerContext::getSuperFunctionEntryLabel(string const& _name, ContractDefinition const& _base) +{ + // search for first contract after _base + solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); + auto it = find(m_inheritanceHierarchy.begin(), m_inheritanceHierarchy.end(), &_base); + solAssert(it != m_inheritanceHierarchy.end(), "Base not found in inheritance hierarchy."); + for (++it; it != m_inheritanceHierarchy.end(); ++it) + for (ASTPointer const& function: (*it)->getDefinedFunctions()) + if (!function->isConstructor() && function->getName() == _name) + return getFunctionEntryLabel(*function); + solAssert(false, "Super function " + _name + " not found."); + return m_asm.newTag(); // not reached } -eth::AssemblyItem CompilerContext::getFunctionEntryLabel(FunctionDefinition const& _function) const +set CompilerContext::getFunctionsWithoutCode() { - auto res = m_functionEntryLabels.find(&_function); - solAssert(res != m_functionEntryLabels.end(), "Function entry label not found."); - return res->second.tag(); + set functions; + for (auto const& it: m_functionEntryLabels) + if (m_functionsWithCode.count(it.first) == 0) + functions.insert(it.first); + return move(functions); } -eth::AssemblyItem CompilerContext::getVirtualFunctionEntryLabel(FunctionDefinition const& _function) const +ModifierDefinition const& CompilerContext::getFunctionModifier(string const& _name) const { - auto res = m_virtualFunctionEntryLabels.find(_function.getName()); - solAssert(res != m_virtualFunctionEntryLabels.end(), "Function entry label not found."); - return res->second.tag(); + solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); + for (ContractDefinition const* contract: m_inheritanceHierarchy) + for (ASTPointer const& modifier: contract->getFunctionModifiers()) + if (modifier->getName() == _name) + return *modifier.get(); + BOOST_THROW_EXCEPTION(InternalCompilerError() + << errinfo_comment("Function modifier " + _name + " not found.")); } unsigned CompilerContext::getBaseStackOffsetOfVariable(Declaration const& _declaration) const { auto res = m_localVariables.find(&_declaration); solAssert(res != m_localVariables.end(), "Variable not found on stack."); - return m_localVariablesSize - res->second - 1; + return res->second; } unsigned CompilerContext::baseToCurrentStackOffset(unsigned _baseOffset) const { - return _baseOffset + m_asm.deposit(); + return m_asm.deposit() - _baseOffset - 1; } unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const { - return -baseToCurrentStackOffset(-_offset); + return m_asm.deposit() - _offset - 1; } u256 CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const diff --git a/libsolidity/CompilerContext.h b/libsolidity/CompilerContext.h index cde992d58..6d6a65b61 100644 --- a/libsolidity/CompilerContext.h +++ b/libsolidity/CompilerContext.h @@ -41,10 +41,8 @@ class CompilerContext public: void addMagicGlobal(MagicVariableDeclaration const& _declaration); void addStateVariable(VariableDeclaration const& _declaration); - void startNewFunction() { m_localVariables.clear(); m_asm.setDeposit(0); } - void addVariable(VariableDeclaration const& _declaration); + void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); void addAndInitializeVariable(VariableDeclaration const& _declaration); - void addFunction(FunctionDefinition const& _function); void setCompiledContracts(std::map const& _contracts) { m_compiledContracts = _contracts; } bytes const& getCompiledContract(ContractDefinition const& _contract) const; @@ -52,14 +50,24 @@ public: void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); } bool isMagicGlobal(Declaration const* _declaration) const { return m_magicGlobals.count(_declaration) != 0; } - bool isFunctionDefinition(Declaration const* _declaration) const { return m_functionEntryLabels.count(_declaration) != 0; } bool isLocalVariable(Declaration const* _declaration) const; bool isStateVariable(Declaration const* _declaration) const { return m_stateVariables.count(_declaration) != 0; } - eth::AssemblyItem getFunctionEntryLabel(FunctionDefinition const& _function) const; + eth::AssemblyItem getFunctionEntryLabel(Declaration const& _declaration); + void setInheritanceHierarchy(std::vector const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; } /// @returns the entry label of the given function and takes overrides into account. - eth::AssemblyItem getVirtualFunctionEntryLabel(FunctionDefinition const& _function) const; - /// Returns the distance of the given local variable from the top of the local variable stack. + eth::AssemblyItem getVirtualFunctionEntryLabel(FunctionDefinition const& _function); + /// @returns the entry label of function with the given name from the most derived class just + /// above _base in the current inheritance hierarchy. + eth::AssemblyItem getSuperFunctionEntryLabel(std::string const& _name, ContractDefinition const& _base); + /// @returns the set of functions for which we still need to generate code + std::set getFunctionsWithoutCode(); + /// Resets function specific members, inserts the function entry label and marks the function + /// as "having code". + void startFunction(Declaration const& _function); + + ModifierDefinition const& getFunctionModifier(std::string const& _name) const; + /// Returns the distance of the given local variable from the bottom of the stack (of the current function). unsigned getBaseStackOffsetOfVariable(Declaration const& _declaration) const; /// If supplied by a value returned by @ref getBaseStackOffsetOfVariable(variable), returns /// the distance of that variable from the current top of the stack. @@ -114,12 +122,12 @@ private: std::map m_stateVariables; /// Offsets of local variables on the stack (relative to stack base). std::map m_localVariables; - /// Sum of stack sizes of local variables - unsigned m_localVariablesSize; - /// Labels pointing to the entry points of funcitons. + /// Labels pointing to the entry points of functions. std::map m_functionEntryLabels; - /// Labels pointing to the entry points of function overrides. - std::map m_virtualFunctionEntryLabels; + /// Set of functions for which we did not yet generate code. + std::set m_functionsWithCode; + /// List of current inheritance hierarchy from derived to base. + std::vector m_inheritanceHierarchy; }; } diff --git a/libsolidity/CompilerStack.cpp b/libsolidity/CompilerStack.cpp index c05756167..2d77bc0a4 100644 --- a/libsolidity/CompilerStack.cpp +++ b/libsolidity/CompilerStack.cpp @@ -40,18 +40,39 @@ namespace dev namespace solidity { +const map StandardSources = map{ + {"coin", R"(import "CoinReg";import "Config";import "configUser";contract coin is configUser{function coin(string3 name, uint denom) {CoinReg(Config(configAddr()).lookup(3)).register(name, denom);}})"}, + {"Coin", R"(contract Coin{function isApprovedFor(address _target,address _proxy)constant returns(bool _r){}function isApproved(address _proxy)constant returns(bool _r){}function sendCoinFrom(address _from,uint256 _val,address _to){}function coinBalanceOf(address _a)constant returns(uint256 _r){}function sendCoin(uint256 _val,address _to){}function coinBalance()constant returns(uint256 _r){}function approve(address _a){}})"}, + {"CoinReg", R"(contract CoinReg{function count()constant returns(uint256 r){}function info(uint256 i)constant returns(address addr,string3 name,uint256 denom){}function register(string3 name,uint256 denom){}function unregister(){}})"}, + {"configUser", R"(contract configUser{function configAddr()constant returns(address a){ return 0xc6d9d2cd449a754c494264e1809c50e34d64562b;}})"}, + {"Config", R"(contract Config{function lookup(uint256 service)constant returns(address a){}function kill(){}function unregister(uint256 id){}function register(uint256 id,address service){}})"}, + {"mortal", R"(import "owned";contract mortal is owned {function kill() { if (msg.sender == owner) suicide(owner); }})"}, + {"named", R"(import "Config";import "NameReg";import "configUser";contract named is configUser {function named(string32 name) {NameReg(Config(configAddr()).lookup(1)).register(name);}})"}, + {"NameReg", R"(contract NameReg{function register(string32 name){}function addressOf(string32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(string32 name){}})"}, + {"owned", R"(contract owned{function owned(){owner = msg.sender;}modifier onlyowner(){if(msg.sender==owner)_}address owner;})"}, + {"service", R"(import "Config";import "configUser";contract service is configUser{function service(uint _n){Config(configAddr()).register(_n, this);}})"}, + {"std", R"(import "owned";import "mortal";import "Config";import "configUser";import "NameReg";import "named";)"} +}; + +CompilerStack::CompilerStack(bool _addStandardSources): + m_addStandardSources(_addStandardSources), m_parseSuccessful(false) +{ + if (m_addStandardSources) + addSources(StandardSources); +} + bool CompilerStack::addSource(string const& _name, string const& _content) { bool existed = m_sources.count(_name) != 0; reset(true); - m_sources[_name].scanner = make_shared(CharStream(_content), _name); + m_sources[_name].scanner = make_shared(CharStream(expanded(_content)), _name); return existed; } void CompilerStack::setSource(string const& _sourceCode) { reset(); - addSource("", _sourceCode); + addSource("", expanded(_sourceCode)); } void CompilerStack::parse() @@ -73,6 +94,7 @@ void CompilerStack::parse() { m_globalContext->setCurrentContract(*contract); resolver.updateDeclaration(*m_globalContext->getCurrentThis()); + resolver.updateDeclaration(*m_globalContext->getCurrentSuper()); resolver.resolveNamesAndTypes(*contract); m_contracts[contract->getName()].contract = contract; } @@ -104,37 +126,27 @@ vector CompilerStack::getContractNames() const return contractNames; } -void CompilerStack::compile(bool _optimize) -{ - if (!m_parseSuccessful) - parse(); - - map contractBytecode; - for (Source const* source: m_sourceOrder) - for (ASTPointer const& node: source->ast->getNodes()) - if (ContractDefinition* contract = dynamic_cast(node.get())) - { - shared_ptr compiler = make_shared(_optimize); - compiler->compileContract(*contract, contractBytecode); - Contract& compiledContract = m_contracts[contract->getName()]; - compiledContract.bytecode = compiler->getAssembledBytecode(); - compiledContract.runtimeBytecode = compiler->getRuntimeBytecode(); - compiledContract.compiler = move(compiler); - contractBytecode[compiledContract.contract] = &compiledContract.bytecode; - } -} +////// BEGIN: TEMPORARY ONLY +/// +/// NOTE: THIS INVALIDATES SOURCE POINTERS AND CAN CRASH THE COMPILER +/// +/// remove once import works properly and we have genesis contracts string CompilerStack::expanded(string const& _sourceCode) { - // TODO: populate some nicer way. - static const map c_requires = { + const map c_standardSources = map{ { "Config", "contract Config{function lookup(uint256 service)constant returns(address a){}function kill(){}function unregister(uint256 id){}function register(uint256 id,address service){}}" }, - { "owned", "contract owned{function owned(){owner = msg.sender;}address owner;}" }, + { "Coin", "contract Coin{function isApprovedFor(address _target,address _proxy)constant returns(bool _r){}function isApproved(address _proxy)constant returns(bool _r){}function sendCoinFrom(address _from,uint256 _val,address _to){}function coinBalanceOf(address _a)constant returns(uint256 _r){}function sendCoin(uint256 _val,address _to){}function coinBalance()constant returns(uint256 _r){}function approve(address _a){}}"}, + { "CoinReg", "contract CoinReg{function count()constant returns(uint256 r){}function info(uint256 i)constant returns(address addr,string3 name,uint256 denom){}function register(string3 name,uint256 denom){}function unregister(){}}" }, + { "coin", "#require CoinReg\ncontract coin {function coin(string3 name, uint denom) {CoinReg(Config().lookup(3)).register(name, denom);}}" }, + { "service", "#require Config\ncontract service{function service(uint _n){Config().register(_n, this);}}" }, + { "owned", "contract owned{function owned(){owner = msg.sender;}modifier onlyowner(){if(msg.sender==owner)_}address owner;}" }, { "mortal", "#require owned\ncontract mortal is owned {function kill() { if (msg.sender == owner) suicide(owner); }}" }, { "NameReg", "contract NameReg{function register(string32 name){}function addressOf(string32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(string32 name){}}" }, - { "named", "#require Config NameReg\ncontract named is mortal, owned {function named(string32 name) {NameReg(Config().lookup(1)).register(name);}" }, + { "named", "#require Config NameReg\ncontract named {function named(string32 name) {NameReg(Config().lookup(1)).register(name);}}" }, { "std", "#require owned mortal Config NameReg named" }, }; + string sub; set got; function localExpanded; @@ -151,22 +163,44 @@ string CompilerStack::expanded(string const& _sourceCode) for (auto const& r: rs) if (!got.count(r)) { - if (c_requires.count(r)) - sub.append("\n" + localExpanded(c_requires.at(r)) + "\n"); + if (c_standardSources.count(r)) + sub.append("\n" + localExpanded(c_standardSources.at(r)) + "\n"); got.insert(r); } } // TODO: remove once we have genesis contracts. else if ((p = ret.find("Config()")) != string::npos) - ret.replace(p, 8, "Config(0x661005d2720d855f1d9976f88bb10c1a3398c77f)"); + ret.replace(p, 8, "Config(0xc6d9d2cd449a754c494264e1809c50e34d64562b)"); return ret; }; return sub + localExpanded(_sourceCode); } +////// END: TEMPORARY ONLY + +void CompilerStack::compile(bool _optimize) +{ + if (!m_parseSuccessful) + parse(); + + map contractBytecode; + for (Source const* source: m_sourceOrder) + for (ASTPointer const& node: source->ast->getNodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + { + shared_ptr compiler = make_shared(_optimize); + compiler->compileContract(*contract, contractBytecode); + Contract& compiledContract = m_contracts[contract->getName()]; + compiledContract.bytecode = compiler->getAssembledBytecode(); + compiledContract.runtimeBytecode = compiler->getRuntimeBytecode(); + compiledContract.compiler = move(compiler); + contractBytecode[compiledContract.contract] = &compiledContract.bytecode; + } +} + bytes const& CompilerStack::compile(string const& _sourceCode, bool _optimize) { - parse(expanded(_sourceCode)); + parse(_sourceCode); compile(_optimize); return getBytecode(); } @@ -259,7 +293,11 @@ void CompilerStack::reset(bool _keepSources) for (auto sourcePair: m_sources) sourcePair.second.reset(); else + { m_sources.clear(); + if (m_addStandardSources) + addSources(StandardSources); + } m_globalContext.reset(); m_sourceOrder.clear(); m_contracts.clear(); @@ -301,10 +339,12 @@ CompilerStack::Contract const& CompilerStack::getContract(string const& _contrac BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found.")); string contractName = _contractName; if (_contractName.empty()) - // try to find the "last contract" - for (ASTPointer const& node: m_sourceOrder.back()->ast->getNodes()) - if (auto contract = dynamic_cast(node.get())) - contractName = contract->getName(); + // try to find some user-supplied contract + for (auto const& it: m_sources) + if (!StandardSources.count(it.first)) + for (ASTPointer const& node: it.second.ast->getNodes()) + if (auto contract = dynamic_cast(node.get())) + contractName = contract->getName(); auto it = m_contracts.find(contractName); if (it == m_contracts.end()) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract " + _contractName + " not found.")); diff --git a/libsolidity/CompilerStack.h b/libsolidity/CompilerStack.h index 68c82a350..a6c3df8ee 100644 --- a/libsolidity/CompilerStack.h +++ b/libsolidity/CompilerStack.h @@ -49,6 +49,8 @@ enum class DocumentationType: uint8_t ABI_SOLIDITY_INTERFACE }; +extern const std::map StandardSources; + /** * Easy to use and self-contained Solidity compiler with as few header dependencies as possible. * It holds state and can be used to either step through the compilation stages (and abort e.g. @@ -57,15 +59,17 @@ enum class DocumentationType: uint8_t class CompilerStack: boost::noncopyable { public: - CompilerStack(): m_parseSuccessful(false) {} + /// Creates a new compiler stack. Adds standard sources if @a _addStandardSources. + explicit CompilerStack(bool _addStandardSources = false); /// Adds a source object (e.g. file) to the parser. After this, parse has to be called again. /// @returns true if a source object by the name already existed and was replaced. + void addSources(std::map const& _nameContents) { for (auto const& i: _nameContents) addSource(i.first, i.second); } bool addSource(std::string const& _name, std::string const& _content); void setSource(std::string const& _sourceCode); /// Parses all source units that were added void parse(); - /// Sets the given source code as the only source unit and parses it. + /// Sets the given source code as the only source unit apart from standard sources and parses it. void parse(std::string const& _sourceCode); /// Returns a list of the contract names in the sources. std::vector getContractNames() const; @@ -148,6 +152,7 @@ private: Contract const& getContract(std::string const& _contractName = "") const; Source const& getSource(std::string const& _sourceName = "") const; + bool m_addStandardSources; ///< If true, standard sources are added. bool m_parseSuccessful; std::map m_sources; std::shared_ptr m_globalContext; diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 60c5c4ded..7d58ea2e2 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -48,6 +49,12 @@ void ExpressionCompiler::appendTypeConversion(CompilerContext& _context, Type co compiler.appendTypeConversion(_typeOnStack, _targetType, _cleanupNeeded); } +void ExpressionCompiler::appendStateVariableAccessor(CompilerContext& _context, VariableDeclaration const& _varDecl, bool _optimize) +{ + ExpressionCompiler compiler(_context, _optimize); + compiler.appendStateVariableAccessor(_varDecl); +} + bool ExpressionCompiler::visit(Assignment const& _assignment) { _assignment.getRightHandSide().accept(*this); @@ -60,7 +67,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) { if (m_currentLValue.storesReferenceOnStack()) m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; - m_currentLValue.retrieveValue(_assignment, true); + m_currentLValue.retrieveValue(_assignment.getType(), _assignment.getLocation(), true); appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.getType()); if (m_currentLValue.storesReferenceOnStack()) m_context << eth::Instruction::SWAP1; @@ -101,7 +108,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) case Token::INC: // ++ (pre- or postfix) case Token::DEC: // -- (pre- or postfix) solAssert(m_currentLValue.isValid(), "LValue not retrieved."); - m_currentLValue.retrieveValue(_unaryOperation); + m_currentLValue.retrieveValue(_unaryOperation.getType(), _unaryOperation.getLocation()); if (!_unaryOperation.isPrefixOperation()) { if (m_currentLValue.storesReferenceOnStack()) @@ -298,10 +305,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) m_context << eth::Instruction::SUICIDE; break; case Location::SHA3: - arguments.front()->accept(*this); - appendTypeConversion(*arguments.front()->getType(), *function.getParameterTypes().front(), true); - // @todo move this once we actually use memory - CompilerUtils(m_context).storeInMemory(0); + appendExpressionCopyToMemory(*function.getParameterTypes().front(), *arguments.front()); m_context << u256(32) << u256(0) << eth::Instruction::SHA3; break; case Location::LOG0: @@ -311,14 +315,41 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case Location::LOG4: { unsigned logNumber = int(function.getLocation()) - int(Location::LOG0); - for (int arg = logNumber; arg >= 0; --arg) + for (unsigned arg = logNumber; arg > 0; --arg) { arguments[arg]->accept(*this); appendTypeConversion(*arguments[arg]->getType(), *function.getParameterTypes()[arg], true); } - // @todo move this once we actually use memory - CompilerUtils(m_context).storeInMemory(0); - m_context << u256(32) << u256(0) << eth::logInstruction(logNumber); + unsigned length = appendExpressionCopyToMemory(*function.getParameterTypes().front(), + *arguments.front()); + solAssert(length == 32, "Log data should be 32 bytes long (for now)."); + m_context << u256(length) << u256(0) << eth::logInstruction(logNumber); + break; + } + case Location::EVENT: + { + _functionCall.getExpression().accept(*this); + auto const& event = dynamic_cast(function.getDeclaration()); + // Copy all non-indexed arguments to memory (data) + unsigned numIndexed = 0; + unsigned memLength = 0; + for (unsigned arg = 0; arg < arguments.size(); ++arg) + if (!event.getParameters()[arg]->isIndexed()) + memLength += appendExpressionCopyToMemory(*function.getParameterTypes()[arg], + *arguments[arg], memLength); + // All indexed arguments go to the stack + for (unsigned arg = arguments.size(); arg > 0; --arg) + if (event.getParameters()[arg - 1]->isIndexed()) + { + ++numIndexed; + arguments[arg - 1]->accept(*this); + appendTypeConversion(*arguments[arg - 1]->getType(), + *function.getParameterTypes()[arg - 1], true); + } + m_context << u256(h256::Arith(dev::sha3(function.getCanonicalSignature(event.getName())))); + ++numIndexed; + solAssert(numIndexed <= 4, "Too many indexed arguments."); + m_context << u256(memLength) << u256(0) << eth::logInstruction(numIndexed); break; } case Location::BLOCKHASH: @@ -359,15 +390,25 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) { case Type::Category::CONTRACT: { + bool alsoSearchInteger = false; ContractType const& type = dynamic_cast(*_memberAccess.getExpression().getType()); - u256 identifier = type.getFunctionIdentifier(member); - if (identifier != Invalid256) + if (type.isSuper()) + m_context << m_context.getSuperFunctionEntryLabel(member, type.getContractDefinition()).pushTag(); + else { - appendTypeConversion(type, IntegerType(0, IntegerType::Modifier::ADDRESS), true); - m_context << identifier; - break; + // ordinary contract type + u256 identifier = type.getFunctionIdentifier(member); + if (identifier != Invalid256) + { + appendTypeConversion(type, IntegerType(0, IntegerType::Modifier::ADDRESS), true); + m_context << identifier; + } + else + // not found in contract, search in members inherited from address + alsoSearchInteger = true; } - // fall-through to "integer" otherwise (address) + if (!alsoSearchInteger) + break; } case Type::Category::INTEGER: if (member == "balance") @@ -443,14 +484,13 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) { _indexAccess.getBaseExpression().accept(*this); - _indexAccess.getIndexExpression().accept(*this); - appendTypeConversion(*_indexAccess.getIndexExpression().getType(), - *dynamic_cast(*_indexAccess.getBaseExpression().getType()).getKeyType(), - true); + + TypePointer const& keyType = dynamic_cast(*_indexAccess.getBaseExpression().getType()).getKeyType(); + unsigned length = appendExpressionCopyToMemory(*keyType, _indexAccess.getIndexExpression()); + solAssert(length == 32, "Mapping key has to take 32 bytes in memory (for now)."); // @todo move this once we actually use memory - CompilerUtils(m_context).storeInMemory(0); - CompilerUtils(m_context).storeInMemory(32); - m_context << u256(64) << u256(0) << eth::Instruction::SHA3; + length += CompilerUtils(m_context).storeInMemory(length); + m_context << u256(length) << u256(0) << eth::Instruction::SHA3; m_currentLValue = LValue(m_context, LValue::STORAGE, *_indexAccess.getType()); m_currentLValue.retrieveValueIfLValueNotRequested(_indexAccess); @@ -463,8 +503,10 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) Declaration const* declaration = _identifier.getReferencedDeclaration(); if (MagicVariableDeclaration const* magicVar = dynamic_cast(declaration)) { - if (magicVar->getType()->getCategory() == Type::Category::CONTRACT) // must be "this" - m_context << eth::Instruction::ADDRESS; + if (magicVar->getType()->getCategory() == Type::Category::CONTRACT) + // "this" or "super" + if (!dynamic_cast(*magicVar->getType()).isSuper()) + m_context << eth::Instruction::ADDRESS; } else if (FunctionDefinition const* functionDef = dynamic_cast(declaration)) m_context << m_context.getVirtualFunctionEntryLabel(*functionDef).pushTag(); @@ -477,6 +519,10 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) { // no-op } + else if (dynamic_cast(declaration)) + { + // no-op + } else { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Identifier type not expected in expression context.")); @@ -628,38 +674,65 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con return; Type::Category stackTypeCategory = _typeOnStack.getCategory(); Type::Category targetTypeCategory = _targetType.getCategory(); - if (stackTypeCategory == Type::Category::INTEGER || stackTypeCategory == Type::Category::CONTRACT || - stackTypeCategory == Type::Category::INTEGER_CONSTANT) + + if (stackTypeCategory == Type::Category::STRING) { - solAssert(targetTypeCategory == Type::Category::INTEGER || targetTypeCategory == Type::Category::CONTRACT, ""); - IntegerType addressType(0, IntegerType::Modifier::ADDRESS); - IntegerType const& targetType = targetTypeCategory == Type::Category::INTEGER - ? dynamic_cast(_targetType) : addressType; - if (stackTypeCategory == Type::Category::INTEGER_CONSTANT) + if (targetTypeCategory == Type::Category::INTEGER) { - IntegerConstantType const& constType = dynamic_cast(_typeOnStack); - // We know that the stack is clean, we only have to clean for a narrowing conversion - // where cleanup is forced. - if (targetType.getNumBits() < constType.getIntegerType()->getNumBits() && _cleanupNeeded) - appendHighBitsCleanup(targetType); + // conversion from string to hash. no need to clean the high bit + // only to shift right because of opposite alignment + IntegerType const& targetIntegerType = dynamic_cast(_targetType); + StaticStringType const& typeOnStack = dynamic_cast(_typeOnStack); + solAssert(targetIntegerType.isHash(), "Only conversion between String and Hash is allowed."); + solAssert(targetIntegerType.getNumBits() == typeOnStack.getNumBytes() * 8, "The size should be the same."); + m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; } else { - IntegerType const& typeOnStack = stackTypeCategory == Type::Category::INTEGER - ? dynamic_cast(_typeOnStack) : addressType; - // Widening: clean up according to source type width - // Non-widening and force: clean up according to target type bits - if (targetType.getNumBits() > typeOnStack.getNumBits()) - appendHighBitsCleanup(typeOnStack); - else if (_cleanupNeeded) - appendHighBitsCleanup(targetType); + solAssert(targetTypeCategory == Type::Category::STRING, "Invalid type conversion requested."); + // nothing to do, strings are high-order-bit-aligned + //@todo clear lower-order bytes if we allow explicit conversion to shorter strings } } - else if (stackTypeCategory == Type::Category::STRING) + else if (stackTypeCategory == Type::Category::INTEGER || stackTypeCategory == Type::Category::CONTRACT || + stackTypeCategory == Type::Category::INTEGER_CONSTANT) { - solAssert(targetTypeCategory == Type::Category::STRING, ""); - // nothing to do, strings are high-order-bit-aligned - //@todo clear lower-order bytes if we allow explicit conversion to shorter strings + if (targetTypeCategory == Type::Category::STRING && stackTypeCategory == Type::Category::INTEGER) + { + // conversion from hash to string. no need to clean the high bit + // only to shift left because of opposite alignment + StaticStringType const& targetStringType = dynamic_cast(_targetType); + IntegerType const& typeOnStack = dynamic_cast(_typeOnStack); + solAssert(typeOnStack.isHash(), "Only conversion between String and Hash is allowed."); + solAssert(typeOnStack.getNumBits() == targetStringType.getNumBytes() * 8, "The size should be the same."); + m_context << (u256(1) << (256 - typeOnStack.getNumBits())) << eth::Instruction::MUL; + } + else + { + solAssert(targetTypeCategory == Type::Category::INTEGER || targetTypeCategory == Type::Category::CONTRACT, ""); + IntegerType addressType(0, IntegerType::Modifier::ADDRESS); + IntegerType const& targetType = targetTypeCategory == Type::Category::INTEGER + ? dynamic_cast(_targetType) : addressType; + if (stackTypeCategory == Type::Category::INTEGER_CONSTANT) + { + IntegerConstantType const& constType = dynamic_cast(_typeOnStack); + // We know that the stack is clean, we only have to clean for a narrowing conversion + // where cleanup is forced. + if (targetType.getNumBits() < constType.getIntegerType()->getNumBits() && _cleanupNeeded) + appendHighBitsCleanup(targetType); + } + else + { + IntegerType const& typeOnStack = stackTypeCategory == Type::Category::INTEGER + ? dynamic_cast(_typeOnStack) : addressType; + // Widening: clean up according to source type width + // Non-widening and force: clean up according to target type bits + if (targetType.getNumBits() > typeOnStack.getNumBits()) + appendHighBitsCleanup(typeOnStack); + else if (_cleanupNeeded) + appendHighBitsCleanup(targetType); + } + } } else if (_typeOnStack != _targetType) // All other types should not be convertible to non-equal types. @@ -746,22 +819,32 @@ unsigned ExpressionCompiler::appendArgumentCopyToMemory(TypePointers const& _typ { unsigned length = 0; for (unsigned i = 0; i < _arguments.size(); ++i) - { - _arguments[i]->accept(*this); - appendTypeConversion(*_arguments[i]->getType(), *_types[i], true); - unsigned const c_numBytes = _types[i]->getCalldataEncodedSize(); - if (c_numBytes == 0 || c_numBytes > 32) - BOOST_THROW_EXCEPTION(CompilerError() - << errinfo_sourceLocation(_arguments[i]->getLocation()) - << errinfo_comment("Type " + _types[i]->toString() + " not yet supported.")); - bool const c_leftAligned = _types[i]->getCategory() == Type::Category::STRING; - bool const c_padToWords = true; - length += CompilerUtils(m_context).storeInMemory(_memoryOffset + length, c_numBytes, - c_leftAligned, c_padToWords); - } + length += appendExpressionCopyToMemory(*_types[i], *_arguments[i], _memoryOffset + length); return length; } +unsigned ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, + Expression const& _expression, unsigned _memoryOffset) +{ + _expression.accept(*this); + appendTypeConversion(*_expression.getType(), _expectedType, true); + unsigned const c_numBytes = CompilerUtils::getPaddedSize(_expectedType.getCalldataEncodedSize()); + if (c_numBytes == 0 || c_numBytes > 32) + BOOST_THROW_EXCEPTION(CompilerError() + << errinfo_sourceLocation(_expression.getLocation()) + << errinfo_comment("Type " + _expectedType.toString() + " not yet supported.")); + bool const c_leftAligned = _expectedType.getCategory() == Type::Category::STRING; + bool const c_padToWords = true; + return CompilerUtils(m_context).storeInMemory(_memoryOffset, c_numBytes, c_leftAligned, c_padToWords); +} + +void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl) +{ + m_currentLValue.fromStateVariable(_varDecl, _varDecl.getType()); + solAssert(m_currentLValue.isInStorage(), ""); + m_currentLValue.retrieveValue(_varDecl.getType(), Location(), true); +} + ExpressionCompiler::LValue::LValue(CompilerContext& _compilerContext, LValueType _type, Type const& _dataType, unsigned _baseStackOffset): m_context(&_compilerContext), m_type(_type), m_baseStackOffset(_baseStackOffset) @@ -774,7 +857,7 @@ ExpressionCompiler::LValue::LValue(CompilerContext& _compilerContext, LValueType m_size = unsigned(_dataType.getSizeOnStack()); } -void ExpressionCompiler::LValue::retrieveValue(Expression const& _expression, bool _remove) const +void ExpressionCompiler::LValue::retrieveValue(TypePointer const& _type, Location const& _location, bool _remove) const { switch (m_type) { @@ -782,42 +865,47 @@ void ExpressionCompiler::LValue::retrieveValue(Expression const& _expression, bo { unsigned stackPos = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset)); if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); for (unsigned i = 0; i < m_size; ++i) *m_context << eth::dupInstruction(stackPos + 1); break; } case STORAGE: - if (!_expression.getType()->isValueType()) - break; // no distinction between value and reference for non-value types - if (!_remove) - *m_context << eth::Instruction::DUP1; - if (m_size == 1) - *m_context << eth::Instruction::SLOAD; - else - for (unsigned i = 0; i < m_size; ++i) - { - *m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1; - if (i + 1 < m_size) - *m_context << u256(1) << eth::Instruction::ADD; - else - *m_context << eth::Instruction::POP; - } + retrieveValueFromStorage(_type, _remove); break; case MEMORY: - if (!_expression.getType()->isValueType()) + if (!_type->isValueType()) break; // no distinction between value and reference for non-value types - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Location type not yet implemented.")); break; default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) + BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Unsupported location type.")); break; } } +void ExpressionCompiler::LValue::retrieveValueFromStorage(TypePointer const& _type, bool _remove) const +{ + if (!_type->isValueType()) + return; // no distinction between value and reference for non-value types + if (!_remove) + *m_context << eth::Instruction::DUP1; + if (m_size == 1) + *m_context << eth::Instruction::SLOAD; + else + for (unsigned i = 0; i < m_size; ++i) + { + *m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1; + if (i + 1 < m_size) + *m_context << u256(1) << eth::Instruction::ADD; + else + *m_context << eth::Instruction::POP; + } +} + void ExpressionCompiler::LValue::storeValue(Expression const& _expression, bool _move) const { switch (m_type) @@ -832,7 +920,7 @@ void ExpressionCompiler::LValue::storeValue(Expression const& _expression, bool for (unsigned i = 0; i < m_size; ++i) *m_context << eth::swapInstruction(stackDiff) << eth::Instruction::POP; if (!_move) - retrieveValue(_expression); + retrieveValue(_expression.getType(), _expression.getLocation()); break; } case LValue::STORAGE: @@ -919,11 +1007,19 @@ void ExpressionCompiler::LValue::retrieveValueIfLValueNotRequested(Expression co { if (!_expression.lvalueRequested()) { - retrieveValue(_expression, true); + retrieveValue(_expression.getType(), _expression.getLocation(), true); reset(); } } +void ExpressionCompiler::LValue::fromStateVariable(Declaration const& _varDecl, TypePointer const& _type) +{ + m_type = STORAGE; + solAssert(_type->getStorageSize() <= numeric_limits::max(), "The storage size of " + _type->toString() + " should fit in an unsigned"); + *m_context << m_context->getStorageLocationOfVariable(_varDecl); + m_size = unsigned(_type->getStorageSize()); +} + void ExpressionCompiler::LValue::fromIdentifier(Identifier const& _identifier, Declaration const& _declaration) { if (m_context->isLocalVariable(&_declaration)) @@ -934,10 +1030,7 @@ void ExpressionCompiler::LValue::fromIdentifier(Identifier const& _identifier, D } else if (m_context->isStateVariable(&_declaration)) { - m_type = STORAGE; - solAssert(_identifier.getType()->getStorageSize() <= numeric_limits::max(), "The storage size of " + _identifier.getType()->toString() + " should fit in unsigned"); - m_size = unsigned(_identifier.getType()->getStorageSize()); - *m_context << m_context->getStorageLocationOfVariable(_declaration); + fromStateVariable(_declaration, _identifier.getType()); } else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_identifier.getLocation()) diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index 8f784fde9..caecbfe8d 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -53,6 +53,8 @@ public: /// Appends code to remove dirty higher order bits in case of an implicit promotion to a wider type. static void appendTypeConversion(CompilerContext& _context, Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded = false); + /// Appends code for a State Variable accessor function + static void appendStateVariableAccessor(CompilerContext& _context, VariableDeclaration const& _varDecl, bool _optimize = false); private: explicit ExpressionCompiler(CompilerContext& _compilerContext, bool _optimize = false): @@ -92,8 +94,16 @@ private: bool bare = false); /// Appends code that copies the given arguments to memory (with optional offset). /// @returns the number of bytes copied to memory - unsigned appendArgumentCopyToMemory(TypePointers const& _functionType, std::vector> const& _arguments, + unsigned appendArgumentCopyToMemory(TypePointers const& _types, + std::vector> const& _arguments, unsigned _memoryOffset = 0); + /// Appends code that evaluates a single expression and copies it to memory (with optional offset). + /// @returns the number of bytes copied to memory + unsigned appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression, + unsigned _memoryOffset = 0); + + /// Appends code for a State Variable accessor function + void appendStateVariableAccessor(VariableDeclaration const& _varDecl); /** * Helper class to store and retrieve lvalues to and from various locations. @@ -111,6 +121,8 @@ private: /// Set type according to the declaration and retrieve the reference. /// @a _expression is the current expression void fromIdentifier(Identifier const& _identifier, Declaration const& _declaration); + /// Convenience function to set type for a state variable and retrieve the reference + void fromStateVariable(Declaration const& _varDecl, TypePointer const& _type); void reset() { m_type = NONE; m_baseStackOffset = 0; m_size = 0; } bool isValid() const { return m_type != NONE; } @@ -123,8 +135,9 @@ private: /// Copies the value of the current lvalue to the top of the stack and, if @a _remove is true, /// also removes the reference from the stack (note that is does not reset the type to @a NONE). - /// @a _expression is the current expression, used for error reporting. - void retrieveValue(Expression const& _expression, bool _remove = false) const; + /// @a _type is the type of the current expression and @ _location its location, used for error reporting. + /// @a _location can be a nullptr for expressions that don't have an actual ASTNode equivalent + void retrieveValue(TypePointer const& _type, Location const& _location, bool _remove = false) const; /// Stores a value (from the stack directly beneath the reference, which is assumed to /// be on the top of the stack, if any) in the lvalue and removes the reference. /// Also removes the stored value from the stack if @a _move is @@ -138,6 +151,9 @@ private: void retrieveValueIfLValueNotRequested(Expression const& _expression); private: + /// Convenience function to retrieve Value from Storage. Specific version of @ref retrieveValue + void retrieveValueFromStorage(TypePointer const& _type, bool _remove = false) const; + CompilerContext* m_context; LValueType m_type = NONE; /// If m_type is STACK, this is base stack offset (@see diff --git a/libsolidity/GlobalContext.cpp b/libsolidity/GlobalContext.cpp index c7eea92dc..687c9c9d4 100644 --- a/libsolidity/GlobalContext.cpp +++ b/libsolidity/GlobalContext.cpp @@ -83,5 +83,13 @@ MagicVariableDeclaration const* GlobalContext::getCurrentThis() const } +MagicVariableDeclaration const* GlobalContext::getCurrentSuper() const +{ + if (!m_superPointer[m_currentContract]) + m_superPointer[m_currentContract] = make_shared( + "super", make_shared(*m_currentContract, true)); + return m_superPointer[m_currentContract].get(); +} + } } diff --git a/libsolidity/GlobalContext.h b/libsolidity/GlobalContext.h index dfdc66623..f861c67d7 100644 --- a/libsolidity/GlobalContext.h +++ b/libsolidity/GlobalContext.h @@ -48,6 +48,7 @@ public: GlobalContext(); void setCurrentContract(ContractDefinition const& _contract); MagicVariableDeclaration const* getCurrentThis() const; + MagicVariableDeclaration const* getCurrentSuper() const; /// @returns a vector of all implicit global declarations excluding "this". std::vector getDeclarations() const; @@ -56,6 +57,7 @@ private: std::vector> m_magicVariables; ContractDefinition const* m_currentContract = nullptr; std::map> mutable m_thisPointer; + std::map> mutable m_superPointer; }; } diff --git a/libsolidity/InterfaceHandler.cpp b/libsolidity/InterfaceHandler.cpp index 1adce8cb8..82486c5d1 100644 --- a/libsolidity/InterfaceHandler.cpp +++ b/libsolidity/InterfaceHandler.cpp @@ -37,34 +37,52 @@ std::unique_ptr InterfaceHandler::getDocumentation(ContractDefiniti std::unique_ptr InterfaceHandler::getABIInterface(ContractDefinition const& _contractDef) { - Json::Value methods(Json::arrayValue); - + Json::Value abi(Json::arrayValue); for (auto const& it: _contractDef.getInterfaceFunctions()) { - Json::Value method; - Json::Value inputs(Json::arrayValue); - Json::Value outputs(Json::arrayValue); - - auto populateParameters = [](std::vector> const& _vars) + auto populateParameters = [](vector const& _paramNames, vector const& _paramTypes) { Json::Value params(Json::arrayValue); - for (ASTPointer const& var: _vars) + solAssert(_paramNames.size() == _paramTypes.size(), "Names and types vector size does not match"); + for (unsigned i = 0; i < _paramNames.size(); ++i) { - Json::Value input; - input["name"] = var->getName(); - input["type"] = var->getType()->toString(); - params.append(input); + Json::Value param; + param["name"] = _paramNames[i]; + param["type"] = _paramTypes[i]; + params.append(param); } return params; }; - method["name"] = it.second->getName(); - method["constant"] = it.second->isDeclaredConst(); - method["inputs"] = populateParameters(it.second->getParameters()); - method["outputs"] = populateParameters(it.second->getReturnParameters()); - methods.append(method); + Json::Value method; + method["type"] = "function"; + method["name"] = it.second->getDeclaration().getName(); + method["constant"] = it.second->isConstant(); + method["inputs"] = populateParameters(it.second->getParameterNames(), + it.second->getParameterTypeNames()); + method["outputs"] = populateParameters(it.second->getReturnParameterNames(), + it.second->getReturnParameterTypeNames()); + abi.append(method); + } + + for (auto const& it: _contractDef.getInterfaceEvents()) + { + Json::Value event; + event["type"] = "event"; + event["name"] = it->getName(); + Json::Value params(Json::arrayValue); + for (auto const& p: it->getParameters()) + { + Json::Value input; + input["name"] = p->getName(); + input["type"] = p->getType()->toString(); + input["indexed"] = p->isIndexed(); + params.append(input); + } + event["inputs"] = params; + abi.append(event); } - return std::unique_ptr(new std::string(m_writer.write(methods))); + return std::unique_ptr(new std::string(m_writer.write(abi))); } unique_ptr InterfaceHandler::getABISolidityInterface(ContractDefinition const& _contractDef) @@ -72,21 +90,34 @@ unique_ptr InterfaceHandler::getABISolidityInterface(ContractDefinition string ret = "contract " + _contractDef.getName() + "{"; for (auto const& it: _contractDef.getInterfaceFunctions()) { - FunctionDefinition const* f = it.second; - auto populateParameters = [](vector> const& _vars) + auto populateParameters = [](vector const& _paramNames, + vector const& _paramTypes) { string r = ""; - for (ASTPointer const& var: _vars) - r += (r.size() ? "," : "(") + var->getType()->toString() + " " + var->getName(); + solAssert(_paramNames.size() == _paramTypes.size(), "Names and types vector size does not match"); + for (unsigned i = 0; i < _paramNames.size(); ++i) + r += (r.size() ? "," : "(") + _paramTypes[i] + " " + _paramNames[i]; return r.size() ? r + ")" : "()"; }; - ret += "function " + f->getName() + populateParameters(f->getParameters()) + (f->isDeclaredConst() ? "constant " : ""); - if (f->getReturnParameters().size()) - ret += "returns" + populateParameters(f->getReturnParameters()); + ret += "function " + it.second->getDeclaration().getName() + + populateParameters(it.second->getParameterNames(), it.second->getParameterTypeNames()) + + (it.second->isConstant() ? "constant " : ""); + if (it.second->getReturnParameterTypes().size()) + ret += "returns" + populateParameters(it.second->getReturnParameterNames(), it.second->getReturnParameterTypeNames()); else if (ret.back() == ' ') ret.pop_back(); ret += "{}"; } + for (auto const& it: _contractDef.getInterfaceEvents()) + { + std::string params; + for (auto const& p: it->getParameters()) + params += (params.empty() ? "(" : ",") + p->getType()->toString() + (p->isIndexed() ? " indexed " : " ") + p->getName(); + if (!params.empty()) + params += ")"; + + ret += "event " + it->getName() + params + ";"; + } return unique_ptr(new string(ret + "}")); } diff --git a/libsolidity/NameAndTypeResolver.cpp b/libsolidity/NameAndTypeResolver.cpp index ba5ca1345..7dc42bc62 100644 --- a/libsolidity/NameAndTypeResolver.cpp +++ b/libsolidity/NameAndTypeResolver.cpp @@ -60,6 +60,13 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) ReferencesResolver resolver(*structDef, *this, &_contract, nullptr); for (ASTPointer const& variable: _contract.getStateVariables()) ReferencesResolver resolver(*variable, *this, &_contract, nullptr); + for (ASTPointer const& event: _contract.getEvents()) + ReferencesResolver resolver(*event, *this, &_contract, nullptr); + for (ASTPointer const& modifier: _contract.getFunctionModifiers()) + { + m_currentScope = &m_scopes[modifier.get()]; + ReferencesResolver resolver(*modifier, *this, &_contract, nullptr); + } for (ASTPointer const& function: _contract.getDefinedFunctions()) { m_currentScope = &m_scopes[function.get()]; @@ -111,7 +118,7 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract) { // order in the lists is from derived to base // list of lists to linearize, the last element is the list of direct bases - list> input(1, {&_contract}); + list> input(1, {}); for (ASTPointer const& baseSpecifier: _contract.getBaseContracts()) { ASTPointer baseName = baseSpecifier->getName(); @@ -119,14 +126,15 @@ void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract) baseName->getReferencedDeclaration()); if (!base) BOOST_THROW_EXCEPTION(baseName->createTypeError("Contract expected.")); - // "push_back" has the effect that bases mentioned earlier can overwrite members of bases - // mentioned later - input.back().push_back(base); + // "push_front" has the effect that bases mentioned later can overwrite members of bases + // mentioned earlier + input.back().push_front(base); vector const& basesBases = base->getLinearizedBaseContracts(); if (basesBases.empty()) BOOST_THROW_EXCEPTION(baseName->createTypeError("Definition of base has to precede definition of derived contract")); input.push_front(list(basesBases.begin(), basesBases.end())); } + input.back().push_front(&_contract); vector result = cThreeMerge(input); if (result.empty()) BOOST_THROW_EXCEPTION(_contract.createTypeError("Linearization of inheritance graph impossible")); @@ -226,6 +234,19 @@ void DeclarationRegistrationHelper::endVisit(FunctionDefinition&) closeCurrentScope(); } +bool DeclarationRegistrationHelper::visit(ModifierDefinition& _modifier) +{ + registerDeclaration(_modifier, true); + m_currentFunction = &_modifier; + return true; +} + +void DeclarationRegistrationHelper::endVisit(ModifierDefinition&) +{ + m_currentFunction = nullptr; + closeCurrentScope(); +} + void DeclarationRegistrationHelper::endVisit(VariableDefinition& _variableDefinition) { // Register the local variables with the function @@ -240,6 +261,17 @@ bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration) return true; } +bool DeclarationRegistrationHelper::visit(EventDefinition& _event) +{ + registerDeclaration(_event, true); + return true; +} + +void DeclarationRegistrationHelper::endVisit(EventDefinition&) +{ + closeCurrentScope(); +} + void DeclarationRegistrationHelper::enterNewSubScope(Declaration const& _declaration) { map::iterator iter; @@ -292,8 +324,7 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable) bool ReferencesResolver::visit(Return& _return) { - solAssert(m_returnParameters, "Return parameters not set."); - _return.setFunctionReturnParameters(*m_returnParameters); + _return.setFunctionReturnParameters(m_returnParameters); return true; } diff --git a/libsolidity/NameAndTypeResolver.h b/libsolidity/NameAndTypeResolver.h index f97c7ae56..4b7ce6e7d 100644 --- a/libsolidity/NameAndTypeResolver.h +++ b/libsolidity/NameAndTypeResolver.h @@ -94,14 +94,18 @@ public: DeclarationRegistrationHelper(std::map& _scopes, ASTNode& _astRoot); private: - bool visit(ContractDefinition& _contract); - void endVisit(ContractDefinition& _contract); - bool visit(StructDefinition& _struct); - void endVisit(StructDefinition& _struct); - bool visit(FunctionDefinition& _function); - void endVisit(FunctionDefinition& _function); - void endVisit(VariableDefinition& _variableDefinition); - bool visit(VariableDeclaration& _declaration); + bool visit(ContractDefinition& _contract) override; + void endVisit(ContractDefinition& _contract) override; + bool visit(StructDefinition& _struct) override; + void endVisit(StructDefinition& _struct) override; + bool visit(FunctionDefinition& _function) override; + void endVisit(FunctionDefinition& _function) override; + bool visit(ModifierDefinition& _modifier) override; + void endVisit(ModifierDefinition& _modifier) override; + void endVisit(VariableDefinition& _variableDefinition) override; + bool visit(VariableDeclaration& _declaration) override; + bool visit(EventDefinition& _event) override; + void endVisit(EventDefinition& _event) override; void enterNewSubScope(Declaration const& _declaration); void closeCurrentScope(); @@ -109,7 +113,7 @@ private: std::map& m_scopes; Declaration const* m_currentScope; - FunctionDefinition* m_currentFunction; + VariableScope* m_currentFunction; }; /** diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index fcabdb29b..d2e888a8d 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -121,6 +121,8 @@ ASTPointer Parser::parseContractDefinition() vector> structs; vector> stateVariables; vector> functions; + vector> modifiers; + vector> events; if (m_scanner->getCurrentToken() == Token::IS) do { @@ -148,23 +150,29 @@ ASTPointer Parser::parseContractDefinition() else if (currentToken == Token::IDENTIFIER || currentToken == Token::MAPPING || Token::isElementaryTypeName(currentToken)) { - bool const allowVar = false; - stateVariables.push_back(parseVariableDeclaration(allowVar)); + VarDeclParserOptions options; + options.isPublic = visibilityIsPublic; + options.isStateVariable = true; + stateVariables.push_back(parseVariableDeclaration(options)); expectToken(Token::SEMICOLON); } + else if (currentToken == Token::MODIFIER) + modifiers.push_back(parseModifierDefinition()); + else if (currentToken == Token::EVENT) + events.push_back(parseEventDefinition()); else - BOOST_THROW_EXCEPTION(createParserError("Function, variable or struct declaration expected.")); + BOOST_THROW_EXCEPTION(createParserError("Function, variable, struct or modifier declaration expected.")); } nodeFactory.markEndPosition(); expectToken(Token::RBRACE); return nodeFactory.createNode(name, docString, baseContracts, structs, - stateVariables, functions); + stateVariables, functions, modifiers, events); } ASTPointer Parser::parseInheritanceSpecifier() { ASTNodeFactory nodeFactory(*this); - ASTPointer name = ASTNodeFactory(*this).createNode(expectIdentifierToken()); + ASTPointer name(parseIdentifier()); vector> arguments; if (m_scanner->getCurrentToken() == Token::LPAREN) { @@ -186,13 +194,25 @@ ASTPointer Parser::parseFunctionDefinition(bool _isPublic, A docstring = make_shared(m_scanner->getCurrentCommentLiteral()); expectToken(Token::FUNCTION); - ASTPointer name(expectIdentifierToken()); + ASTPointer name; + if (m_scanner->getCurrentToken() == Token::LPAREN) + name = make_shared(); // anonymous function + else + name = expectIdentifierToken(); ASTPointer parameters(parseParameterList()); bool isDeclaredConst = false; - if (m_scanner->getCurrentToken() == Token::CONST) + vector> modifiers; + while (true) { - isDeclaredConst = true; - m_scanner->next(); + if (m_scanner->getCurrentToken() == Token::CONST) + { + isDeclaredConst = true; + m_scanner->next(); + } + else if (m_scanner->getCurrentToken() == Token::IDENTIFIER) + modifiers.push_back(parseModifierInvocation()); + else + break; } ASTPointer returnParameters; if (m_scanner->getCurrentToken() == Token::RETURNS) @@ -202,18 +222,13 @@ ASTPointer Parser::parseFunctionDefinition(bool _isPublic, A returnParameters = parseParameterList(permitEmptyParameterList); } else - { - // create an empty parameter list at a zero-length location - ASTNodeFactory nodeFactory(*this); - nodeFactory.setLocationEmpty(); - returnParameters = nodeFactory.createNode(vector>()); - } + returnParameters = createEmptyParameterList(); ASTPointer block = parseBlock(); nodeFactory.setEndPositionFromNode(block); bool const c_isConstructor = (_contractName && *name == *_contractName); return nodeFactory.createNode(name, _isPublic, c_isConstructor, docstring, - parameters, - isDeclaredConst, returnParameters, block); + parameters, isDeclaredConst, modifiers, + returnParameters, block); } ASTPointer Parser::parseStructDefinition() @@ -225,8 +240,7 @@ ASTPointer Parser::parseStructDefinition() expectToken(Token::LBRACE); while (m_scanner->getCurrentToken() != Token::RBRACE) { - bool const allowVar = false; - members.push_back(parseVariableDeclaration(allowVar)); + members.push_back(parseVariableDeclaration()); expectToken(Token::SEMICOLON); } nodeFactory.markEndPosition(); @@ -234,12 +248,85 @@ ASTPointer Parser::parseStructDefinition() return nodeFactory.createNode(name, members); } -ASTPointer Parser::parseVariableDeclaration(bool _allowVar) +ASTPointer Parser::parseVariableDeclaration(VarDeclParserOptions const& _options) +{ + ASTNodeFactory nodeFactory(*this); + ASTPointer type = parseTypeName(_options.allowVar); + bool isIndexed = false; + if (_options.allowIndexed && m_scanner->getCurrentToken() == Token::INDEXED) + { + isIndexed = true; + m_scanner->next(); + } + nodeFactory.markEndPosition(); + return nodeFactory.createNode(type, expectIdentifierToken(), + _options.isPublic, _options.isStateVariable, + isIndexed); +} + +ASTPointer Parser::parseModifierDefinition() +{ + ScopeGuard resetModifierFlag([this]() { m_insideModifier = false; }); + m_insideModifier = true; + + ASTNodeFactory nodeFactory(*this); + ASTPointer docstring; + if (m_scanner->getCurrentCommentLiteral() != "") + docstring = make_shared(m_scanner->getCurrentCommentLiteral()); + + expectToken(Token::MODIFIER); + ASTPointer name(expectIdentifierToken()); + ASTPointer parameters; + if (m_scanner->getCurrentToken() == Token::LPAREN) + parameters = parseParameterList(); + else + parameters = createEmptyParameterList(); + ASTPointer block = parseBlock(); + nodeFactory.setEndPositionFromNode(block); + return nodeFactory.createNode(name, docstring, parameters, block); +} + +ASTPointer Parser::parseEventDefinition() +{ + ASTNodeFactory nodeFactory(*this); + ASTPointer docstring; + if (m_scanner->getCurrentCommentLiteral() != "") + docstring = make_shared(m_scanner->getCurrentCommentLiteral()); + + expectToken(Token::EVENT); + ASTPointer name(expectIdentifierToken()); + ASTPointer parameters; + if (m_scanner->getCurrentToken() == Token::LPAREN) + parameters = parseParameterList(true, true); + else + parameters = createEmptyParameterList(); + nodeFactory.markEndPosition(); + expectToken(Token::SEMICOLON); + return nodeFactory.createNode(name, docstring, parameters); +} + +ASTPointer Parser::parseModifierInvocation() +{ + ASTNodeFactory nodeFactory(*this); + ASTPointer name(parseIdentifier()); + vector> arguments; + if (m_scanner->getCurrentToken() == Token::LPAREN) + { + m_scanner->next(); + arguments = parseFunctionCallArguments(); + nodeFactory.markEndPosition(); + expectToken(Token::RPAREN); + } + else + nodeFactory.setEndPositionFromNode(name); + return nodeFactory.createNode(name, arguments); +} + +ASTPointer Parser::parseIdentifier() { ASTNodeFactory nodeFactory(*this); - ASTPointer type = parseTypeName(_allowVar); nodeFactory.markEndPosition(); - return nodeFactory.createNode(type, expectIdentifierToken()); + return nodeFactory.createNode(expectIdentifierToken()); } ASTPointer Parser::parseTypeName(bool _allowVar) @@ -290,19 +377,20 @@ ASTPointer Parser::parseMapping() return nodeFactory.createNode(keyType, valueType); } -ASTPointer Parser::parseParameterList(bool _allowEmpty) +ASTPointer Parser::parseParameterList(bool _allowEmpty, bool _allowIndexed) { ASTNodeFactory nodeFactory(*this); vector> parameters; + VarDeclParserOptions options; + options.allowIndexed = _allowIndexed; expectToken(Token::LPAREN); if (!_allowEmpty || m_scanner->getCurrentToken() != Token::RPAREN) { - bool const allowVar = false; - parameters.push_back(parseVariableDeclaration(allowVar)); + parameters.push_back(parseVariableDeclaration(options)); while (m_scanner->getCurrentToken() != Token::RPAREN) { expectToken(Token::COMMA); - parameters.push_back(parseVariableDeclaration(allowVar)); + parameters.push_back(parseVariableDeclaration(options)); } } nodeFactory.markEndPosition(); @@ -354,8 +442,16 @@ ASTPointer Parser::parseStatement() nodeFactory.setEndPositionFromNode(expression); } statement = nodeFactory.createNode(expression); + break; } - break; + case Token::IDENTIFIER: + if (m_insideModifier && m_scanner->getCurrentLiteral() == "_") + { + statement = ASTNodeFactory(*this).createNode(); + m_scanner->next(); + return statement; + } + // fall-through default: statement = parseVarDefOrExprStmt(); } @@ -436,8 +532,9 @@ ASTPointer Parser::parseVarDefOrExprStmt() ASTPointer Parser::parseVariableDefinition() { ASTNodeFactory nodeFactory(*this); - bool const allowVar = true; - ASTPointer variable = parseVariableDeclaration(allowVar); + VarDeclParserOptions options; + options.allowVar = true; + ASTPointer variable = parseVariableDeclaration(options); ASTPointer value; if (m_scanner->getCurrentToken() == Token::ASSIGN) { @@ -521,8 +618,8 @@ ASTPointer Parser::parseLeftHandSideExpression() if (m_scanner->getCurrentToken() == Token::NEW) { expectToken(Token::NEW); - ASTPointer contractName = ASTNodeFactory(*this).createNode(expectIdentifierToken()); - nodeFactory.markEndPosition(); + ASTPointer contractName(parseIdentifier()); + nodeFactory.setEndPositionFromNode(contractName); expression = nodeFactory.createNode(contractName); } else @@ -666,6 +763,13 @@ ASTPointer Parser::getLiteralAndAdvance() return identifier; } +ASTPointer Parser::createEmptyParameterList() +{ + ASTNodeFactory nodeFactory(*this); + nodeFactory.setLocationEmpty(); + return nodeFactory.createNode(vector>()); +} + ParserError Parser::createParserError(string const& _description) const { return ParserError() << errinfo_sourceLocation(Location(getPosition(), getPosition(), getSourceName())) diff --git a/libsolidity/Parser.h b/libsolidity/Parser.h index 5905a0420..413a2711e 100644 --- a/libsolidity/Parser.h +++ b/libsolidity/Parser.h @@ -45,6 +45,14 @@ private: /// End position of the current token int getEndPosition() const; + struct VarDeclParserOptions { + VarDeclParserOptions() {} + bool allowVar = false; + bool isPublic = false; + bool isStateVariable = false; + bool allowIndexed = false; + }; + ///@{ ///@name Parsing functions for the AST nodes ASTPointer parseImportDirective(); @@ -52,10 +60,14 @@ private: ASTPointer parseInheritanceSpecifier(); ASTPointer parseFunctionDefinition(bool _isPublic, ASTString const* _contractName); ASTPointer parseStructDefinition(); - ASTPointer parseVariableDeclaration(bool _allowVar); + ASTPointer parseVariableDeclaration(VarDeclParserOptions const& _options = VarDeclParserOptions()); + ASTPointer parseModifierDefinition(); + ASTPointer parseEventDefinition(); + ASTPointer parseModifierInvocation(); + ASTPointer parseIdentifier(); ASTPointer parseTypeName(bool _allowVar); ASTPointer parseMapping(); - ASTPointer parseParameterList(bool _allowEmpty = true); + ASTPointer parseParameterList(bool _allowEmpty = true, bool _allowIndexed = false); ASTPointer parseBlock(); ASTPointer parseStatement(); ASTPointer parseIfStatement(); @@ -85,11 +97,16 @@ private: ASTPointer getLiteralAndAdvance(); ///@} + /// Creates an empty ParameterList at the current location (used if parameters can be omitted). + ASTPointer createEmptyParameterList(); + /// Creates a @ref ParserError exception and annotates it with the current position and the /// given @a _description. ParserError createParserError(std::string const& _description) const; std::shared_ptr m_scanner; + /// Flag that signifies whether '_' is parsed as a PlaceholderStatement or a regular identifier. + bool m_insideModifier = false; }; } diff --git a/libsolidity/Token.h b/libsolidity/Token.h index 552e9a75e..ed42f90cc 100644 --- a/libsolidity/Token.h +++ b/libsolidity/Token.h @@ -153,12 +153,15 @@ namespace solidity K(DEFAULT, "default", 0) \ K(DO, "do", 0) \ K(ELSE, "else", 0) \ + K(EVENT, "event", 0) \ K(IS, "is", 0) \ + K(INDEXED, "indexed", 0) \ K(FOR, "for", 0) \ K(FUNCTION, "function", 0) \ K(IF, "if", 0) \ K(IMPORT, "import", 0) \ K(MAPPING, "mapping", 0) \ + K(MODIFIER, "modifier", 0) \ K(NEW, "new", 0) \ K(PUBLIC, "public", 0) \ K(PRIVATE, "private", 0) \ diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 2446c513c..ab401332a 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -140,6 +140,11 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const { + if (_convertTo.getCategory() == Category::STRING) + { + StaticStringType const& convertTo = dynamic_cast(_convertTo); + return isHash() && (m_bits == convertTo.getNumBytes() * 8); + } return _convertTo.getCategory() == getCategory() || _convertTo.getCategory() == Category::CONTRACT; } @@ -367,6 +372,17 @@ bool StaticStringType::isImplicitlyConvertibleTo(Type const& _convertTo) const return convertTo.m_bytes >= m_bytes; } +bool StaticStringType::isExplicitlyConvertibleTo(Type const& _convertTo) const +{ + if (_convertTo.getCategory() == Category::INTEGER) + { + IntegerType const& convertTo = dynamic_cast(_convertTo); + if (convertTo.isHash() && (m_bytes * 8 == convertTo.getNumBits())) + return true; + } + return isImplicitlyConvertibleTo(_convertTo); +} + bool StaticStringType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) @@ -434,7 +450,9 @@ bool ContractType::isImplicitlyConvertibleTo(Type const& _convertTo) const if (_convertTo.getCategory() == Category::CONTRACT) { auto const& bases = getContractDefinition().getLinearizedBaseContracts(); - return find(bases.begin(), bases.end(), + if (m_super && bases.size() <= 1) + return false; + return find(m_super ? ++bases.begin() : bases.begin(), bases.end(), &dynamic_cast(_convertTo).getContractDefinition()) != bases.end(); } return false; @@ -456,12 +474,12 @@ bool ContractType::operator==(Type const& _other) const if (_other.getCategory() != getCategory()) return false; ContractType const& other = dynamic_cast(_other); - return other.m_contract == m_contract; + return other.m_contract == m_contract && other.m_super == m_super; } string ContractType::toString() const { - return "contract " + m_contract.getName(); + return "contract " + string(m_super ? "super " : "") + m_contract.getName(); } MemberList const& ContractType::getMembers() const @@ -472,8 +490,16 @@ MemberList const& ContractType::getMembers() const // All address members and all interface functions map> members(IntegerType::AddressMemberList.begin(), IntegerType::AddressMemberList.end()); - for (auto const& it: m_contract.getInterfaceFunctions()) - members[it.second->getName()] = make_shared(*it.second, false); + if (m_super) + { + for (ContractDefinition const* base: m_contract.getLinearizedBaseContracts()) + for (ASTPointer const& function: base->getDefinedFunctions()) + if (!function->isConstructor() && !function->getName().empty()) + members.insert(make_pair(function->getName(), make_shared(*function, true))); + } + else + for (auto const& it: m_contract.getInterfaceFunctions()) + members[it.second->getDeclaration().getName()] = it.second; m_members.reset(new MemberList(members)); } return *m_members; @@ -495,9 +521,9 @@ shared_ptr const& ContractType::getConstructorType() const u256 ContractType::getFunctionIdentifier(string const& _functionName) const { auto interfaceFunctions = m_contract.getInterfaceFunctions(); - for (auto it = interfaceFunctions.cbegin(); it != interfaceFunctions.cend(); ++it) - if (it->second->getName() == _functionName) - return FixedHash<4>::Arith(it->first); + for (auto const& it: m_contract.getInterfaceFunctions()) + if (it.second->getDeclaration().getName() == _functionName) + return FixedHash<4>::Arith(it.first); return Invalid256; } @@ -563,18 +589,64 @@ u256 StructType::getStorageOffsetOfMember(string const& _name) const } FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal): - m_location(_isInternal ? Location::INTERNAL : Location::EXTERNAL) + m_location(_isInternal ? Location::INTERNAL : Location::EXTERNAL), + m_isConstant(_function.isDeclaredConst()), + m_declaration(&_function) { TypePointers params; + vector paramNames; TypePointers retParams; + vector retParamNames; + params.reserve(_function.getParameters().size()); + paramNames.reserve(_function.getParameters().size()); for (ASTPointer const& var: _function.getParameters()) + { + paramNames.push_back(var->getName()); params.push_back(var->getType()); + } retParams.reserve(_function.getReturnParameters().size()); + retParamNames.reserve(_function.getReturnParameters().size()); for (ASTPointer const& var: _function.getReturnParameters()) + { + retParamNames.push_back(var->getName()); retParams.push_back(var->getType()); + } swap(params, m_parameterTypes); + swap(paramNames, m_parameterNames); swap(retParams, m_returnParameterTypes); + swap(retParamNames, m_returnParameterNames); +} + +FunctionType::FunctionType(VariableDeclaration const& _varDecl): + m_location(Location::EXTERNAL), m_isConstant(true), m_declaration(&_varDecl) +{ + TypePointers params({}); + vector paramNames({}); + TypePointers retParams({_varDecl.getType()}); + vector retParamNames({ _varDecl.getName()}); + // for now, no input parameters LTODO: change for some things like mapping + + swap(params, m_parameterTypes); + swap(paramNames, m_parameterNames); + swap(retParams, m_returnParameterTypes); + swap(retParamNames, m_returnParameterNames); +} + +FunctionType::FunctionType(const EventDefinition& _event): + m_location(Location::EVENT), m_declaration(&_event) +{ + TypePointers params; + vector paramNames; + params.reserve(_event.getParameters().size()); + paramNames.reserve(_event.getParameters().size()); + for (ASTPointer const& var: _event.getParameters()) + { + paramNames.push_back(var->getName()); + params.push_back(var->getType()); + } + swap(params, m_parameterTypes); + swap(paramNames, m_parameterNames); } bool FunctionType::operator==(Type const& _other) const @@ -585,6 +657,9 @@ bool FunctionType::operator==(Type const& _other) const if (m_location != other.m_location) return false; + if (m_isConstant != other.isConstant()) + return false; + if (m_parameterTypes.size() != other.m_parameterTypes.size() || m_returnParameterTypes.size() != other.m_returnParameterTypes.size()) return false; @@ -656,9 +731,15 @@ MemberList const& FunctionType::getMembers() const } } -string FunctionType::getCanonicalSignature() const +string FunctionType::getCanonicalSignature(std::string const& _name) const { - string ret = "("; + std::string funcName = _name; + if (_name == "") + { + solAssert(m_declaration != nullptr, "Function type without name needs a declaration"); + funcName = m_declaration->getName(); + } + string ret = funcName + "("; for (auto it = m_parameterTypes.cbegin(); it != m_parameterTypes.cend(); ++it) ret += (*it)->toString() + (it + 1 == m_parameterTypes.cend() ? "" : ","); @@ -681,6 +762,33 @@ TypePointer FunctionType::copyAndSetGasOrValue(bool _setGas, bool _setValue) con m_gasSet || _setGas, m_valueSet || _setValue); } +vector const FunctionType::getParameterTypeNames() const +{ + vector names; + for (TypePointer const& t: m_parameterTypes) + names.push_back(t->toString()); + + return names; +} + +vector const FunctionType::getReturnParameterTypeNames() const +{ + vector names; + for (TypePointer const& t: m_returnParameterTypes) + names.push_back(t->toString()); + + return names; +} + +ASTPointer FunctionType::getDocumentation() const +{ + auto function = dynamic_cast(m_declaration); + if (function) + return function->getDocumentation(); + + return ASTPointer(); +} + bool MappingType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) @@ -716,7 +824,7 @@ MemberList const& TypeType::getMembers() const // We are accessing the type of a base contract, so add all public and private // functions. Note that this does not add inherited functions on purpose. for (ASTPointer const& f: contract.getDefinedFunctions()) - if (!f->isConstructor()) + if (!f->isConstructor() && !f->getName().empty()) members[f->getName()] = make_shared(*f); } m_members.reset(new MemberList(members)); @@ -724,6 +832,38 @@ MemberList const& TypeType::getMembers() const return *m_members; } +ModifierType::ModifierType(const ModifierDefinition& _modifier) +{ + TypePointers params; + params.reserve(_modifier.getParameters().size()); + for (ASTPointer const& var: _modifier.getParameters()) + params.push_back(var->getType()); + swap(params, m_parameterTypes); +} + +bool ModifierType::operator==(Type const& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + ModifierType const& other = dynamic_cast(_other); + + if (m_parameterTypes.size() != other.m_parameterTypes.size()) + return false; + auto typeCompare = [](TypePointer const& _a, TypePointer const& _b) -> bool { return *_a == *_b; }; + + if (!equal(m_parameterTypes.cbegin(), m_parameterTypes.cend(), + other.m_parameterTypes.cbegin(), typeCompare)) + return false; + return true; +} + +string ModifierType::toString() const +{ + string name = "modifier ("; + for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it) + name += (*it)->toString() + (it + 1 == m_parameterTypes.end() ? "" : ","); + return name + ")"; +} MagicType::MagicType(MagicType::Kind _kind): m_kind(_kind) diff --git a/libsolidity/Types.h b/libsolidity/Types.h index e6c99fe3b..1f4d27a25 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -41,6 +41,7 @@ namespace solidity class Type; // forward class FunctionType; // forward using TypePointer = std::shared_ptr; +using FunctionTypePointer = std::shared_ptr; using TypePointers = std::vector; /** @@ -75,7 +76,7 @@ class Type: private boost::noncopyable, public std::enable_shared_from_this smallestTypeForLiteral(std::string const& _literal); - StaticStringType(int _bytes); + explicit StaticStringType(int _bytes); virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool operator==(Type const& _other) const override; virtual unsigned getCalldataEncodedSize() const override { return m_bytes; } @@ -276,7 +278,8 @@ class ContractType: public Type { public: virtual Category getCategory() const override { return Category::CONTRACT; } - ContractType(ContractDefinition const& _contract): m_contract(_contract) {} + explicit ContractType(ContractDefinition const& _contract, bool _super = false): + m_contract(_contract), m_super(_super) {} /// Contracts can be implicitly converted to super classes and to addresses. virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; /// Contracts can be converted to themselves and to integers. @@ -288,11 +291,12 @@ public: virtual MemberList const& getMembers() const override; + bool isSuper() const { return m_super; } ContractDefinition const& getContractDefinition() const { return m_contract; } /// Returns the function type of the constructor. Note that the location part of the function type /// is not used, as this type cannot be the type of a variable or expression. - std::shared_ptr const& getConstructorType() const; + FunctionTypePointer const& getConstructorType() const; /// @returns the identifier of the function with the given name or Invalid256 if such a name does /// not exist. @@ -300,8 +304,11 @@ public: private: ContractDefinition const& m_contract; + /// If true, it is the "super" type of the current contract, i.e. it contains only inherited + /// members. + bool m_super; /// Type of the constructor, @see getConstructorType. Lazily initialized. - mutable std::shared_ptr m_constructorType; + mutable FunctionTypePointer m_constructorType; /// List of member types, will be lazy-initialized because of recursive references. mutable std::unique_ptr m_members; }; @@ -313,7 +320,7 @@ class StructType: public Type { public: virtual Category getCategory() const override { return Category::STRUCT; } - StructType(StructDefinition const& _struct): m_struct(_struct) {} + explicit StructType(StructDefinition const& _struct): m_struct(_struct) {} virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(Type const& _other) const override; virtual u256 getStorageSize() const override; @@ -343,15 +350,18 @@ public: /// INTERNAL: jump tag, EXTERNAL: contract address + function identifier, /// BARE: contract address (non-abi contract call) /// OTHERS: special virtual function, nothing on the stack + /// @todo This documentation is outdated, and Location should rather be named "Type" enum class Location { INTERNAL, EXTERNAL, CREATION, SEND, SHA3, SUICIDE, ECRECOVER, SHA256, RIPEMD160, - LOG0, LOG1, LOG2, LOG3, LOG4, + LOG0, LOG1, LOG2, LOG3, LOG4, EVENT, SET_GAS, SET_VALUE, BLOCKHASH, BARE }; virtual Category getCategory() const override { return Category::FUNCTION; } explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true); + explicit FunctionType(VariableDeclaration const& _varDecl); + explicit FunctionType(EventDefinition const& _event); FunctionType(strings const& _parameterTypes, strings const& _returnParameterTypes, Location _location = Location::INTERNAL): FunctionType(parseElementaryTypeVector(_parameterTypes), parseElementaryTypeVector(_returnParameterTypes), @@ -363,7 +373,11 @@ public: m_location(_location), m_gasSet(_gasSet), m_valueSet(_valueSet) {} TypePointers const& getParameterTypes() const { return m_parameterTypes; } + std::vector const& getParameterNames() const { return m_parameterNames; } + std::vector const getParameterTypeNames() const; TypePointers const& getReturnParameterTypes() const { return m_returnParameterTypes; } + std::vector const& getReturnParameterNames() const { return m_returnParameterNames; } + std::vector const getReturnParameterTypeNames() const; virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override; @@ -374,7 +388,20 @@ public: virtual MemberList const& getMembers() const override; Location const& getLocation() const { return m_location; } - std::string getCanonicalSignature() const; + /// @returns the canonical 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; + Declaration const& getDeclaration() const + { + solAssert(m_declaration, "Requested declaration from a FunctionType that has none"); + return *m_declaration; + } + bool hasDeclaration() const { return !!m_declaration; } + bool isConstant() const { return m_isConstant; } + /// @return A shared pointer of an ASTString. + /// Can contain a nullptr in which case indicates absence of documentation + ASTPointer getDocumentation() const; bool gasSet() const { return m_gasSet; } bool valueSet() const { return m_valueSet; } @@ -388,10 +415,14 @@ private: TypePointers m_parameterTypes; TypePointers m_returnParameterTypes; + std::vector m_parameterNames; + std::vector m_returnParameterNames; Location const m_location; bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack bool const m_valueSet = false; ///< true iff the value to be sent is on the stack + bool m_isConstant; mutable std::unique_ptr m_members; + Declaration const* m_declaration = nullptr; }; /** @@ -442,7 +473,7 @@ class TypeType: public Type { public: virtual Category getCategory() const override { return Category::TYPE; } - TypeType(TypePointer const& _actualType, ContractDefinition const* _currentContract = nullptr): + explicit TypeType(TypePointer const& _actualType, ContractDefinition const* _currentContract = nullptr): m_actualType(_actualType), m_currentContract(_currentContract) {} TypePointer const& getActualType() const { return m_actualType; } @@ -451,6 +482,7 @@ public: virtual bool canBeStored() const override { return false; } virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); } virtual bool canLiveOutsideStorage() const override { return false; } + virtual unsigned getSizeOnStack() const override { return 0; } virtual std::string toString() const override { return "type(" + m_actualType->toString() + ")"; } virtual MemberList const& getMembers() const override; @@ -463,6 +495,28 @@ private: }; +/** + * The type of a function modifier. Not used for anything for now. + */ +class ModifierType: public Type +{ +public: + virtual Category getCategory() const override { return Category::MODIFIER; } + explicit ModifierType(ModifierDefinition const& _modifier); + + virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } + virtual bool canBeStored() const override { return false; } + virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); } + virtual bool canLiveOutsideStorage() const override { return false; } + virtual unsigned getSizeOnStack() const override { return 0; } + virtual bool operator==(Type const& _other) const override; + virtual std::string toString() const override; + +private: + TypePointers m_parameterTypes; +}; + + /** * Special type for magic variables (block, msg, tx), similar to a struct but without any reference * (it always references a global singleton by name). @@ -473,7 +527,7 @@ public: enum class Kind { BLOCK, MSG, TX }; virtual Category getCategory() const override { return Category::MAGIC; } - MagicType(Kind _kind); + explicit MagicType(Kind _kind); virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index 11d99854c..b97dac5db 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -1,14 +1,14 @@ ContractDefinition = 'contract' Identifier ( 'is' InheritanceSpecifier (',' InheritanceSpecifier )* )? '{' ContractPart* '}' -ContractPart = VariableDeclaration ';' | StructDefinition | +ContractPart = VariableDeclaration ';' | StructDefinition | ModifierDefinition | FunctionDefinition | 'public:' | 'private:' InheritanceSpecifier = Identifier ( '(' Expression ( ',' Expression )* ')' )? StructDefinition = 'struct' Identifier '{' ( VariableDeclaration (';' VariableDeclaration)* )? '} - -FunctionDefinition = 'function' Identifier ParameterList 'const'? +ModifierDefinition = 'modifier' Identifier ParameterList? Block +FunctionDefinition = 'function' Identifier ParameterList ( Identifier | 'constant' )* ( 'returns' ParameterList )? Block ParameterList = '(' ( VariableDeclaration (',' VariableDeclaration)* )? ')' // semantic restriction: mappings and structs (recursively) containing mappings diff --git a/libweb3jsonrpc/CorsHttpServer.cpp b/libweb3jsonrpc/CorsHttpServer.cpp deleted file mode 100644 index ef4f2be37..000000000 --- a/libweb3jsonrpc/CorsHttpServer.cpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - 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 CorsHttpServer.cpp - * @author Marek Kotewicz - * @date 2014 - */ - -#include "CorsHttpServer.h" - -namespace jsonrpc -{ - -bool CorsHttpServer::SendResponse(std::string const& _response, void* _addInfo) -{ - struct mg_connection* conn = (struct mg_connection*) _addInfo; - if (mg_printf(conn, "HTTP/1.1 200 OK\r\n" - "Content-Type: application/json\r\n" - "Content-Length: %d\r\n" - "Access-Control-Allow-Origin: *\r\n" - "Access-Control-Allow-Headers: Content-Type\r\n" - "\r\n" - "%s",(int)_response.length(), _response.c_str()) > 0) - return true; - return false; - -} - -} diff --git a/libweb3jsonrpc/CorsHttpServer.h b/libweb3jsonrpc/CorsHttpServer.h deleted file mode 100644 index e697ecaa1..000000000 --- a/libweb3jsonrpc/CorsHttpServer.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - 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 CorsHttpServer.h - * @author Marek Kotewicz - * @date 2014 - */ - -#include - -namespace jsonrpc -{ - -class CorsHttpServer : public HttpServer -{ -public: - using HttpServer::HttpServer; - bool virtual SendResponse(std::string const& _response, void* _addInfo = NULL); -}; - -} - diff --git a/libweb3jsonrpc/WebThreeStubServerBase.cpp b/libweb3jsonrpc/WebThreeStubServerBase.cpp index 640ad3679..c8aff938f 100644 --- a/libweb3jsonrpc/WebThreeStubServerBase.cpp +++ b/libweb3jsonrpc/WebThreeStubServerBase.cpp @@ -62,7 +62,7 @@ static Json::Value toJson(dev::eth::Transaction const& _t) res["hash"] = toJS(_t.sha3()); res["input"] = jsFromBinary(_t.data()); res["to"] = toJS(_t.receiveAddress()); - res["from"] = toJS(_t.sender()); + res["from"] = toJS(_t.safeSender()); res["gas"] = (int)_t.gas(); res["gasPrice"] = toJS(_t.gasPrice()); res["nonce"] = toJS(_t.nonce()); @@ -77,7 +77,7 @@ static Json::Value toJson(dev::eth::LocalisedLogEntry const& _e) res["data"] = jsFromBinary(_e.data); res["address"] = toJS(_e.address); for (auto const& t: _e.topics) - res["topics"].append(toJS(t)); + res["topic"].append(toJS(t)); res["number"] = _e.number; return res; } @@ -123,16 +123,18 @@ static dev::eth::LogFilter toLogFilter(Json::Value const& _json) // commented to else if (_json["address"].isString()) filter.address(jsToAddress(_json["address"].asString())); } - if (!_json["topics"].empty()) + if (!_json["topic"].empty() && _json["topic"].isArray()) { - if (_json["topics"].isArray()) + unsigned i = 0; + for (auto t: _json["topic"]) { - for (auto i: _json["topics"]) - if (i.isString()) - filter.topic(jsToU256(i.asString())); + if (t.isArray()) + for (auto tt: t) + filter.topic(i, jsToFixed<32>(tt.asString())); + else if (t.isString()) + filter.topic(i, jsToFixed<32>(t.asString())); + i++; } - else if(_json["topics"].isString()) - filter.topic(jsToU256(_json["topics"].asString())); } return filter; } @@ -171,9 +173,9 @@ static shh::Envelope toSealed(Json::Value const& _json, shh::Message const& _m, return _m.seal(_from, bt, ttl, workToProve); } -static pair toWatch(Json::Value const& _json) +static pair toWatch(Json::Value const& _json) { - shh::BuildTopicMask bt; + shh::BuildTopic bt; Public to; if (_json["to"].isString()) @@ -188,7 +190,7 @@ static pair toWatch(Json::Value const& _json) if (i.isString()) bt.shift(jsToBytes(i.asString())); } - return make_pair(bt.toTopicMask(), to); + return make_pair(bt, to); } static Json::Value toJson(h256 const& _h, shh::Envelope const& _e, shh::Message const& _m) @@ -199,8 +201,8 @@ static Json::Value toJson(h256 const& _h, shh::Envelope const& _e, shh::Message res["sent"] = (int)_e.sent(); res["ttl"] = (int)_e.ttl(); res["workProved"] = (int)_e.workProved(); - for (auto const& t: _e.topics()) - res["topics"].append(toJS(t)); + for (auto const& t: _e.topic()) + res["topic"].append(toJS(t)); res["payload"] = toJS(_m.payload()); res["from"] = toJS(_m.from()); res["to"] = toJS(_m.to()); @@ -216,8 +218,11 @@ WebThreeStubServerBase::WebThreeStubServerBase(jsonrpc::AbstractServerConnector& void WebThreeStubServerBase::setAccounts(std::vector const& _accounts) { m_accounts.clear(); - for (auto i: _accounts) - m_accounts[i.address()] = i.secret(); + for (auto const& i: _accounts) + { + m_accounts.push_back(i.address()); + m_accountsLookup[i.address()] = i; + } } void WebThreeStubServerBase::setIdentities(std::vector const& _ids) @@ -235,8 +240,8 @@ std::string WebThreeStubServerBase::web3_sha3(std::string const& _param1) Json::Value WebThreeStubServerBase::eth_accounts() { Json::Value ret(Json::arrayValue); - for (auto i: m_accounts) - ret.append(toJS(i.first)); + for (auto const& i: m_accounts) + ret.append(toJS(i)); return ret; } @@ -307,25 +312,31 @@ static TransactionSkeleton toTransaction(Json::Value const& _json) return ret; } +bool WebThreeStubServerBase::eth_flush() +{ + client()->flushTransactions(); + return true; +} + std::string WebThreeStubServerBase::eth_call(Json::Value const& _json) { std::string ret; TransactionSkeleton t = toTransaction(_json); if (!t.from && m_accounts.size()) { - auto b = m_accounts.begin()->first; - for (auto a: m_accounts) - if (client()->balanceAt(a.first) > client()->balanceAt(b)) - b = a.first; + auto b = m_accounts.front(); + for (auto const& a: m_accounts) + if (client()->balanceAt(a) > client()->balanceAt(b)) + b = a; t.from = b; } - if (!m_accounts.count(t.from)) + if (!m_accountsLookup.count(t.from)) return ret; if (!t.gasPrice) t.gasPrice = 10 * dev::eth::szabo; if (!t.gas) t.gas = min(client()->gasLimitRemaining(), client()->balanceAt(t.from) / t.gasPrice); - ret = toJS(client()->call(m_accounts[t.from].secret(), t.value, t.to, t.data, t.gas, t.gasPrice)); + ret = toJS(client()->call(m_accountsLookup[t.from].secret(), t.value, t.to, t.data, t.gas, t.gasPrice)); return ret; } @@ -565,12 +576,12 @@ Json::Value WebThreeStubServerBase::shh_changed(int const& _id) if (pub) { cwarn << "Silently decrypting message from identity" << pub.abridged() << ": User validation hook goes here."; - m = e.open(m_ids[pub]); - if (!m) - continue; + m = e.open(face()->fullTopic(_id), m_ids[pub]); } else - m = e.open(); + m = e.open(face()->fullTopic(_id)); + if (!m) + continue; ret.append(toJson(h, e, m)); } @@ -607,13 +618,13 @@ std::string WebThreeStubServerBase::eth_transact(Json::Value const& _json) TransactionSkeleton t = toTransaction(_json); if (!t.from && m_accounts.size()) { - auto b = m_accounts.begin()->first; - for (auto a: m_accounts) - if (client()->balanceAt(a.first) > client()->balanceAt(b)) - b = a.first; + auto b = m_accounts.front(); + for (auto const& a: m_accounts) + if (client()->balanceAt(a) > client()->balanceAt(b)) + b = a; t.from = b; } - if (!m_accounts.count(t.from)) + if (!m_accountsLookup.count(t.from)) return ret; if (!t.gasPrice) t.gasPrice = 10 * dev::eth::szabo; @@ -623,15 +634,15 @@ std::string WebThreeStubServerBase::eth_transact(Json::Value const& _json) { if (t.to) // TODO: from qethereum, insert validification hook here. - client()->transact(m_accounts[t.from].secret(), t.value, t.to, t.data, t.gas, t.gasPrice); + client()->transact(m_accountsLookup[t.from].secret(), t.value, t.to, t.data, t.gas, t.gasPrice); else - ret = toJS(client()->transact(m_accounts[t.from].secret(), t.value, t.data, t.gas, t.gasPrice)); + ret = toJS(client()->transact(m_accountsLookup[t.from].secret(), t.value, t.data, t.gas, t.gasPrice)); client()->flushTransactions(); } return ret; } -bool WebThreeStubServerBase::authenticate(TransactionSkeleton const& _t) const +bool WebThreeStubServerBase::authenticate(TransactionSkeleton const& _t) { cwarn << "Silently signing transaction from address" << _t.from.abridged() << ": User validation hook goes here."; return true; diff --git a/libweb3jsonrpc/WebThreeStubServerBase.h b/libweb3jsonrpc/WebThreeStubServerBase.h index 24a6a9962..ee583efa3 100644 --- a/libweb3jsonrpc/WebThreeStubServerBase.h +++ b/libweb3jsonrpc/WebThreeStubServerBase.h @@ -79,6 +79,7 @@ public: virtual int eth_defaultBlock(); virtual std::string eth_gasPrice(); virtual Json::Value eth_filterLogs(int const& _id); + virtual bool eth_flush(); virtual Json::Value eth_logs(Json::Value const& _json); virtual bool eth_listening(); virtual bool eth_mining(); @@ -121,7 +122,7 @@ public: std::map const& ids() const { return m_ids; } protected: - virtual bool authenticate(dev::TransactionSkeleton const& _t) const; + virtual bool authenticate(dev::TransactionSkeleton const& _t); protected: virtual dev::eth::Interface* client() = 0; @@ -129,7 +130,8 @@ protected: virtual dev::WebThreeNetworkFace* network() = 0; virtual dev::WebThreeStubDatabaseFace* db() = 0; - std::map m_accounts; + std::map m_accountsLookup; + std::vector m_accounts; std::map m_ids; std::map m_shhWatches; diff --git a/libweb3jsonrpc/abstractwebthreestubserver.h b/libweb3jsonrpc/abstractwebthreestubserver.h index 15c53e0fa..0c25f9842 100644 --- a/libweb3jsonrpc/abstractwebthreestubserver.h +++ b/libweb3jsonrpc/abstractwebthreestubserver.h @@ -32,6 +32,7 @@ class AbstractWebThreeStubServer : public jsonrpc::AbstractServerbindAndAddMethod(new jsonrpc::Procedure("eth_codeAt", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_codeAtI); this->bindAndAddMethod(new jsonrpc::Procedure("eth_transact", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_OBJECT, NULL), &AbstractWebThreeStubServer::eth_transactI); this->bindAndAddMethod(new jsonrpc::Procedure("eth_call", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_OBJECT, NULL), &AbstractWebThreeStubServer::eth_callI); + this->bindAndAddMethod(new jsonrpc::Procedure("eth_flush", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, NULL), &AbstractWebThreeStubServer::eth_flushI); this->bindAndAddMethod(new jsonrpc::Procedure("eth_blockByHash", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_blockByHashI); this->bindAndAddMethod(new jsonrpc::Procedure("eth_blockByNumber", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_blockByNumberI); this->bindAndAddMethod(new jsonrpc::Procedure("eth_transactionByHash", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_transactionByHashI); @@ -142,6 +143,10 @@ class AbstractWebThreeStubServer : public jsonrpc::AbstractServereth_call(request[0u]); } + inline virtual void eth_flushI(const Json::Value &request, Json::Value &response) + { + response = this->eth_flush(); + } inline virtual void eth_blockByHashI(const Json::Value &request, Json::Value &response) { response = this->eth_blockByHash(request[0u].asString()); @@ -274,6 +279,7 @@ class AbstractWebThreeStubServer : public jsonrpc::AbstractServer; +using CollapsedTopicPart = FixedHash<4>; +using FullTopicPart = h256; -using Topic = std::vector; +using CollapsedTopic = std::vector; +using FullTopic = h256s; + +CollapsedTopicPart collapse(FullTopicPart const& _fullTopicPart); +CollapsedTopic collapse(FullTopic const& _fullTopic); class BuildTopic { @@ -74,8 +79,10 @@ public: BuildTopic& shiftRaw(h256 const& _part) { m_parts.push_back(_part); return *this; } - operator Topic() const { return toTopic(); } - Topic toTopic() const; + operator CollapsedTopic() const { return toTopic(); } + operator FullTopic() const { return toFullTopic(); } + CollapsedTopic toTopic() const; + FullTopic toFullTopic() const { return m_parts; } protected: BuildTopic& shiftBytes(bytes const& _b); @@ -83,13 +90,14 @@ protected: h256s m_parts; }; -using TopicMask = std::vector>; +using TopicMask = std::vector>; using TopicMasks = std::vector; class TopicFilter { public: TopicFilter() {} + TopicFilter(FullTopic const& _m) { m_topicMasks.push_back(TopicMask()); for (auto const& h: _m) m_topicMasks.back().push_back(std::make_pair(collapse(h), h ? ~CollapsedTopicPart() : CollapsedTopicPart())); } TopicFilter(TopicMask const& _m): m_topicMasks(1, _m) {} TopicFilter(TopicMasks const& _m): m_topicMasks(_m) {} TopicFilter(RLP const& _r)//: m_topicMasks(_r.toVector>()) @@ -123,7 +131,9 @@ public: template BuildTopicMask& operator()(T const& _t) { shift(_t); return *this; } operator TopicMask() const { return toTopicMask(); } + operator FullTopic() const { return toFullTopic(); } TopicMask toTopicMask() const; + FullTopic toFullTopic() const { return m_parts; } }; } diff --git a/libwhisper/Interface.cpp b/libwhisper/Interface.cpp index c00c3ebb2..72bca9785 100644 --- a/libwhisper/Interface.cpp +++ b/libwhisper/Interface.cpp @@ -34,7 +34,6 @@ using namespace dev::shh; #endif #define clogS(X) dev::LogOutputStream(false) << "| " << std::setw(2) << session()->socketId() << "] " -unsigned Interface::installWatch(TopicMask const& _mask) +Interface::~Interface() { - return installWatch(TopicFilter(_mask)); } diff --git a/libwhisper/Interface.h b/libwhisper/Interface.h index 0b7b52cf7..37651a826 100644 --- a/libwhisper/Interface.h +++ b/libwhisper/Interface.h @@ -47,8 +47,9 @@ class Watch; struct InstalledFilter { - InstalledFilter(TopicFilter const& _f): filter(_f) {} + InstalledFilter(FullTopic const& _f): full(_f), filter(_f) {} + FullTopic full; TopicFilter filter; unsigned refCount = 1; }; @@ -65,12 +66,12 @@ struct ClientWatch class Interface { public: - virtual ~Interface() {} + virtual ~Interface(); virtual void inject(Envelope const& _m, WhisperPeer* _from = nullptr) = 0; - unsigned installWatch(TopicMask const& _mask); - virtual unsigned installWatch(TopicFilter const& _filter) = 0; + virtual FullTopic const& fullTopic(unsigned _id) const = 0; + virtual unsigned installWatch(FullTopic const& _mask) = 0; virtual unsigned installWatchOnId(h256 _filterId) = 0; virtual void uninstallWatch(unsigned _watchId) = 0; virtual h256s peekWatch(unsigned _watchId) const = 0; @@ -79,10 +80,10 @@ public: virtual Envelope envelope(h256 _m) const = 0; - void post(bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_topic, _ttl, _workToProve)); } - void post(Public _to, bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).sealTo(_to, _topic, _ttl, _workToProve)); } - void post(Secret _from, bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_from, _topic, _ttl, _workToProve)); } - void post(Secret _from, Public _to, bytes const& _payload, Topic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).sealTo(_from, _to, _topic, _ttl, _workToProve)); } + void post(bytes const& _payload, FullTopic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_topic, _ttl, _workToProve)); } + void post(Public _to, bytes const& _payload, FullTopic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).sealTo(_to, _topic, _ttl, _workToProve)); } + void post(Secret _from, bytes const& _payload, FullTopic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).seal(_from, _topic, _ttl, _workToProve)); } + void post(Secret _from, Public _to, bytes const& _payload, FullTopic _topic, unsigned _ttl = 50, unsigned _workToProve = 50) { inject(Message(_payload).sealTo(_from, _to, _topic, _ttl, _workToProve)); } }; struct WatshhChannel: public dev::LogChannel { static const char* name() { return "shh"; } static const int verbosity = 1; }; @@ -104,8 +105,7 @@ class Watch: public boost::noncopyable public: Watch() {} - Watch(Interface& _c, TopicMask const& _f): m_c(&_c), m_id(_c.installWatch(_f)) {} - Watch(Interface& _c, TopicFilter const& _tf): m_c(&_c), m_id(_c.installWatch(_tf)) {} + Watch(Interface& _c, FullTopic const& _f): m_c(&_c), m_id(_c.installWatch(_f)) {} ~Watch() { if (m_c) m_c->uninstallWatch(m_id); } h256s check() { return m_c ? m_c->checkWatch(m_id) : h256s(); } diff --git a/libwhisper/Message.cpp b/libwhisper/Message.cpp index 07620a7cf..07bcea0c1 100644 --- a/libwhisper/Message.cpp +++ b/libwhisper/Message.cpp @@ -26,7 +26,7 @@ using namespace dev; using namespace dev::p2p; using namespace dev::shh; -Message::Message(Envelope const& _e, Secret const& _s) +Message::Message(Envelope const& _e, FullTopic const& _fk, Secret const& _s) { try { @@ -34,8 +34,39 @@ Message::Message(Envelope const& _e, Secret const& _s) if (_s) if (!decrypt(_s, &(_e.data()), b)) return; - if (populate(_s ? b : _e.data())) - m_to = KeyPair(_s).pub(); + else{} + else + { + // public - need to get the key through combining with the topic/topicIndex we know. + unsigned topicIndex = 0; + Secret topicSecret; + + // determine topicSecret/topicIndex from knowledge of the collapsed topics (which give the order) and our full-size filter topic. + CollapsedTopic knownTopic = collapse(_fk); + for (unsigned ti = 0; ti < _fk.size() && !topicSecret; ++ti) + for (unsigned i = 0; i < _e.topic().size(); ++i) + if (_e.topic()[i] == knownTopic[ti]) + { + topicSecret = _fk[ti]; + topicIndex = i; + break; + } + + if (_e.data().size() < _e.topic().size() * 32) + return; + + // get key from decrypted topic key: just xor + h256 tk = h256(bytesConstRef(&(_e.data())).cropped(32 * topicIndex, 32)); + bytesConstRef cipherText = bytesConstRef(&(_e.data())).cropped(32 * _e.topic().size()); + cnote << "Decrypting(" << topicIndex << "): " << topicSecret << tk << (topicSecret ^ tk) << toHex(cipherText); + if (!decryptSym(topicSecret ^ tk, cipherText, b)) + return; + cnote << "Got: " << toHex(b); + } + + if (populate(b)) + if (_s) + m_to = KeyPair(_s).pub(); } catch (...) // Invalid secret? TODO: replace ... with InvalidSecret { @@ -63,9 +94,10 @@ bool Message::populate(bytes const& _data) return true; } -Envelope Message::seal(Secret _from, Topic const& _topic, unsigned _ttl, unsigned _workToProve) const +Envelope Message::seal(Secret _from, FullTopic const& _fullTopic, unsigned _ttl, unsigned _workToProve) const { - Envelope ret(time(0) + _ttl, _ttl, _topic); + CollapsedTopic topic = collapse(_fullTopic); + Envelope ret(time(0) + _ttl, _ttl, topic); bytes input(1 + m_payload.size()); input[0] = 0; @@ -83,7 +115,25 @@ Envelope Message::seal(Secret _from, Topic const& _topic, unsigned _ttl, unsigne if (m_to) encrypt(m_to, &input, ret.m_data); else - swap(ret.m_data, input); + { + // create the shared secret and encrypt + Secret s = Secret::random(); + for (h256 const& t: _fullTopic) + ret.m_data += (t ^ s).asBytes(); + bytes d; + encryptSym(s, &input, d); + ret.m_data += d; + + for (unsigned i = 0; i < _fullTopic.size(); ++i) + { + bytes b; + h256 tk = h256(bytesConstRef(&(ret.m_data)).cropped(32 * i, 32)); + bytesConstRef cipherText = bytesConstRef(&(ret.m_data)).cropped(32 * ret.topic().size()); + cnote << "Test decrypting(" << i << "): " << _fullTopic[i] << tk << (_fullTopic[i] ^ tk) << toHex(cipherText); + assert(decryptSym(_fullTopic[i] ^ tk, cipherText, b)); + cnote << "Got: " << toHex(b); + } + } ret.proveWork(_workToProve); return ret; @@ -98,9 +148,9 @@ Envelope::Envelope(RLP const& _m) m_nonce = _m[4].toInt(); } -Message Envelope::open(Secret const& _s) const +Message Envelope::open(FullTopic const& _ft, Secret const& _s) const { - return Message(*this, _s); + return Message(*this, _ft, _s); } unsigned Envelope::workProved() const diff --git a/libwhisper/Message.h b/libwhisper/Message.h index f582b14c7..7e5df5a95 100644 --- a/libwhisper/Message.h +++ b/libwhisper/Message.h @@ -39,6 +39,16 @@ namespace shh class Message; +static const unsigned Undefined = (unsigned)-1; + +struct FilterKey +{ + FilterKey() {} + FilterKey(unsigned _tI, Secret const& _k): topicIndex(_tI), key(_k) {} + unsigned topicIndex = Undefined; + Secret key; +}; + enum IncludeNonce { WithoutNonce = 0, @@ -61,22 +71,22 @@ public: unsigned sent() const { return m_expiry - m_ttl; } unsigned expiry() const { return m_expiry; } unsigned ttl() const { return m_ttl; } - Topic const& topics() const { return m_topic; } + CollapsedTopic const& topic() const { return m_topic; } bytes const& data() const { return m_data; } - Message open(Secret const& _s = Secret()) const; + Message open(FullTopic const& _ft, Secret const& _s = Secret()) const; unsigned workProved() const; void proveWork(unsigned _ms); private: - Envelope(unsigned _exp, unsigned _ttl, Topic const& _topic): m_expiry(_exp), m_ttl(_ttl), m_topic(_topic) {} + Envelope(unsigned _exp, unsigned _ttl, CollapsedTopic const& _topic): m_expiry(_exp), m_ttl(_ttl), m_topic(_topic) {} unsigned m_expiry = 0; unsigned m_ttl = 0; u256 m_nonce; - Topic m_topic; + CollapsedTopic m_topic; bytes m_data; }; @@ -91,7 +101,7 @@ class Message { public: Message() {} - Message(Envelope const& _e, Secret const& _s = Secret()); + Message(Envelope const& _e, FullTopic const& _ft, Secret const& _s = Secret()); Message(bytes const& _payload): m_payload(_payload) {} Message(bytesConstRef _payload): m_payload(_payload.toBytes()) {} Message(bytes&& _payload) { std::swap(_payload, m_payload); } @@ -108,11 +118,11 @@ public: operator bool() const { return !!m_payload.size() || m_from || m_to; } /// Turn this message into a ditributable Envelope. - Envelope seal(Secret _from, Topic const& _topic, unsigned _workToProve = 50, unsigned _ttl = 50) const; + Envelope seal(Secret _from, FullTopic const& _topic, unsigned _workToProve = 50, unsigned _ttl = 50) const; // Overloads for skipping _from or specifying _to. - Envelope seal(Topic const& _topic, unsigned _ttl = 50, unsigned _workToProve = 50) const { return seal(Secret(), _topic, _workToProve, _ttl); } - Envelope sealTo(Public _to, Topic const& _topic, unsigned _workToProve = 50, unsigned _ttl = 50) { m_to = _to; return seal(Secret(), _topic, _workToProve, _ttl); } - Envelope sealTo(Secret _from, Public _to, Topic const& _topic, unsigned _workToProve = 50, unsigned _ttl = 50) { m_to = _to; return seal(_from, _topic, _workToProve, _ttl); } + Envelope seal(FullTopic const& _topic, unsigned _ttl = 50, unsigned _workToProve = 50) const { return seal(Secret(), _topic, _workToProve, _ttl); } + Envelope sealTo(Public _to, FullTopic const& _topic, unsigned _workToProve = 50, unsigned _ttl = 50) { m_to = _to; return seal(Secret(), _topic, _workToProve, _ttl); } + Envelope sealTo(Secret _from, Public _to, FullTopic const& _topic, unsigned _workToProve = 50, unsigned _ttl = 50) { m_to = _to; return seal(_from, _topic, _workToProve, _ttl); } private: bool populate(bytes const& _data); diff --git a/libwhisper/WhisperHost.cpp b/libwhisper/WhisperHost.cpp index 589f8f047..213134db9 100644 --- a/libwhisper/WhisperHost.cpp +++ b/libwhisper/WhisperHost.cpp @@ -49,14 +49,14 @@ void WhisperHost::streamMessage(h256 _m, RLPStream& _s) const { UpgradeGuard ll(l); auto const& m = m_messages.at(_m); - cnote << "streamRLP: " << m.expiry() << m.ttl() << m.topics() << toHex(m.data()); + cnote << "streamRLP: " << m.expiry() << m.ttl() << m.topic() << toHex(m.data()); m.streamRLP(_s); } } void WhisperHost::inject(Envelope const& _m, WhisperPeer* _p) { - cnote << "inject: " << _m.expiry() << _m.ttl() << _m.topics() << toHex(_m.data()); + cnote << this << ": inject: " << _m.expiry() << _m.ttl() << _m.topic() << toHex(_m.data()); if (_m.expiry() <= time(0)) return; @@ -104,14 +104,15 @@ unsigned WhisperHost::installWatchOnId(h256 _h) return ret; } -unsigned WhisperHost::installWatch(shh::TopicFilter const& _f) +unsigned WhisperHost::installWatch(shh::FullTopic const& _ft) { Guard l(m_filterLock); - h256 h = _f.sha3(); + InstalledFilter f(_ft); + h256 h = f.filter.sha3(); if (!m_filters.count(h)) - m_filters.insert(make_pair(h, _f)); + m_filters.insert(make_pair(h, f)); return installWatchOnId(h); } diff --git a/libwhisper/WhisperHost.h b/libwhisper/WhisperHost.h index 1a4ec2a71..b6e683778 100644 --- a/libwhisper/WhisperHost.h +++ b/libwhisper/WhisperHost.h @@ -39,6 +39,8 @@ namespace dev namespace shh { +static const FullTopic EmptyFullTopic; + class WhisperHost: public HostCapability, public Interface, public Worker { friend class WhisperPeer; @@ -47,12 +49,12 @@ public: WhisperHost(); virtual ~WhisperHost(); - unsigned protocolVersion() const { return 1; } + unsigned protocolVersion() const { return 2; } virtual void inject(Envelope const& _e, WhisperPeer* _from = nullptr) override; - using Interface::installWatch; - virtual unsigned installWatch(TopicFilter const& _filter) override; + virtual FullTopic const& fullTopic(unsigned _id) const { try { return m_filters.at(m_watches.at(_id).id).full; } catch (...) { return EmptyFullTopic; } } + virtual unsigned installWatch(FullTopic const& _filter) override; virtual unsigned installWatchOnId(h256 _filterId) override; virtual void uninstallWatch(unsigned _watchId) override; virtual h256s peekWatch(unsigned _watchId) const override { dev::Guard l(m_filterLock); try { return m_watches.at(_watchId).changes; } catch (...) { return h256s(); } } diff --git a/libwhisper/WhisperPeer.cpp b/libwhisper/WhisperPeer.cpp index 1dbd6e16e..7480a104e 100644 --- a/libwhisper/WhisperPeer.cpp +++ b/libwhisper/WhisperPeer.cpp @@ -62,6 +62,9 @@ bool WhisperPeer::interpret(unsigned _id, RLP const& _r) if (protocolVersion != version()) disable("Invalid protocol version."); + for (auto const& m: host()->all()) + m_unseen.insert(make_pair(0, m.first)); + if (session()->id() < host()->host()->id()) sendMessages(); break; @@ -103,7 +106,7 @@ void WhisperPeer::sendMessages() } } -void WhisperPeer::noteNewMessage(h256 _h, Message const& _m) +void WhisperPeer::noteNewMessage(h256 _h, Envelope const& _m) { Guard l(x_unseen); m_unseen.insert(make_pair(rating(_m), _h)); diff --git a/libwhisper/WhisperPeer.h b/libwhisper/WhisperPeer.h index d21f33725..5dd265e5a 100644 --- a/libwhisper/WhisperPeer.h +++ b/libwhisper/WhisperPeer.h @@ -63,8 +63,8 @@ private: void sendMessages(); - unsigned rating(Message const&) const { return 0; } // TODO - void noteNewMessage(h256 _h, Message const& _m); + unsigned rating(Envelope const&) const { return 0; } // TODO + void noteNewMessage(h256 _h, Envelope const& _m); mutable dev::Mutex x_unseen; std::multimap m_unseen; ///< Rated according to what they want. diff --git a/mix/AppContext.cpp b/mix/AppContext.cpp index fb44a1cde..ad9c78e9b 100644 --- a/mix/AppContext.cpp +++ b/mix/AppContext.cpp @@ -23,9 +23,11 @@ */ #include +#include #include #include #include +#include #include "CodeModel.h" #include "FileIo.h" #include "ClientModel.h" @@ -33,7 +35,7 @@ #include "Exceptions.h" #include "AppContext.h" #include "QEther.h" -#include +#include "HttpServer.h" using namespace dev; using namespace dev::eth; @@ -44,19 +46,9 @@ const QString c_projectFileName = "project.mix"; AppContext::AppContext(QQmlApplicationEngine* _engine) { m_applicationEngine = _engine; - //m_webThree = std::unique_ptr(new WebThreeDirect(std::string("Mix/v") + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), getDataDir() + "/Mix", false, {"eth", "shh"})); m_codeModel.reset(new CodeModel(this)); m_clientModel.reset(new ClientModel(this)); m_fileIo.reset(new FileIo()); -/* - m_applicationEngine->rootContext()->setContextProperty("appContext", this); - qmlRegisterType("org.ethereum.qml", 1, 0, "FileIo"); - qmlRegisterSingletonType(QUrl("qrc:/qml/ProjectModel.qml"), "org.ethereum.qml.ProjectModel", 1, 0, "ProjectModel"); - qmlRegisterType("org.ethereum.qml.QEther", 1, 0, "QEther"); - qmlRegisterType("org.ethereum.qml.QBigInt", 1, 0, "QBigInt"); - m_applicationEngine->rootContext()->setContextProperty("codeModel", m_codeModel.get()); - m_applicationEngine->rootContext()->setContextProperty("fileIo", m_fileIo.get()); -*/ } AppContext::~AppContext() @@ -82,7 +74,10 @@ void AppContext::load() } m_applicationEngine->rootContext()->setContextProperty("projectModel", projectModel); qmlRegisterType("CodeEditorExtensionManager", 1, 0, "CodeEditorExtensionManager"); + qmlRegisterType("HttpServer", 1, 0, "HttpServer"); m_applicationEngine->load(QUrl("qrc:/qml/main.qml")); + QQuickWindow *window = qobject_cast(m_applicationEngine->rootObjects().at(0)); + window->setIcon(QIcon(":/res/mix_256x256x32.png")); appLoaded(); } @@ -103,3 +98,9 @@ void AppContext::displayMessageDialog(QString _title, QString _message) dialogWin->findChild("messageContent", Qt::FindChildrenRecursively)->setProperty("text", _message); QMetaObject::invokeMethod(dialogWin, "open"); } + +void AppContext::toClipboard(QString _text) +{ + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(_text); +} diff --git a/mix/AppContext.h b/mix/AppContext.h index e959397f7..31ecd3198 100644 --- a/mix/AppContext.h +++ b/mix/AppContext.h @@ -32,14 +32,6 @@ #include class QQmlApplicationEngine; -namespace dev -{ - class WebThreeDirect; - namespace solidity - { - class CompilerStack; - } -} namespace dev { @@ -70,6 +62,8 @@ public: ClientModel* clientModel() { return m_clientModel.get(); } /// Display an alert message. void displayMessageDialog(QString _title, QString _message); + /// Copy text to clipboard + Q_INVOKABLE void toClipboard(QString _text); signals: /// Triggered once components have been loaded @@ -77,7 +71,6 @@ signals: private: QQmlApplicationEngine* m_applicationEngine; //owned by app - std::unique_ptr m_webThree; std::unique_ptr m_codeModel; std::unique_ptr m_clientModel; std::unique_ptr m_fileIo; diff --git a/mix/AssemblyDebuggerControl.cpp b/mix/AssemblyDebuggerControl.cpp index f52245450..e065513a8 100644 --- a/mix/AssemblyDebuggerControl.cpp +++ b/mix/AssemblyDebuggerControl.cpp @@ -29,7 +29,6 @@ using namespace dev::mix; AssemblyDebuggerControl::AssemblyDebuggerControl(AppContext* _context): Extension(_context, ExtensionDisplayBehavior::RightView) { - connect(_context->clientModel(), &ClientModel::showDebuggerWindow, this, &AssemblyDebuggerControl::showDebugger, Qt::QueuedConnection); } QString AssemblyDebuggerControl::contentUrl() const @@ -45,9 +44,3 @@ QString AssemblyDebuggerControl::title() const void AssemblyDebuggerControl::start() const { } - -void AssemblyDebuggerControl::showDebugger() -{ - QObject* debugPanel = m_view->findChild("debugPanel", Qt::FindChildrenRecursively); - QMetaObject::invokeMethod(debugPanel, "update", Q_ARG(QVariant, true)); -} diff --git a/mix/AssemblyDebuggerControl.h b/mix/AssemblyDebuggerControl.h index 1240b3807..701cbc5fd 100644 --- a/mix/AssemblyDebuggerControl.h +++ b/mix/AssemblyDebuggerControl.h @@ -42,10 +42,6 @@ public: void start() const override; QString title() const override; QString contentUrl() const override; - -private slots: - /// Update UI with machine states result. Displayed in the right side tab. - void showDebugger(); }; } diff --git a/mix/CMakeLists.txt b/mix/CMakeLists.txt index 5c22ccf33..f1b837f01 100644 --- a/mix/CMakeLists.txt +++ b/mix/CMakeLists.txt @@ -12,8 +12,8 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) aux_source_directory(. SRC_LIST) include_directories(..) -find_package (Qt5WebEngine) -qt5_add_resources(UI_RESOURCES qml.qrc) +find_package (Qt5WebEngine QUIET) +qt5_add_resources(UI_RESOURCES res.qrc) file(GLOB HEADERS "*.h") @@ -34,8 +34,11 @@ eth_add_executable(${EXECUTABLE} target_link_libraries(${EXECUTABLE} Qt5::Core) target_link_libraries(${EXECUTABLE} Qt5::Gui) +target_link_libraries(${EXECUTABLE} Qt5::Widgets) +target_link_libraries(${EXECUTABLE} Qt5::Network) +target_link_libraries(${EXECUTABLE} Qt5::Quick) +target_link_libraries(${EXECUTABLE} Qt5::Qml) target_link_libraries(${EXECUTABLE} webthree) -target_link_libraries(${EXECUTABLE} qwebthree) target_link_libraries(${EXECUTABLE} ethereum) target_link_libraries(${EXECUTABLE} evm) target_link_libraries(${EXECUTABLE} ethcore) @@ -59,6 +62,7 @@ eth_install_executable(${EXECUTABLE} QMLDIR ${CMAKE_CURRENT_SOURCE_DIR}/qml ) -#add qml files to project tree in Qt creator +#add qml asnd stdc files to project tree in Qt creator file(GLOB_RECURSE QMLFILES "qml/*.*") -add_custom_target(dummy SOURCES ${QMLFILES}) +file(GLOB_RECURSE SOLFILES "stdc/*.*") +add_custom_target(dummy SOURCES ${QMLFILES} ${SOLFILES}) diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index 0b3d44d64..914edf52d 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -22,11 +22,12 @@ #include #include #include +#include #include #include -#include #include "AppContext.h" #include "DebuggingStateWrapper.h" +#include "Exceptions.h" #include "QContractDefinition.h" #include "QVariableDeclaration.h" #include "ContractCallDataEncoder.h" @@ -38,10 +39,31 @@ using namespace dev; using namespace dev::eth; -using namespace dev::mix; + +namespace dev +{ +namespace mix +{ + +class RpcConnector: public jsonrpc::AbstractServerConnector +{ +public: + virtual bool StartListening() override { return true; } + virtual bool StopListening() override { return true; } + virtual bool SendResponse(std::string const& _response, void*) override + { + m_response = QString::fromStdString(_response); + return true; + } + QString response() const { return m_response; } + +private: + QString m_response; +}; + ClientModel::ClientModel(AppContext* _context): - m_context(_context), m_running(false), m_qWebThree(nullptr) + m_context(_context), m_running(false), m_rpcConnector(new RpcConnector()), m_contractAddress(Address()) { qRegisterMetaType("QBigInt*"); qRegisterMetaType("QEther*"); @@ -50,16 +72,17 @@ ClientModel::ClientModel(AppContext* _context): qRegisterMetaType>("QList"); qRegisterMetaType>("QList"); qRegisterMetaType("QVariableDeclaration*"); - qRegisterMetaType("AssemblyDebuggerData"); + qRegisterMetaType("QMachineState"); + qRegisterMetaType("QInstruction"); + qRegisterMetaType("QCode"); + qRegisterMetaType("QCallData"); + qRegisterMetaType("TransactionLogEntry"); - connect(this, &ClientModel::dataAvailable, this, &ClientModel::showDebugger, Qt::QueuedConnection); + connect(this, &ClientModel::runComplete, this, &ClientModel::showDebugger, Qt::QueuedConnection); m_client.reset(new MixClient()); - m_qWebThree = new QWebThree(this); - m_qWebThreeConnector.reset(new QWebThreeConnector()); - m_qWebThreeConnector->setQWeb(m_qWebThree); - m_web3Server.reset(new Web3Server(*m_qWebThreeConnector.get(), std::vector { m_client->userAccount() }, m_client.get())); - connect(m_qWebThree, &QWebThree::response, this, &ClientModel::apiResponse); + m_web3Server.reset(new Web3Server(*m_rpcConnector.get(), std::vector { m_client->userAccount() }, m_client.get())); + connect(m_web3Server.get(), &Web3Server::newTransaction, this, &ClientModel::onNewTransaction, Qt::DirectConnection); _context->appEngine()->rootContext()->setContextProperty("clientModel", this); } @@ -68,15 +91,21 @@ ClientModel::~ClientModel() { } -void ClientModel::apiRequest(const QString& _message) +QString ClientModel::apiCall(QString const& _message) { - m_qWebThree->postMessage(_message); + m_rpcConnector->OnRequest(_message.toStdString(), nullptr); + return m_rpcConnector->response(); +} + +void ClientModel::mine() +{ + m_client->mine(); + newBlock(); } QString ClientModel::contractAddress() const { - QString address = QString::fromStdString(dev::toJS(m_client->lastContractAddress())); - return address; + return QString::fromStdString(dev::toJS(m_contractAddress)); } void ClientModel::debugDeployment() @@ -84,31 +113,45 @@ void ClientModel::debugDeployment() executeSequence(std::vector(), 10000000 * ether); } -void ClientModel::debugState(QVariantMap _state) +void ClientModel::setupState(QVariantMap _state) { u256 balance = (qvariant_cast(_state.value("balance")))->toU256Wei(); QVariantList transactions = _state.value("transactions").toList(); std::vector transactionSequence; - for (auto const& t: transactions) { QVariantMap transaction = t.toMap(); - QString functionId = transaction.value("functionId").toString(); u256 gas = (qvariant_cast(transaction.value("gas")))->toU256Wei(); u256 value = (qvariant_cast(transaction.value("value")))->toU256Wei(); u256 gasPrice = (qvariant_cast(transaction.value("gasPrice")))->toU256Wei(); - QVariantMap params = transaction.value("parameters").toMap(); - TransactionSettings transactionSettings(functionId, value, gas, gasPrice); - for (auto p = params.cbegin(); p != params.cend(); ++p) + bool isStdContract = (transaction.value("stdContract").toBool()); + if (isStdContract) { - QBigInt* param = qvariant_cast(p.value()); - transactionSettings.parameterValues.insert(std::make_pair(p.key(), boost::get(param->internalValue()))); + TransactionSettings transactionSettings(functionId, transaction.value("url").toString()); + transactionSettings.gasPrice = 10000000000000; + transactionSettings.gas = 125000; + transactionSettings.value = 100; + transactionSequence.push_back(transactionSettings); } + else + { + QVariantMap params = transaction.value("parameters").toMap(); + TransactionSettings transactionSettings(functionId, value, gas, gasPrice); + + for (auto p = params.cbegin(); p != params.cend(); ++p) + { + QBigInt* param = qvariant_cast(p.value()); + transactionSettings.parameterValues.insert(std::make_pair(p.key(), boost::get(param->internalValue()))); + } + + if (transaction.value("executeConstructor").toBool()) + transactionSettings.functionId.clear(); - transactionSequence.push_back(transactionSettings); + transactionSequence.push_back(transactionSettings); + } } executeSequence(transactionSequence, balance); } @@ -116,13 +159,13 @@ void ClientModel::debugState(QVariantMap _state) void ClientModel::executeSequence(std::vector const& _sequence, u256 _balance) { if (m_running) - throw (std::logic_error("debugging already running")); - auto compilerRes = m_context->codeModel()->code(); + BOOST_THROW_EXCEPTION(ExecutionStateException()); + CompilationResult* compilerRes = m_context->codeModel()->code(); std::shared_ptr contractDef = compilerRes->sharedContract(); m_running = true; emit runStarted(); - emit stateChanged(); + emit runStateChanged(); //run sequence QtConcurrent::run([=]() @@ -130,60 +173,62 @@ void ClientModel::executeSequence(std::vector const& _seque try { bytes contractCode = compilerRes->bytes(); - std::vector transactonData; - QFunctionDefinition* f = nullptr; - ContractCallDataEncoder c; - //encode data for all transactions - for (auto const& t: _sequence) + m_client->resetState(_balance); + onStateReset(); + for (TransactionSettings const& transaction: _sequence) { - f = nullptr; - for (int tf = 0; tf < contractDef->functionsList().size(); tf++) + ContractCallDataEncoder encoder; + QFunctionDefinition const* f = nullptr; + if (!transaction.stdContractUrl.isEmpty()) { - if (contractDef->functionsList().at(tf)->name() == t.functionId) - { - f = contractDef->functionsList().at(tf); - break; - } + //std contract + dev::bytes const& stdContractCode = m_context->codeModel()->getStdContractCode(transaction.functionId, transaction.stdContractUrl); + Address address = deployContract(stdContractCode, transaction); + m_stdContractAddresses[transaction.functionId] = address; + m_stdContractNames[address] = transaction.functionId; } - if (!f) - throw std::runtime_error("function " + t.functionId.toStdString() + " not found"); - - c.encode(f); - for (int p = 0; p < f->parametersList().size(); p++) + else { - QVariableDeclaration* var = (QVariableDeclaration*)f->parametersList().at(p); - u256 value = 0; - auto v = t.parameterValues.find(var->name()); - if (v != t.parameterValues.cend()) - value = v->second; - c.encode(var, value); - } - transactonData.emplace_back(c.encodedData()); - } - - //run contract creation first - m_client->resetState(_balance); - ExecutionResult debuggingContent = deployContract(contractCode); - Address address = debuggingContent.contractAddress; - for (unsigned i = 0; i < _sequence.size(); ++i) - debuggingContent = callContract(address, transactonData.at(i), _sequence.at(i)); + //encode data + f = nullptr; + if (transaction.functionId.isEmpty()) + f = contractDef->constructor(); + else + for (QFunctionDefinition const* tf: contractDef->functionsList()) + if (tf->name() == transaction.functionId) + { + f = tf; + break; + } + if (!f) + BOOST_THROW_EXCEPTION(FunctionNotFoundException() << FunctionName(transaction.functionId.toStdString())); - QList returnParameters; - - if (f) - returnParameters = c.decode(f->returnParameters(), debuggingContent.returnValue); + encoder.encode(f); + for (int p = 0; p < f->parametersList().size(); p++) + { + QVariableDeclaration* var = f->parametersList().at(p); + u256 value = 0; + auto v = transaction.parameterValues.find(var->name()); + if (v != transaction.parameterValues.cend()) + value = v->second; + encoder.encode(var, value); + } - //we need to wrap states in a QObject before sending to QML. - QList wStates; - for (unsigned i = 0; i < debuggingContent.machineStates.size(); i++) - { - QPointer s(new DebuggingStateWrapper(debuggingContent.executionCode, debuggingContent.executionData.toBytes())); - s->setState(debuggingContent.machineStates[i]); - wStates.append(s); + if (transaction.functionId.isEmpty()) + { + Address newAddress = deployContract(contractCode, transaction); + if (newAddress != m_contractAddress) + { + m_contractAddress = newAddress; + contractAddressChanged(); + } + } + else + callContract(m_contractAddress, encoder.encodedData(), transaction); + } + onNewTransaction(); } - //collect states for last transaction - AssemblyDebuggerData code = DebuggingStateWrapper::getHumanReadableCode(debuggingContent.executionCode); - emit dataAvailable(returnParameters, wStates, code); + m_running = false; emit runComplete(); } catch(boost::exception const&) @@ -196,17 +241,47 @@ void ClientModel::executeSequence(std::vector const& _seque emit runFailed(e.what()); } m_running = false; - emit stateChanged(); + emit runStateChanged(); }); } -void ClientModel::showDebugger(QList const& _returnParam, QList const& _wStates, AssemblyDebuggerData const& _code) +void ClientModel::showDebugger() { - m_context->appEngine()->rootContext()->setContextProperty("debugStates", QVariant::fromValue(_wStates)); - m_context->appEngine()->rootContext()->setContextProperty("humanReadableExecutionCode", QVariant::fromValue(std::get<0>(_code))); - m_context->appEngine()->rootContext()->setContextProperty("bytesCodeMapping", QVariant::fromValue(std::get<1>(_code))); - m_context->appEngine()->rootContext()->setContextProperty("contractCallReturnParameters", QVariant::fromValue(new QVariableDefinitionList(_returnParam))); - showDebuggerWindow(); + ExecutionResult const& last = m_client->record().back().transactions.back(); + showDebuggerForTransaction(last); +} + +void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) +{ + //we need to wrap states in a QObject before sending to QML. + QDebugData* debugData = new QDebugData(); + QQmlEngine::setObjectOwnership(debugData, QQmlEngine::JavaScriptOwnership); + QList codes; + for (bytes const& code: _t.executionCode) + codes.push_back(QMachineState::getHumanReadableCode(debugData, code)); + + QList data; + for (bytes const& d: _t.transactionData) + data.push_back(QMachineState::getDebugCallData(debugData, d)); + + QVariantList states; + for (MachineState const& s: _t.machineStates) + states.append(QVariant::fromValue(new QMachineState(debugData, s, codes[s.codeIndex], data[s.dataIndex]))); + + debugData->setStates(std::move(states)); + + //QList returnParameters; + //returnParameters = encoder.decode(f->returnParameters(), debuggingContent.returnValue); + + //collect states for last transaction + debugDataReady(debugData); +} + + +void ClientModel::debugTransaction(unsigned _block, unsigned _index) +{ + auto const& t = m_client->record().at(_block).transactions.at(_index); + showDebuggerForTransaction(t); } void ClientModel::showDebugError(QString const& _error) @@ -215,25 +290,88 @@ void ClientModel::showDebugError(QString const& _error) m_context->displayMessageDialog(tr("Debugger"), _error); } -ExecutionResult ClientModel::deployContract(bytes const& _code) +Address ClientModel::deployContract(bytes const& _code, TransactionSettings const& _ctrTransaction) { - u256 gasPrice = 10000000000000; - u256 gas = 125000; - u256 amount = 100; - - Address lastAddress = m_client->lastContractAddress(); - Address newAddress = m_client->transact(m_client->userAccount().secret(), amount, _code, gas, gasPrice); - ExecutionResult r = m_client->lastExecutionResult(); - if (newAddress != lastAddress) - contractAddressChanged(); - return r; + Address newAddress = m_client->transact(m_client->userAccount().secret(), _ctrTransaction.value, _code, _ctrTransaction.gas, _ctrTransaction.gasPrice); + return newAddress; } -ExecutionResult ClientModel::callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr) +void ClientModel::callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr) { m_client->transact(m_client->userAccount().secret(), _tr.value, _contract, _data, _tr.gas, _tr.gasPrice); - ExecutionResult r = m_client->lastExecutionResult(); - r.contractAddress = _contract; - return r; } +void ClientModel::onStateReset() +{ + m_contractAddress = dev::Address(); + m_stdContractAddresses.clear(); + m_stdContractNames.clear(); + emit stateCleared(); +} + +void ClientModel::onNewTransaction() +{ + unsigned block = m_client->number(); + unsigned index = m_client->record().back().transactions.size() - 1; + ExecutionResult const& tr = m_client->record().back().transactions.back(); + QString address = QString::fromStdString(toJS(tr.address)); + QString value = QString::fromStdString(dev::toString(tr.value)); + QString contract = address; + QString function; + QString returned; + + bool creation = tr.contractAddress != 0; + + if (creation) + returned = QString::fromStdString(toJS(tr.contractAddress)); + else + returned = QString::fromStdString(toJS(tr.returnValue)); + + //TODO: handle value transfer + FixedHash<4> functionHash; + bool call = false; + if (creation) + { + //contract creation + auto const stdContractName = m_stdContractNames.find(tr.contractAddress); + if (stdContractName != m_stdContractNames.end()) + { + function = stdContractName->second; + contract = function; + } + else + function = QObject::tr("Constructor"); + } + else + { + //call + if (tr.transactionData.size() > 0 && tr.transactionData.front().size() >= 4) + { + functionHash = FixedHash<4>(tr.transactionData.front().data(), FixedHash<4>::ConstructFromPointer); + function = QString::fromStdString(toJS(functionHash)); + call = true; + } + else + function = QObject::tr(""); + } + + if (m_contractAddress != 0 && (tr.address == m_contractAddress || tr.contractAddress == m_contractAddress)) + { + auto compilerRes = m_context->codeModel()->code(); + QContractDefinition* def = compilerRes->contract(); + contract = def->name(); + if (call) + { + QFunctionDefinition* funcDef = def->getFunction(functionHash); + if (funcDef) + function = funcDef->name(); + } + } + + TransactionLogEntry* log = new TransactionLogEntry(block, index, contract, function, value, address, returned); + QQmlEngine::setObjectOwnership(log, QQmlEngine::JavaScriptOwnership); + emit newTransaction(log); +} + +} +} diff --git a/mix/ClientModel.h b/mix/ClientModel.h index f74206245..63731badf 100644 --- a/mix/ClientModel.h +++ b/mix/ClientModel.h @@ -24,17 +24,10 @@ #pragma once #include -#include "DebuggingStateWrapper.h" +#include +#include #include "MixClient.h" -using AssemblyDebuggerData = std::tuple, dev::mix::QQMLMap*>; - -Q_DECLARE_METATYPE(AssemblyDebuggerData) -Q_DECLARE_METATYPE(dev::mix::ExecutionResult) - -class QWebThree; -class QWebThreeConnector; - namespace dev { namespace mix @@ -42,12 +35,20 @@ namespace mix class AppContext; class Web3Server; +class RpcConnector; +class QEther; +class QDebugData; /// Backend transaction config class struct TransactionSettings { + TransactionSettings() {} TransactionSettings(QString const& _functionId, u256 _value, u256 _gas, u256 _gasPrice): functionId(_functionId), value(_value), gas(_gas), gasPrice(_gasPrice) {} + TransactionSettings(u256 _value, u256 _gas, u256 _gasPrice): + value(_value), gas(_gas), gasPrice(_gasPrice) {} + TransactionSettings(QString const& _stdContractName, QString const& _stdContractUrl): + functionId(_stdContractName), stdContractUrl(_stdContractUrl) {} /// Contract function name QString functionId; @@ -59,9 +60,46 @@ struct TransactionSettings u256 gasPrice; /// Mapping from contract function parameter name to value std::map parameterValues; + /// Standard contract url + QString stdContractUrl; }; +/// UI Transaction log record +class TransactionLogEntry: public QObject +{ + Q_OBJECT + /// Transaction block number + Q_PROPERTY(unsigned block MEMBER m_block CONSTANT) + /// Transaction index within the block + Q_PROPERTY(unsigned tindex MEMBER m_index CONSTANT) + /// Contract name if any + Q_PROPERTY(QString contract MEMBER m_contract CONSTANT) + /// Function name if any + Q_PROPERTY(QString function MEMBER m_function CONSTANT) + /// Transaction value + Q_PROPERTY(QString value MEMBER m_value CONSTANT) + /// Receiving address + Q_PROPERTY(QString address MEMBER m_address CONSTANT) + /// Returned value or transaction address in case of creation + Q_PROPERTY(QString returned MEMBER m_returned CONSTANT) + +public: + TransactionLogEntry(): + m_block(0), m_index(0) {} + TransactionLogEntry(int _block, int _index, QString _contract, QString _function, QString _value, QString _address, QString _returned): + m_block(_block), m_index(_index), m_contract(_contract), m_function(_function), m_value(_value), m_address(_address), m_returned(_returned) {} + +private: + unsigned m_block; + unsigned m_index; + QString m_contract; + QString m_function; + QString m_value; + QString m_address; + QString m_returned; +}; + /** * @brief Ethereum state control */ @@ -73,23 +111,29 @@ public: ClientModel(AppContext* _context); ~ClientModel(); /// @returns true if currently executing contract code - Q_PROPERTY(bool running MEMBER m_running NOTIFY stateChanged) + Q_PROPERTY(bool running MEMBER m_running NOTIFY runStateChanged) /// @returns address of the last executed contract Q_PROPERTY(QString contractAddress READ contractAddress NOTIFY contractAddressChanged) - -public slots: /// ethereum.js RPC request entry point /// @param _message RPC request in Json format - void apiRequest(QString const& _message); + /// @returns RPC response in Json format + Q_INVOKABLE QString apiCall(QString const& _message); + + /// Simulate mining. Creates a new block + Q_INVOKABLE void mine(); + +public slots: /// Run the contract constructor and show debugger window. void debugDeployment(); /// Setup state, run transaction sequence, show debugger for the last transaction /// @param _state JS object with state configuration - void debugState(QVariantMap _state); + void setupState(QVariantMap _state); + /// Show the debugger for a specified transaction + Q_INVOKABLE void debugTransaction(unsigned _block, unsigned _index); private slots: /// Update UI with machine states result. Display a modal dialog. - void showDebugger(QList const& _returnParams = QList(), QList const& _wStates = QList(), AssemblyDebuggerData const& _code = AssemblyDebuggerData()); + void showDebugger(); /// Update UI with transaction run error. void showDebugError(QString const& _error); @@ -104,28 +148,36 @@ signals: /// Contract address changed void contractAddressChanged(); /// Execution state changed - void stateChanged(); + void newBlock(); + /// Execution state changed + void runStateChanged(); /// Show debugger window request - void showDebuggerWindow(); + void debugDataReady(QObject* _debugData); /// ethereum.js RPC response ready /// @param _message RPC response in Json format void apiResponse(QString const& _message); - - /// Emited when machine states are available. - void dataAvailable(QList const& _returnParams = QList(), QList const& _wStates = QList(), AssemblyDebuggerData const& _code = AssemblyDebuggerData()); + /// New transaction log entry + void newTransaction(TransactionLogEntry* _tr); + /// State (transaction log) cleared + void stateCleared(); private: QString contractAddress() const; void executeSequence(std::vector const& _sequence, u256 _balance); - ExecutionResult deployContract(bytes const& _code); - ExecutionResult callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr); + dev::Address deployContract(bytes const& _code, TransactionSettings const& _tr = TransactionSettings()); + void callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr); + void onNewTransaction(); + void onStateReset(); + void showDebuggerForTransaction(ExecutionResult const& _t); AppContext* m_context; std::atomic m_running; std::unique_ptr m_client; - QWebThree* m_qWebThree; - std::unique_ptr m_qWebThreeConnector; + std::unique_ptr m_rpcConnector; std::unique_ptr m_web3Server; + Address m_contractAddress; + std::map m_stdContractAddresses; + std::map m_stdContractNames; }; } diff --git a/mix/CodeEditorExtensionManager.cpp b/mix/CodeEditorExtensionManager.cpp index 2b1f03ff4..c7528486a 100644 --- a/mix/CodeEditorExtensionManager.cpp +++ b/mix/CodeEditorExtensionManager.cpp @@ -51,7 +51,6 @@ void CodeEditorExtensionManager::loadEditor(QQuickItem* _editor) { if (!_editor) return; - } void CodeEditorExtensionManager::initExtensions() diff --git a/mix/CodeHighlighter.cpp b/mix/CodeHighlighter.cpp index ab8a61ff5..49d01b418 100644 --- a/mix/CodeHighlighter.cpp +++ b/mix/CodeHighlighter.cpp @@ -102,7 +102,8 @@ void CodeHighlighter::processAST(dev::solidity::ASTNode const& _ast) void CodeHighlighter::processError(dev::Exception const& _exception) { Location const* location = boost::get_error_info(_exception); - m_formats.push_back(FormatRange(CodeHighlighterSettings::CompilationError, *location)); + if (location) + m_formats.push_back(FormatRange(CodeHighlighterSettings::CompilationError, *location)); } void CodeHighlighter::processComments(std::string const& _source) diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index 9a33e18ee..a9cfcc336 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -32,6 +32,7 @@ #include "QFunctionDefinition.h" #include "QVariableDeclaration.h" #include "CodeHighlighter.h" +#include "FileIo.h" #include "CodeModel.h" using namespace dev::mix; @@ -46,6 +47,7 @@ CompilationResult::CompilationResult(): m_successful(false), m_codeHash(qHash(QString())), m_contract(new QContractDefinition()), + m_contractInterface("[]"), m_codeHighlighter(new CodeHighlighter()) {} @@ -62,6 +64,8 @@ CompilationResult::CompilationResult(const dev::solidity::CompilerStack& _compil m_assemblyCode = QString::fromStdString(dev::eth::disassemble(m_bytes)); dev::solidity::InterfaceHandler interfaceHandler; m_contractInterface = QString::fromStdString(*interfaceHandler.getABIInterface(contractDefinition)); + if (m_contractInterface.isEmpty()) + m_contractInterface = "[]"; } else m_contract.reset(new QContractDefinition()); @@ -129,7 +133,7 @@ void CodeModel::runCompilationJob(int _jobId, QString const& _code) if (_jobId != m_backgroundJobId) return; //obsolete job - solidity::CompilerStack cs; + solidity::CompilerStack cs(true); std::unique_ptr result; std::string source = _code.toStdString(); @@ -138,10 +142,12 @@ void CodeModel::runCompilationJob(int _jobId, QString const& _code) auto codeHighlighter = std::make_shared(); codeHighlighter->processSource(source); + cs.addSource("configUser", R"(contract configUser{function configAddr()constant returns(address a){ return 0xf025d81196b72fba60a1d4dddad12eeb8360d828;}})"); + // run compilation try { - cs.setSource(source); + cs.addSource("", source); cs.compile(false); codeHighlighter->processAST(cs.getAST()); result.reset(new CompilationResult(cs)); @@ -185,3 +191,23 @@ void CodeModel::updateFormatting(QTextDocument* _document) { m_result->codeHighlighter()->updateFormatting(_document, *m_codeHighlighterSettings); } + +dev::bytes const& CodeModel::getStdContractCode(const QString& _contractName, const QString& _url) +{ + auto cached = m_compiledContracts.find(_contractName); + if (cached != m_compiledContracts.end()) + return cached->second; + + FileIo fileIo; + std::string source = fileIo.readFile(_url).toStdString(); + solidity::CompilerStack cs(false); + cs.setSource(source); + cs.compile(false); + for (std::string const& name: cs.getContractNames()) + { + dev::bytes code = cs.getBytecode(name); + m_compiledContracts.insert(std::make_pair(QString::fromStdString(name), std::move(code))); + } + return m_compiledContracts.at(_contractName); +} + diff --git a/mix/CodeModel.h b/mix/CodeModel.h index 365d2d580..5f2add874 100644 --- a/mix/CodeModel.h +++ b/mix/CodeModel.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -131,6 +132,8 @@ public: bool hasContract() const; /// Apply text document formatting. @todo Move this to editor module void updateFormatting(QTextDocument* _document); + /// Get contract code by url. Contract is compiled on first access and cached + dev::bytes const& getStdContractCode(QString const& _contractName, QString const& _url); signals: /// Emited on compilation state change @@ -163,6 +166,7 @@ private: QThread m_backgroundThread; BackgroundWorker m_backgroundWorker; int m_backgroundJobId = 0; //protects from starting obsolete compilation job + std::map m_compiledContracts; //by name friend class BackgroundWorker; }; diff --git a/mix/DebuggingStateWrapper.cpp b/mix/DebuggingStateWrapper.cpp index 1eacac120..42d0c97ac 100644 --- a/mix/DebuggingStateWrapper.cpp +++ b/mix/DebuggingStateWrapper.cpp @@ -20,10 +20,11 @@ * Used to translate c++ type (u256, bytes, ...) into friendly value (to be used by QML). */ -#include +#include #include #include #include +#include #include #include #include @@ -35,10 +36,42 @@ using namespace dev; using namespace dev::eth; using namespace dev::mix; -std::tuple, QQMLMap*> DebuggingStateWrapper::getHumanReadableCode(const bytes& _code) +namespace { - QList codeStr; - QMap codeMapping; + static QVariantList memDumpToList(bytes const& _bytes, unsigned _width) + { + QVariantList dumpList; + for (unsigned i = 0; i < _bytes.size(); i += _width) + { + std::stringstream ret; + + for (unsigned j = i; j < i + _width; ++j) + if (j < _bytes.size()) + if (_bytes[j] >= 32 && _bytes[j] < 127) + ret << (char)_bytes[j]; + else + ret << '?'; + else + ret << ' '; + QString strPart = QString::fromStdString(ret.str()); + + ret.clear(); + ret.str(std::string()); + + for (unsigned j = i; j < i + _width && j < _bytes.size(); ++j) + ret << std::setfill('0') << std::setw(2) << std::hex << (unsigned)_bytes[j] << " "; + QString hexPart = QString::fromStdString(ret.str()); + + QStringList line = { strPart, hexPart }; + dumpList.push_back(line); + } + return dumpList; + } +} + +QCode* QMachineState::getHumanReadableCode(QObject* _owner, const bytes& _code) +{ + QVariantList codeStr; for (unsigned i = 0; i <= _code.size(); ++i) { byte b = i < _code.size() ? _code[i] : 0; @@ -47,7 +80,6 @@ std::tuple, QQMLMap*> DebuggingStateWrapper::getHumanReadableCod QString s = QString::fromStdString(instructionInfo((Instruction)b).name); std::ostringstream out; out << std::hex << std::setw(4) << std::setfill('0') << i; - codeMapping[i] = codeStr.size(); int line = i; if (b >= (byte)Instruction::PUSH1 && b <= (byte)Instruction::PUSH32) { @@ -55,8 +87,7 @@ std::tuple, QQMLMap*> DebuggingStateWrapper::getHumanReadableCod s = "PUSH 0x" + QString::fromStdString(toHex(bytesConstRef(&_code[i + 1], bc))); i += bc; } - QPointer humanCode(new HumanReadableCode(QString::fromStdString(out.str()) + " " + s, line)); - codeStr.append(humanCode); + codeStr.append(QVariant::fromValue(new QInstruction(_owner, QString::fromStdString(out.str()) + " " + s, line))); } catch (...) { @@ -65,160 +96,76 @@ std::tuple, QQMLMap*> DebuggingStateWrapper::getHumanReadableCod break; // probably hit data segment } } - return std::make_tuple(codeStr, QPointer(new QQMLMap(codeMapping))); + return new QCode(_owner, std::move(codeStr)); } -QBigInt* DebuggingStateWrapper::gasCost() +QBigInt* QMachineState::gasCost() { return new QBigInt(m_state.gasCost); } -QBigInt* DebuggingStateWrapper::gas() +QBigInt* QMachineState::gas() { return new QBigInt(m_state.gas); } -QBigInt* DebuggingStateWrapper::newMemSize() +QBigInt* QMachineState::newMemSize() { return new QBigInt(m_state.newMemSize); } -QStringList DebuggingStateWrapper::debugStack() +QStringList QMachineState::debugStack() { QStringList stack; for (std::vector::reverse_iterator i = m_state.stack.rbegin(); i != m_state.stack.rend(); ++i) stack.append(QString::fromStdString(prettyU256(*i))); - return fillList(stack, ""); + return stack; } -QStringList DebuggingStateWrapper::debugStorage() +QStringList QMachineState::debugStorage() { QStringList storage; for (auto const& i: m_state.storage) { std::stringstream s; - s << "@" << prettyU256(i.first) << " " << prettyU256(i.second); + s << "@" << prettyU256(i.first) << "\t" << prettyU256(i.second); storage.append(QString::fromStdString(s.str())); } - return fillList(storage, "@ -"); + return storage; } -QVariantList DebuggingStateWrapper::debugMemory() +QVariantList QMachineState::debugMemory() { - std::vector> dump = memDumpToList(m_state.memory, 16); - QStringList filled; - filled.append(" "); - filled.append(" "); - filled.append(" "); - return fillList(qVariantDump(dump), QVariant(filled)); + return memDumpToList(m_state.memory, 16); } -QVariantList DebuggingStateWrapper::debugCallData() +QCallData* QMachineState::getDebugCallData(QObject* _owner, bytes const& _data) { - std::vector> dump = memDumpToList(m_data, 16); - QStringList filled; - filled.append(" "); - filled.append(" "); - filled.append(" "); - return fillList(qVariantDump(dump), QVariant(filled)); + return new QCallData(_owner, memDumpToList(_data, 16)); } -std::vector> DebuggingStateWrapper::memDumpToList(bytes const& _bytes, unsigned _width) +QVariantList QMachineState::levels() { - std::vector> dump; - for (unsigned i = 0; i < _bytes.size(); i += _width) - { - std::stringstream ret; - std::vector dumpLine; - ret << std::hex << std::setw(4) << std::setfill('0') << i << " "; - dumpLine.push_back(ret.str()); - ret.str(std::string()); - ret.clear(); - - for (unsigned j = i; j < i + _width; ++j) - if (j < _bytes.size()) - if (_bytes[j] >= 32 && _bytes[j] < 127) - ret << (char)_bytes[j]; - else - ret << '?'; - else - ret << ' '; - dumpLine.push_back(ret.str()); - ret.str(std::string()); - ret.clear(); - - for (unsigned j = i; j < i + _width && j < _bytes.size(); ++j) - ret << std::setfill('0') << std::setw(2) << std::hex << (unsigned)_bytes[j] << " "; - dumpLine.push_back(ret.str()); - dump.push_back(dumpLine); - } - return dump; -} - -QVariantList DebuggingStateWrapper::qVariantDump(std::vector> const& _dump) -{ - QVariantList ret; - for (std::vector const& line: _dump) - { - QStringList qLine; - for (std::string const& cell: line) - qLine.push_back(QString::fromStdString(cell)); - ret.append(QVariant(qLine)); - } - return ret; -} - -QStringList DebuggingStateWrapper::fillList(QStringList& _list, QString const& _emptyValue) -{ - if (_list.size() < 20) - { - for (int k = _list.size(); k < 20 - _list.size(); k++) - _list.append(_emptyValue); - } - return _list; -} - -QVariantList DebuggingStateWrapper::fillList(QVariantList _list, QVariant const& _emptyValue) -{ - if (_list.size() < 20) - { - for (int k = _list.size(); k < 20 - _list.size(); k++) - _list.append(_emptyValue); - } - return _list; -} - - -QStringList DebuggingStateWrapper::levels() -{ - QStringList levelsStr; - for (unsigned i = 0; i <= m_state.levels.size(); ++i) - { - std::ostringstream out; - out << m_state.cur.abridged(); - if (i) - out << " " << instructionInfo(m_state.inst).name << " @0x" << std::hex << m_state.curPC; - levelsStr.append(QString::fromStdString(out.str())); - } - return levelsStr; + QVariantList levelList; + for (unsigned l: m_state.levels) + levelList.push_back(l); + return levelList; } -QString DebuggingStateWrapper::headerInfo() +QString QMachineState::address() { - std::ostringstream ss; - ss << std::dec << " " << QApplication::tr("STEP").toStdString() << " : " << m_state.steps << " | PC: 0x" << std::hex << m_state.curPC << " : " << dev::eth::instructionInfo(m_state.inst).name << " | ADDMEM: " << std::dec << m_state.newMemSize << " " << QApplication::tr("words").toStdString() << " | " << QApplication::tr("COST").toStdString() << " : " << std::dec << m_state.gasCost << " | " << QApplication::tr("GAS").toStdString() << " : " << std::dec << m_state.gas; - return QString::fromStdString(ss.str()); + return QString::fromStdString(toString(m_state.address)); } -QString DebuggingStateWrapper::instruction() +QString QMachineState::instruction() { return QString::fromStdString(dev::eth::instructionInfo(m_state.inst).name); } -QString DebuggingStateWrapper::endOfDebug() +QString QMachineState::endOfDebug() { if (m_state.gasCost > m_state.gas) - return QApplication::tr("OUT-OF-GAS"); + return QObject::tr("OUT-OF-GAS"); else if (m_state.inst == Instruction::RETURN && m_state.stack.size() >= 2) { unsigned from = (unsigned)m_state.stack.back(); @@ -227,12 +174,12 @@ QString DebuggingStateWrapper::endOfDebug() bytes out(size, 0); for (; o < size && from + o < m_state.memory.size(); ++o) out[o] = m_state.memory[from + o]; - return QApplication::tr("RETURN") + " " + QString::fromStdString(dev::memDump(out, 16, false)); + return QObject::tr("RETURN") + " " + QString::fromStdString(dev::memDump(out, 16, false)); } else if (m_state.inst == Instruction::STOP) - return QApplication::tr("STOP"); + return QObject::tr("STOP"); else if (m_state.inst == Instruction::SUICIDE && m_state.stack.size() >= 1) - return QApplication::tr("SUICIDE") + " 0x" + QString::fromStdString(toString(right160(m_state.stack.back()))); + return QObject::tr("SUICIDE") + " 0x" + QString::fromStdString(toString(right160(m_state.stack.back()))); else - return QApplication::tr("EXCEPTION"); + return QObject::tr("EXCEPTION"); } diff --git a/mix/DebuggingStateWrapper.h b/mix/DebuggingStateWrapper.h index 0541f0afa..606a6b3ae 100644 --- a/mix/DebuggingStateWrapper.h +++ b/mix/DebuggingStateWrapper.h @@ -39,45 +39,70 @@ namespace mix /** * @brief Contains the line nb of the assembly code and the corresponding index in the code bytes array. */ -class HumanReadableCode: public QObject +class QInstruction: public QObject { Q_OBJECT - Q_PROPERTY(QString line READ line CONSTANT) - Q_PROPERTY(int processIndex READ processIndex CONSTANT) + Q_PROPERTY(QString line MEMBER m_line CONSTANT) + Q_PROPERTY(int processIndex MEMBER m_processIndex CONSTANT) public: - HumanReadableCode(QString _line, int _processIndex): QObject(), m_line(_line), m_processIndex(_processIndex) {} - /// Get the assembly code line. - QString line() { return m_line; } - /// Get corresponding index. - int processIndex() { return m_processIndex; } + QInstruction(QObject* _owner, QString _line, int _processIndex): QObject(_owner), m_line(_line), m_processIndex(_processIndex) {} private: QString m_line; int m_processIndex; }; +/** + * @brief Shared container for lines + */ +class QCode: public QObject +{ + Q_OBJECT + Q_PROPERTY(QVariantList instructions MEMBER m_instructions CONSTANT) + +public: + QCode(QObject* _owner, QVariantList&& _instrunctions): QObject(_owner), m_instructions(_instrunctions) {} + +private: + QVariantList m_instructions; +}; + +/** + * @brief Shared container for call data + */ +class QCallData: public QObject +{ + Q_OBJECT + Q_PROPERTY(QVariantList items MEMBER m_items CONSTANT) + +public: + QCallData(QObject* _owner, QVariantList&& _items): QObject(_owner), m_items(_items) {} + +private: + QVariantList m_items; +}; /** - * @brief Publish QMap type to QML. + * @brief Shared container for machine states */ -class QQMLMap: public QObject +class QDebugData: public QObject { Q_OBJECT + Q_PROPERTY(QVariantList states MEMBER m_states CONSTANT) public: - QQMLMap(QMap _map): QObject(), m_map(_map) { } - /// Get the value associated with _key store in n_map. - Q_INVOKABLE int getValue(int _key) { return m_map.value(_key); } + QDebugData() { } + void setStates(QVariantList&& _states) { m_states = _states; } private: - QMap m_map; + QVariantList m_states; }; /** - * @brief Wrap DebuggingState in QObject + * @brief Wrap MachineState in QObject */ -class DebuggingStateWrapper: public QObject +class QMachineState: public QObject { Q_OBJECT Q_PROPERTY(int step READ step CONSTANT) @@ -85,21 +110,31 @@ class DebuggingStateWrapper: public QObject Q_PROPERTY(QBigInt* gasCost READ gasCost CONSTANT) Q_PROPERTY(QBigInt* gas READ gas CONSTANT) Q_PROPERTY(QString instruction READ instruction CONSTANT) + Q_PROPERTY(QString address READ address CONSTANT) Q_PROPERTY(QStringList debugStack READ debugStack CONSTANT) Q_PROPERTY(QStringList debugStorage READ debugStorage CONSTANT) Q_PROPERTY(QVariantList debugMemory READ debugMemory CONSTANT) - Q_PROPERTY(QVariantList debugCallData READ debugCallData CONSTANT) - Q_PROPERTY(QString headerInfo READ headerInfo CONSTANT) + Q_PROPERTY(QObject* code MEMBER m_code CONSTANT) + Q_PROPERTY(QObject* callData MEMBER m_callData CONSTANT) Q_PROPERTY(QString endOfDebug READ endOfDebug CONSTANT) Q_PROPERTY(QBigInt* newMemSize READ newMemSize CONSTANT) - Q_PROPERTY(QStringList levels READ levels CONSTANT) + Q_PROPERTY(QVariantList levels READ levels CONSTANT) + Q_PROPERTY(unsigned codeIndex READ codeIndex CONSTANT) + Q_PROPERTY(unsigned dataIndex READ dataIndex CONSTANT) public: - DebuggingStateWrapper(bytes _code, bytes _data): QObject(), m_code(_code), m_data(_data) {} + QMachineState(QObject* _owner, MachineState const& _state, QCode* _code, QCallData* _callData): + QObject(_owner), m_state(_state), m_code(_code), m_callData(_callData) {} /// Get the step of this machine states. int step() { return (int)m_state.steps; } /// Get the proccessed code index. int curPC() { return (int)m_state.curPC; } + /// Get the code id + unsigned codeIndex() { return m_state.codeIndex; } + /// Get the call data id + unsigned dataIndex() { return m_state.dataIndex; } + /// Get address for call stack + QString address(); /// Get gas cost. QBigInt* gasCost(); /// Get gas used. @@ -110,10 +145,6 @@ public: QStringList debugStorage(); /// Get memory. QVariantList debugMemory(); - /// Get call data. - QVariantList debugCallData(); - /// Get info to be displayed in the header. - QString headerInfo(); /// get end of debug information. QString endOfDebug(); /// Get the new memory size. @@ -121,25 +152,20 @@ public: /// Get current instruction QString instruction(); /// Get all previous steps. - QStringList levels(); + QVariantList levels(); /// Get the current processed machine state. MachineState state() { return m_state; } /// Set the current processed machine state. void setState(MachineState _state) { m_state = _state; } - /// Convert all machine state in human readable code. - static std::tuple, QQMLMap*> getHumanReadableCode(bytes const& _code); + /// Convert all machine states in human readable code. + static QCode* getHumanReadableCode(QObject* _owner, bytes const& _code); + /// Convert call data into human readable form + static QCallData* getDebugCallData(QObject* _owner, bytes const& _data); private: MachineState m_state; - bytes m_code; - bytes m_data; - QStringList fillList(QStringList& _list, QString const& _emptyValue); - QVariantList fillList(QVariantList _list, QVariant const& _emptyValue); - QVariantList qVariantDump(std::vector> const& _dump); - /// Nicely renders the given bytes to a string, store the content in an array. - /// @a _bytes: bytes array to be rendered as string. @a _width of a bytes line. - std::vector> memDumpToList(bytes const& _bytes, unsigned _width); - + QCode* m_code; + QCallData* m_callData; }; } diff --git a/mix/Exceptions.h b/mix/Exceptions.h index 2a3d813eb..ea4cb87b3 100644 --- a/mix/Exceptions.h +++ b/mix/Exceptions.h @@ -35,9 +35,14 @@ namespace mix struct QmlLoadException: virtual Exception {}; struct FileIoException: virtual Exception {}; +struct InvalidBlockException: virtual Exception {}; +struct FunctionNotFoundException: virtual Exception {}; +struct ExecutionStateException: virtual Exception {}; typedef boost::error_info QmlErrorInfo; typedef boost::error_info FileError; +typedef boost::error_info BlockIndex; +typedef boost::error_info FunctionName; } } diff --git a/mix/HttpServer.cpp b/mix/HttpServer.cpp new file mode 100644 index 000000000..cfe5c37f4 --- /dev/null +++ b/mix/HttpServer.cpp @@ -0,0 +1,170 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file HttpServer.cpp + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ + +#include +#include +#include "HttpServer.h" + + +using namespace dev::mix; + +HttpServer::HttpServer() + : m_port(0) , m_listen(false) , m_accept(true) , m_componentCompleted(true) +{ +} + +HttpServer::~HttpServer() +{ + setListen(false); +} + +void HttpServer::classBegin() +{ + m_componentCompleted = false; +} + +void HttpServer::componentComplete() +{ + init(); + m_componentCompleted = true; +} + +QUrl HttpServer::url() const +{ + QUrl url; + url.setPort(m_port); + url.setHost("localhost"); + url.setScheme("http"); + return url; +} + +void HttpServer::setPort(int _port) +{ + if (_port == m_port) + return; + + m_port = _port; + emit portChanged(_port); + emit urlChanged(url()); + + if (m_componentCompleted && this->isListening()) + updateListening(); +} + +QString HttpServer::errorString() const +{ + return QTcpServer::errorString(); +} + +void HttpServer::setListen(bool _listen) +{ + if (_listen == m_listen) + return; + + m_listen = _listen; + emit listenChanged(_listen); + + if (m_componentCompleted) + updateListening(); +} + +void HttpServer::setAccept(bool _accept) +{ + if (_accept == m_accept) + return; + + m_accept = _accept; + emit acceptChanged(_accept); +} + +void HttpServer::init() +{ + updateListening(); +} + +void HttpServer::updateListening() +{ + if (this->isListening()) + this->close(); + + if (!m_listen || QTcpServer::listen(QHostAddress::LocalHost, m_port)) + return; +} + +void HttpServer::incomingConnection(qintptr _socket) +{ + if (!m_accept) + return; + + QTcpSocket* s = new QTcpSocket(this); + connect(s, SIGNAL(readyRead()), this, SLOT(readClient())); + connect(s, SIGNAL(disconnected()), this, SLOT(discardClient())); + s->setSocketDescriptor(_socket); +} + +void HttpServer::readClient() +{ + if (!m_accept) + return; + + QTcpSocket* socket = (QTcpSocket*)sender(); + try + { + if (socket->canReadLine()) + { + QString hdr = QString(socket->readLine()); + if (hdr.startsWith("POST")) + { + QString l; + do + l = socket->readLine(); + while (!(l.isEmpty() || l == "\r" || l == "\r\n")); + + QString content = socket->readAll(); + QUrl url; + std::unique_ptr request(new HttpRequest(this, url, content)); + clientConnected(request.get()); + QTextStream os(socket); + os.setAutoDetectUnicode(true); + ///@todo: allow setting response content-type, charset, etc + os << "HTTP/1.0 200 Ok\r\n" + "Content-Type: text/plain; charset=\"utf-8\"\r\n" + "\r\n"; + os << request->m_response; + } + } + } + catch(...) + { + delete socket; + throw; + } + socket->close(); + if (socket->state() == QTcpSocket::UnconnectedState) + delete socket; +} + +void HttpServer::discardClient() +{ + QTcpSocket* socket = (QTcpSocket*)sender(); + socket->deleteLater(); +} diff --git a/mix/HttpServer.h b/mix/HttpServer.h new file mode 100644 index 000000000..00d63a073 --- /dev/null +++ b/mix/HttpServer.h @@ -0,0 +1,123 @@ +/* + 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 HttpServer.h + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ + +#pragma once + +#include +#include +#include +#include + +namespace dev +{ +namespace mix +{ + +/// Simple http server for serving jsonrpc requests +class HttpRequest: public QObject +{ + Q_OBJECT + /// Request url + Q_PROPERTY(QUrl url MEMBER m_url CONSTANT) + /// Request body contents + Q_PROPERTY(QString content MEMBER m_content CONSTANT) + +private: + HttpRequest(QObject* _parent, QUrl const& _url, QString const& _content): + QObject(_parent), m_url(_url), m_content(_content) + { + } + +public: + /// Set response for a request + /// @param _response Response body. If no response is set, server returns status 200 with empty body + Q_INVOKABLE void setResponse(QString const& _response) { m_response = _response; } + +private: + QUrl m_url; + QString m_content; + QString m_response; + friend class HttpServer; +}; + +class HttpServer: public QTcpServer, public QQmlParserStatus +{ + Q_OBJECT + Q_DISABLE_COPY(HttpServer) + Q_INTERFACES(QQmlParserStatus) + + /// Server url + Q_PROPERTY(QUrl url READ url NOTIFY urlChanged) + /// Server port + Q_PROPERTY(int port READ port WRITE setPort NOTIFY portChanged) + /// Listen for connections + Q_PROPERTY(bool listen READ listen WRITE setListen NOTIFY listenChanged) + /// Accept new connections + Q_PROPERTY(bool accept READ accept WRITE setAccept NOTIFY acceptChanged) + /// Error string if any + Q_PROPERTY(QString errorString READ errorString NOTIFY errorStringChanged) + +public: + explicit HttpServer(); + virtual ~HttpServer(); + + QUrl url() const; + int port() const { return m_port; } + void setPort(int _port); + bool listen() const { return m_listen; } + void setListen(bool _listen); + bool accept() const { return m_accept; } + void setAccept(bool _accept); + QString errorString() const; + +protected: + virtual void classBegin() override; + virtual void componentComplete() override; + virtual void incomingConnection(qintptr _socket) override; + +signals: + void clientConnected(HttpRequest* _request); + void errorStringChanged(QString const& _errorString); + void urlChanged(QUrl const& _url); + void portChanged(int _port); + void listenChanged(bool _listen); + void acceptChanged(bool _accept); + +private: + void init(); + void updateListening(); + void newConnection(); + void serverError(); + +private slots: + void readClient(); + void discardClient(); + +private: + int m_port; + bool m_listen; + bool m_accept; + bool m_componentCompleted; +}; + +} +} diff --git a/mix/MixApplication.cpp b/mix/MixApplication.cpp index 56ba4ee9f..deff7c011 100644 --- a/mix/MixApplication.cpp +++ b/mix/MixApplication.cpp @@ -36,6 +36,10 @@ using namespace dev::mix; MixApplication::MixApplication(int _argc, char* _argv[]): QApplication(_argc, _argv), m_engine(new QQmlApplicationEngine()), m_appContext(new AppContext(m_engine.get())) { + setOrganizationName(tr("Ethereum")); + setOrganizationDomain(tr("ethereum.org")); + setApplicationName(tr("Mix")); + setApplicationVersion("0.1"); #ifdef ETH_HAVE_WEBENGINE QtWebEngine::initialize(); #endif diff --git a/mix/MixClient.cpp b/mix/MixClient.cpp index 39e876dfc..7d377981e 100644 --- a/mix/MixClient.cpp +++ b/mix/MixClient.cpp @@ -29,14 +29,17 @@ #include #include +#include "Exceptions.h" #include "MixClient.h" using namespace dev; using namespace dev::eth; using namespace dev::mix; +const Secret c_stdSecret = Secret("cb73d9408c4720e230387d956eb0f829d8a4dd2c1055f96257167e14e7169074"); + MixClient::MixClient(): - m_userAccount(KeyPair::create()) + m_userAccount(c_stdSecret) { resetState(10000000 * ether); } @@ -44,38 +47,71 @@ MixClient::MixClient(): void MixClient::resetState(u256 _balance) { WriteGuard l(x_state); - m_state = eth::State(Address(), m_stateDB, BaseState::Empty); + Guard fl(m_filterLock); + m_filters.clear(); + m_watches.clear(); + m_state = eth::State(m_userAccount.address(), m_stateDB, BaseState::Empty); m_state.addBalance(m_userAccount.address(), _balance); + Block genesis; + genesis.state = m_state; + Block open; + m_blocks = Blocks { genesis, open }; //last block contains a list of pending transactions to be finalized } -void MixClient::executeTransaction(bytesConstRef _rlp, State& _state) +void MixClient::executeTransaction(Transaction const& _t, State& _state) { + bytes rlp = _t.rlp(); Executive execution(_state, LastHashes(), 0); - execution.setup(_rlp); - bytes code; - bytesConstRef data; - bool firstIteration = true; + execution.setup(&rlp); std::vector machineStates; - std::vector levels; + std::vector levels; + std::vector codes; + std::map codeIndexes; + std::vector data; + std::map dataIndexes; + bytes const* lastCode = nullptr; + bytesConstRef const* lastData = nullptr; + unsigned codeIndex = 0; + unsigned dataIndex = 0; auto onOp = [&](uint64_t steps, Instruction inst, dev::bigint newMemSize, dev::bigint gasCost, void* voidVM, void const* voidExt) { VM& vm = *(VM*)voidVM; ExtVM const& ext = *(ExtVM const*)voidExt; + if (lastCode == nullptr || lastCode != &ext.code) + { + auto const& iter = codeIndexes.find(&ext.code); + if (iter != codeIndexes.end()) + codeIndex = iter->second; + else + { + codeIndex = codes.size(); + codes.push_back(ext.code); + codeIndexes[&ext.code] = codeIndex; + } + lastCode = &ext.code; + } - if (firstIteration) + if (lastData == nullptr || lastData != &ext.data) { - code = ext.code; - data = ext.data; - firstIteration = false; + auto const& iter = dataIndexes.find(&ext.data); + if (iter != dataIndexes.end()) + dataIndex = iter->second; + else + { + dataIndex = data.size(); + data.push_back(ext.data.toBytes()); + dataIndexes[&ext.data] = dataIndex; + } + lastData = &ext.data; } if (levels.size() < ext.depth) - levels.push_back(&machineStates.back()); + levels.push_back(machineStates.size() - 1); else levels.resize(ext.depth); - machineStates.push_back(MachineState({steps, ext.myAddress, vm.curPC(), inst, newMemSize, vm.gas(), - vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels})); + machineStates.emplace_back(MachineState({steps, ext.myAddress, vm.curPC(), inst, newMemSize, vm.gas(), + vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels, codeIndex, dataIndex})); }; execution.go(onOp); @@ -84,18 +120,63 @@ void MixClient::executeTransaction(bytesConstRef _rlp, State& _state) ExecutionResult d; d.returnValue = execution.out().toVector(); d.machineStates = machineStates; - d.executionCode = code; - d.executionData = data; - d.contentAvailable = true; - d.message = "ok"; - d.contractAddress = m_lastExecutionResult.contractAddress; - m_lastExecutionResult = d; + d.executionCode = std::move(codes); + d.transactionData = std::move(data); + d.address = _t.receiveAddress(); + d.sender = _t.sender(); + d.value = _t.value(); + if (_t.isCreation()) + d.contractAddress = right160(sha3(rlpList(_t.sender(), _t.nonce()))); + d.receipt = TransactionReceipt(m_state.rootHash(), execution.gasUsed(), execution.logs()); //TODO: track gas usage + m_blocks.back().transactions.emplace_back(d); + + h256Set changed; + Guard l(m_filterLock); + for (std::pair& i: m_filters) + if ((unsigned)i.second.filter.latest() > m_blocks.size() - 1) + { + // acceptable number. + auto m = i.second.filter.matches(d.receipt); + if (m.size()) + { + // filter catches them + for (LogEntry const& l: m) + i.second.changes.push_back(LocalisedLogEntry(l, m_blocks.size())); + changed.insert(i.first); + } + } + changed.insert(dev::eth::PendingChangedFilter); + noteChanged(changed); } void MixClient::validateBlock(int _block) const { - //TODO: throw exception here if _block != 0 - (void)_block; + if (_block != -1 && _block != 0 && (unsigned)_block >= m_blocks.size() - 1) + BOOST_THROW_EXCEPTION(InvalidBlockException() << BlockIndex(_block)); +} + +void MixClient::mine() +{ + WriteGuard l(x_state); + Block& block = m_blocks.back(); + m_state.completeMine(); + block.state = m_state; + block.info = m_state.info(); + block.hash = block.info.hash; + m_blocks.push_back(Block()); + h256Set changed { dev::eth::PendingChangedFilter, dev::eth::ChainChangedFilter }; + noteChanged(changed); +} + +State const& MixClient::asOf(int _block) const +{ + validateBlock(_block); + if (_block == 0) + return m_blocks[m_blocks.size() - 2].state; + else if (_block == -1) + return m_state; + else + return m_blocks[_block].state; } void MixClient::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) @@ -103,8 +184,7 @@ void MixClient::transact(Secret _secret, u256 _value, Address _dest, bytes const WriteGuard l(x_state); u256 n = m_state.transactionsFrom(toAddress(_secret)); Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); - bytes rlp = t.rlp(); - executeTransaction(&rlp, m_state); + executeTransaction(t, m_state); } Address MixClient::transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) @@ -112,17 +192,16 @@ Address MixClient::transact(Secret _secret, u256 _endowment, bytes const& _init, WriteGuard l(x_state); u256 n = m_state.transactionsFrom(toAddress(_secret)); eth::Transaction t(_endowment, _gasPrice, _gas, _init, n, _secret); - bytes rlp = t.rlp(); - executeTransaction(&rlp, m_state); + executeTransaction(t, m_state); Address address = right160(sha3(rlpList(t.sender(), t.nonce()))); - m_lastExecutionResult.contractAddress = address; return address; } void MixClient::inject(bytesConstRef _rlp) { WriteGuard l(x_state); - executeTransaction(_rlp, m_state); + eth::Transaction t(_rlp, CheckSignature::None); + executeTransaction(t, m_state); } void MixClient::flushTransactions() @@ -140,92 +219,154 @@ bytes MixClient::call(Secret _secret, u256 _value, Address _dest, bytes const& _ } Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); bytes rlp = t.rlp(); - WriteGuard lw(x_state); //TODO: lock is required only for last executoin state - executeTransaction(&rlp, temp); - return m_lastExecutionResult.returnValue; + WriteGuard lw(x_state); //TODO: lock is required only for last execution state + executeTransaction(t, temp); + return m_blocks.back().transactions.back().returnValue; } u256 MixClient::balanceAt(Address _a, int _block) const { - validateBlock(_block); ReadGuard l(x_state); - return m_state.balance(_a); + return asOf(_block).balance(_a); } u256 MixClient::countAt(Address _a, int _block) const { - validateBlock(_block); ReadGuard l(x_state); - return m_state.transactionsFrom(_a); + return asOf(_block).transactionsFrom(_a); } u256 MixClient::stateAt(Address _a, u256 _l, int _block) const { - validateBlock(_block); ReadGuard l(x_state); - return m_state.storage(_a, _l); + return asOf(_block).storage(_a, _l); } bytes MixClient::codeAt(Address _a, int _block) const { - validateBlock(_block); ReadGuard l(x_state); - return m_state.code(_a); + return asOf(_block).code(_a); } std::map MixClient::storageAt(Address _a, int _block) const { - validateBlock(_block); ReadGuard l(x_state); - return m_state.storage(_a); + return asOf(_block).storage(_a); } eth::LocalisedLogEntries MixClient::logs(unsigned _watchId) const { - (void)_watchId; - return LocalisedLogEntries(); + Guard l(m_filterLock); + h256 h = m_watches.at(_watchId).id; + auto filterIter = m_filters.find(h); + if (filterIter != m_filters.end()) + return logs(filterIter->second.filter); + return eth::LocalisedLogEntries(); } -eth::LocalisedLogEntries MixClient::logs(eth::LogFilter const& _filter) const +eth::LocalisedLogEntries MixClient::logs(eth::LogFilter const& _f) const { - (void)_filter; - return LocalisedLogEntries(); + LocalisedLogEntries ret; + unsigned lastBlock = m_blocks.size() - 1; //last block contains pending transactions + unsigned block = std::min(lastBlock, (unsigned)_f.latest()); + unsigned end = std::min(lastBlock, std::min(block, (unsigned)_f.earliest())); + for (; ret.size() != _f.max() && block != end; block--) + { + bool pendingBlock = (block == lastBlock); + if (pendingBlock || _f.matches(m_blocks[block].info.logBloom)) + for (ExecutionResult const& t: m_blocks[block].transactions) + if (pendingBlock || _f.matches(t.receipt.bloom())) + { + LogEntries logEntries = _f.matches(t.receipt); + if (logEntries.size()) + { + for (unsigned entry = _f.skip(); entry < logEntries.size() && ret.size() != _f.max(); ++entry) + ret.insert(ret.begin(), LocalisedLogEntry(logEntries[entry], block)); + } + } + } + return ret; } -unsigned MixClient::installWatch(eth::LogFilter const& _filter) +unsigned MixClient::installWatch(h256 _h) { - (void)_filter; - BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::installWatch")); + unsigned ret; + { + Guard l(m_filterLock); + ret = m_watches.size() ? m_watches.rbegin()->first + 1 : 0; + m_watches[ret] = ClientWatch(_h); + } + auto ch = logs(ret); + if (ch.empty()) + ch.push_back(eth::InitialChange); + { + Guard l(m_filterLock); + swap(m_watches[ret].changes, ch); + } + return ret; } -unsigned MixClient::installWatch(h256 _filterId) +unsigned MixClient::installWatch(eth::LogFilter const& _f) { - (void)_filterId; - BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::installWatch")); + h256 h = _f.sha3(); + { + Guard l(m_filterLock); + m_filters.insert(std::make_pair(h, _f)); + } + return installWatch(h); } -void MixClient::uninstallWatch(unsigned _watchId) +void MixClient::uninstallWatch(unsigned _i) { - (void)_watchId; - BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::uninstallWatch")); + Guard l(m_filterLock); + + auto it = m_watches.find(_i); + if (it == m_watches.end()) + return; + auto id = it->second.id; + m_watches.erase(it); + + auto fit = m_filters.find(id); + if (fit != m_filters.end()) + if (!--fit->second.refCount) + m_filters.erase(fit); +} + +void MixClient::noteChanged(h256Set const& _filters) +{ + for (auto& i: m_watches) + if (_filters.count(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)); + } + for (auto& i: m_filters) + i.second.changes.clear(); } -eth::LocalisedLogEntries MixClient::peekWatch(unsigned _watchId) const +LocalisedLogEntries MixClient::peekWatch(unsigned _watchId) const { - (void)_watchId; - BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::peekWatch")); + Guard l(m_filterLock); + if (_watchId < m_watches.size()) + return m_watches.at(_watchId).changes; + return LocalisedLogEntries(); } -eth::LocalisedLogEntries MixClient::checkWatch(unsigned _watchId) +LocalisedLogEntries MixClient::checkWatch(unsigned _watchId) { - (void)_watchId; - BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::checkWatch")); + Guard l(m_filterLock); + LocalisedLogEntries ret; + if (_watchId < m_watches.size()) + std::swap(ret, m_watches.at(_watchId).changes); + return ret; } h256 MixClient::hashFromNumber(unsigned _number) const { - (void)_number; - BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::hashFromNumber")); + validateBlock(_number); + return m_blocks[_number].hash; } eth::BlockInfo MixClient::blockInfo(h256 _hash) const @@ -256,7 +397,7 @@ eth::BlockInfo MixClient::uncle(h256 _blockHash, unsigned _i) const unsigned MixClient::number() const { - return 0; + return m_blocks.size() - 1; } eth::Transactions MixClient::pending() const diff --git a/mix/MixClient.h b/mix/MixClient.h index f615d7afc..fede1891e 100644 --- a/mix/MixClient.h +++ b/mix/MixClient.h @@ -23,7 +23,9 @@ #pragma once +#include #include +#include namespace dev { @@ -36,7 +38,7 @@ namespace mix struct MachineState { uint64_t steps; - dev::Address cur; + dev::Address address; dev::u256 curPC; dev::eth::Instruction inst; dev::bigint newMemSize; @@ -45,7 +47,9 @@ struct MachineState dev::bytes memory; dev::bigint gasCost; std::map storage; - std::vector levels; + std::vector levels; + unsigned codeIndex; + unsigned dataIndex; }; /** @@ -53,24 +57,41 @@ struct MachineState */ struct ExecutionResult { + ExecutionResult(): receipt(dev::h256(), dev::h256(), dev::eth::LogEntries()) {} + std::vector machineStates; - bytes executionCode; - bytesConstRef executionData; - Address contractAddress; - bool contentAvailable; - std::string message; + std::vector transactionData; + std::vector executionCode; bytes returnValue; + dev::Address address; + dev::Address sender; + dev::Address contractAddress; + dev::u256 value; + dev::eth::TransactionReceipt receipt; }; +using ExecutionResults = std::vector; + +struct Block +{ + ExecutionResults transactions; + h256 hash; + dev::eth::State state; + dev::eth::BlockInfo info; +}; + +using Blocks = std::vector; + + class MixClient: public dev::eth::Interface { public: MixClient(); /// Reset state to the empty state with given balance. void resetState(u256 _balance); - const KeyPair& userAccount() const { return m_userAccount; } - const ExecutionResult lastExecutionResult() const { ReadGuard l(x_state); return m_lastExecutionResult; } - const Address lastContractAddress() const { ReadGuard l(x_state); return m_lastExecutionResult.contractAddress; } + KeyPair const& userAccount() const { return m_userAccount; } + void mine(); + Blocks const& record() const { return m_blocks; } //dev::eth::Interface void transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) override; @@ -111,14 +132,19 @@ public: eth::MineProgress miningProgress() const override; private: - void executeTransaction(bytesConstRef _rlp, eth::State& _state); + void executeTransaction(dev::eth::Transaction const& _t, eth::State& _state); void validateBlock(int _block) const; + void noteChanged(h256Set const& _filters); + dev::eth::State const& asOf(int _block) const; KeyPair m_userAccount; eth::State m_state; OverlayDB m_stateDB; mutable boost::shared_mutex x_state; - ExecutionResult m_lastExecutionResult; + mutable std::mutex m_filterLock; + std::map m_filters; + std::map m_watches; + Blocks m_blocks; }; } diff --git a/mix/QBasicNodeDefinition.h b/mix/QBasicNodeDefinition.h index 0d7365b72..d276d0eb2 100644 --- a/mix/QBasicNodeDefinition.h +++ b/mix/QBasicNodeDefinition.h @@ -38,6 +38,7 @@ public: QBasicNodeDefinition(): QObject() {} ~QBasicNodeDefinition() {} QBasicNodeDefinition(solidity::Declaration const* _d): QObject(), m_name(QString::fromStdString(_d->getName())) {} + QBasicNodeDefinition(std::string const& _name): QObject(), m_name(QString::fromStdString(_name)) {} /// Get the name of the node. QString name() const { return m_name; } diff --git a/mix/QContractDefinition.cpp b/mix/QContractDefinition.cpp index bee9cfe49..488e08ea3 100644 --- a/mix/QContractDefinition.cpp +++ b/mix/QContractDefinition.cpp @@ -33,9 +33,20 @@ using namespace dev::mix; QContractDefinition::QContractDefinition(dev::solidity::ContractDefinition const* _contract): QBasicNodeDefinition(_contract) { - auto interfaceFunctions = _contract->getInterfaceFunctions(); - unsigned i = 0; - for (auto it = interfaceFunctions.cbegin(); it != interfaceFunctions.cend(); ++it, ++i) - m_functions.append(new QFunctionDefinition(it->second, i)); -} + if (_contract->getConstructor() != nullptr) + m_constructor = new QFunctionDefinition(ContractType(*_contract).getConstructorType()); + else + m_constructor = new QFunctionDefinition(); + + for (auto const& it: _contract->getInterfaceFunctions()) + m_functions.append(new QFunctionDefinition(it.second));} + +QFunctionDefinition* QContractDefinition::getFunction(dev::FixedHash<4> _hash) +{ + for (auto const& f: m_functions) + if (f->hash() == _hash) + return f; + + return nullptr; +} diff --git a/mix/QContractDefinition.h b/mix/QContractDefinition.h index e9c618804..22f913a70 100644 --- a/mix/QContractDefinition.h +++ b/mix/QContractDefinition.h @@ -36,15 +36,21 @@ class QContractDefinition: public QBasicNodeDefinition { Q_OBJECT Q_PROPERTY(QQmlListProperty functions READ functions CONSTANT) + Q_PROPERTY(dev::mix::QFunctionDefinition* constructor READ constructor CONSTANT) public: QContractDefinition() {} QContractDefinition(solidity::ContractDefinition const* _contract); /// Get all the functions of the contract. QQmlListProperty functions() const { return QQmlListProperty(const_cast(this), const_cast(this)->m_functions); } + /// Get the constructor of the contract. + QFunctionDefinition* constructor() const { return m_constructor; } QList const& functionsList() const { return m_functions; } + /// Find function by hash, returns nullptr if not found + QFunctionDefinition* getFunction(dev::FixedHash<4> _hash); private: QList m_functions; + QFunctionDefinition* m_constructor; }; } diff --git a/mix/QFunctionDefinition.cpp b/mix/QFunctionDefinition.cpp index 97ce0ff58..20dbe070b 100644 --- a/mix/QFunctionDefinition.cpp +++ b/mix/QFunctionDefinition.cpp @@ -21,19 +21,22 @@ #include #include +#include #include "QVariableDeclaration.h" #include "QFunctionDefinition.h" using namespace dev::solidity; using namespace dev::mix; -QFunctionDefinition::QFunctionDefinition(dev::solidity::FunctionDefinition const* _f, int _index): QBasicNodeDefinition(_f), m_index(_index), m_hash(dev::sha3(_f->getCanonicalSignature())) +QFunctionDefinition::QFunctionDefinition(dev::solidity::FunctionTypePointer const& _f): QBasicNodeDefinition(&_f->getDeclaration()), m_hash(dev::sha3(_f->getCanonicalSignature())) { - std::vector> parameters = _f->getParameterList().getParameters(); - for (unsigned i = 0; i < parameters.size(); i++) - m_parameters.append(new QVariableDeclaration(parameters.at(i).get())); + auto paramNames = _f->getParameterNames(); + auto paramTypes = _f->getParameterTypeNames(); + auto returnNames = _f->getReturnParameterNames(); + auto returnTypes = _f->getReturnParameterTypeNames(); + for (unsigned i = 0; i < paramNames.size(); ++i) + m_parameters.append(new QVariableDeclaration(paramNames[i], paramTypes[i])); - std::vector> returnParameters = _f->getReturnParameters(); - for (unsigned i = 0; i < returnParameters.size(); i++) - m_returnParameters.append(new QVariableDeclaration(returnParameters.at(i).get())); + for (unsigned i = 0; i < returnNames.size(); ++i) + m_returnParameters.append(new QVariableDeclaration(returnNames[i], returnTypes[i])); } diff --git a/mix/QFunctionDefinition.h b/mix/QFunctionDefinition.h index 7f606c8a1..0bbf093b5 100644 --- a/mix/QFunctionDefinition.h +++ b/mix/QFunctionDefinition.h @@ -36,19 +36,16 @@ class QFunctionDefinition: public QBasicNodeDefinition { Q_OBJECT Q_PROPERTY(QQmlListProperty parameters READ parameters) - Q_PROPERTY(int index READ index) public: QFunctionDefinition() {} - QFunctionDefinition(solidity::FunctionDefinition const* _f, int _index); + QFunctionDefinition(solidity::FunctionTypePointer const& _f); /// Get all input parameters of this function. QList const& parametersList() const { return m_parameters; } /// Get all input parameters of this function as QML property. QQmlListProperty parameters() const { return QQmlListProperty(const_cast(this), const_cast(this)->m_parameters); } /// Get all return parameters of this function. QList returnParameters() const { return m_returnParameters; } - /// Get the index of this function on the contract ABI. - int index() const { return m_index; } /// Get the hash of this function declaration on the contract ABI. FixedHash<4> hash() const { return m_hash; } diff --git a/mix/QVariableDeclaration.h b/mix/QVariableDeclaration.h index 966ee0ff3..f9cc5265f 100644 --- a/mix/QVariableDeclaration.h +++ b/mix/QVariableDeclaration.h @@ -37,6 +37,7 @@ class QVariableDeclaration: public QBasicNodeDefinition public: QVariableDeclaration() {} QVariableDeclaration(solidity::VariableDeclaration const* _v): QBasicNodeDefinition(_v), m_type(QString::fromStdString(_v->getType()->toString())) {} + QVariableDeclaration(std::string const& _name, std::string const& _type): QBasicNodeDefinition(_name), m_type(QString::fromStdString(_type)) {} QString type() const { return m_type; } private: QString m_type; diff --git a/mix/Web3Server.cpp b/mix/Web3Server.cpp index 469ca907d..ead2c792f 100644 --- a/mix/Web3Server.cpp +++ b/mix/Web3Server.cpp @@ -20,8 +20,9 @@ * Ethereum IDE client. */ - #include +#include +#include #include "Web3Server.h" using namespace dev::mix; @@ -53,3 +54,24 @@ void Web3Server::put(std::string const& _name, std::string const& _key, std::str std::string k(_name + "/" + _key); m_db[k] = _value; } + +Json::Value Web3Server::eth_changed(int const& _id) +{ + cnote << "eth_changed(" << _id << ") ->" << client()->peekWatch(_id).size(); + + return WebThreeStubServerBase::eth_changed(_id); +} + +std::string Web3Server::eth_transact(Json::Value const& _json) +{ + std::string ret = WebThreeStubServerBase::eth_transact(_json); + emit newTransaction(); + return ret; +} + +std::string Web3Server::eth_call(Json::Value const& _json) +{ + std::string ret = WebThreeStubServerBase::eth_call(_json); + emit newTransaction(); + return ret; +} diff --git a/mix/Web3Server.h b/mix/Web3Server.h index c603b48a2..b344720e7 100644 --- a/mix/Web3Server.h +++ b/mix/Web3Server.h @@ -24,6 +24,7 @@ #include #include +#include #include namespace dev @@ -32,11 +33,21 @@ namespace dev namespace mix { -class Web3Server: public dev::WebThreeStubServerBase, public dev::WebThreeStubDatabaseFace +class Web3Server: public QObject, public dev::WebThreeStubServerBase, public dev::WebThreeStubDatabaseFace { + Q_OBJECT + public: Web3Server(jsonrpc::AbstractServerConnector& _conn, std::vector const& _accounts, dev::eth::Interface* _client); +signals: + void newTransaction(); + +protected: + virtual Json::Value eth_changed(int const& _id) override; + virtual std::string eth_transact(Json::Value const& _json) override; + virtual std::string eth_call(Json::Value const& _json) override; + private: dev::eth::Interface* client() override { return m_client; } std::shared_ptr face() override; diff --git a/mix/noweb.qrc b/mix/noweb.qrc index 25ce95352..9dc107942 100644 --- a/mix/noweb.qrc +++ b/mix/noweb.qrc @@ -1,5 +1,6 @@ - qml/WebPreviewStub.qml + qml/WebPreviewStub.qml + qml/CodeEditor.qml diff --git a/mix/qml/CallStack.qml b/mix/qml/CallStack.qml new file mode 100644 index 000000000..218c8c02e --- /dev/null +++ b/mix/qml/CallStack.qml @@ -0,0 +1,31 @@ +import QtQuick 2.2 +import QtQuick.Controls.Styles 1.1 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 + +Item { + property alias model: callTable.model + signal frameActivated(int index) + ColumnLayout { + anchors.fill: parent + Text { + text: qsTr("Call Stack") + Layout.fillWidth: true + } + TableView { + id: callTable + Layout.fillWidth: true + Layout.fillHeight: true + headerDelegate: null + + TableViewColumn { + role: "modelData" + title: qsTr("Address") + width: parent.width + } + onActivated: { + frameActivated(row); + } + } + } +} diff --git a/mix/qml/CodeEditor.qml b/mix/qml/CodeEditor.qml index 77e60ee66..0c554b379 100644 --- a/mix/qml/CodeEditor.qml +++ b/mix/qml/CodeEditor.qml @@ -4,78 +4,80 @@ import QtQuick.Layouts 1.0 import QtQuick.Controls 1.0 import QtQuick.Controls.Styles 1.1 -Component { - Item { - signal editorTextChanged +Item { + signal editorTextChanged - function setText(text) { - codeEditor.text = text; - } + function setText(text) { + codeEditor.text = text; + } - function getText() { - return codeEditor.text; - } + function getText() { + return codeEditor.text; + } - anchors.fill: parent - id: contentView - width: parent.width - height: parent.height * 0.7 - Rectangle { - id: lineColumn - property int rowHeight: codeEditor.font.pixelSize + 3 - color: "#202020" - width: 50 - height: parent.height - Column { - y: -codeEditor.flickableItem.contentY + 4 - width: parent.width - Repeater { - model: Math.max(codeEditor.lineCount + 2, (lineColumn.height/lineColumn.rowHeight)) - delegate: Text { - id: text - color: codeEditor.textColor - font: codeEditor.font - width: lineColumn.width - 4 - horizontalAlignment: Text.AlignRight - verticalAlignment: Text.AlignVCenter - height: lineColumn.rowHeight - renderType: Text.NativeRendering - text: index + 1 - } + function setFocus() { + codeEditor.forceActiveFocus(); + } + + anchors.fill: parent + id: contentView + width: parent.width + height: parent.height * 0.7 + Rectangle { + id: lineColumn + property int rowHeight: codeEditor.font.pixelSize + 3 + color: "#202020" + width: 50 + height: parent.height + Column { + y: -codeEditor.flickableItem.contentY + 4 + width: parent.width + Repeater { + model: Math.max(codeEditor.lineCount + 2, (lineColumn.height/lineColumn.rowHeight)) + delegate: Text { + id: text + color: codeEditor.textColor + font: codeEditor.font + width: lineColumn.width - 4 + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + height: lineColumn.rowHeight + renderType: Text.NativeRendering + text: index + 1 } } } + } - TextArea { - id: codeEditor - textColor: "#EEE8D5" - style: TextAreaStyle { - backgroundColor: "#002B36" - } + TextArea { + id: codeEditor + textColor: "#EEE8D5" + style: TextAreaStyle { + backgroundColor: "#002B36" + } - anchors.left: lineColumn.right - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom - wrapMode: TextEdit.NoWrap - frameVisible: false + anchors.left: lineColumn.right + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + wrapMode: TextEdit.NoWrap + frameVisible: false - height: parent.height - font.family: "Monospace" - font.pointSize: 12 - width: parent.width + height: parent.height + font.family: "Monospace" + font.pointSize: 12 + width: parent.width - tabChangesFocus: false - Keys.onPressed: { - if (event.key === Qt.Key_Tab) { - codeEditor.insert(codeEditor.cursorPosition, "\t"); - event.accepted = true; - } + tabChangesFocus: false + Keys.onPressed: { + if (event.key === Qt.Key_Tab) { + codeEditor.insert(codeEditor.cursorPosition, "\t"); + event.accepted = true; } - onTextChanged: { - editorTextChanged(); - } - } + onTextChanged: { + editorTextChanged(); + } + } } diff --git a/mix/qml/CodeEditorView.qml b/mix/qml/CodeEditorView.qml index c84d89bfe..36fc586b3 100644 --- a/mix/qml/CodeEditorView.qml +++ b/mix/qml/CodeEditorView.qml @@ -35,7 +35,7 @@ Item { editor.onEditorTextChanged.connect(function() { codeModel.registerCodeChange(editor.getText()); }); - editor.setText(data); + editor.setText(data, document.syntaxMode); } Connections { @@ -56,26 +56,27 @@ Item { } } - CodeEditor { - id: codeEditor - } - Repeater { id: editors model: editorListModel delegate: Loader { - active: false; + id: loader + active: false asynchronous: true anchors.fill: parent - sourceComponent: codeEditor + source: "CodeEditor.qml" visible: (index >= 0 && index < editorListModel.count && currentDocumentId === editorListModel.get(index).documentId) onVisibleChanged: { loadIfNotLoaded() + if (visible && item) + loader.item.setFocus(); } Component.onCompleted: { loadIfNotLoaded() } - onLoaded: { doLoadDocument(item, editorListModel.get(index)) } + onLoaded: { + doLoadDocument(loader.item, editorListModel.get(index)) + } function loadIfNotLoaded () { if(visible && !active) { diff --git a/mix/qml/ContractLibrary.qml b/mix/qml/ContractLibrary.qml new file mode 100644 index 000000000..557f1cc53 --- /dev/null +++ b/mix/qml/ContractLibrary.qml @@ -0,0 +1,27 @@ +import QtQuick 2.2 + +Item { + id: contractLibrary + property alias model: contractListModel; + + Connections { + target: appContext + Component.onCompleted: { + + //TODO: load a list, dependencies, ets, from external files + contractListModel.append({ + name: "Config", + url: "qrc:///stdc/std.sol", + }); + contractListModel.append({ + name: "NameReg", + url: "qrc:///stdc/std.sol", + }); + } + } + + ListModel { + id: contractListModel + } +} + diff --git a/mix/qml/Debugger.qml b/mix/qml/Debugger.qml index 47975db83..502f3242e 100644 --- a/mix/qml/Debugger.qml +++ b/mix/qml/Debugger.qml @@ -13,13 +13,6 @@ Rectangle { anchors.fill: parent; color: "#ededed" clip: true - Keys.onPressed: - { - if (event.key === Qt.Key_F10) - Debugger.moveSelection(1); - else if (event.key === Qt.Key_F9) - Debugger.moveSelection(-1); - } onVisibleChanged: { @@ -27,11 +20,11 @@ Rectangle { forceActiveFocus(); } - function update(giveFocus) + function update(data, giveFocus) { if (statusPane.result.successful) { - Debugger.init(); + Debugger.init(data); debugScrollArea.visible = true; compilationErrorArea.visible = false; machineStates.visible = true; @@ -50,9 +43,16 @@ Rectangle { forceActiveFocus(); } + Connections { + target: clientModel + onDebugDataReady: { + update(_debugData, true); + } + } + Connections { target: codeModel - onCompilationComplete: update(false) + onCompilationComplete: update(null, false); } Rectangle @@ -114,6 +114,7 @@ Rectangle { contentWidth: parent.width Rectangle { + color: "transparent" anchors.fill: parent ColumnLayout { @@ -128,6 +129,12 @@ Rectangle { anchors.fill: parent Layout.fillWidth: true Layout.fillHeight: true + + TransactionLog { + Layout.fillWidth: true + height: 250 + } + RowLayout { // step button + slider id: buttonRow @@ -152,6 +159,7 @@ Rectangle { onClicked: Debugger.stepOutBack() width: 28 height: 30 + buttonShortcut: "Ctrl+Shift+F11" buttonTooltip: qsTr("Step Out Back") } @@ -163,6 +171,7 @@ Rectangle { onClicked: Debugger.stepIntoBack() width: 28 height: 30 + buttonShortcut: "Ctrl+F11" buttonTooltip: qsTr("Step Into Back") } @@ -174,6 +183,7 @@ Rectangle { onClicked: Debugger.stepOverBack() width: 28 height: 30 + buttonShortcut: "Ctrl+F10" buttonTooltip: qsTr("Step Over Back") } @@ -185,6 +195,7 @@ Rectangle { onClicked: Debugger.stepOverForward() width: 28 height: 30 + buttonShortcut: "F10" buttonTooltip: qsTr("Step Over Forward") } @@ -196,6 +207,7 @@ Rectangle { onClicked: Debugger.stepIntoForward() width: 28 height: 30 + buttonShortcut: "F11" buttonTooltip: qsTr("Step Into Forward") } @@ -207,6 +219,7 @@ Rectangle { onClicked: Debugger.stepOutForward() width: 28 height: 30 + buttonShortcut: "Shift+F11" buttonTooltip: qsTr("Step Out Forward") } } @@ -269,7 +282,8 @@ Rectangle { id: statesList delegate: renderDelegate highlight: highlightBar - highlightFollowsCurrentItem: false + //highlightFollowsCurrentItem: false + model: ListModel {} } Component { @@ -280,9 +294,9 @@ Rectangle { width: statesList.currentItem.width; y: statesList.currentItem.y color: "#4A90E2" - Behavior on y { - PropertyAnimation { properties: "y"; easing.type: Easing.InOutQuad; duration: 50} - } + //Behavior on y { + // PropertyAnimation { properties: "y"; easing.type: Easing.InOutQuad; duration: 50} + //} } } @@ -299,6 +313,7 @@ Rectangle { width: 15 color: "#b2b3ae" text: line.split(' ')[0] + font.family: "monospace" font.pointSize: 9 id: id wrapMode: Text.NoWrap @@ -306,6 +321,7 @@ Rectangle { Text { wrapMode: Text.NoWrap color: parent.ListView.isCurrentItem ? "white" : "black" + font.family: "monospace" text: line.replace(line.split(' ')[0], '') anchors.left: id.right font.pointSize: 9 @@ -317,7 +333,7 @@ Rectangle { Rectangle { Layout.fillWidth: true height: parent.height //- 2 * stateListContainer.border.width - + color: "transparent" ColumnLayout { width: parent.width @@ -428,9 +444,28 @@ Rectangle { orientation: Qt.Vertical width: debugPanel.width - 2 * machineStates.sideMargin + + + Rectangle + { + id: callStackRect; + color: "transparent" + height: 120 + width: parent.width + Layout.minimumHeight: 120 + Layout.maximumHeight: 400 + CallStack { + anchors.fill: parent + id: callStack + onFrameActivated: Debugger.displayFrame(index); + } + } + + Rectangle { id: storageRect + color: "transparent" width: parent.width Layout.minimumHeight: 25 Layout.maximumHeight: 223 @@ -465,8 +500,10 @@ Rectangle { font.family: "monospace" anchors.leftMargin: 5 color: "#4a4a4a" - text: modelData.split(' ')[0].substring(0, 10); + text: modelData.split('\t')[0]; font.pointSize: 9 + width: parent.width - 5 + elide: Text.ElideRight } } Rectangle @@ -486,7 +523,8 @@ Rectangle { font.family: "monospace" anchors.verticalCenter: parent.verticalCenter color: "#4a4a4a" - text: modelData.split(' ')[1].substring(0, 10); + text: modelData.split('\t')[1]; + elide: Text.ElideRight font.pointSize: 9 } } @@ -506,6 +544,7 @@ Rectangle { Rectangle { id: memoryRect; + color: "transparent" height: 25 width: parent.width Layout.minimumHeight: 25 @@ -527,6 +566,7 @@ Rectangle { Rectangle { id: callDataRect + color: "transparent" height: 25 width: parent.width Layout.minimumHeight: 25 diff --git a/mix/qml/Ether.qml b/mix/qml/Ether.qml index 9eb9b63e0..a5fd5dba2 100644 --- a/mix/qml/Ether.qml +++ b/mix/qml/Ether.qml @@ -10,7 +10,7 @@ import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 import QtQuick.Controls.Styles 1.1 -Rectangle { +RowLayout { id: etherEdition property bool displayFormattedValue; property bool edit; @@ -32,84 +32,59 @@ Rectangle { units.currentIndex = unit; } - RowLayout + TextField { - anchors.fill: parent; - id: row - width: 200 - height: parent.height - Rectangle + implicitWidth: 200 + onTextChanged: { - width : 200 - color: edit ? "blue" : "white" - TextField + if (value !== undefined) { - onTextChanged: - { - if (value !== undefined) - { - value.setValue(text) - formattedValue.text = value.format(); - } - } - width: parent.width - readOnly: !edit - visible: edit - id: etherValueEdit; + value.setValue(text) + formattedValue.text = value.format(); } } + readOnly: !edit + visible: edit + id: etherValueEdit; + } - Rectangle + ComboBox + { + id: units + onCurrentTextChanged: { - Layout.fillWidth: true - id: unitContainer - width: 20 - anchors.verticalCenter: parent.verticalCenter - ComboBox - { - id: units - onCurrentTextChanged: - { - if (value !== undefined) - { - value.setUnit(currentText); - formattedValue.text = value.format(); - } - } - model: ListModel { - id: unitsModel - ListElement { text: "Uether"; } - ListElement { text: "Vether"; } - ListElement { text: "Dether"; } - ListElement { text: "Nether"; } - ListElement { text: "Yether"; } - ListElement { text: "Zether"; } - ListElement { text: "Eether"; } - ListElement { text: "Pether"; } - ListElement { text: "Tether"; } - ListElement { text: "Gether"; } - ListElement { text: "Mether"; } - ListElement { text: "grand"; } - ListElement { text: "ether"; } - ListElement { text: "finney"; } - ListElement { text: "szabo"; } - ListElement { text: "Gwei"; } - ListElement { text: "Mwei"; } - ListElement { text: "Kwei"; } - ListElement { text: "wei"; } - } - } - Rectangle + if (value !== undefined) { - anchors.verticalCenter: parent.verticalCenter - anchors.left: units.right - visible: displayFormattedValue - width: 20 - Text - { - id: formattedValue - } + value.setUnit(currentText); + formattedValue.text = value.format(); } } + model: ListModel { + id: unitsModel + ListElement { text: "Uether"; } + ListElement { text: "Vether"; } + ListElement { text: "Dether"; } + ListElement { text: "Nether"; } + ListElement { text: "Yether"; } + ListElement { text: "Zether"; } + ListElement { text: "Eether"; } + ListElement { text: "Pether"; } + ListElement { text: "Tether"; } + ListElement { text: "Gether"; } + ListElement { text: "Mether"; } + ListElement { text: "grand"; } + ListElement { text: "ether"; } + ListElement { text: "finney"; } + ListElement { text: "szabo"; } + ListElement { text: "Gwei"; } + ListElement { text: "Mwei"; } + ListElement { text: "Kwei"; } + ListElement { text: "wei"; } + } + } + Text + { + visible: displayFormattedValue + id: formattedValue } } diff --git a/mix/qml/ItemDelegateDataDump.qml b/mix/qml/ItemDelegateDataDump.qml index 1c0ea183e..a57a61b03 100644 --- a/mix/qml/ItemDelegateDataDump.qml +++ b/mix/qml/ItemDelegateDataDump.qml @@ -49,21 +49,6 @@ Rectangle { font.pointSize: 8 } } - - Rectangle - { - Layout.fillWidth: true - Layout.minimumWidth: 50 - Layout.minimumHeight: parent.height - Text { - anchors.verticalCenter: parent.verticalCenter - anchors.horizontalCenter: parent.horizontalCenter - font.family: "monospace" - color: "#4a4a4a" - text: modelData[2] - font.pointSize: 8 - } - } } Rectangle { diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index 8ea145536..1ed1c11d2 100644 --- a/mix/qml/MainContent.qml +++ b/mix/qml/MainContent.qml @@ -4,6 +4,9 @@ import QtQuick.Layouts 1.0 import QtQuick.Controls.Styles 1.1 import CodeEditorExtensionManager 1.0 import Qt.labs.settings 1.0 +import org.ethereum.qml.QEther 1.0 +import "js/QEtherHelper.js" as QEtherHelper +import "js/TransactionHelper.js" as TransactionHelper Rectangle { @@ -20,6 +23,7 @@ Rectangle { property alias rightViewVisible : rightView.visible property alias webViewVisible : webPreview.visible + property bool webViewHorizontal : codeWebSplitter.orientation === Qt.Vertical //vertical splitter positions elements vertically, splits screen horizontally onWidthChanged: { @@ -29,6 +33,12 @@ Rectangle { contentView.width = parent.width - projectList.width; } + function startQuickDebugging() + { + ensureRightView(); + projectModel.stateListModel.debugDefaultState(); + } + function toggleRightView() { if (!rightView.visible) rightView.show(); @@ -41,6 +51,11 @@ Rectangle { rightView.show(); } + function rightViewIsVisible() + { + return rightView.visible; + } + function hideRightView() { if (rightView.visible) rightView.hide(); @@ -50,8 +65,8 @@ Rectangle { webPreview.visible = !webPreview.visible; } - function rightViewVisible() { - return rightView.visible; + function toggleWebPreviewOrientation() { + codeWebSplitter.orientation = (codeWebSplitter.orientation === Qt.Vertical ? Qt.Horizontal : Qt.Vertical); } CodeEditorExtensionManager { @@ -59,6 +74,13 @@ Rectangle { rightView: rightPaneTabs; } + Settings { + id: mainLayoutSettings + property alias codeWebOrientation: codeWebSplitter.orientation + property alias webWidth: webPreview.width + property alias webHeight: webPreview.height + } + GridLayout { anchors.fill: parent @@ -130,6 +152,12 @@ Rectangle { width: parent.width - projectList.width height: parent.height SplitView { + handleDelegate: Rectangle { + width: 4 + height: 4 + color: "#cccccc" + } + id: codeWebSplitter anchors.fill: parent orientation: Qt.Vertical CodeEditorView { @@ -141,7 +169,10 @@ Rectangle { WebPreview { id: webPreview height: parent.height * 0.4 - Layout.fillWidth: true + Layout.fillWidth: codeWebSplitter.orientation === Qt.Vertical + Layout.fillHeight: codeWebSplitter.orientation === Qt.Horizontal + Layout.minimumHeight: 200 + Layout.minimumWidth: 200 } } } diff --git a/mix/qml/NewProjectDialog.qml b/mix/qml/NewProjectDialog.qml index 64d91341d..8d5afc7ac 100644 --- a/mix/qml/NewProjectDialog.qml +++ b/mix/qml/NewProjectDialog.qml @@ -26,6 +26,11 @@ Window { visible = false; } + function acceptAndClose() { + close(); + accepted(); + } + GridLayout { id: dialogContent columns: 2 @@ -41,6 +46,10 @@ Window { id: titleField focus: true Layout.fillWidth: true + Keys.onReturnPressed: { + if (okButton.enabled) + acceptAndClose(); + } } Label { @@ -50,6 +59,10 @@ Window { TextField { id: pathField Layout.fillWidth: true + Keys.onReturnPressed: { + if (okButton.enabled) + acceptAndClose(); + } } Button { text: qsTr("Browse") @@ -63,11 +76,11 @@ Window { anchors.right: parent.right; Button { + id: okButton; enabled: titleField.text != "" && pathField.text != "" text: qsTr("OK"); onClicked: { - close(); - accepted(); + acceptAndClose(); } } Button { diff --git a/mix/qml/ProjectList.qml b/mix/qml/ProjectList.qml index 34eaf3286..30f945706 100644 --- a/mix/qml/ProjectList.qml +++ b/mix/qml/ProjectList.qml @@ -10,7 +10,7 @@ Item { Text { Layout.fillWidth: true color: "blue" - text: projectModel.projectData ? projectModel.projectData.title : "" + text: projectModel.projectTitle horizontalAlignment: Text.AlignHCenter visible: !projectModel.isEmpty; } @@ -117,12 +117,23 @@ Item { contextMenu.popup(); } } - Connections { - target: projectModel - onProjectLoaded: { - projectList.currentIndex = 0; - } - } + } + } + Connections { + target: projectModel + onProjectLoaded: { + projectList.currentIndex = 0; + if (projectList.currentIndex >= 0 && projectList.currentIndex < projectModel.listModel.count) + projectModel.openDocument(projectModel.listModel.get(projectList.currentIndex).documentId); + + } + onProjectClosed: { + projectList.currentIndex = -1; + } + onDocumentOpened: { + if (projectList.currentItem.documentId !== document.documentId) + projectList.currentIndex = projectModel.getDocumentIndex(document.documentId); + } } } diff --git a/mix/qml/ProjectModel.qml b/mix/qml/ProjectModel.qml index 3c67ca35e..14716483c 100644 --- a/mix/qml/ProjectModel.qml +++ b/mix/qml/ProjectModel.qml @@ -4,7 +4,6 @@ import QtQuick.Layouts 1.0 import QtQuick.Controls 1.0 import QtQuick.Dialogs 1.1 import Qt.labs.settings 1.0 - import "js/ProjectModel.js" as ProjectModelCode Item { @@ -18,6 +17,7 @@ Item { signal documentAdded(var documentId) signal projectSaving(var projectData) signal projectSaved() + signal newProject(var projectData) signal documentSaved(var documentId) property bool isEmpty: (projectPath === "") @@ -26,7 +26,9 @@ Item { property bool haveUnsavedChanges: false property string projectPath: "" property string projectTitle: "" + property string currentDocumentId: "" property var listModel: projectListModel + property var stateListModel: projectStateListModel.model //interface function saveAll() { ProjectModelCode.saveAll(); } @@ -40,9 +42,12 @@ Item { function newJsFile() { ProjectModelCode.newJsFile(); } //function newContract() { ProjectModelCode.newContract(); } function openDocument(documentId) { ProjectModelCode.openDocument(documentId); } + function openNextDocument() { ProjectModelCode.openNextDocument(); } + function openPrevDocument() { ProjectModelCode.openPrevDocument(); } function renameDocument(documentId, newName) { ProjectModelCode.renameDocument(documentId, newName); } function removeDocument(documentId) { ProjectModelCode.removeDocument(documentId); } function getDocument(documentId) { return ProjectModelCode.getDocument(documentId); } + function getDocumentIndex(documentId) { return ProjectModelCode.getDocumentIndex(documentId); } Connections { target: appContext @@ -81,6 +86,10 @@ Item { id: projectListModel } + StateListModel { + id: projectStateListModel + } + Settings { id: projectSettings property string lastProjectPath; diff --git a/mix/qml/StateDialog.qml b/mix/qml/StateDialog.qml index d33123fbb..c00e3226f 100644 --- a/mix/qml/StateDialog.qml +++ b/mix/qml/StateDialog.qml @@ -3,23 +3,26 @@ import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.0 import org.ethereum.qml.QEther 1.0 +import "js/QEtherHelper.js" as QEtherHelper +import "js/TransactionHelper.js" as TransactionHelper Window { id: modalStateDialog modality: Qt.WindowModal - width:640 - height:480 + width: 640 + height: 480 visible: false property alias stateTitle: titleField.text property alias stateBalance: balanceField.value + property alias isDefault: defaultCheckBox.checked property int stateIndex property var stateTransactions: [] signal accepted - function open(index, item) { + function open(index, item, setDefault) { stateIndex = index; stateTitle = item.title; balanceField.value = item.balance; @@ -30,6 +33,7 @@ Window { transactionsModel.append(item.transactions[t]); stateTransactions.push(item.transactions[t]); } + isDefault = setDefault; visible = true; titleField.focus = true; } @@ -75,6 +79,14 @@ Window { Layout.fillWidth: true } + Label { + text: qsTr("Default") + } + CheckBox { + id: defaultCheckBox + Layout.fillWidth: true + } + Label { text: qsTr("Transactions") } @@ -118,27 +130,12 @@ Window { transactionDialog.open(index, transactionsModel.get(index)); } - function ether(_value, _unit) - { - var etherComponent = Qt.createComponent("qrc:/qml/EtherValue.qml"); - var ether = etherComponent.createObject(modalStateDialog); - ether.setValue(_value); - ether.setUnit(_unit); - return ether; - } - function addTransaction() { // Set next id here to work around Qt bug // https://bugreports.qt-project.org/browse/QTBUG-41327 // Second call to signal handler would just edit the item that was just created, no harm done - var item = { - value: ether("0", QEther.Wei), - functionId: "", - gas: ether("125000", QEther.Wei), - gasPrice: ether("100000", QEther.Wei) - }; - + var item = TransactionHelper.defaultTransaction(); transactionDialog.open(transactionsModel.count, item); } @@ -165,6 +162,7 @@ Window { } ToolButton { text: qsTr("Edit"); + visible: !stdContract Layout.fillHeight: true onClicked: transactionsModel.editTransaction(index) } @@ -184,7 +182,7 @@ Window { if (transactionDialog.transactionIndex < transactionsModel.count) { transactionsModel.set(transactionDialog.transactionIndex, item); - stateTransactions[index] = item; + stateTransactions[transactionDialog.transactionIndex] = item; } else { transactionsModel.append(item); stateTransactions.push(item); diff --git a/mix/qml/StateList.qml b/mix/qml/StateList.qml index fbb579cb0..ad14cf30e 100644 --- a/mix/qml/StateList.qml +++ b/mix/qml/StateList.qml @@ -3,7 +3,6 @@ import QtQuick.Controls.Styles 1.1 import QtQuick.Controls 1.1 import QtQuick.Dialogs 1.1 import QtQuick.Layouts 1.1 -import org.ethereum.qml.QEther 1.0 Rectangle { color: "#ededed" @@ -13,32 +12,13 @@ Rectangle { anchors.left: parent.left height: parent.height width: parent.width - property var stateList: [] - - Connections { - target: projectModel - onProjectClosed: { - stateListModel.clear(); - } - onProjectLoaded: { - if (!projectData.states) - projectData.states = []; - var items = projectData.states; - for(var i = 0; i < items.length; i++) { - stateListModel.append(items[i]); - stateList.push(items[i]) - } - } - onProjectSaving: { - projectData.states = stateList; - } - } ListView { + id: list anchors.top: parent.top height: parent.height width: parent.width - model: stateListModel + model: projectModel.stateListModel delegate: renderDelegate } @@ -47,58 +27,6 @@ Rectangle { action: addStateAction } - StateDialog { - id: stateDialog - onAccepted: { - var item = stateDialog.getItem(); - if (stateDialog.stateIndex < stateListModel.count) { - stateList[stateDialog.stateIndex] = item; - stateListModel.set(stateDialog.stateIndex, item); - } else { - stateList.push(item); - stateListModel.append(item); - } - - stateListModel.save(); - } - } - - ListModel { - id: stateListModel - - function addState() { - var etherComponent = Qt.createComponent("qrc:/qml/EtherValue.qml"); - var ether = etherComponent.createObject(stateListContainer); - ether.setValue("100000000000000000000000000"); - ether.setUnit(QEther.Wei); - var item = { - title: "", - balance: ether, - transactions: [] - }; - stateDialog.open(stateListModel.count, item); - } - - function editState(index) { - stateDialog.open(index, stateList[index]); - } - - function runState(index) { - var item = stateList[index]; - clientModel.debugState(item); - } - - function deleteState(index) { - stateListModel.remove(index); - stateList.splice(index, 1); - save(); - } - - function save() { - projectModel.saveProject(); - } - } - Component { id: renderDelegate Item { @@ -117,20 +45,17 @@ Rectangle { ToolButton { text: qsTr("Edit"); Layout.fillHeight: true - onClicked: stateListModel.editState(index); + onClicked: list.model.editState(index); } ToolButton { text: qsTr("Delete"); Layout.fillHeight: true - onClicked: stateListModel.deleteState(index); + onClicked: list.model.deleteState(index); } ToolButton { text: qsTr("Run"); Layout.fillHeight: true - onClicked: - { - stateListModel.runState(index) - } + onClicked: list.model.runState(index); } } } @@ -141,7 +66,7 @@ Rectangle { text: "&Add State" shortcut: "Ctrl+T" enabled: codeModel.hasContract && !clientModel.running; - onTriggered: stateListModel.addState(); + onTriggered: list.model.addState(); } } diff --git a/mix/qml/StateListModel.qml b/mix/qml/StateListModel.qml new file mode 100644 index 000000000..c158112e1 --- /dev/null +++ b/mix/qml/StateListModel.qml @@ -0,0 +1,196 @@ +import QtQuick 2.2 +import QtQuick.Controls.Styles 1.1 +import QtQuick.Controls 1.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Layouts 1.1 +import org.ethereum.qml.QEther 1.0 +import "js/QEtherHelper.js" as QEtherHelper + +Item { + + property int defaultStateIndex: -1 + property alias model: stateListModel + property var stateList: [] + + function fromPlainStateItem(s) { + return { + title: s.title, + balance: QEtherHelper.createEther(s.balance.value, s.balance.unit), + transactions: s.transactions.map(fromPlainTransactionItem) + }; + } + + function fromPlainTransactionItem(t) { + var r = { + functionId: t.functionId, + url: t.url, + value: QEtherHelper.createEther(t.value.value, t.value.unit), + gas: QEtherHelper.createEther(t.gas.value, t.gas.unit), + gasPrice: QEtherHelper.createEther(t.gasPrice.value, t.gasPrice.unit), + executeConstructor: t.executeConstructor, + stdContract: t.stdContract, + parameters: {} + }; + for (var key in t.parameters) { + var intComponent = Qt.createComponent("qrc:/qml/BigIntValue.qml"); + var param = intComponent.createObject(); + param.setValue(t.parameters[key]); + r.parameters[key] = param; + } + return r; + } + + function toPlainStateItem(s) { + return { + title: s.title, + balance: { value: s.balance.value, unit: s.balance.unit }, + transactions: s.transactions.map(toPlainTransactionItem) + }; + } + + function toPlainTransactionItem(t) { + var r = { + functionId: t.functionId, + url: t.url, + value: { value: t.value.value, unit: t.value.unit }, + gas: { value: t.gas.value, unit: t.gas.unit }, + gasPrice: { value: t.gasPrice.value, unit: t.gasPrice.unit }, + executeConstructor: t.executeConstructor, + stdContract: t.stdContract, + parameters: {} + }; + for (var key in t.parameters) + r.parameters[key] = t.parameters[key].value(); + return r; + } + + Connections { + target: projectModel + onProjectClosed: { + stateListModel.clear(); + stateList = []; + } + onProjectLoaded: { + if (!projectData.states) + projectData.states = []; + if (projectData.defaultStateIndex !== undefined) + defaultStateIndex = projectData.defaultStateIndex; + else + defaultStateIndex = -1; + var items = projectData.states; + for(var i = 0; i < items.length; i++) { + var item = fromPlainStateItem(items[i]); + stateListModel.append(item); + stateList.push(item); + } + } + onProjectSaving: { + projectData.states = [] + for(var i = 0; i < stateListModel.count; i++) { + projectData.states.push(toPlainStateItem(stateList[i])); + } + projectData.defaultStateIndex = defaultStateIndex; + } + onNewProject: { + var state = toPlainStateItem(stateListModel.createDefaultState()); + state.title = qsTr("Default"); + projectData.states = [ state ]; + projectData.defaultStateIndex = 0; + } + } + + StateDialog { + id: stateDialog + onAccepted: { + var item = stateDialog.getItem(); + if (stateDialog.stateIndex < stateListModel.count) { + if (stateDialog.isDefault) + defaultStateIndex = stateIndex; + stateList[stateDialog.stateIndex] = item; + stateListModel.set(stateDialog.stateIndex, item); + } else { + if (stateDialog.isDefault) + defaultStateIndex = 0; + stateList.push(item); + stateListModel.append(item); + } + + stateListModel.save(); + } + } + + ContractLibrary { + id: contractLibrary; + } + + ListModel { + id: stateListModel + + function defaultTransactionItem() { + return { + value: QEtherHelper.createEther("100", QEther.Wei), + gas: QEtherHelper.createEther("125000", QEther.Wei), + gasPrice: QEtherHelper.createEther("10000000000000", QEther.Wei), + executeConstructor: false, + stdContract: false + }; + } + + function createDefaultState() { + var ether = QEtherHelper.createEther("100000000000000000000000000", QEther.Wei); + var item = { + title: "", + balance: ether, + transactions: [] + }; + + //add all stdc contracts + for (var i = 0; i < contractLibrary.model.count; i++) { + var contractTransaction = defaultTransactionItem(); + var contractItem = contractLibrary.model.get(i); + contractTransaction.url = contractItem.url; + contractTransaction.functionId = contractItem.name; + contractTransaction.stdContract = true; + item.transactions.push(contractTransaction); + }; + + //add constructor + var ctorTr = defaultTransactionItem(); + ctorTr.executeConstructor = true; + ctorTr.functionId = qsTr("Constructor"); + item.transactions.push(ctorTr); + return item; + } + + function addState() { + var item = createDefaultState(); + stateDialog.open(stateListModel.count, item, defaultStateIndex === -1); + } + + function editState(index) { + stateDialog.open(index, stateList[index], defaultStateIndex === index); + } + + function debugDefaultState() { + if (defaultStateIndex >= 0) + runState(defaultStateIndex); + } + + function runState(index) { + var item = stateList[index]; + clientModel.setupState(item); + } + + function deleteState(index) { + stateListModel.remove(index); + stateList.splice(index, 1); + if (index === defaultStateIndex) + defaultStateIndex = -1; + save(); + } + + function save() { + projectModel.saveProject(); + } + } +} diff --git a/mix/qml/StatusPane.qml b/mix/qml/StatusPane.qml index c6c531a80..57ade7a3a 100644 --- a/mix/qml/StatusPane.qml +++ b/mix/qml/StatusPane.qml @@ -27,6 +27,22 @@ Rectangle { debugRunActionIcon.enabled = statusPane.result.successful; } + function infoMessage(text) + { + status.state = ""; + status.text = text + logslink.visible = false; + } + + + Connections { + target:clientModel + onRunStarted: infoMessage(qsTr("Running transactions..")); + onRunFailed: infoMessage(qsTr("Error running transactions")); + onRunComplete: infoMessage(qsTr("Run complete")); + onNewBlock: infoMessage(qsTr("New block created")); + } + color: "transparent" anchors.fill: parent Rectangle { @@ -113,9 +129,10 @@ Rectangle { Action { id: debugRunActionIcon onTriggered: { - mainContent.toggleRightView(); - if (mainContent.rightViewVisible()) - clientModel.debugDeployment(); + if (mainContent.rightViewIsVisible()) + mainContent.hideRightView() + else + mainContent.startQuickDebugging(); } enabled: false } diff --git a/mix/qml/StepActionImage.qml b/mix/qml/StepActionImage.qml index 64440aa83..a4ff23e54 100644 --- a/mix/qml/StepActionImage.qml +++ b/mix/qml/StepActionImage.qml @@ -9,6 +9,7 @@ Rectangle { property string disableStateImg property string enabledStateImg property string buttonTooltip + property string buttonShortcut signal clicked function enabled(state) @@ -33,6 +34,7 @@ Rectangle { Action { tooltip: buttonTooltip id: buttonAction + shortcut: buttonShortcut onTriggered: { buttonActionContainer.clicked(); } diff --git a/mix/qml/TransactionDialog.qml b/mix/qml/TransactionDialog.qml index 46fe997f0..5eb3fbb13 100644 --- a/mix/qml/TransactionDialog.qml +++ b/mix/qml/TransactionDialog.qml @@ -3,6 +3,7 @@ import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.0 import org.ethereum.qml.QEther 1.0 +import "js/TransactionHelper.js" as TransactionHelper Window { id: modalTransactionDialog @@ -18,15 +19,25 @@ Window { property alias transactionValue: valueField.value; property alias functionId: functionComboBox.currentText; property var itemParams; + property bool isConstructorTransaction; + property bool useTransactionDefaultValue: false signal accepted; function open(index, item) { + rowFunction.visible = !useTransactionDefaultValue; + rowValue.visible = !useTransactionDefaultValue; + rowGas.visible = !useTransactionDefaultValue; + rowGasPrice.visible = !useTransactionDefaultValue; + transactionIndex = index; gasField.value = item.gas; gasPriceField.value = item.gasPrice; valueField.value = item.value; var functionId = item.functionId; + isConstructorTransaction = item.executeConstructor; + rowFunction.visible = !item.executeConstructor; + itemParams = item.parameters !== undefined ? item.parameters : {}; functionsModel.clear(); var functionIndex = -1; @@ -38,10 +49,20 @@ Window { } if (functionIndex == -1 && functionsModel.count > 0) - functionIndex = 0; //@todo suggest unused funtion + functionIndex = 0; //@todo suggest unused function functionComboBox.currentIndex = functionIndex; - loadParameters(); + paramsModel.clear(); + if (!item.executeConstructor) + loadParameters(); + else + { + var parameters = codeModel.code.contract.constructor.parameters; + for (var p = 0; p < parameters.length; p++) { + var pname = parameters[p].name; + paramsModel.append({ name: pname, type: parameters[p].type, value: itemParams[pname] !== undefined ? itemParams[pname].value() : "" }); + } + } visible = true; valueField.focus = true; } @@ -49,7 +70,6 @@ Window { function loadParameters() { if (!paramsModel) return; - paramsModel.clear(); if (functionComboBox.currentIndex >= 0 && functionComboBox.currentIndex < functionsModel.count) { var func = codeModel.code.contract.functions[functionComboBox.currentIndex]; var parameters = func.parameters; @@ -67,112 +87,159 @@ Window { function getItem() { - var item = { - functionId: transactionDialog.functionId, - gas: transactionDialog.gas, - gasPrice: transactionDialog.gasPrice, - value: transactionDialog.transactionValue, - parameters: {} + var item; + if (!useTransactionDefaultValue) + { + item = { + functionId: transactionDialog.functionId, + gas: transactionDialog.gas, + gasPrice: transactionDialog.gasPrice, + value: transactionDialog.transactionValue, + parameters: {}, + executeConstructor: isConstructorTransaction + }; } + else + { + item = TransactionHelper.defaultTransaction(); + item.functionId = transactionDialog.functionId; + item.executeConstructor = isConstructorTransaction; + } + + if (isConstructorTransaction) + item.functionId = qsTr("Constructor"); + for (var p = 0; p < transactionDialog.transactionParams.count; p++) { var parameter = transactionDialog.transactionParams.get(p); var intComponent = Qt.createComponent("qrc:/qml/BigIntValue.qml"); var param = intComponent.createObject(modalTransactionDialog); + param.setValue(parameter.value); item.parameters[parameter.name] = param; } return item; } - GridLayout { + ColumnLayout { id: dialogContent - columns: 2 - anchors.fill: parent + width: parent.width + anchors.left: parent.left + anchors.right: parent.right anchors.margins: 10 - rowSpacing: 10 - columnSpacing: 10 - - Label { - text: qsTr("Function") - } - ComboBox { - id: functionComboBox + spacing: 30 + RowLayout + { + id: rowFunction Layout.fillWidth: true - currentIndex: -1 - textRole: "text" - editable: false - model: ListModel { - id: functionsModel + height: 150 + Label { + Layout.preferredWidth: 75 + text: qsTr("Function") } - onCurrentIndexChanged: { - loadParameters(); + ComboBox { + id: functionComboBox + Layout.fillWidth: true + currentIndex: -1 + textRole: "text" + editable: false + model: ListModel { + id: functionsModel + } + onCurrentIndexChanged: { + loadParameters(); + } } } - Label { - text: qsTr("Value") - } - Rectangle + + RowLayout { + id: rowValue Layout.fillWidth: true - Ether { - id: valueField - edit: true - displayFormattedValue: true + Label { + Layout.preferredWidth: 75 + text: qsTr("Value") + } + Rectangle + { + Layout.fillWidth: true + Ether { + id: valueField + edit: true + displayFormattedValue: true + } } } - Label { - text: qsTr("Gas") - } - Rectangle + + RowLayout { + id: rowGas Layout.fillWidth: true - Ether { - id: gasField - edit: true - displayFormattedValue: true + Label { + Layout.preferredWidth: 75 + text: qsTr("Gas") + } + Rectangle + { + Layout.fillWidth: true + Ether { + id: gasField + edit: true + displayFormattedValue: true + } } } - Label { - text: qsTr("Gas Price") - } - Rectangle + RowLayout { + id: rowGasPrice Layout.fillWidth: true - Ether { - id: gasPriceField - edit: true - displayFormattedValue: true + Label { + Layout.preferredWidth: 75 + text: qsTr("Gas Price") + } + Rectangle + { + Layout.fillWidth: true + Ether { + id: gasPriceField + edit: true + displayFormattedValue: true + } } } - Label { - text: qsTr("Parameters") - } - TableView { - model: paramsModel + RowLayout + { Layout.fillWidth: true - - TableViewColumn { - role: "name" - title: "Name" - width: 120 - } - TableViewColumn { - role: "type" - title: "Type" - width: 120 - } - TableViewColumn { - role: "value" - title: "Value" - width: 120 + Label { + text: qsTr("Parameters") + Layout.preferredWidth: 75 } + TableView { + model: paramsModel + Layout.fillWidth: true - itemDelegate: { - return editableDelegate; + TableViewColumn { + role: "name" + title: qsTr("Name") + width: 120 + } + TableViewColumn { + role: "type" + title: qsTr("Type") + width: 120 + } + TableViewColumn { + role: "value" + title: qsTr("Value") + width: 120 + } + + itemDelegate: { + return editableDelegate; + } } } } diff --git a/mix/qml/TransactionLog.qml b/mix/qml/TransactionLog.qml new file mode 100644 index 000000000..c8e87f025 --- /dev/null +++ b/mix/qml/TransactionLog.qml @@ -0,0 +1,84 @@ +import QtQuick 2.2 +import QtQuick.Controls.Styles 1.1 +import QtQuick.Controls 1.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Layouts 1.1 + +Item { + ColumnLayout { + anchors.fill: parent + CheckBox { + id: recording + text: qsTr("Record transactions"); + checked: true + Layout.fillWidth: true + } + TableView { + Layout.fillWidth: true + Layout.fillHeight: true + model: logModel + + TableViewColumn { + role: "block" + title: qsTr("Block") + width: 40 + } + TableViewColumn { + role: "tindex" + title: qsTr("Index") + width: 40 + } + TableViewColumn { + role: "contract" + title: qsTr("Contract") + width: 120 + } + TableViewColumn { + role: "function" + title: qsTr("Function") + width: 120 + } + TableViewColumn { + role: "value" + title: qsTr("Value") + width: 120 + } + TableViewColumn { + role: "address" + title: qsTr("Address") + width: 120 + } + TableViewColumn { + role: "returned" + title: qsTr("Returned") + width: 120 + } + onActivated: { + var item = logModel.get(row); + clientModel.debugTransaction(item.block, item.tindex); + } + Keys.onPressed: { + if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_C && currentRow >=0 && currentRow < logModel.count) { + var item = logModel.get(currentRow); + appContext.toClipboard(item.returned); + } + } + } + } + + ListModel { + id: logModel + } + + Connections { + target: clientModel + onStateCleared: { + logModel.clear(); + } + onNewTransaction: { + if (recording.checked) + logModel.append(_tr); + } + } + +} diff --git a/mix/qml/WebCodeEditor.qml b/mix/qml/WebCodeEditor.qml new file mode 100644 index 000000000..b5066b1bf --- /dev/null +++ b/mix/qml/WebCodeEditor.qml @@ -0,0 +1,71 @@ +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.0 +import QtQuick.Controls.Styles 1.1 +import CodeEditorExtensionManager 1.0 +import QtWebEngine 1.0 + +Item { + signal editorTextChanged + property string currentText: "" + property string currentMode: "" + property bool initialized: false + + function setText(text, mode) { + currentText = text; + currentMode = mode; + if (initialized) { + editorBrowser.runJavaScript("setTextBase64(\"" + Qt.btoa(text) + "\")"); + editorBrowser.runJavaScript("setMode(\"" + mode + "\")"); + } + setFocus(); + } + + function setFocus() { + editorBrowser.forceActiveFocus(); + } + + function getText() { + return currentText; + } + + anchors.top: parent.top + id: codeEditorView + anchors.fill: parent + WebEngineView { + id: editorBrowser + url: "qrc:///qml/html/codeeditor.html" + anchors.fill: parent + onJavaScriptConsoleMessage: { + console.log("editor: " + sourceID + ":" + lineNumber + ":" + message); + } + + onLoadingChanged: + { + if (!loading) { + initialized = true; + setText(currentText, currentMode); + runJavaScript("getTextChanged()", function(result) { }); + pollTimer.running = true; + } + } + + Timer + { + id: pollTimer + interval: 30 + running: false + repeat: true + onTriggered: { + editorBrowser.runJavaScript("getTextChanged()", function(result) { + if (result === true) { + editorBrowser.runJavaScript("getText()" , function(textValue) { + currentText = textValue; + editorTextChanged(); + }); + } + }); + } + } + } +} diff --git a/mix/qml/WebPreview.qml b/mix/qml/WebPreview.qml index dd9e24bfe..bd48210ef 100644 --- a/mix/qml/WebPreview.qml +++ b/mix/qml/WebPreview.qml @@ -4,7 +4,8 @@ import QtQuick.Layouts 1.0 import QtQuick.Controls 1.0 import QtQuick.Controls.Styles 1.1 import QtWebEngine 1.0 -import Qt.WebSockets 1.0 +import QtWebEngine.experimental 1.0 +import HttpServer 1.0 Item { id: webPreview @@ -53,7 +54,7 @@ Item { onAppLoaded: { //We need to load the container using file scheme so that web security would allow loading local files in iframe var containerPage = fileIo.readFile("qrc:///qml/html/WebContainer.html"); - webView.loadHtml(containerPage, "file:///") + webView.loadHtml(containerPage, "file:///WebContainer.html") } } @@ -61,6 +62,7 @@ Item { Connections { target: clientModel onContractAddressChanged: reload(); + onRunComplete: reload(); } Connections { @@ -104,20 +106,21 @@ Item { id: pageListModel } - WebSocketServer { - id: socketServer + HttpServer { + id: httpServer listen: true - name: "mix" - onClientConnected: - { - webSocket.onTextMessageReceived.connect(function(message) { - console.log("rpc_request: " + message); - clientModel.apiRequest(message); - }); - clientModel.onApiResponse.connect(function(message) { - console.log("rpc_response: " + message); - webSocket.sendTextMessage(message); - }); + accept: true + port: 8893 + onClientConnected: { + //filter polling spam + //TODO: do it properly + var log = _request.content.indexOf("eth_changed") < 0; + if (log) + console.log(_request.content); + var response = clientModel.apiCall(_request.content); + if (log) + console.log(response); + _request.setResponse(response); } } @@ -152,13 +155,14 @@ Item { Layout.fillWidth: true Layout.fillHeight: true id: webView + experimental.settings.localContentCanAccessRemoteUrls: true onJavaScriptConsoleMessage: { console.log(sourceID + ":" + lineNumber + ":" + message); } onLoadingChanged: { if (!loading) { initialized = true; - webView.runJavaScript("init(\"" + socketServer.url + "\")"); + webView.runJavaScript("init(\"" + httpServer.url + "\")"); if (pendingPageUrl) setPreviewUrl(pendingPageUrl); } diff --git a/mix/qml/html/WebContainer.html b/mix/qml/html/WebContainer.html index cd8d2614a..04ba8ab73 100644 --- a/mix/qml/html/WebContainer.html +++ b/mix/qml/html/WebContainer.html @@ -1,7 +1,6 @@ - + + + + + + + + + + + + + diff --git a/mix/qml/html/codeeditor.js b/mix/qml/html/codeeditor.js new file mode 100644 index 000000000..2cebc121e --- /dev/null +++ b/mix/qml/html/codeeditor.js @@ -0,0 +1,43 @@ + +var editor = CodeMirror(document.body, { + lineNumbers: true, + //styleActiveLine: true, + matchBrackets: true, + autofocus: true, + }); + +editor.setOption("theme", "solarized dark"); +editor.setOption("indentUnit", 4); +editor.setOption("indentWithTabs", true); +editor.setOption("fullScreen", true); + +editor.changeRegistered = false; + +editor.on("change", function(eMirror, object) { + editor.changeRegistered = true; + +}); + +getTextChanged = function() { + return editor.changeRegistered; +}; + + +getText = function() { + editor.changeRegistered = false; + return editor.getValue(); +}; + + +setTextBase64 = function(text) { + editor.setValue(window.atob(text)); + editor.focus(); +}; + +setText = function(text) { + editor.setValue(text); +}; + +setMode = function(mode) { + this.editor.setOption("mode", mode); +}; diff --git a/mix/qml/js/Debugger.js b/mix/qml/js/Debugger.js index 9078befdb..de1f87256 100644 --- a/mix/qml/js/Debugger.js +++ b/mix/qml/js/Debugger.js @@ -1,110 +1,149 @@ -//humanReadableExecutionCode => contain human readable code. -//debugStates => contain all debug states. -//bytesCodeMapping => mapping between humanReadableExecutionCode and bytesCode. +//debugData => contain all debug states. //statesList => ListView var currentSelectedState = null; -var jumpStartingPoint = null; -function init() +var currentDisplayedState = null; +var debugData = null; +var codeMap = null; + +function init(data) { - if (typeof(debugStates) === "undefined") + jumpOutBackAction.enabled(false); + jumpIntoBackAction.enabled(false); + jumpIntoForwardAction.enabled(false); + jumpOutForwardAction.enabled(false); + jumpOverBackAction.enabled(false); + jumpOverForwardAction.enabled(false); + + if (data === null) { + statesList.model.clear(); + statesSlider.maximumValue = 0; + statesSlider.value = 0; + currentSelectedState = null; + currentDisplayedState = null; + debugData = null; return; + } - statesSlider.maximumValue = debugStates.length - 1; - statesSlider.value = 0; - statesList.model = humanReadableExecutionCode; + debugData = data; currentSelectedState = 0; + currentDisplayedState = 0; + setupInstructions(currentSelectedState); + setupCallData(currentSelectedState); + statesSlider.maximumValue = data.states.length - 1; + statesSlider.value = 0; select(currentSelectedState); +} - jumpOutBackAction.enabled(false); - jumpIntoBackAction.enabled(false); - jumpIntoForwardAction.enabled(false); - jumpOutForwardAction.enabled(false); +function setupInstructions(stateIndex) +{ + var instructions = debugData.states[stateIndex].code.instructions; + codeMap = {}; + statesList.model.clear(); + for (var i = 0; i < instructions.length; i++) { + statesList.model.append(instructions[i]); + codeMap[instructions[i].processIndex] = i; + } + callDataDump.listModel = debugData.states[stateIndex].callData.items; } -function moveSelection(incr) +function setupCallData(stateIndex) { - if (typeof(debugStates) === "undefined") - return; + callDataDump.listModel = debugData.states[stateIndex].callData.items; +} +function moveSelection(incr) +{ + var prevState = currentSelectedState; if (currentSelectedState + incr >= 0) { - if (currentSelectedState + incr < debugStates.length) + if (currentSelectedState + incr < debugData.states.length) select(currentSelectedState + incr); - statesSlider.value = currentSelectedState; } } -function select(stateIndex) +function display(stateIndex) { - if (typeof(debugStates) === "undefined") - return; - + if (stateIndex < 0) + stateIndex = 0; + if (stateIndex >= debugData.states.length) + stateIndex = debugData.state.length - 1; + if (debugData.states[stateIndex].codeIndex !== debugData.states[currentDisplayedState].codeIndex) + setupInstructions(stateIndex); + if (debugData.states[stateIndex].dataIndex !== debugData.states[currentDisplayedState].dataIndex) + setupCallData(stateIndex); var codeLine = codeStr(stateIndex); - var state = debugStates[stateIndex]; + var state = debugData.states[stateIndex]; highlightSelection(codeLine); - currentSelectedState = stateIndex; completeCtxInformation(state); + currentDisplayedState = stateIndex; +} - if (state.instruction === "JUMP") - jumpIntoForwardAction.enabled(true); +function displayFrame(frameIndex) +{ + var state = debugData.states[currentSelectedState]; + if (frameIndex === 0) + display(currentSelectedState); else - jumpIntoForwardAction.enabled(false); + display(state.levels[frameIndex - 1]); +} - if (state.instruction === "JUMPDEST") - jumpIntoBackAction.enabled(true); - else - jumpIntoBackAction.enabled(false); +function select(stateIndex) +{ + display(stateIndex); + currentSelectedState = stateIndex; + var state = debugData.states[stateIndex]; + statesSlider.value = stateIndex; + jumpIntoForwardAction.enabled(stateIndex < debugData.states.length - 1) + jumpIntoBackAction.enabled(stateIndex > 0); + jumpOverForwardAction.enabled(stateIndex < debugData.states.length - 1); + jumpOverBackAction.enabled(stateIndex > 0); + jumpOutBackAction.enabled(state.levels.length > 1); + jumpOutForwardAction.enabled(state.levels.length > 1); + + var callStackData = []; + for (var l = 0; l < state.levels.length; l++) { + var address = debugData.states[state.levels[l] + 1].address; + callStackData.push(address); + } + callStackData.push(debugData.states[0].address); + callStack.model = callStackData; } function codeStr(stateIndex) { - if (typeof(debugStates) === "undefined") - return; - - var state = debugStates[stateIndex]; - return bytesCodeMapping.getValue(state.curPC); + var state = debugData.states[stateIndex]; + return codeMap[state.curPC]; } function highlightSelection(index) { statesList.currentIndex = index; + statesList.positionViewAtIndex(index, ListView.Center); } function completeCtxInformation(state) { - if (typeof(debugStates) === "undefined") - return; - currentStep.update(state.step); mem.update(state.newMemSize.value() + " " + qsTr("words")); stepCost.update(state.gasCost.value()); - gasSpent.update(debugStates[0].gas.subtract(state.gas).value()); + gasSpent.update(debugData.states[0].gas.subtract(state.gas).value()); stack.listModel = state.debugStack; storage.listModel = state.debugStorage; memoryDump.listModel = state.debugMemory; - callDataDump.listModel = state.debugCallData; } -function displayReturnValue() +function isCallInstruction(index) { - headerReturnList.model = contractCallReturnParameters; - headerReturnList.update(); + var state = debugData.states[index]; + return state.instruction === "CALL" || state.instruction === "CREATE"; } -function stepOutBack() +function isReturnInstruction(index) { - if (typeof(debugStates) === "undefined") - return; - - if (jumpStartingPoint != null) - { - select(jumpStartingPoint); - jumpStartingPoint = null; - jumpOutBackAction.enabled(false); - jumpOutForwardAction.enabled(false); - } + var state = debugData.states[index]; + return state.instruction === "RETURN" } function stepIntoBack() @@ -114,76 +153,55 @@ function stepIntoBack() function stepOverBack() { - if (typeof(debugStates) === "undefined") - return; - - var state = debugStates[currentSelectedState]; - if (state.instruction === "JUMPDEST") - { - for (var k = currentSelectedState; k > 0; k--) - { - var line = bytesCodeMapping.getValue(debugStates[k].curPC); - if (line === statesList.currentIndex - 2) - { - select(k); - break; - } - } - } + if (currentSelectedState > 0 && isReturnInstruction(currentSelectedState - 1)) + stepOutBack(); else moveSelection(-1); } function stepOverForward() { - if (typeof(debugStates) === "undefined") - return; - - var state = debugStates[currentSelectedState]; - if (state.instruction === "JUMP") - { - for (var k = currentSelectedState; k < debugStates.length; k++) - { - var line = bytesCodeMapping.getValue(debugStates[k].curPC); - if (line === statesList.currentIndex + 2) - { - select(k); - break; - } - } - } + if (isCallInstruction(currentSelectedState)) + stepOutForward(); else moveSelection(1); } function stepIntoForward() { - if (typeof(debugStates) === "undefined") - return; + moveSelection(1); +} - var state = debugStates[currentSelectedState]; - if (state.instruction === "JUMP") - { - jumpStartingPoint = currentSelectedState; - moveSelection(1); - jumpOutBackAction.enabled(true); - jumpOutForwardAction.enabled(true); - } +function stepOutBack() +{ + var i = currentSelectedState - 1; + var depth = 0; + while (--i >= 0) + if (isCallInstruction(i)) + if (depth == 0) + break; + else depth--; + else if (isReturnInstruction(i)) + depth++; + select(i); } function stepOutForward() { - if (jumpStartingPoint != null) - { - stepOutBack(); - stepOverForward(); - jumpOutBackAction.enabled(false); - jumpOutForwardAction.enabled(false); - } + var i = currentSelectedState; + var depth = 0; + while (++i < debugData.states.length) + if (isReturnInstruction(i)) + if (depth == 0) + break; + else + depth--; + else if (isCallInstruction(i)) + depth++; + select(i + 1); } function jumpTo(value) { - currentSelectedState = value; - select(currentSelectedState); + select(value); } diff --git a/mix/qml/js/ProjectModel.js b/mix/qml/js/ProjectModel.js index 0e889ae03..ed7ebd3ce 100644 --- a/mix/qml/js/ProjectModel.js +++ b/mix/qml/js/ProjectModel.js @@ -47,7 +47,7 @@ function saveProject() { for (var i = 0; i < projectListModel.count; i++) projectData.files.push(projectListModel.get(i).fileName) projectSaving(projectData); - var json = JSON.stringify(projectData); + var json = JSON.stringify(projectData, null, "\t"); var projectFile = projectPath + projectFileName; fileIo.writeFile(projectFile, json); projectSaved(); @@ -85,13 +85,17 @@ function addFile(fileName) { var extension = fileName.substring(fileName.lastIndexOf("."), fileName.length); var isContract = extension === ".sol"; var isHtml = extension === ".html"; + var isCss = extension === ".css"; + var isJs = extension === ".js"; + var syntaxMode = isContract ? "solidity" : isJs ? "javascript" : isHtml ? "htmlmixed" : isCss ? "css" : ""; var docData = { contract: false, path: p, fileName: fileName, name: isContract ? "Contract" : fileName, documentId: fileName, - isText: isContract || isHtml || extension === ".js", + syntaxMode: syntaxMode, + isText: isContract || isHtml || isCss || isJs, isContract: isContract, isHtml: isHtml, }; @@ -100,7 +104,7 @@ function addFile(fileName) { return docData.documentId; } -function findDocument(documentId) +function getDocumentIndex(documentId) { for (var i = 0; i < projectListModel.count; i++) if (projectListModel.get(i).documentId === documentId) @@ -110,7 +114,38 @@ function findDocument(documentId) } function openDocument(documentId) { - documentOpened(projectListModel.get(findDocument(documentId))); + if (documentId !== currentDocumentId) { + documentOpened(projectListModel.get(getDocumentIndex(documentId))); + currentDocumentId = documentId; + } +} + +function openNextDocument() { + var docIndex = getDocumentIndex(currentDocumentId); + var nextDocId = ""; + while (nextDocId === "") { + docIndex++; + if (docIndex >= projectListModel.count) + docIndex = 0; + var document = projectListModel.get(docIndex); + if (document.isText) + nextDocId = document.documentId; + } + openDocument(nextDocId); +} + +function openPrevDocument() { + var docIndex = getDocumentIndex(currentDocumentId); + var prevDocId = ""; + while (prevDocId === "") { + docIndex--; + if (docIndex < 0) + docIndex = projectListModel.count - 1; + var document = projectListModel.get(docIndex); + if (document.isText) + prevDocId = document.documentId; + } + openDocument(prevDocId); } function doCloseProject() { @@ -138,7 +173,8 @@ function doCreateProject(title, path) { //TODO: copy from template fileIo.writeFile(dirPath + indexFile, ""); fileIo.writeFile(dirPath + contractsFile, "contract MyContract {\n}\n"); - var json = JSON.stringify(projectData); + newProject(projectData); + var json = JSON.stringify(projectData, null, "\t"); fileIo.writeFile(projectFile, json); loadProject(dirPath); } @@ -156,7 +192,7 @@ function doAddExistingFiles(files) { } function renameDocument(documentId, newName) { - var i = findDocument(documentId); + var i = getDocumentIndex(documentId); var document = projectListModel.get(i); if (!document.isContract) { var sourcePath = document.path; @@ -170,12 +206,12 @@ function renameDocument(documentId, newName) { } function getDocument(documentId) { - var i = findDocument(documentId); + var i = getDocumentIndex(documentId); return projectListModel.get(i); } function removeDocument(documentId) { - var i = findDocument(documentId); + var i = getDocumentIndex(documentId); var document = projectListModel.get(i); if (!document.isContract) { projectListModel.remove(i); diff --git a/mix/qml/js/QEtherHelper.js b/mix/qml/js/QEtherHelper.js new file mode 100644 index 000000000..9761b2f45 --- /dev/null +++ b/mix/qml/js/QEtherHelper.js @@ -0,0 +1,8 @@ +function createEther(_value, _unit, _parent) +{ + var etherComponent = Qt.createComponent("qrc:/qml/EtherValue.qml"); + var ether = etherComponent.createObject(); + ether.setValue(_value); + ether.setUnit(_unit); + return ether; +} diff --git a/mix/qml/js/TransactionHelper.js b/mix/qml/js/TransactionHelper.js new file mode 100644 index 000000000..f404685cf --- /dev/null +++ b/mix/qml/js/TransactionHelper.js @@ -0,0 +1,13 @@ +Qt.include("QEtherHelper.js") + +function defaultTransaction() +{ + return { + value: createEther("0", QEther.Wei), + functionId: "", + gas: createEther("125000", QEther.Wei), + gasPrice: createEther("100000", QEther.Wei), + executeConstructor: false, + parameters: {} + }; +} diff --git a/mix/qml/main.qml b/mix/qml/main.qml index 7970c4027..a0a4ba423 100644 --- a/mix/qml/main.qml +++ b/mix/qml/main.qml @@ -4,13 +4,14 @@ import QtQuick.Controls.Styles 1.1 import QtQuick.Dialogs 1.1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.1 -import CodeEditorExtensionManager 1.0 +import Qt.labs.settings 1.0 +import org.ethereum.qml.QEther 1.0 ApplicationWindow { id: mainApplication visible: true width: 1200 - height: 600 + height: 800 minimumWidth: 400 minimumHeight: 300 title: qsTr("mix") @@ -36,19 +37,19 @@ ApplicationWindow { title: qsTr("Debug") MenuItem { action: debugRunAction } MenuItem { action: debugResetStateAction } + MenuItem { action: mineAction } } Menu { title: qsTr("Windows") - MenuItem { action: showHideRightPanel } - MenuItem { action: toggleWebPreview } + MenuItem { action: openNextDocumentAction } + MenuItem { action: openPrevDocumentAction } + MenuSeparator {} + MenuItem { action: showHideRightPanelAction } + MenuItem { action: toggleWebPreviewAction } + MenuItem { action: toggleWebPreviewOrientationAction } } } - Component.onCompleted: { - setX(Screen.width / 2 - width / 2); - setY(Screen.height / 2 - height / 2); - } - MainContent { id: mainContent; anchors.fill: parent @@ -64,6 +65,14 @@ ApplicationWindow { id: messageDialog } + Settings { + id: mainWindowSettings + property alias mainWidth: mainApplication.width + property alias mainHeight: mainApplication.height + property alias mainX: mainApplication.x + property alias mainY: mainApplication.y + } + Action { id: exitAppAction text: qsTr("Exit") @@ -71,15 +80,19 @@ ApplicationWindow { onTriggered: Qt.quit(); } + Action { + id: mineAction + text: "Mine" + shortcut: "Ctrl+M" + onTriggered: clientModel.mine(); + enabled: codeModel.hasContract && !clientModel.running + } Action { id: debugRunAction text: "&Run" shortcut: "F5" - onTriggered: { - mainContent.ensureRightView(); - clientModel.debugDeployment(); - } - enabled: codeModel.hasContract && !clientModel.running; + onTriggered: mainContent.startQuickDebugging() + enabled: codeModel.hasContract && !clientModel.running } Action { @@ -90,7 +103,7 @@ ApplicationWindow { } Action { - id: toggleWebPreview + id: toggleWebPreviewAction text: "Show Web View" shortcut: "F2" checkable: true @@ -99,7 +112,16 @@ ApplicationWindow { } Action { - id: showHideRightPanel + id: toggleWebPreviewOrientationAction + text: "Horizontal Web View" + shortcut: "" + checkable: true + checked: mainContent.webViewHorizontal + onTriggered: mainContent.toggleWebPreviewOrientation(); + } + + Action { + id: showHideRightPanelAction text: "Show Right View" shortcut: "F7" checkable: true @@ -170,4 +192,21 @@ ApplicationWindow { enabled: !projectModel.isEmpty onTriggered: projectModel.closeProject(); } + + Action { + id: openNextDocumentAction + text: qsTr("Next Document") + shortcut: "Ctrl+Tab" + enabled: !projectModel.isEmpty + onTriggered: projectModel.openNextDocument(); + } + + Action { + id: openPrevDocumentAction + text: qsTr("Previous Document") + shortcut: "Ctrl+Shift+Tab" + enabled: !projectModel.isEmpty + onTriggered: projectModel.openPrevDocument(); + } + } diff --git a/mix/qml.qrc b/mix/res.qrc similarity index 82% rename from mix/qml.qrc rename to mix/res.qrc index e6e7b15df..639881e6a 100644 --- a/mix/qml.qrc +++ b/mix/res.qrc @@ -10,6 +10,7 @@ qml/ProjectList.qml qml/StateDialog.qml qml/StateList.qml + qml/StateListModel.qml qml/img/jumpintoback.png qml/img/jumpintoforward.png qml/img/jumpoutback.png @@ -36,12 +37,20 @@ qml/js/Debugger.js qml/NewProjectDialog.qml qml/ProjectModel.qml - qml/CodeEditor.qml qml/CodeEditorView.qml qml/js/ProjectModel.js qml/Ether.qml qml/EtherValue.qml qml/BigIntValue.qml + qml/js/QEtherHelper.js + qml/js/TransactionHelper.js qml/Splitter.qml + qml/ContractLibrary.qml + stdc/config.sol + stdc/namereg.sol + stdc/std.sol + qml/TransactionLog.qml + res/mix_256x256x32.png + qml/CallStack.qml diff --git a/mix/res/mix_256x256x32.png b/mix/res/mix_256x256x32.png new file mode 100644 index 000000000..722b6b9bc Binary files /dev/null and b/mix/res/mix_256x256x32.png differ diff --git a/mix/stdc/config.sol b/mix/stdc/config.sol new file mode 100644 index 000000000..f05412f25 --- /dev/null +++ b/mix/stdc/config.sol @@ -0,0 +1,45 @@ +//sol Config +// Simple global configuration registrar. +// @authors: +// Gav Wood +#require mortal +contract Config is mortal { + function register(uint id, address service) { + if (tx.origin != owner) + return; + services[id] = service; + log1(0, id); + } + + function unregister(uint id) { + if (msg.sender != owner && services[id] != msg.sender) + return; + services[id] = address(0); + log1(0, id); + } + + function lookup(uint service) constant returns(address a) { + return services[service]; + } + + mapping (uint => address) services; +} + +/* + +// Solidity Interface: +contract Config{function lookup(uint256 service)constant returns(address a){}function kill(){}function unregister(uint256 id){}function register(uint256 id,address service){}} + +// Example Solidity use: +address addrConfig = 0xf025d81196b72fba60a1d4dddad12eeb8360d828; +address addrNameReg = Config(addrConfig).lookup(1); + +// JS Interface: +var abiConfig = [{"constant":false,"inputs":[],"name":"kill","outputs":[]},{"constant":true,"inputs":[{"name":"service","type":"uint256"}],"name":"lookup","outputs":[{"name":"a","type":"address"}]},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"service","type":"address"}],"name":"register","outputs":[]},{"constant":false,"inputs":[{"name":"id","type":"uint256"}],"name":"unregister","outputs":[]}]; + +// Example JS use: +var addrConfig = "0x661005d2720d855f1d9976f88bb10c1a3398c77f"; +var addrNameReg; +web3.eth.contract(addrConfig, abiConfig).lookup(1).call().then(function(r){ addrNameReg = r; }) + +*/ diff --git a/mix/stdc/namereg.sol b/mix/stdc/namereg.sol new file mode 100644 index 000000000..0886f5f71 --- /dev/null +++ b/mix/stdc/namereg.sol @@ -0,0 +1,74 @@ +//sol NameReg +// Simple global name registrar. +// @authors: +// kobigurk (from #ethereum-dev) +// Gav Wood + +contract NameRegister { + function getAddress(string32 _name) constant returns (address o_owner) {} + function getName(address _owner) constant returns (string32 o_name) {} +} + +#require Config, owned +contract NameReg is owned, NameRegister { + function NameReg() { + address addrConfig = 0xf025d81196b72fba60a1d4dddad12eeb8360d828; + toName[addrConfig] = "Config"; + toAddress["Config"] = addrConfig; + toName[this] = "NameReg"; + toAddress["NameReg"] = this; + Config(addrConfig).register(1, this); + log1(0, hash256(Config())); + log1(0, hash256(this)); + } + + function register(string32 name) { + // Don't allow the same name to be overwritten. + if (toAddress[name] != address(0)) + return; + // Unregister previous name if there was one. + if (toName[msg.sender] != "") + toAddress[toName[msg.sender]] = 0; + + toName[msg.sender] = name; + toAddress[name] = msg.sender; + log1(0, hash256(msg.sender)); + } + + function unregister() { + string32 n = toName[msg.sender]; + if (n == "") + return; + log1(0, hash256(toAddress[n])); + toName[msg.sender] = ""; + toAddress[n] = address(0); + } + + function addressOf(string32 name) constant returns (address addr) { + return toAddress[name]; + } + + function nameOf(address addr) constant returns (string32 name) { + return toName[addr]; + } + + mapping (address => string32) toName; + mapping (string32 => address) toAddress; +} + + +/* + +// Solidity Interface: +contract NameReg{function kill(){}function register(string32 name){}function addressOf(string32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(string32 name){}} + +// Example Solidity use: +NameReg(addrNameReg).register("Some Contract"); + +// JS Interface: +var abiNameReg = [{"constant":true,"inputs":[{"name":"name","type":"string32"}],"name":"addressOf","outputs":[{"name":"addr","type":"address"}]},{"constant":false,"inputs":[],"name":"kill","outputs":[]},{"constant":true,"inputs":[{"name":"addr","type":"address"}],"name":"nameOf","outputs":[{"name":"name","type":"string32"}]},{"constant":false,"inputs":[{"name":"name","type":"string32"}],"name":"register","outputs":[]},{"constant":false,"inputs":[],"name":"unregister","outputs":[]}]; + +// Example JS use: +web3.eth.contract(addrNameReg, abiNameReg).register("My Name").transact(); + +*/ diff --git a/mix/stdc/std.sol b/mix/stdc/std.sol new file mode 100644 index 000000000..97a74ac56 --- /dev/null +++ b/mix/stdc/std.sol @@ -0,0 +1,124 @@ +//TODO: use imports +contract owned{function owned(){owner = msg.sender;}modifier onlyowner(){if(msg.sender==owner)_}address owner;} +contract mortal is owned {function kill() { if (msg.sender == owner) suicide(owner); }} + +//sol Config +// Simple global configuration registrar. +// @authors: +// Gav Wood + + +contract Config is mortal { + function register(uint id, address service) { + if (tx.origin != owner) + return; + services[id] = service; + log1(0, id); + } + + function unregister(uint id) { + if (msg.sender != owner && services[id] != msg.sender) + return; + services[id] = address(0); + log1(0, id); + } + + function lookup(uint service) constant returns(address a) { + return services[service]; + } + + mapping (uint => address) services; +} + +/* + +// Solidity Interface: +contract Config{function lookup(uint256 service)constant returns(address a){}function kill(){}function unregister(uint256 id){}function register(uint256 id,address service){}} + +// Example Solidity use: +address addrConfig = 0xf025d81196b72fba60a1d4dddad12eeb8360d828; +address addrNameReg = Config(addrConfig).lookup(1); + +// JS Interface: +var abiConfig = [{"constant":false,"inputs":[],"name":"kill","outputs":[]},{"constant":true,"inputs":[{"name":"service","type":"uint256"}],"name":"lookup","outputs":[{"name":"a","type":"address"}]},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"service","type":"address"}],"name":"register","outputs":[]},{"constant":false,"inputs":[{"name":"id","type":"uint256"}],"name":"unregister","outputs":[]}]; + +// Example JS use: +var addrConfig = "0x661005d2720d855f1d9976f88bb10c1a3398c77f"; +var addrNameReg; +web3.eth.contract(addrConfig, abiConfig).lookup(1).call().then(function(r){ addrNameReg = r; }) + +*/ + +//sol NameReg +// Simple global name registrar. +// @authors: +// kobigurk (from #ethereum-dev) +// Gav Wood + +contract NameRegister { + function getAddress(string32 _name) constant returns (address o_owner) {} + function getName(address _owner) constant returns (string32 o_name) {} +} + +contract NameReg is owned, NameRegister { + function NameReg() { + address addrConfig = 0xf025d81196b72fba60a1d4dddad12eeb8360d828; + toName[addrConfig] = "Config"; + toAddress["Config"] = addrConfig; + toName[this] = "NameReg"; + toAddress["NameReg"] = this; + Config(addrConfig).register(1, this); + log1(0, hash256(addrConfig)); + log1(0, hash256(this)); + } + + function register(string32 name) { + // Don't allow the same name to be overwritten. + if (toAddress[name] != address(0)) + return; + // Unregister previous name if there was one. + if (toName[msg.sender] != "") + toAddress[toName[msg.sender]] = 0; + + toName[msg.sender] = name; + toAddress[name] = msg.sender; + log1(0, hash256(msg.sender)); + } + + function unregister() { + string32 n = toName[msg.sender]; + if (n == "") + return; + log1(0, hash256(toAddress[n])); + toName[msg.sender] = ""; + toAddress[n] = address(0); + } + + function addressOf(string32 name) constant returns (address addr) { + return toAddress[name]; + } + + function nameOf(address addr) constant returns (string32 name) { + return toName[addr]; + } + + mapping (address => string32) toName; + mapping (string32 => address) toAddress; +} + + +/* + +// Solidity Interface: +contract NameReg{function kill(){}function register(string32 name){}function addressOf(string32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(string32 name){}} + +// Example Solidity use: +NameReg(addrNameReg).register("Some Contract"); + +// JS Interface: +var abiNameReg = [{"constant":true,"inputs":[{"name":"name","type":"string32"}],"name":"addressOf","outputs":[{"name":"addr","type":"address"}]},{"constant":false,"inputs":[],"name":"kill","outputs":[]},{"constant":true,"inputs":[{"name":"addr","type":"address"}],"name":"nameOf","outputs":[{"name":"name","type":"string32"}]},{"constant":false,"inputs":[{"name":"name","type":"string32"}],"name":"register","outputs":[]},{"constant":false,"inputs":[],"name":"unregister","outputs":[]}]; + +// Example JS use: +web3.eth.contract(addrNameReg, abiNameReg).register("My Name").transact(); + +*/ diff --git a/mix/web.qrc b/mix/web.qrc index a50863a2c..e71e5b7c5 100644 --- a/mix/web.qrc +++ b/mix/web.qrc @@ -2,5 +2,24 @@ qml/WebPreview.qml qml/html/WebContainer.html + qml/html/cm/active-line.js + qml/html/codeeditor.html + qml/html/cm/codemirror.css + qml/html/cm/codemirror.js + qml/html/cm/javascript.js + qml/html/cm/matchbrackets.js + qml/WebCodeEditor.qml + qml/html/codeeditor.js + qml/html/cm/fullscreen.css + qml/html/cm/fullscreen.js + qml/html/cm/solarized.css + qml/html/cm/xml.js + qml/html/cm/htmlmixed.js + qml/html/cm/css.js + qml/html/cm/solidity.js + qml/html/cm/dialog.css + qml/html/cm/dialog.js + qml/html/cm/search.js + qml/html/cm/searchcursor.js diff --git a/neth/main.cpp b/neth/main.cpp index abd045b1c..2c1a85241 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -32,7 +32,7 @@ #include #if ETH_JSONRPC #include -#include +#include #endif #include #include "BuildInfo.h" diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 3888f2314..aa651eb41 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -61,6 +61,7 @@ static string const g_argBinaryStr = "binary"; static string const g_argOpcodesStr = "opcodes"; static string const g_argNatspecDevStr = "natspec-dev"; static string const g_argNatspecUserStr = "natspec-user"; +static string const g_argAddStandard = "add-std"; static void version() { @@ -116,13 +117,13 @@ void CommandLineInterface::handleBinary(string const& _contract) if (outputToStdout(choice)) { cout << "Binary: " << endl; - cout << toHex(m_compiler.getBytecode(_contract)) << endl; + cout << toHex(m_compiler->getBytecode(_contract)) << endl; } if (outputToFile(choice)) { ofstream outFile(_contract + ".binary"); - outFile << toHex(m_compiler.getBytecode(_contract)); + outFile << toHex(m_compiler->getBytecode(_contract)); outFile.close(); } } @@ -133,14 +134,14 @@ void CommandLineInterface::handleOpcode(string const& _contract) if (outputToStdout(choice)) { cout << "Opcodes: " << endl; - cout << eth::disassemble(m_compiler.getBytecode(_contract)); + cout << eth::disassemble(m_compiler->getBytecode(_contract)); cout << endl; } if (outputToFile(choice)) { ofstream outFile(_contract + ".opcode"); - outFile << eth::disassemble(m_compiler.getBytecode(_contract)); + outFile << eth::disassemble(m_compiler->getBytecode(_contract)); outFile.close(); } } @@ -191,13 +192,13 @@ void CommandLineInterface::handleMeta(DocumentationType _type, string const& _co if (outputToStdout(choice)) { cout << title << endl; - cout << m_compiler.getMetadata(_contract, _type) << endl; + cout << m_compiler->getMetadata(_contract, _type) << endl; } if (outputToFile(choice)) { ofstream outFile(_contract + suffix); - outFile << m_compiler.getMetadata(_contract, _type); + outFile << m_compiler->getMetadata(_contract, _type); outFile.close(); } } @@ -205,36 +206,32 @@ void CommandLineInterface::handleMeta(DocumentationType _type, string const& _co bool CommandLineInterface::parseArguments(int argc, char** argv) { -#define OUTPUT_TYPE_STR "Legal values:\n" \ - "\tstdout: Print it to standard output\n" \ - "\tfile: Print it to a file with same name\n" \ - "\tboth: Print both to a file and the stdout\n" // Declare the supported options. po::options_description desc("Allowed options"); desc.add_options() ("help", "Show help message and exit") ("version", "Show version and exit") ("optimize", po::value()->default_value(false), "Optimize bytecode for size") + ("add-std", po::value()->default_value(false), "Add standard contracts") ("input-file", po::value>(), "input file") - (g_argAstStr.c_str(), po::value(), - "Request to output the AST of the contract. " OUTPUT_TYPE_STR) - (g_argAstJson.c_str(), po::value(), - "Request to output the AST of the contract in JSON format. " OUTPUT_TYPE_STR) - (g_argAsmStr.c_str(), po::value(), - "Request to output the EVM assembly of the contract. " OUTPUT_TYPE_STR) - (g_argOpcodesStr.c_str(), po::value(), - "Request to output the Opcodes of the contract. " OUTPUT_TYPE_STR) - (g_argBinaryStr.c_str(), po::value(), - "Request to output the contract in binary (hexadecimal). " OUTPUT_TYPE_STR) - (g_argAbiStr.c_str(), po::value(), - "Request to output the contract's JSON ABI interface. " OUTPUT_TYPE_STR) - (g_argSolAbiStr.c_str(), po::value(), - "Request to output the contract's Solidity ABI interface. " OUTPUT_TYPE_STR) - (g_argNatspecUserStr.c_str(), po::value(), - "Request to output the contract's Natspec user documentation. " OUTPUT_TYPE_STR) - (g_argNatspecDevStr.c_str(), po::value(), - "Request to output the contract's Natspec developer documentation. " OUTPUT_TYPE_STR); -#undef OUTPUT_TYPE_STR + (g_argAstStr.c_str(), po::value()->value_name("stdout|file|both"), + "Request to output the AST of the contract.") + (g_argAstJson.c_str(), po::value()->value_name("stdout|file|both"), + "Request to output the AST of the contract in JSON format.") + (g_argAsmStr.c_str(), po::value()->value_name("stdout|file|both"), + "Request to output the EVM assembly of the contract.") + (g_argOpcodesStr.c_str(), po::value()->value_name("stdout|file|both"), + "Request to output the Opcodes of the contract.") + (g_argBinaryStr.c_str(), po::value()->value_name("stdout|file|both"), + "Request to output the contract in binary (hexadecimal).") + (g_argAbiStr.c_str(), po::value()->value_name("stdout|file|both"), + "Request to output the contract's JSON ABI interface.") + (g_argSolAbiStr.c_str(), po::value()->value_name("stdout|file|both"), + "Request to output the contract's Solidity ABI interface.") + (g_argNatspecUserStr.c_str(), po::value()->value_name("stdout|file|both"), + "Request to output the contract's Natspec user documentation.") + (g_argNatspecDevStr.c_str(), po::value()->value_name("stdout|file|both"), + "Request to output the contract's Natspec developer documentation."); // All positional options should be interpreted as input files po::positional_options_description p; @@ -297,31 +294,32 @@ bool CommandLineInterface::processInput() m_sourceCodes[infile] = asString(dev::contents(infile)); } + m_compiler.reset(new CompilerStack(m_args["add-std"].as())); try { for (auto const& sourceCode: m_sourceCodes) - m_compiler.addSource(sourceCode.first, sourceCode.second); + m_compiler->addSource(sourceCode.first, sourceCode.second); // TODO: Perhaps we should not compile unless requested - m_compiler.compile(m_args["optimize"].as()); + m_compiler->compile(m_args["optimize"].as()); } catch (ParserError const& _exception) { - SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Parser error", m_compiler); + SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Parser error", *m_compiler); return false; } catch (DeclarationError const& _exception) { - SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Declaration error", m_compiler); + SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Declaration error", *m_compiler); return false; } catch (TypeError const& _exception) { - SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Type error", m_compiler); + SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Type error", *m_compiler); return false; } catch (CompilerError const& _exception) { - SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Compiler error", m_compiler); + SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Compiler error", *m_compiler); return false; } catch (InternalCompilerError const& _exception) @@ -374,12 +372,12 @@ void CommandLineInterface::handleAst(string const& _argStr) cout << endl << "======= " << sourceCode.first << " =======" << endl; if (_argStr == g_argAstStr) { - ASTPrinter printer(m_compiler.getAST(sourceCode.first), sourceCode.second); + ASTPrinter printer(m_compiler->getAST(sourceCode.first), sourceCode.second); printer.print(cout); } else { - ASTJsonConverter converter(m_compiler.getAST(sourceCode.first)); + ASTJsonConverter converter(m_compiler->getAST(sourceCode.first)); converter.print(cout); } } @@ -393,12 +391,12 @@ void CommandLineInterface::handleAst(string const& _argStr) ofstream outFile(p.stem().string() + ".ast"); if (_argStr == g_argAstStr) { - ASTPrinter printer(m_compiler.getAST(sourceCode.first), sourceCode.second); + ASTPrinter printer(m_compiler->getAST(sourceCode.first), sourceCode.second); printer.print(outFile); } else { - ASTJsonConverter converter(m_compiler.getAST(sourceCode.first)); + ASTJsonConverter converter(m_compiler->getAST(sourceCode.first)); converter.print(outFile); } outFile.close(); @@ -413,7 +411,7 @@ void CommandLineInterface::actOnInput() handleAst(g_argAstStr); handleAst(g_argAstJson); - vector contracts = m_compiler.getContractNames(); + vector contracts = m_compiler->getContractNames(); for (string const& contract: contracts) { if (needStdout(m_args)) @@ -426,13 +424,13 @@ void CommandLineInterface::actOnInput() if (outputToStdout(choice)) { cout << "EVM assembly:" << endl; - m_compiler.streamAssembly(cout, contract); + m_compiler->streamAssembly(cout, contract); } if (outputToFile(choice)) { ofstream outFile(contract + ".evm"); - m_compiler.streamAssembly(outFile, contract); + m_compiler->streamAssembly(outFile, contract); outFile.close(); } } diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index 2862773ba..79029f9d1 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -21,9 +21,9 @@ */ #pragma once -#include - #include +#include +#include namespace dev { @@ -65,7 +65,7 @@ private: /// map of input files to source code strings std::map m_sourceCodes; /// Solidity compiler stack - dev::solidity::CompilerStack m_compiler; + std::unique_ptr m_compiler; }; } diff --git a/solc/docker_emscripten/Dockerfile b/solc/docker_emscripten/Dockerfile new file mode 100644 index 000000000..b593cbf74 --- /dev/null +++ b/solc/docker_emscripten/Dockerfile @@ -0,0 +1,61 @@ +FROM ubuntu:14.04 + +ENV DEBIAN_FRONTEND noninteractive +RUN apt-get update +RUN apt-get upgrade -y + +# Ethereum dependencies +RUN apt-get install -qy build-essential git cmake libcurl4-openssl-dev wget +RUN apt-get install -qy automake libtool yasm scons + +RUN useradd -ms /bin/bash user +USER user +WORKDIR /home/user + +# Emscripten SDK +RUN wget -c https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz +RUN tar xzf emsdk-portable.tar.gz +WORKDIR /home/user/emsdk_portable +RUN ./emsdk update && ./emsdk install latest && ./emsdk activate latest +ENV PATH $PATH:/home/user/emsdk_portable:/home/user/emsdk_portable/clang/fastcomp/build_master_64/bin:/home/user/emsdk_portable/emscripten/master + +USER root +RUN apt-get install -qy nodejs +USER user +RUN sed -i "s/NODE_JS = 'node'/NODE_JS = 'nodejs'/g" ~/.emscripten + +# CryptoPP +WORKDIR /home/user +RUN git clone https://github.com/mmoss/cryptopp.git +WORKDIR /home/user/cryptopp +RUN emcmake cmake -DCRYPTOPP_LIBRARY_TYPE=STATIC -DCRYPTOPP_RUNTIME_TYPE=STATIC && emmake make -j 4 +RUN ln -s . src/cryptopp + +# Boost +WORKDIR /home/user +RUN wget 'http://downloads.sourceforge.net/project/boost/boost/1.57.0/boost_1_57_0.tar.bz2?r=http%3A%2F%2Fsourceforge.net%2Fprojects%2Fboost%2Ffiles%2Fboost%2F1.57.0%2F&ts=1421887207&use_mirror=cznic' -O boost_1_57_0.tar.bz2 +RUN tar xjf boost_1_57_0.tar.bz2 +WORKDIR /home/user/boost_1_57_0 +RUN ./bootstrap.sh --with-libraries=thread,system,regex +RUN sed -i 's/using gcc ;/using gcc : : \/home\/user\/emsdk_portable\/emscripten\/master\/em++ ;/g' ./project-config.jam +RUN sed -i 's/$(archiver\[1\])/\/home\/user\/emsdk_portable\/emscripten\/master\/emar/g' ./tools/build/src/tools/gcc.jam +RUN sed -i 's/$(ranlib\[1\])/\/home\/user\/emsdk_portable\/emscripten\/master\/emranlib/g' ./tools/build/src/tools/gcc.jam +RUN ./b2 link=static variant=release threading=single runtime-link=static thread system regex + +# Build soljs +WORKDIR /home/user +ADD https://api.github.com/repos/ethereum/cpp-ethereum/git/refs/heads/develop unused.txt +RUN git clone --depth=1 https://github.com/ethereum/cpp-ethereum +WORKDIR /home/user/cpp-ethereum +RUN git config --global user.email "me@example.com" +RUN git config --global user.name "Jane Doe" +ADD https://api.github.com/repos/chriseth/cpp-ethereum/git/refs/heads/solidity-js unused2.txt +RUN git remote add -f solidityjs https://github.com/chriseth/cpp-ethereum +# TODO this should be a proper merge but somehow causes problems +# NOTE that we only get the latest commit of that branch +RUN git cherry-pick solidityjs/solidity-js +RUN emcmake cmake -DETH_STATIC=1 -DONLY_SOLIDITY=1 -DHEADLESS=1 -DCMAKE_CXX_COMPILER=/home/user/emsdk_portable/emscripten/master/em++ -DCMAKE_C_COMPILER=/home/user/emsdk_portable/emscripten/master/emcc +RUN emmake make -j 6 soljs + +ENTRYPOINT cat soljs/soljs.js + diff --git a/standard.js b/standard.js index 64126a278..0f9cf6fa4 100644 --- a/standard.js +++ b/standard.js @@ -1,65 +1,29 @@ -var compile = function(name) { return web3.eth.solidity(env.contents("../../dapp-bin/" + name + "/" + name + ".sol")); }; -var create = function(code) { return web3.eth.transact({ 'code': code }); }; -var createVal = function(code, val) { return web3.eth.transact(val ? { 'code': code, 'value': val } : { 'code': code }); }; -var send = function(from, val, to) { return web3.eth.transact({ 'from': from, 'value': val, 'to': to }); }; -var initService = function(name, dep) { return dep.then(function(){ return compile(name).then(create); }); }; -var initServiceVal = function(name, dep, val) { return dep.then(function(){ return compile(name).then(function(c) { createVal(c, val); }); }); }; - -var addrConfig = compile("config").then(create); -var addrNameReg = initService("namereg", addrConfig); -var addrGavsino = initServiceVal("gavmble", addrNameReg, "1000000000000000000"); - -var abiNameReg = [{"constant":true,"inputs":[{"name":"name","type":"string32"}],"name":"addressOf","outputs":[{"name":"addr","type":"address"}]},{"constant":false,"inputs":[],"name":"kill","outputs":[]},{"constant":true,"inputs":[{"name":"addr","type":"address"}],"name":"nameOf","outputs":[{"name":"name","type":"string32"}]},{"constant":false,"inputs":[{"name":"name","type":"string32"}],"name":"register","outputs":[]},{"constant":false,"inputs":[],"name":"unregister","outputs":[]}]; -var regName = function(account, name) { return web3.contract(addrNameReg, abiNameReg).register(name).transact({'from': account, 'gas': 10000}); }; -/* -var coins = initService("coins", 1, nameReg); -var coin = initService("coin", 2, coins); -var approve = function(account, approvedAddress) { web3.eth.transact({ 'from': account, 'to': coin, 'gas': '10000', 'data': [ web3.fromAscii('approve'), approvedAddress ] }); }; -var exchange = initService("exchange", 3, coin); -var offer = function(account, haveCoin, haveVal, wantCoin, wantVal) { web3.eth.transact({ 'from': account, 'to': exchange, 'gas': '10000', 'data': [web3.fromAscii('new'), haveCoin, haveVal, wantCoin, wantVal] }); }; -*/ -addrConfig.then(function() { - env.note("config ready"); - web3.eth.accounts.then(function(accounts) - { - env.note("accounts ready"); - var funded = send(accounts[0], '100000000000000000000', accounts[1]); - funded.then(function(){ - env.note("second account funded"); - regName(accounts[1], 'Gav Would'); - }); - regName(accounts[0], 'Gav'); - - // TODO: once we have the exchange. -// approve(accounts[0], exchange).then(function(){ offer(accounts[0], coin, '5000', '0', '5000000000000000000'); }); -// funded.then(function(){ approve(accounts[1], exchange); }); - - // TODO: once we have a new implementation of DNSReg. - // env.note('Register gav.eth...') - // eth.transact({ 'to': dnsReg, 'data': [web3.fromAscii('register'), web3.fromAscii('gav'), web3.fromAscii('opensecrecy.com')] }); - }); -}); - -// TODO -/* -var nameRegJeff; - -env.note('Create NameRegJeff...') -eth.transact({ 'code': nameRegCode }, function(a) { nameRegJeff = a; }); - -env.note('Register NameRegJeff...') -eth.transact({ 'to': config, 'data': ['4', nameRegJeff] }); - -var dnsRegCode = '0x60006000546000600053602001546000600053604001546020604060206020600073661005d2720d855f1d9976f88bb10c1a3398c77f6103e8f17f7265676973746572000000000000000000000000000000000000000000000000600053606001600060200201547f446e735265670000000000000000000000000000000000000000000000000000600053606001600160200201546000600060006000604060606000600053604001536103e8f1327f6f776e65720000000000000000000000000000000000000000000000000000005761011663000000e46000396101166000f20060006000547f72656769737465720000000000000000000000000000000000000000000000006000602002350e0f630000006d596000600160200235560e0f630000006c59600032560e0f0f6300000057596000325657600260200235600160200235576001602002353257007f64657265676973746572000000000000000000000000000000000000000000006000602002350e0f63000000b95960016020023532560e0f63000000b959600032576000600160200235577f6b696c6c000000000000000000000000000000000000000000000000000000006000602002350e0f630000011559327f6f776e6572000000000000000000000000000000000000000000000000000000560e0f63000001155932ff00'; - -var dnsReg; -env.note('Create DnsReg...') -eth.transact({ 'code': dnsRegCode }, function(a) { dnsReg = a; }); - -env.note('DnsReg at address ' + dnsReg) - -env.note('Register DnsReg...') -eth.transact({ 'to': config, 'data': ['4', dnsReg] }); -*/ - -// env.load('/home/gav/Eth/cpp-ethereum/stdserv.js') +var compile = function(name) { return web3.eth.solidity(env.contents("/home/gav/Eth/dapp-bin/" + name + "/" + name + ".sol")); web3.eth.flush(); }; +var create = function(code) { return web3.eth.transact({ 'code': code }); web3.eth.flush(); }; +var createVal = function(code, val) { return web3.eth.transact(val ? { 'code': code, 'value': val } : { 'code': code }); web3.eth.flush(); }; +var send = function(from, val, to) { web3.eth.transact({ 'from': from, 'value': val, 'to': to }); web3.eth.flush(); }; +var initService = function(name) { return create(compile(name)); }; +var initServiceVal = function(name, val) { createVal(compile(name), val); }; + +var addrConfig = create(compile("config")); +var addrNameReg = initService("namereg"); +var addrGavsino = initServiceVal("gavmble", "1000000000000000000"); +var addrCoinReg = initService("coins"); +var addrGavCoin = initService("coin"); +var addrRegistrar = initService("registrar"); + +var abiNameReg = [{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"getName","outputs":[{"name":"o_name","type":"string32"}]},{"constant":false,"inputs":[{"name":"name","type":"string32"}],"name":"register","outputs":[]},{"constant":true,"inputs":[{"name":"name","type":"string32"}],"name":"addressOf","outputs":[{"name":"addr","type":"address"}]},{"constant":true,"inputs":[{"name":"_name","type":"string32"}],"name":"getAddress","outputs":[{"name":"o_owner","type":"address"}]},{"constant":false,"inputs":[],"name":"unregister","outputs":[]},{"constant":true,"inputs":[{"name":"addr","type":"address"}],"name":"nameOf","outputs":[{"name":"name","type":"string32"}]}]; +var regName = function(account, name) { return web3.eth.contract(addrNameReg, abiNameReg).transact({'from': account, 'gas': 10000}).register(name); }; + +send(web3.eth.accounts[0], '100000000000000000000', web3.eth.accounts[1]); +regName(web3.eth.accounts[0], 'Gav'); +regName(web3.eth.accounts[1], 'Gav Would'); + +// TODO: once we have the exchange. +// var offer = function(account, haveCoin, haveVal, wantCoin, wantVal) { web3.eth.transact({ 'from': account, 'to': exchange, 'gas': '10000', 'data': [web3.fromAscii('new'), haveCoin, haveVal, wantCoin, wantVal] }); }; +// approve(accounts[0], exchange).then(function(){ offer(accounts[0], coin, '5000', '0', '5000000000000000000'); }); +// funded.then(function(){ approve(accounts[1], exchange); }); + +// TODO: once we have a new implementation of DNSReg. +// env.note('Register gav.eth...') +// eth.transact({ 'to': dnsReg, 'data': [web3.fromAscii('register'), web3.fromAscii('gav'), web3.fromAscii('opensecrecy.com')] }); diff --git a/test/SolidityABIJSON.cpp b/test/SolidityABIJSON.cpp index 892b71f15..1f7d35ab1 100644 --- a/test/SolidityABIJSON.cpp +++ b/test/SolidityABIJSON.cpp @@ -35,7 +35,9 @@ namespace test class InterfaceChecker { public: - void checkInterface(std::string const& _code, std::string const& _expectedInterfaceString) + InterfaceChecker(): m_compilerStack(false) {} + + void checkInterface(std::string const& _code, std::string const& _expectedInterfaceString, std::string const& expectedSolidityInterface) { try { @@ -47,13 +49,17 @@ public: BOOST_FAIL(msg); } std::string generatedInterfaceString = m_compilerStack.getMetadata("", DocumentationType::ABI_INTERFACE); + std::string generatedSolidityInterfaceString = m_compilerStack.getMetadata("", DocumentationType::ABI_SOLIDITY_INTERFACE); Json::Value generatedInterface; m_reader.parse(generatedInterfaceString, generatedInterface); Json::Value expectedInterface; m_reader.parse(_expectedInterfaceString, expectedInterface); BOOST_CHECK_MESSAGE(expectedInterface == generatedInterface, - "Expected " << _expectedInterfaceString << - "\n but got:\n" << generatedInterfaceString); + "Expected:\n" << expectedInterface.toStyledString() << + "\n but got:\n" << generatedInterface.toStyledString()); + BOOST_CHECK_MESSAGE(expectedSolidityInterface == generatedSolidityInterfaceString, + "Expected:\n" << expectedSolidityInterface << + "\n but got:\n" << generatedSolidityInterfaceString); } private: @@ -69,10 +75,13 @@ BOOST_AUTO_TEST_CASE(basic_test) " function f(uint a) returns(uint d) { return a * 7; }\n" "}\n"; + char const* sourceInterface = "contract test{function f(uint256 a)returns(uint256 d){}}"; + char const* interface = R"([ { "name": "f", "constant": false, + "type": "function", "inputs": [ { "name": "a", @@ -88,17 +97,18 @@ BOOST_AUTO_TEST_CASE(basic_test) } ])"; - checkInterface(sourceCode, interface); + checkInterface(sourceCode, interface, sourceInterface); } BOOST_AUTO_TEST_CASE(empty_contract) { char const* sourceCode = "contract test {\n" "}\n"; + char const* sourceInterface = "contract test{}"; char const* interface = "[]"; - checkInterface(sourceCode, interface); + checkInterface(sourceCode, interface, sourceInterface); } BOOST_AUTO_TEST_CASE(multiple_methods) @@ -107,11 +117,13 @@ BOOST_AUTO_TEST_CASE(multiple_methods) " function f(uint a) returns(uint d) { return a * 7; }\n" " function g(uint b) returns(uint e) { return b * 8; }\n" "}\n"; + char const* sourceInterface = "contract test{function f(uint256 a)returns(uint256 d){}function g(uint256 b)returns(uint256 e){}}"; char const* interface = R"([ { "name": "f", "constant": false, + "type": "function", "inputs": [ { "name": "a", @@ -128,6 +140,7 @@ BOOST_AUTO_TEST_CASE(multiple_methods) { "name": "g", "constant": false, + "type": "function", "inputs": [ { "name": "b", @@ -143,7 +156,7 @@ BOOST_AUTO_TEST_CASE(multiple_methods) } ])"; - checkInterface(sourceCode, interface); + checkInterface(sourceCode, interface, sourceInterface); } BOOST_AUTO_TEST_CASE(multiple_params) @@ -151,11 +164,13 @@ BOOST_AUTO_TEST_CASE(multiple_params) char const* sourceCode = "contract test {\n" " function f(uint a, uint b) returns(uint d) { return a + b; }\n" "}\n"; + char const* sourceInterface = "contract test{function f(uint256 a,uint256 b)returns(uint256 d){}}"; char const* interface = R"([ { "name": "f", "constant": false, + "type": "function", "inputs": [ { "name": "a", @@ -175,7 +190,7 @@ BOOST_AUTO_TEST_CASE(multiple_params) } ])"; - checkInterface(sourceCode, interface); + checkInterface(sourceCode, interface, sourceInterface); } BOOST_AUTO_TEST_CASE(multiple_methods_order) @@ -185,11 +200,13 @@ BOOST_AUTO_TEST_CASE(multiple_methods_order) " function f(uint a) returns(uint d) { return a * 7; }\n" " function c(uint b) returns(uint e) { return b * 8; }\n" "}\n"; + char const* sourceInterface = "contract test{function c(uint256 b)returns(uint256 e){}function f(uint256 a)returns(uint256 d){}}"; char const* interface = R"([ { "name": "c", "constant": false, + "type": "function", "inputs": [ { "name": "b", @@ -206,6 +223,7 @@ BOOST_AUTO_TEST_CASE(multiple_methods_order) { "name": "f", "constant": false, + "type": "function", "inputs": [ { "name": "a", @@ -221,7 +239,7 @@ BOOST_AUTO_TEST_CASE(multiple_methods_order) } ])"; - checkInterface(sourceCode, interface); + checkInterface(sourceCode, interface, sourceInterface); } BOOST_AUTO_TEST_CASE(const_function) @@ -230,11 +248,13 @@ BOOST_AUTO_TEST_CASE(const_function) " function foo(uint a, uint b) returns(uint d) { return a + b; }\n" " function boo(uint32 a) constant returns(uint b) { return a * 4; }\n" "}\n"; + char const* sourceInterface = "contract test{function foo(uint256 a,uint256 b)returns(uint256 d){}function boo(uint32 a)constant returns(uint256 b){}}"; char const* interface = R"([ { "name": "foo", "constant": false, + "type": "function", "inputs": [ { "name": "a", @@ -255,6 +275,7 @@ BOOST_AUTO_TEST_CASE(const_function) { "name": "boo", "constant": true, + "type": "function", "inputs": [{ "name": "a", "type": "uint32" @@ -268,9 +289,144 @@ BOOST_AUTO_TEST_CASE(const_function) } ])"; - checkInterface(sourceCode, interface); + checkInterface(sourceCode, interface, sourceInterface); +} + +BOOST_AUTO_TEST_CASE(exclude_fallback_function) +{ + char const* sourceCode = "contract test { function() {} }"; + char const* sourceInterface = "contract test{}"; + + char const* interface = "[]"; + + checkInterface(sourceCode, interface, sourceInterface); +} + +BOOST_AUTO_TEST_CASE(events) +{ + char const* sourceCode = "contract test {\n" + " function f(uint a) returns(uint d) { return a * 7; }\n" + " event e1(uint b, address indexed c); \n" + " event e2(); \n" + "}\n"; + char const* sourceInterface = "contract test{function f(uint256 a)returns(uint256 d){}event e1(uint256 b,address indexed c);event e2;}"; + + char const* interface = R"([ + { + "name": "f", + "constant": false, + "type": "function", + "inputs": [ + { + "name": "a", + "type": "uint256" + } + ], + "outputs": [ + { + "name": "d", + "type": "uint256" + } + ] + }, + { + "name": "e1", + "type": "event", + "inputs": [ + { + "indexed": false, + "name": "b", + "type": "uint256" + }, + { + "indexed": true, + "name": "c", + "type": "address" + } + ] + }, + { + "name": "e2", + "type": "event", + "inputs": [] + } + + ])"; + + checkInterface(sourceCode, interface, sourceInterface); } + +BOOST_AUTO_TEST_CASE(inherited) +{ + char const* sourceCode = + " contract Base { \n" + " function baseFunction(uint p) returns (uint i) { return p; } \n" + " event baseEvent(string32 indexed evtArgBase); \n" + " } \n" + " contract Derived is Base { \n" + " function derivedFunction(string32 p) returns (string32 i) { return p; } \n" + " event derivedEvent(uint indexed evtArgDerived); \n" + " }"; + char const* sourceInterface = "contract Derived{function baseFunction(uint256 p)returns(uint256 i){}function derivedFunction(string32 p)returns(string32 i){}event derivedEvent(uint256 indexed evtArgDerived);event baseEvent(string32 indexed evtArgBase);}"; + + char const* interface = R"([ + { + "name": "baseFunction", + "constant": false, + "type": "function", + "inputs": + [{ + "name": "p", + "type": "uint256" + }], + "outputs": + [{ + "name": "i", + "type": "uint256" + }] + }, + { + "name": "derivedFunction", + "constant": false, + "type": "function", + "inputs": + [{ + "name": "p", + "type": "string32" + }], + "outputs": + [{ + "name": "i", + "type": "string32" + }] + }, + { + "name": "derivedEvent", + "type": "event", + "inputs": + [{ + "indexed": true, + "name": "evtArgDerived", + "type": "uint256" + }] + }, + { + "name": "baseEvent", + "type": "event", + "inputs": + [{ + "indexed": true, + "name": "evtArgBase", + "type": "string32" + }] + }])"; + + + checkInterface(sourceCode, interface, sourceInterface); +} + + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/SolidityCompiler.cpp b/test/SolidityCompiler.cpp index 53daa9dfe..17d9a7c07 100644 --- a/test/SolidityCompiler.cpp +++ b/test/SolidityCompiler.cpp @@ -96,7 +96,7 @@ BOOST_AUTO_TEST_CASE(smoke_test) "}\n"; bytes code = compileContract(sourceCode); - unsigned boilerplateSize = 73; + unsigned boilerplateSize = 69; bytes expectation({byte(Instruction::JUMPDEST), byte(Instruction::PUSH1), 0x0, // initialize local variable x byte(Instruction::PUSH1), 0x2, @@ -108,65 +108,14 @@ BOOST_AUTO_TEST_CASE(smoke_test) checkCodePresentAt(code, expectation, boilerplateSize); } -BOOST_AUTO_TEST_CASE(different_argument_numbers) -{ - char const* sourceCode = "contract test {\n" - " function f(uint a, uint b, uint c) returns(uint d) { return b; }\n" - " function g() returns (uint e, uint h) { h = f(1, 2, 3); }\n" - "}\n"; - bytes code = compileContract(sourceCode); - unsigned shift = 103; - unsigned boilerplateSize = 116; - bytes expectation({byte(Instruction::JUMPDEST), - byte(Instruction::PUSH1), 0x0, // initialize return variable d - byte(Instruction::DUP3), - byte(Instruction::SWAP1), // assign b to d - byte(Instruction::POP), - byte(Instruction::PUSH1), byte(0xa + shift), // jump to return - byte(Instruction::JUMP), - byte(Instruction::JUMPDEST), - byte(Instruction::SWAP4), // store d and fetch return address - byte(Instruction::SWAP3), // store return address - byte(Instruction::POP), - byte(Instruction::POP), - byte(Instruction::POP), - byte(Instruction::JUMP), // end of f - byte(Instruction::JUMPDEST), // beginning of g - byte(Instruction::PUSH1), 0x0, - byte(Instruction::PUSH1), 0x0, // initialized e and h - byte(Instruction::PUSH1), byte(0x21 + shift), // ret address - byte(Instruction::PUSH1), 0x1, - byte(Instruction::PUSH1), 0x2, - byte(Instruction::PUSH1), 0x3, - byte(Instruction::PUSH1), byte(0x1 + shift), - // stack here: ret e h 0x20 1 2 3 0x1 - byte(Instruction::JUMP), - byte(Instruction::JUMPDEST), - // stack here: ret e h f(1,2,3) - byte(Instruction::SWAP1), - // stack here: ret e f(1,2,3) h - byte(Instruction::POP), - byte(Instruction::DUP1), // retrieve it again as "value of expression" - byte(Instruction::POP), // end of assignment - // stack here: ret e f(1,2,3) - byte(Instruction::JUMPDEST), - byte(Instruction::SWAP1), - // ret e f(1,2,3) - byte(Instruction::SWAP2), - // f(1,2,3) e ret - byte(Instruction::JUMP) // end of g - }); - checkCodePresentAt(code, expectation, boilerplateSize); -} - BOOST_AUTO_TEST_CASE(ifStatement) { char const* sourceCode = "contract test {\n" " function f() { bool x; if (x) 77; else if (!x) 78; else 79; }" "}\n"; bytes code = compileContract(sourceCode); - unsigned shift = 60; - unsigned boilerplateSize = 73; + unsigned shift = 56; + unsigned boilerplateSize = 69; bytes expectation({byte(Instruction::JUMPDEST), byte(Instruction::PUSH1), 0x0, byte(Instruction::DUP1), @@ -206,8 +155,8 @@ BOOST_AUTO_TEST_CASE(loops) " function f() { while(true){1;break;2;continue;3;return;4;} }" "}\n"; bytes code = compileContract(sourceCode); - unsigned shift = 60; - unsigned boilerplateSize = 73; + unsigned shift = 56; + unsigned boilerplateSize = 69; bytes expectation({byte(Instruction::JUMPDEST), byte(Instruction::JUMPDEST), byte(Instruction::PUSH1), 0x1, diff --git a/test/SolidityEndToEndTest.cpp b/test/SolidityEndToEndTest.cpp index cf04edaad..7edc250c8 100644 --- a/test/SolidityEndToEndTest.cpp +++ b/test/SolidityEndToEndTest.cpp @@ -882,6 +882,43 @@ BOOST_AUTO_TEST_CASE(constructor) testSolidityAgainstCpp("get(uint256)", get, u256(7)); } +BOOST_AUTO_TEST_CASE(simple_accessor) +{ + char const* sourceCode = "contract test {\n" + " uint256 data;\n" + " function test() {\n" + " data = 8;\n" + " }\n" + "}\n"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("data()") == encodeArgs(8)); +} + +BOOST_AUTO_TEST_CASE(multiple_elementary_accessors) +{ + char const* sourceCode = "contract test {\n" + " uint256 data;\n" + " string6 name;\n" + " hash a_hash;\n" + " address an_address;\n" + " function test() {\n" + " data = 8;\n" + " name = \"Celina\";\n" + " a_hash = sha3(123);\n" + " an_address = address(0x1337);\n" + " super_secret_data = 42;\n" + " }\n" + " private:" + " uint256 super_secret_data;" + "}\n"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("data()") == encodeArgs(8)); + BOOST_CHECK(callContractFunction("name()") == encodeArgs("Celina")); + BOOST_CHECK(callContractFunction("a_hash()") == encodeArgs(dev::sha3(toBigEndian(u256(123))))); + BOOST_CHECK(callContractFunction("an_address()") == encodeArgs(toBigEndian(u160(0x1337)))); + BOOST_CHECK(callContractFunction("super_secret_data()") == bytes()); +} + BOOST_AUTO_TEST_CASE(balance) { char const* sourceCode = "contract test {\n" @@ -940,6 +977,97 @@ BOOST_AUTO_TEST_CASE(type_conversions_cleanup) 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22})); } +BOOST_AUTO_TEST_CASE(convert_string_to_string) +{ + char const* sourceCode = R"( + contract Test { + function pipeTrough(string3 input) returns (string3 ret) { + return string3(input); + } + })"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("pipeTrough(string3)", "abc") == encodeArgs("abc")); +} + +BOOST_AUTO_TEST_CASE(convert_hash_to_string_same_size) +{ + char const* sourceCode = R"( + contract Test { + function hashToString(hash h) returns (string32 s) { + return string32(h); + } + })"; + compileAndRun(sourceCode); + u256 a("0x6162630000000000000000000000000000000000000000000000000000000000"); + BOOST_CHECK(callContractFunction("hashToString(hash256)", a) == encodeArgs(a)); +} + +BOOST_AUTO_TEST_CASE(convert_hash_to_string_different_size) +{ + char const* sourceCode = R"( + contract Test { + function hashToString(hash160 h) returns (string20 s) { + return string20(h); + } + })"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("hashToString(hash160)", u160("0x6161626361626361626361616263616263616263")) == + encodeArgs(string("aabcabcabcaabcabcabc"))); +} + +BOOST_AUTO_TEST_CASE(convert_string_to_hash_same_size) +{ + char const* sourceCode = R"( + contract Test { + function stringToHash(string32 s) returns (hash h) { + return hash(s); + } + })"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("stringToHash(string32)", string("abc2")) == + encodeArgs(u256("0x6162633200000000000000000000000000000000000000000000000000000000"))); +} + +BOOST_AUTO_TEST_CASE(convert_string_to_hash_different_size) +{ + char const* sourceCode = R"( + contract Test { + function stringToHash(string20 s) returns (hash160 h) { + return hash160(s); + } + })"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("stringToHash(string20)", string("aabcabcabcaabcabcabc")) == + encodeArgs(u160("0x6161626361626361626361616263616263616263"))); +} + + +BOOST_AUTO_TEST_CASE(convert_string_to_hash_different_min_size) +{ + char const* sourceCode = R"( + contract Test { + function stringToHash(string1 s) returns (hash8 h) { + return hash8(s); + } + })"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("stringToHash(string1)", string("a")) == + encodeArgs(u256("0x61"))); +} + + +BOOST_AUTO_TEST_CASE(convert_hash_to_string_different_min_size) +{ + char const* sourceCode = R"( + contract Test { + function HashToString(hash8 h) returns (string1 s) { + return string1(h); + } + })"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("HashToString(hash8)", u256("0x61")) == + encodeArgs(string("a"))); +} BOOST_AUTO_TEST_CASE(send_ether) { @@ -1545,7 +1673,7 @@ BOOST_AUTO_TEST_CASE(single_copy_with_multiple_inheritance) } contract A is Base { function setViaA(uint i) { setData(i); } } contract B is Base { function getViaB() returns (uint i) { return getViaBase(); } } - contract Derived is A, B, Base { } + contract Derived is Base, B, A { } )"; compileAndRun(sourceCode, 0, "Derived"); BOOST_CHECK(callContractFunction("getViaB()") == encodeArgs(0)); @@ -1642,7 +1770,7 @@ BOOST_AUTO_TEST_CASE(constructor_argument_overriding) } } contract Base is BaseBase(2) { } - contract Derived is Base, BaseBase(3) { + contract Derived is BaseBase(3), Base { function getA() returns (uint r) { return m_a; } } )"; @@ -1650,6 +1778,284 @@ BOOST_AUTO_TEST_CASE(constructor_argument_overriding) BOOST_CHECK(callContractFunction("getA()") == encodeArgs(3)); } +BOOST_AUTO_TEST_CASE(function_modifier) +{ + char const* sourceCode = R"( + contract C { + function getOne() nonFree returns (uint r) { return 1; } + modifier nonFree { if (msg.value > 0) _ } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("getOne()") == encodeArgs(0)); + BOOST_CHECK(callContractFunctionWithValue("getOne()", 1) == encodeArgs(1)); +} + +BOOST_AUTO_TEST_CASE(function_modifier_local_variables) +{ + char const* sourceCode = R"( + contract C { + modifier mod1 { var a = 1; var b = 2; _ } + modifier mod2(bool a) { if (a) return; else _ } + function f(bool a) mod1 mod2(a) returns (uint r) { return 3; } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("f(bool)", true) == encodeArgs(0)); + BOOST_CHECK(callContractFunction("f(bool)", false) == encodeArgs(3)); +} + +BOOST_AUTO_TEST_CASE(function_modifier_loop) +{ + char const* sourceCode = R"( + contract C { + modifier repeat(uint count) { for (var i = 0; i < count; ++i) _ } + function f() repeat(10) returns (uint r) { r += 1; } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(10)); +} + +BOOST_AUTO_TEST_CASE(function_modifier_multi_invocation) +{ + char const* sourceCode = R"( + contract C { + modifier repeat(bool twice) { if (twice) _ _ } + function f(bool twice) repeat(twice) returns (uint r) { r += 1; } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("f(bool)", false) == encodeArgs(1)); + BOOST_CHECK(callContractFunction("f(bool)", true) == encodeArgs(2)); +} + +BOOST_AUTO_TEST_CASE(function_modifier_multi_with_return) +{ + // Here, the explicit return prevents the second execution + char const* sourceCode = R"( + contract C { + modifier repeat(bool twice) { if (twice) _ _ } + function f(bool twice) repeat(twice) returns (uint r) { r += 1; return r; } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("f(bool)", false) == encodeArgs(1)); + BOOST_CHECK(callContractFunction("f(bool)", true) == encodeArgs(1)); +} + +BOOST_AUTO_TEST_CASE(function_modifier_overriding) +{ + char const* sourceCode = R"( + contract A { + function f() mod returns (bool r) { return true; } + modifier mod { _ } + } + contract C is A { + modifier mod { } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(false)); +} + +BOOST_AUTO_TEST_CASE(function_modifier_calling_functions_in_creation_context) +{ + char const* sourceCode = R"( + contract A { + uint data; + function A() mod1 { f1(); } + function f1() mod2 { data |= 0x1; } + function f2() { data |= 0x20; } + function f3() { } + modifier mod1 { f2(); _ } + modifier mod2 { f3(); } + function getData() returns (uint r) { return data; } + } + contract C is A { + modifier mod1 { f4(); _ } + function f3() { data |= 0x300; } + function f4() { data |= 0x4000; } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("getData()") == encodeArgs(0x4300)); +} + +BOOST_AUTO_TEST_CASE(function_modifier_for_constructor) +{ + char const* sourceCode = R"( + contract A { + uint data; + function A() mod1 { data |= 2; } + modifier mod1 { data |= 1; _ } + function getData() returns (uint r) { return data; } + } + contract C is A { + modifier mod1 { data |= 4; _ } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("getData()") == encodeArgs(4 | 2)); +} + +BOOST_AUTO_TEST_CASE(use_std_lib) +{ + char const* sourceCode = R"( + import "mortal"; + contract Icarus is mortal { } + )"; + m_addStandardSources = true; + u256 amount(130); + u160 address(23); + compileAndRun(sourceCode, amount, "Icarus"); + u256 balanceBefore = m_state.balance(m_sender); + BOOST_CHECK(callContractFunction("kill()") == bytes()); + BOOST_CHECK(!m_state.addressHasCode(m_contractAddress)); + BOOST_CHECK(m_state.balance(m_sender) > balanceBefore); +} + +BOOST_AUTO_TEST_CASE(crazy_elementary_typenames_on_stack) +{ + char const* sourceCode = R"( + contract C { + function f() returns (uint r) { + uint; uint; uint; uint; + int x = -7; + var a = uint; + return a(x); + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(-7))); +} + +BOOST_AUTO_TEST_CASE(super) +{ + char const* sourceCode = R"( + contract A { function f() returns (uint r) { return 1; } } + contract B is A { function f() returns (uint r) { return super.f() | 2; } } + contract C is A { function f() returns (uint r) { return super.f() | 4; } } + contract D is B, C { function f() returns (uint r) { return super.f() | 8; } } + )"; + compileAndRun(sourceCode, 0, "D"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(1 | 2 | 4 | 8)); +} + +BOOST_AUTO_TEST_CASE(super_in_constructor) +{ + char const* sourceCode = R"( + contract A { function f() returns (uint r) { return 1; } } + contract B is A { function f() returns (uint r) { return super.f() | 2; } } + contract C is A { function f() returns (uint r) { return super.f() | 4; } } + contract D is B, C { uint data; function D() { data = super.f() | 8; } function f() returns (uint r) { return data; } } + )"; + compileAndRun(sourceCode, 0, "D"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(1 | 2 | 4 | 8)); +} + +BOOST_AUTO_TEST_CASE(fallback_function) +{ + char const* sourceCode = R"( + contract A { + uint data; + function() returns (uint r) { data = 1; return 2; } + function getData() returns (uint r) { return data; } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("getData()") == encodeArgs(0)); + BOOST_CHECK(callContractFunction("") == encodeArgs(2)); + BOOST_CHECK(callContractFunction("getData()") == encodeArgs(1)); +} + +BOOST_AUTO_TEST_CASE(inherited_fallback_function) +{ + char const* sourceCode = R"( + contract A { + uint data; + function() returns (uint r) { data = 1; return 2; } + function getData() returns (uint r) { return data; } + } + contract B is A {} + )"; + compileAndRun(sourceCode, 0, "B"); + BOOST_CHECK(callContractFunction("getData()") == encodeArgs(0)); + BOOST_CHECK(callContractFunction("") == encodeArgs(2)); + BOOST_CHECK(callContractFunction("getData()") == encodeArgs(1)); +} + +BOOST_AUTO_TEST_CASE(event) +{ + char const* sourceCode = R"( + contract ClientReceipt { + event Deposit(address indexed _from, hash indexed _id, uint _value); + function deposit(hash _id, bool _manually) { + if (_manually) { + hash s = 0x50cb9fe53daa9737b786ab3646f04d0150dc50ef4e75f59509d83667ad5adb20; + log3(msg.value, s, hash32(msg.sender), _id); + } else + Deposit(hash32(msg.sender), _id, msg.value); + } + } + )"; + compileAndRun(sourceCode); + u256 value(18); + u256 id(0x1234); + for (bool manually: {true, false}) + { + callContractFunctionWithValue("deposit(hash256,bool)", value, id, manually); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK_EQUAL(h256(m_logs[0].data), h256(u256(value))); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 3); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::sha3(string("Deposit(address,hash256,uint256)"))); + BOOST_CHECK_EQUAL(m_logs[0].topics[1], h256(m_sender)); + BOOST_CHECK_EQUAL(m_logs[0].topics[2], h256(id)); + } +} + +BOOST_AUTO_TEST_CASE(event_no_arguments) +{ + char const* sourceCode = R"( + contract ClientReceipt { + event Deposit; + function deposit() { + Deposit(); + } + } + )"; + compileAndRun(sourceCode); + callContractFunction("deposit()"); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data.empty()); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::sha3(string("Deposit()"))); +} + +BOOST_AUTO_TEST_CASE(event_lots_of_data) +{ + char const* sourceCode = R"( + contract ClientReceipt { + event Deposit(address _from, hash _id, uint _value, bool _flag); + function deposit(hash _id) { + Deposit(msg.sender, hash32(_id), msg.value, true); + } + } + )"; + compileAndRun(sourceCode); + u256 value(18); + u256 id(0x1234); + callContractFunctionWithValue("deposit(hash256)", value, id); + BOOST_REQUIRE_EQUAL(m_logs.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress); + BOOST_CHECK(m_logs[0].data == encodeArgs(m_sender, id, value, true)); + BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1); + BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::sha3(string("Deposit(address,hash256,uint256,bool)"))); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/SolidityExpressionCompiler.cpp b/test/SolidityExpressionCompiler.cpp index d50dc253e..a0cca3a3a 100644 --- a/test/SolidityExpressionCompiler.cpp +++ b/test/SolidityExpressionCompiler.cpp @@ -86,7 +86,8 @@ Declaration const& resolveDeclaration(vector const& _namespacedName, } bytes compileFirstExpression(const string& _sourceCode, vector> _functions = {}, - vector> _localVariables = {}, vector> _globalDeclarations = {}) + vector> _localVariables = {}, + vector> _globalDeclarations = {}) { Parser parser; ASTPointer sourceUnit; @@ -99,10 +100,12 @@ bytes compileFirstExpression(const string& _sourceCode, vector> _ NameAndTypeResolver resolver(declarations); resolver.registerDeclarations(*sourceUnit); + vector inheritanceHierarchy; for (ASTPointer const& node: sourceUnit->getNodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) { BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract)); + inheritanceHierarchy = vector(1, contract); } for (ASTPointer const& node: sourceUnit->getNodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) @@ -116,10 +119,12 @@ bytes compileFirstExpression(const string& _sourceCode, vector> _ BOOST_REQUIRE(extractor.getExpression() != nullptr); CompilerContext context; - for (vector const& function: _functions) - context.addFunction(dynamic_cast(resolveDeclaration(function, resolver))); + context.setInheritanceHierarchy(inheritanceHierarchy); + unsigned parametersSize = _localVariables.size(); // assume they are all one slot on the stack + context.adjustStackOffset(parametersSize); for (vector const& variable: _localVariables) - context.addVariable(dynamic_cast(resolveDeclaration(variable, resolver))); + context.addVariable(dynamic_cast(resolveDeclaration(variable, resolver)), + parametersSize--); ExpressionCompiler::compileExpression(context, *extractor.getExpression()); diff --git a/test/SolidityNameAndTypeResolution.cpp b/test/SolidityNameAndTypeResolution.cpp index 6c8fd1b1c..b9a7140f7 100644 --- a/test/SolidityNameAndTypeResolution.cpp +++ b/test/SolidityNameAndTypeResolution.cpp @@ -23,12 +23,15 @@ #include #include +#include #include #include #include #include #include +using namespace std; + namespace dev { namespace solidity @@ -38,6 +41,7 @@ namespace test namespace { + ASTPointer parseTextAndResolveNames(std::string const& _source) { Parser parser; @@ -53,6 +57,48 @@ ASTPointer parseTextAndResolveNames(std::string const& _source) return sourceUnit; } + +ASTPointer parseTextAndResolveNamesWithChecks(std::string const& _source) +{ + Parser parser; + ASTPointer sourceUnit; + try + { + sourceUnit = parser.parse(std::make_shared(CharStream(_source))); + NameAndTypeResolver resolver({}); + resolver.registerDeclarations(*sourceUnit); + for (ASTPointer const& node: sourceUnit->getNodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + resolver.resolveNamesAndTypes(*contract); + for (ASTPointer const& node: sourceUnit->getNodes()) + if (ContractDefinition* contract = dynamic_cast(node.get())) + resolver.checkTypeRequirements(*contract); + } + catch(boost::exception const& _e) + { + auto msg = std::string("Parsing text and resolving names failed with: \n") + boost::diagnostic_information(_e); + BOOST_FAIL(msg); + } + return sourceUnit; +} + +static ContractDefinition const* retrieveContract(ASTPointer _source, unsigned index) +{ + ContractDefinition* contract; + unsigned counter = 0; + for (ASTPointer const& node: _source->getNodes()) + if ((contract = dynamic_cast(node.get())) && counter == index) + return contract; + + return NULL; +} + +static FunctionTypePointer const& retrieveFunctionBySignature(ContractDefinition const* _contract, + std::string const& _signature) +{ + FixedHash<4> hash(dev::sha3(_signature)); + return _contract->getInterfaceFunctions()[hash]; +} } BOOST_AUTO_TEST_SUITE(SolidityNameAndTypeResolution) @@ -63,7 +109,7 @@ BOOST_AUTO_TEST_CASE(smoke_test) " uint256 stateVariable1;\n" " function fun(uint256 arg1) { var x; uint256 y; }" "}\n"; - BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); + BOOST_CHECK_NO_THROW(parseTextAndResolveNamesWithChecks(text)); } BOOST_AUTO_TEST_CASE(double_stateVariable_declaration) @@ -386,7 +432,7 @@ BOOST_AUTO_TEST_CASE(inheritance_diamond_basic) contract root { function rootFunction() {} } contract inter1 is root { function f() {} } contract inter2 is root { function f() {} } - contract derived is inter1, inter2, root { + contract derived is root, inter2, inter1 { function g() { f(); rootFunction(); } } )"; @@ -479,6 +525,7 @@ BOOST_AUTO_TEST_CASE(implicit_derived_to_base_conversion) )"; BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); } + BOOST_AUTO_TEST_CASE(implicit_base_to_derived_conversion) { char const* text = R"( @@ -490,6 +537,249 @@ BOOST_AUTO_TEST_CASE(implicit_base_to_derived_conversion) BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); } +BOOST_AUTO_TEST_CASE(function_modifier_invocation) +{ + char const* text = R"( + contract B { + function f() mod1(2, true) mod2("0123456") { } + modifier mod1(uint a, bool b) { if (b) _ } + modifier mod2(string7 a) { while (a == "1234567") _ } + } + )"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(invalid_function_modifier_type) +{ + char const* text = R"( + contract B { + function f() mod1(true) { } + modifier mod1(uint a) { if (a > 0) _ } + } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + +BOOST_AUTO_TEST_CASE(function_modifier_invocation_parameters) +{ + char const* text = R"( + contract B { + function f(uint8 a) mod1(a, true) mod2(r) returns (string7 r) { } + modifier mod1(uint a, bool b) { if (b) _ } + modifier mod2(string7 a) { while (a == "1234567") _ } + } + )"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(function_modifier_invocation_local_variables) +{ + char const* text = R"( + contract B { + function f() mod(x) { uint x = 7; } + modifier mod(uint a) { if (a > 0) _ } + } + )"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(legal_modifier_override) +{ + char const* text = R"( + contract A { modifier mod(uint a) {} } + contract B is A { modifier mod(uint a) {} } + )"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(illegal_modifier_override) +{ + char const* text = R"( + contract A { modifier mod(uint a) {} } + contract B is A { modifier mod(uint8 a) {} } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + +BOOST_AUTO_TEST_CASE(modifier_overrides_function) +{ + char const* text = R"( + contract A { modifier mod(uint a) {} } + contract B is A { function mod(uint a) {} } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + +BOOST_AUTO_TEST_CASE(function_overrides_modifier) +{ + char const* text = R"( + contract A { function mod(uint a) {} } + contract B is A { modifier mod(uint a) {} } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + +BOOST_AUTO_TEST_CASE(modifier_returns_value) +{ + char const* text = R"( + contract A { + function f(uint a) mod(2) returns (uint r) {} + modifier mod(uint a) { return 7; } + } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + +BOOST_AUTO_TEST_CASE(state_variable_accessors) +{ + char const* text = "contract test {\n" + " function fun() {\n" + " uint64(2);\n" + " }\n" + "uint256 foo;\n" + "}\n"; + + ASTPointer source; + ContractDefinition const* contract; + BOOST_CHECK_NO_THROW(source = parseTextAndResolveNamesWithChecks(text)); + BOOST_REQUIRE((contract = retrieveContract(source, 0)) != nullptr); + FunctionTypePointer function = retrieveFunctionBySignature(contract, "foo()"); + BOOST_REQUIRE(function->hasDeclaration()); + auto returnParams = function->getReturnParameterTypeNames(); + BOOST_CHECK_EQUAL(returnParams.at(0), "uint256"); + BOOST_CHECK(function->isConstant()); +} + +BOOST_AUTO_TEST_CASE(function_clash_with_state_variable_accessor) +{ + char const* text = "contract test {\n" + " function fun() {\n" + " uint64(2);\n" + " }\n" + "uint256 foo;\n" + " function foo() {}\n" + "}\n"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), DeclarationError); +} + +BOOST_AUTO_TEST_CASE(private_state_variable) +{ + char const* text = "contract test {\n" + " function fun() {\n" + " uint64(2);\n" + " }\n" + "private:\n" + "uint256 foo;\n" + "}\n"; + + ASTPointer source; + ContractDefinition const* contract; + BOOST_CHECK_NO_THROW(source = parseTextAndResolveNamesWithChecks(text)); + BOOST_CHECK((contract = retrieveContract(source, 0)) != nullptr); + FunctionTypePointer function = retrieveFunctionBySignature(contract, "foo()"); + BOOST_CHECK_MESSAGE(function == nullptr, "Accessor function of a private variable should not exist"); +} + +BOOST_AUTO_TEST_CASE(fallback_function) +{ + char const* text = R"( + contract C { + uint x; + function() { x = 2; } + } + )"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(fallback_function_with_arguments) +{ + char const* text = R"( + contract C { + uint x; + function(uint a) { x = 2; } + } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + +BOOST_AUTO_TEST_CASE(fallback_function_twice) +{ + char const* text = R"( + contract C { + uint x; + function() { x = 2; } + function() { x = 3; } + } + )"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), DeclarationError); +} + +BOOST_AUTO_TEST_CASE(fallback_function_inheritance) +{ + char const* text = R"( + contract A { + uint x; + function() { x = 1; } + } + contract C is A { + function() { x = 2; } + } + )"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(event) +{ + char const* text = R"( + contract c { + event e(uint indexed a, string3 indexed s, bool indexed b); + function f() { e(2, "abc", true); } + })"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(event_too_many_indexed) +{ + char const* text = R"( + contract c { + event e(uint indexed a, string3 indexed b, bool indexed c, uint indexed d); + function f() { e(2, "abc", true); } + })"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError); +} + +BOOST_AUTO_TEST_CASE(event_call) +{ + char const* text = R"( + contract c { + event e(uint a, string3 indexed s, bool indexed b); + function f() { e(2, "abc", true); } + })"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(event_inheritance) +{ + char const* text = R"( + contract base { + event e(uint a, string3 indexed s, bool indexed b); + } + contract c is base { + function f() { e(2, "abc", true); } + })"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(multiple_events_argument_clash) +{ + char const* text = R"( + contract c { + event e1(uint a, uint e1, uint e2); + event e2(uint a, uint e1, uint e2); + })"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/SolidityNatspecJSON.cpp b/test/SolidityNatspecJSON.cpp index 743651d54..911820ddd 100644 --- a/test/SolidityNatspecJSON.cpp +++ b/test/SolidityNatspecJSON.cpp @@ -36,6 +36,8 @@ namespace test class DocumentationChecker { public: + DocumentationChecker(): m_compilerStack(false) {} + void checkNatspec(std::string const& _code, std::string const& _expectedDocumentationString, bool _userDocumentation) diff --git a/test/SolidityParser.cpp b/test/SolidityParser.cpp index 91e571306..4adee9c66 100644 --- a/test/SolidityParser.cpp +++ b/test/SolidityParser.cpp @@ -66,6 +66,14 @@ ASTPointer parseTextExplainError(std::string const& _source) } } +static void checkFunctionNatspec(ASTPointer _function, + std::string const& _expectedDoc) +{ + auto doc = _function->getDocumentation(); + BOOST_CHECK_MESSAGE(doc != nullptr, "Function does not have Natspec Doc as expected"); + BOOST_CHECK_EQUAL(*doc, _expectedDoc); +} + } @@ -121,14 +129,16 @@ BOOST_AUTO_TEST_CASE(function_natspec_documentation) ASTPointer contract; ASTPointer function; char const* text = "contract test {\n" - " uint256 stateVar;\n" + " private:\n" + " uint256 stateVar;\n" + " public:\n" " /// This is a test function\n" " function functionName(hash hashin) returns (hash hashout) {}\n" "}\n"; BOOST_REQUIRE_NO_THROW(contract = parseText(text)); auto functions = contract->getDefinedFunctions(); BOOST_REQUIRE_NO_THROW(function = functions.at(0)); - BOOST_CHECK_EQUAL(*function->getDocumentation(), "This is a test function"); + checkFunctionNatspec(function, "This is a test function"); } BOOST_AUTO_TEST_CASE(function_normal_comments) @@ -144,7 +154,7 @@ BOOST_AUTO_TEST_CASE(function_normal_comments) auto functions = contract->getDefinedFunctions(); BOOST_REQUIRE_NO_THROW(function = functions.at(0)); BOOST_CHECK_MESSAGE(function->getDocumentation() == nullptr, - "Should not have gotten a Natspect comment for this function"); + "Should not have gotten a Natspecc comment for this function"); } BOOST_AUTO_TEST_CASE(multiple_functions_natspec_documentation) @@ -152,7 +162,9 @@ BOOST_AUTO_TEST_CASE(multiple_functions_natspec_documentation) ASTPointer contract; ASTPointer function; char const* text = "contract test {\n" + " private:\n" " uint256 stateVar;\n" + " public:\n" " /// This is test function 1\n" " function functionName1(hash hashin) returns (hash hashout) {}\n" " /// This is test function 2\n" @@ -166,17 +178,17 @@ BOOST_AUTO_TEST_CASE(multiple_functions_natspec_documentation) auto functions = contract->getDefinedFunctions(); BOOST_REQUIRE_NO_THROW(function = functions.at(0)); - BOOST_CHECK_EQUAL(*function->getDocumentation(), "This is test function 1"); + checkFunctionNatspec(function, "This is test function 1"); BOOST_REQUIRE_NO_THROW(function = functions.at(1)); - BOOST_CHECK_EQUAL(*function->getDocumentation(), "This is test function 2"); + checkFunctionNatspec(function, "This is test function 2"); BOOST_REQUIRE_NO_THROW(function = functions.at(2)); BOOST_CHECK_MESSAGE(function->getDocumentation() == nullptr, "Should not have gotten natspec comment for functionName3()"); BOOST_REQUIRE_NO_THROW(function = functions.at(3)); - BOOST_CHECK_EQUAL(*function->getDocumentation(), "This is test function 4"); + checkFunctionNatspec(function, "This is test function 4"); } BOOST_AUTO_TEST_CASE(multiline_function_documentation) @@ -193,9 +205,8 @@ BOOST_AUTO_TEST_CASE(multiline_function_documentation) auto functions = contract->getDefinedFunctions(); BOOST_REQUIRE_NO_THROW(function = functions.at(0)); - BOOST_CHECK_EQUAL(*function->getDocumentation(), - "This is a test function\n" - " and it has 2 lines"); + checkFunctionNatspec(function, "This is a test function\n" + " and it has 2 lines"); } BOOST_AUTO_TEST_CASE(natspec_comment_in_function_body) @@ -211,7 +222,6 @@ BOOST_AUTO_TEST_CASE(natspec_comment_in_function_body) " mapping(address=>hash) d;\n" " string name = \"Solidity\";" " }\n" - " uint256 stateVar;\n" " /// This is a test function\n" " /// and it has 2 lines\n" " function fun(hash hashin) returns (hash hashout) {}\n" @@ -220,12 +230,11 @@ BOOST_AUTO_TEST_CASE(natspec_comment_in_function_body) auto functions = contract->getDefinedFunctions(); BOOST_REQUIRE_NO_THROW(function = functions.at(0)); - BOOST_CHECK_EQUAL(*function->getDocumentation(), "fun1 description"); + checkFunctionNatspec(function, "fun1 description"); BOOST_REQUIRE_NO_THROW(function = functions.at(1)); - BOOST_CHECK_EQUAL(*function->getDocumentation(), - "This is a test function\n" - " and it has 2 lines"); + checkFunctionNatspec(function, "This is a test function\n" + " and it has 2 lines"); } BOOST_AUTO_TEST_CASE(natspec_docstring_between_keyword_and_signature) @@ -540,6 +549,78 @@ BOOST_AUTO_TEST_CASE(contract_multiple_inheritance_with_arguments) BOOST_CHECK_NO_THROW(parseText(text)); } +BOOST_AUTO_TEST_CASE(placeholder_in_function_context) +{ + char const* text = "contract c {\n" + " function fun() returns (uint r) {\n" + " var _ = 8;\n" + " return _ + 1;" + " }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(modifier) +{ + char const* text = "contract c {\n" + " modifier mod { if (msg.sender == 0) _ }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(modifier_arguments) +{ + char const* text = "contract c {\n" + " modifier mod(uint a) { if (msg.sender == a) _ }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(modifier_invocation) +{ + char const* text = "contract c {\n" + " modifier mod1(uint a) { if (msg.sender == a) _ }\n" + " modifier mod2 { if (msg.sender == 2) _ }\n" + " function f() mod1(7) mod2 { }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(fallback_function) +{ + char const* text = "contract c {\n" + " function() { }\n" + "}\n"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(event) +{ + char const* text = R"( + contract c { + event e(); + })"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(event_arguments) +{ + char const* text = R"( + contract c { + event e(uint a, string32 s); + })"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + +BOOST_AUTO_TEST_CASE(event_arguments_indexed) +{ + char const* text = R"( + contract c { + event e(uint a, string32 indexed s, bool indexed b); + })"; + BOOST_CHECK_NO_THROW(parseText(text)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/commonjs.cpp b/test/commonjs.cpp new file mode 100644 index 000000000..860b713dd --- /dev/null +++ b/test/commonjs.cpp @@ -0,0 +1,57 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file commonjs.cpp + * @author Marek Kotewicz + * @date 2014 + */ + +#include +#include + +BOOST_AUTO_TEST_SUITE(commonjs) +using namespace std; +using namespace dev; +using namespace dev::eth; + +BOOST_AUTO_TEST_CASE(jsToPublic) +{ + cnote << "Testing jsToPublic..."; + KeyPair kp = KeyPair::create(); + string string = toJS(kp.pub()); + Public pub = dev::jsToPublic(string); + BOOST_CHECK_EQUAL(kp.pub(), pub); +} + +BOOST_AUTO_TEST_CASE(jsToAddress) +{ + cnote << "Testing jsToPublic..."; + KeyPair kp = KeyPair::create(); + string string = toJS(kp.address()); + Address address = dev::jsToAddress(string); + BOOST_CHECK_EQUAL(kp.address(), address); +} + +BOOST_AUTO_TEST_CASE(jsToSecret) +{ + cnote << "Testing jsToPublic..."; + KeyPair kp = KeyPair::create(); + string string = toJS(kp.secret()); + Secret secret = dev::jsToSecret(string); + BOOST_CHECK_EQUAL(kp.secret(), secret); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/jsonrpc.cpp b/test/jsonrpc.cpp index 42b1a5ebb..1f0a466b2 100644 --- a/test/jsonrpc.cpp +++ b/test/jsonrpc.cpp @@ -31,8 +31,6 @@ #include #include #include -#include -//#include #include #include #include diff --git a/test/solidityExecutionFramework.h b/test/solidityExecutionFramework.h index 271a594c4..5a6935365 100644 --- a/test/solidityExecutionFramework.h +++ b/test/solidityExecutionFramework.h @@ -45,10 +45,11 @@ public: bytes const& compileAndRun(std::string const& _sourceCode, u256 const& _value = 0, std::string const& _contractName = "") { - dev::solidity::CompilerStack compiler; + dev::solidity::CompilerStack compiler(m_addStandardSources); try { - compiler.compile(_sourceCode, m_optimize); + compiler.addSource("", _sourceCode); + compiler.compile(m_optimize); } catch(boost::exception const& _e) { @@ -66,7 +67,6 @@ public: bytes const& callContractFunctionWithValue(std::string _sig, u256 const& _value, Args const&... _arguments) { - FixedHash<4> hash(dev::sha3(_sig)); sendMessage(hash.asBytes() + encodeArgs(_arguments...), false, _value); return m_output; @@ -173,6 +173,7 @@ private: protected: bool m_optimize = false; + bool m_addStandardSources = false; Address m_sender; Address m_contractAddress; eth::State m_state; diff --git a/test/stSpecialTestFiller.json b/test/stSpecialTestFiller.json index fcb1d74a6..3691df80f 100644 --- a/test/stSpecialTestFiller.json +++ b/test/stSpecialTestFiller.json @@ -37,5 +37,36 @@ "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", "data" : "" } + }, + + "OverflowGasMakeMoney" : { + "env" : { + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "currentDifficulty" : "45678256", + "currentGasLimit" : "(2**256)-1", + "currentGasLimit" : "115792089237316195423570985008687907853269984665640564039457584007913129639935", + "currentNumber" : "0", + "currentTimestamp" : 1, + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6" + }, + "pre" : { + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "1000", + "code" : "", + "nonce" : "0", + "storage" : { + } + } + }, + "transaction" : + { + "data" : "", + "gasLimit" : "115792089237316195423570985008687907853269984665640564039457584007913129639435", + "gasPrice" : "1", + "nonce" : "0", + "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "to" : "b94f5374fce5edbc8e2a8697c15331677e6ebf0b", + "value" : "501" + } } } diff --git a/test/whisperTopic.cpp b/test/whisperTopic.cpp index 79adf3d6a..0162124b8 100644 --- a/test/whisperTopic.cpp +++ b/test/whisperTopic.cpp @@ -46,16 +46,16 @@ BOOST_AUTO_TEST_CASE(topic) auto wh = ph.registerCapability(new WhisperHost()); ph.start(); - started = true; - /// Only interested in odd packets auto w = wh->installWatch(BuildTopicMask("odd")); - for (int i = 0, last = 0; i < 200 && last < 81; ++i) + started = true; + + for (int iterout = 0, last = 0; iterout < 200 && last < 81; ++iterout) { for (auto i: wh->checkWatch(w)) { - Message msg = wh->envelope(i).open(); + Message msg = wh->envelope(i).open(wh->fullTopic(w)); last = RLP(msg.payload()).toInt(); cnote << "New message from:" << msg.from().abridged() << RLP(msg.payload()).toInt(); result += last; @@ -68,7 +68,7 @@ BOOST_AUTO_TEST_CASE(topic) this_thread::sleep_for(chrono::milliseconds(50)); Host ph("Test", NetworkPreferences(50300, "", false, true)); - auto wh = ph.registerCapability(new WhisperHost()); + shared_ptr wh = ph.registerCapability(new WhisperHost()); this_thread::sleep_for(chrono::milliseconds(500)); ph.start(); this_thread::sleep_for(chrono::milliseconds(500)); @@ -87,4 +87,188 @@ BOOST_AUTO_TEST_CASE(topic) BOOST_REQUIRE_EQUAL(result, 1 + 9 + 25 + 49 + 81); } +BOOST_AUTO_TEST_CASE(forwarding) +{ + cnote << "Testing Whisper forwarding..."; + auto oldLogVerbosity = g_logVerbosity; + g_logVerbosity = 0; + + unsigned result = 0; + bool done = false; + + bool startedListener = false; + std::thread listener([&]() + { + setThreadName("listener"); + + // Host must be configured not to share peers. + Host ph("Listner", NetworkPreferences(50303, "", false, true)); + ph.setIdealPeerCount(0); + auto wh = ph.registerCapability(new WhisperHost()); + ph.start(); + + startedListener = true; + + /// Only interested in odd packets + auto w = wh->installWatch(BuildTopicMask("test")); + + for (int i = 0; i < 200 && !result; ++i) + { + for (auto i: wh->checkWatch(w)) + { + Message msg = wh->envelope(i).open(wh->fullTopic(w)); + unsigned last = RLP(msg.payload()).toInt(); + cnote << "New message from:" << msg.from().abridged() << RLP(msg.payload()).toInt(); + result = last; + } + this_thread::sleep_for(chrono::milliseconds(50)); + } + }); + + bool startedForwarder = false; + std::thread forwarder([&]() + { + setThreadName("forwarder"); + + while (!startedListener) + this_thread::sleep_for(chrono::milliseconds(50)); + + // Host must be configured not to share peers. + Host ph("Forwarder", NetworkPreferences(50305, "", false, true)); + ph.setIdealPeerCount(0); + auto wh = ph.registerCapability(new WhisperHost()); + this_thread::sleep_for(chrono::milliseconds(500)); + ph.start(); + + this_thread::sleep_for(chrono::milliseconds(500)); + ph.connect("127.0.0.1", 50303); + + startedForwarder = true; + + /// Only interested in odd packets + auto w = wh->installWatch(BuildTopicMask("test")); + + while (!done) + { + for (auto i: wh->checkWatch(w)) + { + Message msg = wh->envelope(i).open(wh->fullTopic(w)); + cnote << "New message from:" << msg.from().abridged() << RLP(msg.payload()).toInt(); + } + this_thread::sleep_for(chrono::milliseconds(50)); + } + }); + + while (!startedForwarder) + this_thread::sleep_for(chrono::milliseconds(50)); + + Host ph("Sender", NetworkPreferences(50300, "", false, true)); + ph.setIdealPeerCount(0); + shared_ptr wh = ph.registerCapability(new WhisperHost()); + this_thread::sleep_for(chrono::milliseconds(500)); + ph.start(); + this_thread::sleep_for(chrono::milliseconds(500)); + ph.connect("127.0.0.1", 50305); + + KeyPair us = KeyPair::create(); + wh->post(us.sec(), RLPStream().append(1).out(), BuildTopic("test")); + this_thread::sleep_for(chrono::milliseconds(250)); + + listener.join(); + done = true; + forwarder.join(); + g_logVerbosity = oldLogVerbosity; + + BOOST_REQUIRE_EQUAL(result, 1); +} + +BOOST_AUTO_TEST_CASE(asyncforwarding) +{ + cnote << "Testing Whisper async forwarding..."; + auto oldLogVerbosity = g_logVerbosity; + g_logVerbosity = 2; + + unsigned result = 0; + bool done = false; + + bool startedForwarder = false; + std::thread forwarder([&]() + { + setThreadName("forwarder"); + + // Host must be configured not to share peers. + Host ph("Forwarder", NetworkPreferences(50305, "", false, true)); + ph.setIdealPeerCount(0); + auto wh = ph.registerCapability(new WhisperHost()); + this_thread::sleep_for(chrono::milliseconds(500)); + ph.start(); + + this_thread::sleep_for(chrono::milliseconds(500)); + ph.connect("127.0.0.1", 50303); + + startedForwarder = true; + + /// Only interested in odd packets + auto w = wh->installWatch(BuildTopicMask("test")); + + while (!done) + { + for (auto i: wh->checkWatch(w)) + { + Message msg = wh->envelope(i).open(wh->fullTopic(w)); + cnote << "New message from:" << msg.from().abridged() << RLP(msg.payload()).toInt(); + } + this_thread::sleep_for(chrono::milliseconds(50)); + } + }); + + while (!startedForwarder) + this_thread::sleep_for(chrono::milliseconds(50)); + + { + Host ph("Sender", NetworkPreferences(50300, "", false, true)); + ph.setIdealPeerCount(0); + shared_ptr wh = ph.registerCapability(new WhisperHost()); + this_thread::sleep_for(chrono::milliseconds(500)); + ph.start(); + this_thread::sleep_for(chrono::milliseconds(500)); + ph.connect("127.0.0.1", 50305); + + KeyPair us = KeyPair::create(); + wh->post(us.sec(), RLPStream().append(1).out(), BuildTopic("test")); + this_thread::sleep_for(chrono::milliseconds(250)); + } + + { + Host ph("Listener", NetworkPreferences(50300, "", false, true)); + ph.setIdealPeerCount(0); + shared_ptr wh = ph.registerCapability(new WhisperHost()); + this_thread::sleep_for(chrono::milliseconds(500)); + ph.start(); + this_thread::sleep_for(chrono::milliseconds(500)); + ph.connect("127.0.0.1", 50305); + + /// Only interested in odd packets + auto w = wh->installWatch(BuildTopicMask("test")); + + for (int i = 0; i < 200 && !result; ++i) + { + for (auto i: wh->checkWatch(w)) + { + Message msg = wh->envelope(i).open(wh->fullTopic(w)); + unsigned last = RLP(msg.payload()).toInt(); + cnote << "New message from:" << msg.from().abridged() << RLP(msg.payload()).toInt(); + result = last; + } + this_thread::sleep_for(chrono::milliseconds(50)); + } + } + + done = true; + forwarder.join(); + g_logVerbosity = oldLogVerbosity; + + BOOST_REQUIRE_EQUAL(result, 1); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/third/MainWin.cpp b/third/MainWin.cpp index 2e967e407..eae421c24 100644 --- a/third/MainWin.cpp +++ b/third/MainWin.cpp @@ -135,7 +135,6 @@ Main::Main(QWidget *parent) : connect(ui->webView, &QWebView::loadFinished, [=]() { - m_qweb->poll(); }); connect(ui->webView, &QWebView::titleChanged, [=]() @@ -198,21 +197,21 @@ unsigned Main::installWatch(dev::h256 _tf, WatchHandler const& _f) void Main::installWatches() { - installWatch(dev::eth::LogFilter().topic((u256)(u160)c_config).topic((u256)0), [=](LocalisedLogEntries const&){ installNameRegWatch(); }); - installWatch(dev::eth::LogFilter().topic((u256)(u160)c_config).topic((u256)1), [=](LocalisedLogEntries const&){ installCurrenciesWatch(); }); + installWatch(dev::eth::LogFilter().address(c_config).topic(0, (u256)0), [=](LocalisedLogEntries const&){ installNameRegWatch(); }); + installWatch(dev::eth::LogFilter().address(c_config).topic(0, (u256)1), [=](LocalisedLogEntries const&){ installCurrenciesWatch(); }); installWatch(dev::eth::ChainChangedFilter, [=](LocalisedLogEntries const&){ onNewBlock(); }); } void Main::installNameRegWatch() { ethereum()->uninstallWatch(m_nameRegFilter); - m_nameRegFilter = installWatch(dev::eth::LogFilter().topic(ethereum()->stateAt(c_config, 0)), [=](LocalisedLogEntries const&){ onNameRegChange(); }); + m_nameRegFilter = installWatch(dev::eth::LogFilter().address(u160(ethereum()->stateAt(c_config, 0))), [=](LocalisedLogEntries const&){ onNameRegChange(); }); } void Main::installCurrenciesWatch() { ethereum()->uninstallWatch(m_currenciesFilter); - m_currenciesFilter = installWatch(dev::eth::LogFilter().topic(ethereum()->stateAt(c_config, 1)), [=](LocalisedLogEntries const&){ onCurrenciesChange(); }); + m_currenciesFilter = installWatch(dev::eth::LogFilter().address(u160(ethereum()->stateAt(c_config, 1))), [=](LocalisedLogEntries const&){ onCurrenciesChange(); }); } void Main::installBalancesWatch() @@ -225,7 +224,7 @@ void Main::installBalancesWatch() altCoins.push_back(right160(ethereum()->stateAt(coinsAddr, i + 1))); for (auto i: m_myKeys) for (auto c: altCoins) - tf.address(c).topic((u256)(u160)i.address()); + tf.address(c).topic(0, (u256)(u160)i.address()); ethereum()->uninstallWatch(m_balancesFilter); m_balancesFilter = installWatch(tf, [=](LocalisedLogEntries const&){ onBalancesChange(); }); @@ -531,9 +530,6 @@ void Main::timerEvent(QTimerEvent*) } else interval += 100; - - if (m_qweb) - m_qweb->poll(); for (auto const& i: m_handlers) {