From 7af5ac1252a289790e8d0ff346e29b0b6c850d2a Mon Sep 17 00:00:00 2001 From: arkpar Date: Sun, 15 Feb 2015 20:02:40 +0100 Subject: [PATCH 1/3] allow more than one contract --- libsolidity/Compiler.h | 2 + mix/CMakeLists.txt | 2 +- mix/ClientModel.cpp | 67 ++++++---- mix/ClientModel.h | 21 ++-- mix/CodeModel.cpp | 213 ++++++++++++++++++-------------- mix/CodeModel.h | 76 ++++++------ mix/QContractDefinition.cpp | 2 +- mix/QContractDefinition.h | 2 +- mix/StatusPane.cpp | 11 -- mix/StatusPane.h | 4 - mix/qml/CodeEditorView.qml | 2 +- mix/qml/Debugger.qml | 2 +- mix/qml/MainContent.qml | 2 +- mix/qml/ProjectList.qml | 21 ++-- mix/qml/ProjectModel.qml | 2 +- mix/qml/StateListModel.qml | 18 +-- mix/qml/StatusPane.qml | 14 ++- mix/qml/TransactionDialog.qml | 107 ++++++++++++---- mix/qml/WebPreview.qml | 12 +- mix/qml/html/WebContainer.html | 16 +-- mix/qml/js/ProjectModel.js | 77 ++++++++---- mix/qml/js/TransactionHelper.js | 1 - mix/qml/main.qml | 2 +- 23 files changed, 409 insertions(+), 267 deletions(-) diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index b3eae5b17..cbfb8dae0 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -20,6 +20,8 @@ * Solidity AST to EVM bytecode compiler. */ +#pragma once + #include #include #include diff --git a/mix/CMakeLists.txt b/mix/CMakeLists.txt index 4390a4eb0..892f2c1ed 100644 --- a/mix/CMakeLists.txt +++ b/mix/CMakeLists.txt @@ -71,4 +71,4 @@ eth_install_executable(${EXECUTABLE} #add qml asnd stdc files to project tree in Qt creator file(GLOB_RECURSE QMLFILES "qml/*.*") file(GLOB_RECURSE SOLFILES "stdc/*.*") -add_custom_target(dummy SOURCES ${QMLFILES} ${SOLFILES}) +add_custom_target(mix_qml SOURCES ${QMLFILES} ${SOLFILES}) diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index 57caf573c..91e5c98ab 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -65,7 +65,7 @@ private: ClientModel::ClientModel(AppContext* _context): - m_context(_context), m_running(false), m_rpcConnector(new RpcConnector()), m_contractAddress(Address()) + m_context(_context), m_running(false), m_rpcConnector(new RpcConnector()) { qRegisterMetaType("QBigInt*"); qRegisterMetaType("QIntType*"); @@ -136,9 +136,12 @@ void ClientModel::mine() }); } -QString ClientModel::contractAddress() const +QVariantMap ClientModel::contractAddresses() const { - return QString::fromStdString(dev::toJS(m_contractAddress)); + QVariantMap res; + for (auto const& c: m_contractAddresses) + res.insert(c.first, QString::fromStdString(dev::toJS(c.second))); + return res; } void ClientModel::debugDeployment() @@ -155,8 +158,8 @@ void ClientModel::setupState(QVariantMap _state) for (auto const& t: transactions) { QVariantMap transaction = t.toMap(); + QString contractId = transaction.value("contractId").toString(); QString functionId = transaction.value("functionId").toString(); - u256 gas = boost::get(qvariant_cast(transaction.value("gas"))->internalValue()); u256 value = (qvariant_cast(transaction.value("value")))->toU256Wei(); u256 gasPrice = (qvariant_cast(transaction.value("gasPrice")))->toU256Wei(); @@ -164,7 +167,9 @@ void ClientModel::setupState(QVariantMap _state) bool isStdContract = (transaction.value("stdContract").toBool()); if (isStdContract) { - TransactionSettings transactionSettings(functionId, transaction.value("url").toString()); + if (contractId.isEmpty()) //TODO: This is to support old project files, remove later + contractId = functionId; + TransactionSettings transactionSettings(contractId, transaction.value("url").toString()); transactionSettings.gasPrice = 10000000000000; transactionSettings.gas = 125000; transactionSettings.value = 0; @@ -172,8 +177,10 @@ void ClientModel::setupState(QVariantMap _state) } else { + if (contractId.isEmpty() && m_context->codeModel()->hasContract()) //TODO: This is to support old project files, remove later + contractId = m_context->codeModel()->contracts().keys()[0]; QVariantList qParams = transaction.value("qType").toList(); - TransactionSettings transactionSettings(functionId, value, gas, gasPrice); + TransactionSettings transactionSettings(contractId, functionId, value, gas, gasPrice); for (QVariant const& variant: qParams) { @@ -181,7 +188,7 @@ void ClientModel::setupState(QVariantMap _state) transactionSettings.parameterValues.push_back(param); } - if (transaction.value("executeConstructor").toBool()) + if (contractId == functionId || functionId == "Constructor") transactionSettings.functionId.clear(); transactionSequence.push_back(transactionSettings); @@ -194,8 +201,6 @@ void ClientModel::executeSequence(std::vector const& _seque { if (m_running) BOOST_THROW_EXCEPTION(ExecutionStateException()); - CompilationResult* compilerRes = m_context->codeModel()->code(); - std::shared_ptr contractDef = compilerRes->sharedContract(); m_running = true; emit runStarted(); @@ -206,7 +211,6 @@ void ClientModel::executeSequence(std::vector const& _seque { try { - bytes contractCode = compilerRes->bytes(); m_client->resetState(_balance); onStateReset(); for (TransactionSettings const& transaction: _sequence) @@ -216,14 +220,17 @@ void ClientModel::executeSequence(std::vector const& _seque if (!transaction.stdContractUrl.isEmpty()) { //std contract - dev::bytes const& stdContractCode = m_context->codeModel()->getStdContractCode(transaction.functionId, transaction.stdContractUrl); + dev::bytes const& stdContractCode = m_context->codeModel()->getStdContractCode(transaction.contractId, transaction.stdContractUrl); Address address = deployContract(stdContractCode, transaction); - m_stdContractAddresses[transaction.functionId] = address; - m_stdContractNames[address] = transaction.functionId; + m_stdContractAddresses[transaction.contractId] = address; + m_stdContractNames[address] = transaction.contractId; } else { //encode data + CompiledContract const& compilerRes = m_context->codeModel()->contract(transaction.contractId); + bytes contractCode = compilerRes.bytes(); + std::shared_ptr contractDef = compilerRes.sharedContract(); f = nullptr; if (transaction.functionId.isEmpty()) f = contractDef->constructor(); @@ -240,24 +247,31 @@ void ClientModel::executeSequence(std::vector const& _seque encoder.encode(f); for (int p = 0; p < transaction.parameterValues.size(); p++) { - if (f->parametersList().at(p)->type() != transaction.parameterValues.at(p)->declaration()->type()) - BOOST_THROW_EXCEPTION(ParameterChangedException() << FunctionName(f->parametersList().at(p)->type().toStdString())); + if (f->parametersList().size() <= p || f->parametersList().at(p)->type() != transaction.parameterValues.at(p)->declaration()->type()) + BOOST_THROW_EXCEPTION(ParameterChangedException() << FunctionName(transaction.functionId.toStdString())); encoder.push(transaction.parameterValues.at(p)->encodeValue()); } - if (transaction.functionId.isEmpty()) + if (transaction.functionId.isEmpty() || transaction.functionId == transaction.contractId) { bytes param = encoder.encodedData(); contractCode.insert(contractCode.end(), param.begin(), param.end()); Address newAddress = deployContract(contractCode, transaction); - if (newAddress != m_contractAddress) + auto contractAddressIter = m_contractAddresses.find(transaction.contractId); + if (contractAddressIter == m_contractAddresses.end() || newAddress != contractAddressIter->second) { - m_contractAddress = newAddress; - contractAddressChanged(); + m_contractAddresses[transaction.contractId] = newAddress; + m_contractNames[newAddress] = transaction.contractId; + contractAddressesChanged(); } } else - callContract(m_contractAddress, encoder.encodedData(), transaction); + { + auto contractAddressIter = m_contractAddresses.find(transaction.contractId); + if (contractAddressIter == m_contractAddresses.end()) + BOOST_THROW_EXCEPTION(dev::Exception() << dev::errinfo_comment("Contract not deployed: " + transaction.contractId.toStdString())); + callContract(contractAddressIter->second, encoder.encodedData(), transaction); + } } onNewTransaction(); } @@ -338,7 +352,8 @@ void ClientModel::callContract(Address const& _contract, bytes const& _data, Tra void ClientModel::onStateReset() { - m_contractAddress = dev::Address(); + m_contractAddresses.clear(); + m_contractNames.clear(); m_stdContractAddresses.clear(); m_stdContractNames.clear(); emit stateCleared(); @@ -389,14 +404,16 @@ void ClientModel::onNewTransaction() if (creation) returned = QString::fromStdString(toJS(tr.contractAddress)); - if (m_contractAddress != 0 && (tr.address == m_contractAddress || tr.contractAddress == m_contractAddress)) + Address contractAddress = tr.address != 0 ? tr.address : tr.contractAddress; + auto contractAddressIter = m_contractNames.find(contractAddress); + if (contractAddressIter != m_contractNames.end()) { - auto compilerRes = m_context->codeModel()->code(); - QContractDefinition* def = compilerRes->contract(); + CompiledContract const& compilerRes = m_context->codeModel()->contract(contractAddressIter->second); + const QContractDefinition* def = compilerRes.contract(); contract = def->name(); if (abi) { - QFunctionDefinition* funcDef = def->getFunction(functionHash); + QFunctionDefinition const* funcDef = def->getFunction(functionHash); if (funcDef) { function = funcDef->name(); diff --git a/mix/ClientModel.h b/mix/ClientModel.h index 530dc50cf..dda60cb10 100644 --- a/mix/ClientModel.h +++ b/mix/ClientModel.h @@ -46,13 +46,13 @@ class QVariableDefinition; 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& _contractId, QString const& _functionId, u256 _value, u256 _gas, u256 _gasPrice): + contractId(_contractId), functionId(_functionId), value(_value), gas(_gas), gasPrice(_gasPrice) {} TransactionSettings(QString const& _stdContractName, QString const& _stdContractUrl): - functionId(_stdContractName), stdContractUrl(_stdContractUrl) {} + contractId(_stdContractName), stdContractUrl(_stdContractUrl) {} + /// Contract name + QString contractId; /// Contract function name QString functionId; /// Transaction value @@ -121,8 +121,8 @@ public: Q_PROPERTY(bool running MEMBER m_running NOTIFY runStateChanged) /// @returns true if currently mining Q_PROPERTY(bool mining MEMBER m_mining NOTIFY miningStateChanged) - /// @returns address of the last executed contract - Q_PROPERTY(QString contractAddress READ contractAddress NOTIFY contractAddressChanged) + /// @returns deployed contracts addresses + Q_PROPERTY(QVariantMap contractAddresses READ contractAddresses NOTIFY contractAddressesChanged) /// ethereum.js RPC request entry point /// @param _message RPC request in Json format /// @returns RPC response in Json format @@ -161,7 +161,7 @@ signals: /// @param _message Error message void runFailed(QString const& _message); /// Contract address changed - void contractAddressChanged(); + void contractAddressesChanged(); /// Execution state changed void newBlock(); /// Execution state changed @@ -177,7 +177,7 @@ signals: void stateCleared(); private: - QString contractAddress() const; + QVariantMap contractAddresses() const; void executeSequence(std::vector const& _sequence, u256 _balance); dev::Address deployContract(bytes const& _code, TransactionSettings const& _tr = TransactionSettings()); void callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr); @@ -191,7 +191,8 @@ private: std::unique_ptr m_client; std::unique_ptr m_rpcConnector; std::unique_ptr m_web3Server; - Address m_contractAddress; + std::map m_contractAddresses; + std::map m_contractNames; std::map m_stdContractAddresses; std::map m_stdContractNames; }; diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index aae9dac86..d605f1c6e 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -21,6 +21,7 @@ */ #include +#include #include #include #include @@ -38,51 +39,31 @@ using namespace dev::mix; -void BackgroundWorker::queueCodeChange(int _jobId, QString const& _content) +const std::set c_predefinedContracts = + { "Config", "Coin", "CoinReg", "coin", "service", "owned", "mortal", "NameReg", "named", "std", "configUser" }; + +void BackgroundWorker::queueCodeChange(int _jobId) { - m_model->runCompilationJob(_jobId, _content); + m_model->runCompilationJob(_jobId); } -CompilationResult::CompilationResult(): - QObject(nullptr), - m_successful(false), - m_codeHash(qHash(QString())), - m_contract(new QContractDefinition()), - m_contractInterface("[]"), - m_codeHighlighter(new CodeHighlighter()) -{} - -CompilationResult::CompilationResult(const dev::solidity::CompilerStack& _compiler): +CompiledContract::CompiledContract(const dev::solidity::CompilerStack& _compiler, QString const& _contractName, QString const& _source): QObject(nullptr), - m_successful(true), - m_codeHash(qHash(QString())) + m_sourceHash(qHash(_source)) { - if (!_compiler.getContractNames().empty()) - { - auto const& contractDefinition = _compiler.getContractDefinition(std::string()); - m_contract.reset(new QContractDefinition(&contractDefinition)); - m_bytes = _compiler.getBytecode(); - dev::solidity::InterfaceHandler interfaceHandler; - m_contractInterface = QString::fromStdString(*interfaceHandler.getABIInterface(contractDefinition)); - if (m_contractInterface.isEmpty()) - m_contractInterface = "[]"; - } - else - m_contract.reset(new QContractDefinition()); + auto const& contractDefinition = _compiler.getContractDefinition(_contractName.toStdString()); + m_contract.reset(new QContractDefinition(&contractDefinition)); + QQmlEngine::setObjectOwnership(m_contract.get(), QQmlEngine::CppOwnership); + m_bytes = _compiler.getBytecode(_contractName.toStdString()); + dev::solidity::InterfaceHandler interfaceHandler; + m_contractInterface = QString::fromStdString(*interfaceHandler.getABIInterface(contractDefinition)); + if (m_contractInterface.isEmpty()) + m_contractInterface = "[]"; + if (contractDefinition.getLocation().sourceName.get()) + m_documentId = QString::fromStdString(*contractDefinition.getLocation().sourceName); } -CompilationResult::CompilationResult(CompilationResult const& _prev, QString const& _compilerMessage): - QObject(nullptr), - m_successful(false), - m_codeHash(qHash(QString())), - m_contract(_prev.m_contract), - m_compilerMessage(_compilerMessage), - m_bytes(_prev.m_bytes), - m_contractInterface(_prev.m_contractInterface), - m_codeHighlighter(_prev.m_codeHighlighter) -{} - -QString CompilationResult::codeHex() const +QString CompiledContract::codeHex() const { return QString::fromStdString(toJS(m_bytes)); } @@ -90,27 +71,26 @@ QString CompilationResult::codeHex() const CodeModel::CodeModel(QObject* _parent): QObject(_parent), m_compiling(false), - m_result(new CompilationResult()), m_codeHighlighterSettings(new CodeHighlighterSettings()), m_backgroundWorker(this), m_backgroundJobId(0) { + m_backgroundThread.start(); m_backgroundWorker.moveToThread(&m_backgroundThread); connect(this, &CodeModel::scheduleCompilationJob, &m_backgroundWorker, &BackgroundWorker::queueCodeChange, Qt::QueuedConnection); - connect(this, &CodeModel::compilationCompleteInternal, this, &CodeModel::onCompilationComplete, Qt::QueuedConnection); - qRegisterMetaType("CompilationResult*"); + qRegisterMetaType("CompiledContract*"); qRegisterMetaType("QContractDefinition*"); qRegisterMetaType("QFunctionDefinition*"); qRegisterMetaType("QVariableDeclaration*"); qmlRegisterType("org.ethereum.qml", 1, 0, "QFunctionDefinition"); qmlRegisterType("org.ethereum.qml", 1, 0, "QVariableDeclaration"); - m_backgroundThread.start(); } CodeModel::~CodeModel() { stop(); disconnect(this); + releaseContracts(); } void CodeModel::stop() @@ -120,80 +100,133 @@ void CodeModel::stop() m_backgroundThread.wait(); } -void CodeModel::registerCodeChange(QString const& _code) +void CodeModel::reset(QVariantMap const& _documents) { + ///@todo: cancel bg job + Guard l(x_contractMap); + releaseContracts(); + Guard pl(x_pendingContracts); + m_pendingContracts.clear(); + + for (QVariantMap::const_iterator d = _documents.cbegin(); d != _documents.cend(); d++) + m_pendingContracts[d.key()] = d.value().toString(); // launch the background thread - uint hash = qHash(_code); - if (m_result->m_codeHash == hash) - return; - m_backgroundJobId++; m_compiling = true; emit stateChanged(); - emit scheduleCompilationJob(m_backgroundJobId, _code); + emit scheduleCompilationJob(++m_backgroundJobId); } -void CodeModel::runCompilationJob(int _jobId, QString const& _code) +void CodeModel::registerCodeChange(QString const& _documentId, QString const& _code) { - if (_jobId != m_backgroundJobId) - return; //obsolete job + { + Guard l(x_contractMap); + CompiledContract* contract = m_contractMap.value(_documentId); + if (contract != nullptr && contract->m_sourceHash == qHash(_code)) + return; - solidity::CompilerStack cs(true); - std::unique_ptr result; + Guard pl(x_pendingContracts); + m_pendingContracts[_documentId] = _code; + } + + // launch the background thread + m_compiling = true; + emit stateChanged(); + emit scheduleCompilationJob(++m_backgroundJobId); +} + +QVariantMap CodeModel::contracts() const +{ + QVariantMap result; + Guard l(x_contractMap); + for (ContractMap::const_iterator c = m_contractMap.cbegin(); c != m_contractMap.cend(); c++) + result.insert(c.key(), QVariant::fromValue(c.value())); + return result; +} - std::string source = _code.toStdString(); - // run syntax highlighting first - // @todo combine this with compilation step - auto codeHighlighter = std::make_shared(); - codeHighlighter->processSource(source); +CompiledContract* CodeModel::contractByDocumentId(QString _documentId) const +{ + Guard l(x_contractMap); + for (ContractMap::const_iterator c = m_contractMap.cbegin(); c != m_contractMap.cend(); c++) + if (c.value()->m_documentId == _documentId) + return c.value(); + return nullptr; +} + +CompiledContract const& CodeModel::contract(QString _name) const +{ + Guard l(x_contractMap); + CompiledContract* res = m_contractMap.value(_name); + if (res == nullptr) + BOOST_THROW_EXCEPTION(dev::Exception() << dev::errinfo_comment("Contract not found: " + _name.toStdString())); + return *res; +} - cs.addSource("configUser", R"(contract configUser{function configAddr()constant returns(address a){ return 0xf025d81196b72fba60a1d4dddad12eeb8360d828;}})"); +void CodeModel::releaseContracts() +{ + for (ContractMap::iterator c = m_contractMap.begin(); c != m_contractMap.end(); c++) + c.value()->deleteLater(); + m_contractMap.clear(); +} + +void CodeModel::runCompilationJob(int _jobId) +{ + if (_jobId != m_backgroundJobId) + return; //obsolete job - // run compilation + ContractMap result; + solidity::CompilerStack cs(true); try { - cs.addSource("", source); + cs.addSource("configUser", R"(contract configUser{function configAddr()constant returns(address a){ return 0xf025d81196b72fba60a1d4dddad12eeb8360d828;}})"); + { + Guard l(x_pendingContracts); + for (auto const& c: m_pendingContracts) + cs.addSource(c.first.toStdString(), c.second.toStdString()); + } cs.compile(false); - codeHighlighter->processAST(cs.getAST()); - result.reset(new CompilationResult(cs)); - qDebug() << QString(QApplication::tr("compilation succeeded")); + + { + Guard pl(x_pendingContracts); + Guard l(x_contractMap); + for (std::string n: cs.getContractNames()) + { + if (c_predefinedContracts.count(n) != 0) + continue; + QString name = QString::fromStdString(n); + auto sourceIter = m_pendingContracts.find(name); + QString source = sourceIter != m_pendingContracts.end() ? sourceIter->second : QString(); + CompiledContract* contract = new CompiledContract(cs, name, source); + QQmlEngine::setObjectOwnership(contract, QQmlEngine::CppOwnership); + result[name] = contract; + CompiledContract* prevContract = m_contractMap.value(name); + if (prevContract != nullptr && prevContract->contractInterface() != result[name]->contractInterface()) + emit contractInterfaceChanged(name); + } + releaseContracts(); + m_contractMap.swap(result); + emit codeChanged(); + emit compilationComplete(); + } } catch (dev::Exception const& _exception) { std::ostringstream error; solidity::SourceReferenceFormatter::printExceptionInformation(error, _exception, "Error", cs); - result.reset(new CompilationResult(*m_result, QString::fromStdString(error.str()))); - codeHighlighter->processError(_exception); - qDebug() << QString(QApplication::tr("compilation failed:") + " " + result->compilerMessage()); + solidity::Location const* location = boost::get_error_info(_exception); + QString message = QString::fromStdString(error.str()); + CompiledContract* contract = nullptr; + if (location && location->sourceName.get() && (contract = contractByDocumentId(QString::fromStdString(*location->sourceName)))) + message = message.replace(QString::fromStdString(*location->sourceName), contract->contract()->name()); //substitute the location to match our contract names + compilationError(message); } - result->m_codeHighlighter = codeHighlighter; - result->m_codeHash = qHash(_code); - - emit compilationCompleteInternal(result.release()); -} - -void CodeModel::onCompilationComplete(CompilationResult* _newResult) -{ m_compiling = false; - bool contractChanged = m_result->contractInterface() != _newResult->contractInterface(); - m_result.reset(_newResult); - emit compilationComplete(); emit stateChanged(); - if (m_result->successful()) - { - emit codeChanged(); - if (contractChanged) - emit contractInterfaceChanged(); - } } bool CodeModel::hasContract() const { - return m_result->successful(); -} - -void CodeModel::updateFormatting(QTextDocument* _document) -{ - m_result->codeHighlighter()->updateFormatting(_document, *m_codeHighlighterSettings); + Guard l(x_contractMap); + return m_contractMap.size() != 0; } dev::bytes const& CodeModel::getStdContractCode(const QString& _contractName, const QString& _url) diff --git a/mix/CodeModel.h b/mix/CodeModel.h index 0262aa094..48dbbcd6c 100644 --- a/mix/CodeModel.h +++ b/mix/CodeModel.h @@ -27,7 +27,9 @@ #include #include #include +#include #include +#include class QTextDocument; @@ -56,59 +58,50 @@ public: BackgroundWorker(CodeModel* _model): QObject(), m_model(_model) {} public slots: - void queueCodeChange(int _jobId, QString const& _content); + void queueCodeChange(int _jobId); private: CodeModel* m_model; }; ///Compilation result model. Contains all the compiled contract data required by UI -class CompilationResult: public QObject +class CompiledContract: public QObject { Q_OBJECT Q_PROPERTY(QContractDefinition* contract READ contract) - Q_PROPERTY(QString compilerMessage READ compilerMessage CONSTANT) - Q_PROPERTY(bool successful READ successful CONSTANT) Q_PROPERTY(QString contractInterface READ contractInterface CONSTANT) Q_PROPERTY(QString codeHex READ codeHex CONSTANT) + Q_PROPERTY(QString documentId MEMBER m_documentId CONSTANT) public: - /// Empty compilation result constructor - CompilationResult(); /// Successful compilation result constructor - CompilationResult(solidity::CompilerStack const& _compiler); - /// Failed compilation result constructor - CompilationResult(CompilationResult const& _prev, QString const& _compilerMessage); + CompiledContract(solidity::CompilerStack const& _compiler, QString const& _contractName, QString const& _source); /// @returns contract definition for QML property - QContractDefinition* contract() { return m_contract.get(); } + QContractDefinition* contract() const { return m_contract.get(); } /// @returns contract definition - std::shared_ptr sharedContract() { return m_contract; } - /// Indicates if the compilation was successful - bool successful() const { return m_successful; } - /// @returns compiler error message in case of unsuccessful compilation - QString compilerMessage() const { return m_compilerMessage; } + std::shared_ptr sharedContract() const { return m_contract; } /// @returns contract bytecode dev::bytes const& bytes() const { return m_bytes; } /// @returns contract bytecode as hex string QString codeHex() const; /// @returns contract definition in JSON format QString contractInterface() const { return m_contractInterface; } - /// Get code highlighter - std::shared_ptr codeHighlighter() { return m_codeHighlighter; } private: - bool m_successful; - uint m_codeHash; + uint m_sourceHash; std::shared_ptr m_contract; QString m_compilerMessage; ///< @todo: use some structure here dev::bytes m_bytes; QString m_contractInterface; - std::shared_ptr m_codeHighlighter; + QString m_documentId; friend class CodeModel; }; -/// Background code compiler + +using ContractMap = QHash; + +/// Code compilation model. Compiles contracts in background an provides compiled contract data class CodeModel: public QObject { Q_OBJECT @@ -117,56 +110,59 @@ public: CodeModel(QObject* _parent); ~CodeModel(); - /// @returns latest compilation result - CompilationResult* code() { return m_result.get(); } - /// @returns latest compilation resul - CompilationResult const* code() const { return m_result.get(); } - - Q_PROPERTY(CompilationResult* code READ code NOTIFY codeChanged) + Q_PROPERTY(QVariantMap contracts READ contracts NOTIFY codeChanged) Q_PROPERTY(bool compiling READ isCompiling NOTIFY stateChanged) Q_PROPERTY(bool hasContract READ hasContract NOTIFY codeChanged) + /// @returns latest compilation results for contracts + QVariantMap contracts() const; /// @returns compilation status bool isCompiling() const { return m_compiling; } - /// @returns true if contract has at least one function + /// @returns true there is a contract which has at least one function 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); + /// Get contract by name + CompiledContract const& contract(QString _name) const; + /// Find a contract by document id + /// @returns CompiledContract object or null if not found + Q_INVOKABLE CompiledContract* contractByDocumentId(QString _documentId) const; signals: /// Emited on compilation state change void stateChanged(); /// Emitted on compilation complete void compilationComplete(); + /// Emitted on compilation error + void compilationError(QString _error); /// Internal signal used to transfer compilation job to background thread - void scheduleCompilationJob(int _jobId, QString const& _content); + void scheduleCompilationJob(int _jobId); /// Emitted if there are any changes in the code model void codeChanged(); /// Emitted if there are any changes in the contract interface - void contractInterfaceChanged(); - /// Emitted on compilation complete. Internal - void compilationCompleteInternal(CompilationResult* _newResult); - -private slots: - void onCompilationComplete(CompilationResult* _newResult); + void contractInterfaceChanged(QString _documentId); public slots: /// Update code model on source code change - void registerCodeChange(QString const& _code); + void registerCodeChange(QString const& _documentId, QString const& _code); + /// Reset code model for a new project + void reset(QVariantMap const& _documents); private: - void runCompilationJob(int _jobId, QString const& _content); + void runCompilationJob(int _jobId); void stop(); + void releaseContracts(); std::atomic m_compiling; - std::unique_ptr m_result; + mutable dev::Mutex x_contractMap; + ContractMap m_contractMap; std::unique_ptr m_codeHighlighterSettings; QThread m_backgroundThread; BackgroundWorker m_backgroundWorker; int m_backgroundJobId = 0; //protects from starting obsolete compilation job std::map m_compiledContracts; //by name + dev::Mutex x_pendingContracts; + std::map m_pendingContracts; //name to source friend class BackgroundWorker; }; diff --git a/mix/QContractDefinition.cpp b/mix/QContractDefinition.cpp index 488e08ea3..eacaee2f9 100644 --- a/mix/QContractDefinition.cpp +++ b/mix/QContractDefinition.cpp @@ -42,7 +42,7 @@ QContractDefinition::QContractDefinition(dev::solidity::ContractDefinition const m_functions.append(new QFunctionDefinition(it.second));} -QFunctionDefinition* QContractDefinition::getFunction(dev::FixedHash<4> _hash) +QFunctionDefinition const* QContractDefinition::getFunction(dev::FixedHash<4> _hash) const { for (auto const& f: m_functions) if (f->hash() == _hash) diff --git a/mix/QContractDefinition.h b/mix/QContractDefinition.h index 22f913a70..4d586a239 100644 --- a/mix/QContractDefinition.h +++ b/mix/QContractDefinition.h @@ -47,7 +47,7 @@ public: 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); + QFunctionDefinition const* getFunction(dev::FixedHash<4> _hash) const; private: QList m_functions; QFunctionDefinition* m_constructor; diff --git a/mix/StatusPane.cpp b/mix/StatusPane.cpp index f74b8f22b..9022b7033 100644 --- a/mix/StatusPane.cpp +++ b/mix/StatusPane.cpp @@ -36,7 +36,6 @@ using namespace dev::mix; StatusPane::StatusPane(AppContext* _context): Extension(_context, ExtensionDisplayBehavior::HeaderView) { - connect(_context->codeModel(), &CodeModel::compilationComplete, this, &StatusPane::update); _context->appEngine()->rootContext()->setContextProperty("statusPane", this); } @@ -54,13 +53,3 @@ void StatusPane::start() const { } -CompilationResult* StatusPane::result() const -{ - return m_ctx->codeModel()->code(); -} - -void StatusPane::update() -{ - QObject* ctrl = m_view->findChild("statusPane", Qt::FindChildrenRecursively); - QMetaObject::invokeMethod(ctrl, "updateStatus"); -} diff --git a/mix/StatusPane.h b/mix/StatusPane.h index ee65252b5..28b5b449b 100644 --- a/mix/StatusPane.h +++ b/mix/StatusPane.h @@ -20,7 +20,6 @@ #pragma once #include "Extension.h" -#include "CodeModel.h" namespace dev { @@ -33,7 +32,6 @@ namespace mix class StatusPane: public Extension { Q_OBJECT - Q_PROPERTY(CompilationResult* result READ result CONSTANT) public: StatusPane(AppContext* _appContext); @@ -41,10 +39,8 @@ public: void start() const override; QString title() const override; QString contentUrl() const override; - CompilationResult* result() const; public slots: - void update(); }; } diff --git a/mix/qml/CodeEditorView.qml b/mix/qml/CodeEditorView.qml index 9c0b804d7..439c36199 100644 --- a/mix/qml/CodeEditorView.qml +++ b/mix/qml/CodeEditorView.qml @@ -43,7 +43,7 @@ Item { editor.onEditorTextChanged.connect(function() { documentEdit(document.documentId); if (document.isContract) - codeModel.registerCodeChange(editor.getText()); + codeModel.registerCodeChange(document.documentId, editor.getText()); }); editor.setText(data, document.syntaxMode); } diff --git a/mix/qml/Debugger.qml b/mix/qml/Debugger.qml index 098cc77d1..316ec86c7 100644 --- a/mix/qml/Debugger.qml +++ b/mix/qml/Debugger.qml @@ -25,7 +25,7 @@ Rectangle { function update(data, giveFocus) { - if (statusPane && statusPane.result.successful) + if (statusPane && codeModel.hasContract) { Debugger.init(data); debugScrollArea.visible = true; diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index 6c6781878..15a7a638f 100644 --- a/mix/qml/MainContent.qml +++ b/mix/qml/MainContent.qml @@ -34,7 +34,7 @@ Rectangle { onCompilationComplete: { if (firstCompile) { firstCompile = false; - if (codeModel.code.successful && runOnProjectLoad) + if (runOnProjectLoad) startQuickDebugging(); } } diff --git a/mix/qml/ProjectList.qml b/mix/qml/ProjectList.qml index 925bb0bab..d4875220a 100644 --- a/mix/qml/ProjectList.qml +++ b/mix/qml/ProjectList.qml @@ -101,14 +101,19 @@ Item { Connections { target: codeModel onCompilationComplete: { - if (modelData === "Contracts") - { - var ctr = projectModel.listModel.get(0); - if (codeModel.code.contract.name !== ctr.name) - { - ctr.name = codeModel.code.contract.name; - projectModel.listModel.set(0, ctr); - sectionModel.set(0, ctr); + if (modelData === "Contracts") { + var ci = 0; + for (var si = 0; si < projectModel.listModel.count; si++) { + var document = projectModel.listModel.get(si); + if (document.isContract) { + var compiledDoc = codeModel.contractByDocumentId(document.documentId); + if (compiledDoc && compiledDoc.documentId === document.documentId && compiledDoc.contract.name !== document.name) { + document.name = compiledDoc.contract.name; + projectModel.listModel.set(si, document); + sectionModel.set(ci, document); + } + ci++; + } } } } diff --git a/mix/qml/ProjectModel.qml b/mix/qml/ProjectModel.qml index e74be7a9b..97dec227c 100644 --- a/mix/qml/ProjectModel.qml +++ b/mix/qml/ProjectModel.qml @@ -45,7 +45,7 @@ Item { function newHtmlFile() { ProjectModelCode.newHtmlFile(); } function newJsFile() { ProjectModelCode.newJsFile(); } function newCssFile() { ProjectModelCode.newCssFile(); } - //function newContract() { ProjectModelCode.newContract(); } + function newContract() { ProjectModelCode.newContract(); } function openDocument(documentId) { ProjectModelCode.openDocument(documentId); } function openNextDocument() { ProjectModelCode.openNextDocument(); } function openPrevDocument() { ProjectModelCode.openPrevDocument(); } diff --git a/mix/qml/StateListModel.qml b/mix/qml/StateListModel.qml index c68a433f7..da02ce5e6 100644 --- a/mix/qml/StateListModel.qml +++ b/mix/qml/StateListModel.qml @@ -22,12 +22,12 @@ Item { function fromPlainTransactionItem(t) { var r = { + contractId: t.contractId, functionId: t.functionId, url: t.url, value: QEtherHelper.createEther(t.value.value, t.value.unit), gas: QEtherHelper.createBigInt(t.gas.value), //t.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: {} }; @@ -78,12 +78,12 @@ Item { function toPlainTransactionItem(t) { var r = { + contractId: t.contractId, functionId: t.functionId, url: t.url, value: { value: t.value.value, unit: t.value.unit }, gas: { value: t.gas.value() }, gasPrice: { value: t.gasPrice.value, unit: t.gasPrice.unit }, - executeConstructor: t.executeConstructor, stdContract: t.stdContract, parameters: {} }; @@ -159,7 +159,6 @@ Item { value: QEtherHelper.createEther("100", QEther.Wei), gas: QEtherHelper.createBigInt("125000"), gasPrice: QEtherHelper.createEther("10000000000000", QEther.Wei), - executeConstructor: false, stdContract: false }; } @@ -178,16 +177,19 @@ Item { var contractTransaction = defaultTransactionItem(); var contractItem = contractLibrary.model.get(i); contractTransaction.url = contractItem.url; + contractTransaction.contractId = contractItem.name; 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); + //add constructors, //TODO: order by dependencies + for(var c in codeModel.contracts) { + var ctorTr = defaultTransactionItem(); + ctorTr.functionId = c; + ctorTr.contractId = c; + item.transactions.push(ctorTr); + } return item; } diff --git a/mix/qml/StatusPane.qml b/mix/qml/StatusPane.qml index ddadb9953..6a8a0093d 100644 --- a/mix/qml/StatusPane.qml +++ b/mix/qml/StatusPane.qml @@ -8,9 +8,9 @@ Rectangle { id: statusHeader objectName: "statusPane" - function updateStatus() + function updateStatus(message) { - if (statusPane.result.successful) + if (!message) { status.state = ""; status.text = qsTr("Compile without errors."); @@ -20,12 +20,12 @@ Rectangle { else { status.state = "error"; - var errorInfo = ErrorLocationFormater.extractErrorInfo(statusPane.result.compilerMessage, true); + var errorInfo = ErrorLocationFormater.extractErrorInfo(message, true); status.text = errorInfo.errorLocation + " " + errorInfo.errorDetail; logslink.visible = true; debugImg.state = ""; } - debugRunActionIcon.enabled = statusPane.result.successful; + debugRunActionIcon.enabled = codeModel.hasContract; } function infoMessage(text) @@ -35,7 +35,6 @@ Rectangle { logslink.visible = false; } - Connections { target:clientModel onRunStarted: infoMessage(qsTr("Running transactions...")); @@ -49,6 +48,11 @@ Rectangle { onDeploymentError: infoMessage(error); onDeploymentComplete: infoMessage(qsTr("Deployment complete")); } + Connections { + target: codeModel + onCompilationComplete: updateStatus(); + onCompilationError: updateStatus(_error); + } color: "transparent" anchors.fill: parent diff --git a/mix/qml/TransactionDialog.qml b/mix/qml/TransactionDialog.qml index c71bd4155..e5a8bc746 100644 --- a/mix/qml/TransactionDialog.qml +++ b/mix/qml/TransactionDialog.qml @@ -20,9 +20,9 @@ Window { property alias gas: gasValueEdit.gasValue; property alias gasPrice: gasPriceField.value; property alias transactionValue: valueField.value; + property string contractId: contractComboBox.currentValue(); property alias functionId: functionComboBox.currentText; property var itemParams; - property bool isConstructorTransaction; property bool useTransactionDefaultValue: false property var qType; @@ -39,32 +39,47 @@ Window { gasValueEdit.gasValue = item.gas; gasPriceField.value = item.gasPrice; valueField.value = item.value; + var contractId = item.contractId; var functionId = item.functionId; - isConstructorTransaction = item.executeConstructor; - rowFunction.visible = !item.executeConstructor; + rowFunction.visible = true; itemParams = item.parameters !== undefined ? item.parameters : {}; - functionsModel.clear(); + + contractsModel.clear(); + var contractIndex = -1; + var contracts = codeModel.contracts; + for (var c in contracts) { + contractsModel.append({ cid: c, text: contracts[c].contract.name }); + if (contracts[c].contract.name === contractId) + contractIndex = contractsModel.count - 1; + } + + if (contractIndex == -1 && contractsModel.count > 0) + contractIndex = 0; //@todo suggest unused contract + contractComboBox.currentIndex = contractIndex; + + loadFunctions(contractComboBox.currentValue()); + var functionIndex = -1; - var functions = codeModel.code.contract.functions; - for (var f = 0; f < functions.length; f++) { - functionsModel.append({ text: functions[f].name }); - if (functions[f].name === item.functionId) + for (var f = 0; f < functionsModel.count; f++) + if (functionsModel.get(f).text === item.functionId) functionIndex = f; - } if (functionIndex == -1 && functionsModel.count > 0) functionIndex = 0; //@todo suggest unused function functionComboBox.currentIndex = functionIndex; + paramsModel.clear(); - if (!item.executeConstructor) + if (functionId !== contractComboBox.currentValue()) loadParameters(); - else - { - var parameters = codeModel.code.contract.constructor.parameters; - for (var p = 0; p < parameters.length; p++) - loadParameter(parameters[p]); + else { + var contract = codeModel.contracts[contractId]; + if (contract) { + var parameters = contract.contract.constructor.parameters; + for (var p = 0; p < parameters.length; p++) + loadParameter(parameters[p]); + } } modalTransactionDialog.setX((Screen.width - width) / 2); modalTransactionDialog.setY((Screen.height - height) / 2); @@ -73,6 +88,21 @@ Window { valueField.focus = true; } + function loadFunctions(contractId) + { + functionsModel.clear(); + var contract = codeModel.contracts[contractId]; + if (contract) { + var functions = codeModel.contracts[contractId].contract.functions; + for (var f = 0; f < functions.length; f++) { + functionsModel.append({ text: functions[f].name }); + } + } + //append constructor + functionsModel.append({ text: contractId }); + + } + function loadParameter(parameter) { var type = parameter.type; @@ -104,10 +134,15 @@ Window { if (!paramsModel) return; if (functionComboBox.currentIndex >= 0 && functionComboBox.currentIndex < functionsModel.count) { - var func = codeModel.code.contract.functions[functionComboBox.currentIndex]; - var parameters = func.parameters; - for (var p = 0; p < parameters.length; p++) - loadParameter(parameters[p]); + var contract = codeModel.contracts[contractComboBox.currentValue()]; + if (contract) { + var func = contract.contract.functions[functionComboBox.currentIndex]; + if (func) { + var parameters = func.parameters; + for (var p = 0; p < parameters.length; p++) + loadParameter(parameters[p]); + } + } } } @@ -140,24 +175,21 @@ Window { if (!useTransactionDefaultValue) { item = { + contractId: transactionDialog.contractId, functionId: transactionDialog.functionId, gas: transactionDialog.gas, gasPrice: transactionDialog.gasPrice, value: transactionDialog.transactionValue, parameters: {}, - executeConstructor: isConstructorTransaction }; } else { item = TransactionHelper.defaultTransaction(); + item.contractId = transactionDialog.contractId; item.functionId = transactionDialog.functionId; - item.executeConstructor = isConstructorTransaction; } - if (isConstructorTransaction) - item.functionId = qsTr("Constructor"); - var orderedQType = []; for (var p = 0; p < transactionDialog.transactionParams.count; p++) { var parameter = transactionDialog.transactionParams.get(p); @@ -178,6 +210,33 @@ Window { id: dialogContent anchors.top: parent.top spacing: 10 + RowLayout + { + id: rowContract + Layout.fillWidth: true + height: 150 + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Contract") + } + ComboBox { + id: contractComboBox + function currentValue() { + return (currentIndex >=0 && currentIndex < contractsModel.count) ? contractsModel.get(currentIndex).cid : ""; + } + Layout.preferredWidth: 350 + currentIndex: -1 + textRole: "text" + editable: false + model: ListModel { + id: contractsModel + } + onCurrentIndexChanged: { + loadFunctions(currentValue()); + } + } + } + RowLayout { id: rowFunction diff --git a/mix/qml/WebPreview.qml b/mix/qml/WebPreview.qml index 5d4d0e617..9886a71c6 100644 --- a/mix/qml/WebPreview.qml +++ b/mix/qml/WebPreview.qml @@ -29,7 +29,16 @@ Item { } function updateContract() { - webView.runJavaScript("updateContract(\"" + codeModel.code.contract.name + "\", \"" + clientModel.contractAddress + "\", " + codeModel.code.contractInterface + ")"); + var contracts = {}; + for (var c in codeModel.contracts) { + var contract = codeModel.contracts[c]; + contracts[c] = { + name: contract.contract.name, + address: clientModel.contractAddresses[contract.contract.name], + interface: JSON.parse(contract.contractInterface), + }; + } + webView.runJavaScript("updateContracts(" + JSON.stringify(contracts) + ")"); } function reloadOnSave() { @@ -62,7 +71,6 @@ Item { Connections { target: clientModel - onContractAddressChanged: reload(); onRunComplete: reload(); } diff --git a/mix/qml/html/WebContainer.html b/mix/qml/html/WebContainer.html index 09a8734d5..26cac5103 100644 --- a/mix/qml/html/WebContainer.html +++ b/mix/qml/html/WebContainer.html @@ -15,16 +15,18 @@ reloadPage = function() { preview.contentWindow.location.reload(); }; -updateContract = function(name, address, contractFace) { +updateContracts = function(contracts) { if (window.web3) { window.web3.provider.polls = []; - var contract = window.web3.eth.contract(address, contractFace); window.contracts = {}; - window.contracts[name] = { - address: address, - interface: contractFace, - contract: contract, - }; + for (var c in contracts) { + var contract = window.web3.eth.contract(contracts[c].address, contracts[c].interface); + window.contracts[c] = { + address: c.address, + interface: c.interface, + contract: contract, + }; + } } }; diff --git a/mix/qml/js/ProjectModel.js b/mix/qml/js/ProjectModel.js index be6c07c5b..5a7bf4332 100644 --- a/mix/qml/js/ProjectModel.js +++ b/mix/qml/js/ProjectModel.js @@ -20,6 +20,9 @@ * Ethereum IDE client. */ +var htmlTemplate = "\n\n\n\n\n\n\n"; +var contractTemplate = "contract Contract {\n}\n"; + function saveAll() { saveProject(); } @@ -76,6 +79,16 @@ function loadProject(path) { projectSettings.lastProjectPath = path; projectLoading(projectData); projectLoaded() + + //TODO: move this to codemodel + var contractSources = {}; + for (var d = 0; d < listModel.count; d++) { + var doc = listModel.get(d); + if (doc.isContract) + contractSources[doc.documentId] = fileIo.readFile(doc.path); + } + codeModel.reset(contractSources); + } function addFile(fileName) { @@ -92,7 +105,7 @@ function addFile(fileName) { contract: false, path: p, fileName: fileName, - name: isContract ? "Contract" : fileName, + name: fileName, documentId: fileName, syntaxMode: syntaxMode, isText: isContract || isHtml || isCss || isJs, @@ -150,7 +163,7 @@ function openPrevDocument() { } function doCloseProject() { - console.log("closing project"); + console.log("Closing project"); projectListModel.clear(); projectPath = ""; currentDocumentId = ""; @@ -167,14 +180,14 @@ function doCreateProject(title, path) { var projectFile = dirPath + projectFileName; var indexFile = "index.html"; - var contractsFile = "contracts.sol"; + var contractsFile = "contract.sol"; var projectData = { title: title, files: [ contractsFile, indexFile ] }; //TODO: copy from template - fileIo.writeFile(dirPath + indexFile, "\n\n\n\n\n\n\n"); - fileIo.writeFile(dirPath + contractsFile, "contract Contract {\n}\n"); + fileIo.writeFile(dirPath + indexFile, htmlTemplate); + fileIo.writeFile(dirPath + contractsFile, contractTemplate); newProject(projectData); var json = JSON.stringify(projectData, null, "\t"); fileIo.writeFile(projectFile, json); @@ -222,7 +235,7 @@ function removeDocument(documentId) { } function newHtmlFile() { - createAndAddFile("page", "html", "\n"); + createAndAddFile("page", "html", htmlTemplate); } function newCssFile() { @@ -233,6 +246,11 @@ function newJsFile() { createAndAddFile("script", "js", "function foo() {\n}\n"); } +function newContract() { + createAndAddFile("contract", "sol", contractTemplate); +} + + function createAndAddFile(name, extension, content) { var fileName = generateFileName(name, extension); var filePath = projectPath + fileName; @@ -267,15 +285,21 @@ function deployProject(force) { var jsonRpcUrl = "http://localhost:8080"; console.log("Deploying " + deploymentId + " to " + jsonRpcUrl); deploymentStarted(); - var code = codeModel.codeHex - var rpcRequest = JSON.stringify({ - jsonrpc: "2.0", - method: "eth_transact", - params: [ { - "code": code - } ], - id: jsonRpcRequestId++ - }); + + var requests = []; + var requestNames = []; + for (var c in codeModel.contracts) { //TODO: order based on dependencies + var code = codeModel.contracts[c].codeHex; + requests.push({ + jsonrpc: "2.0", + method: "eth_transact", + params: [ { "code": code } ], + id: jsonRpcRequestId++ + }); + requestNames.push(c); + } + + var rpcRequest = JSON.stringify(requests);; var httpRequest = new XMLHttpRequest() httpRequest.open("POST", jsonRpcUrl, true); httpRequest.setRequestHeader("Content-type", "application/json"); @@ -285,9 +309,12 @@ function deployProject(force) { if (httpRequest.readyState === XMLHttpRequest.DONE) { if (httpRequest.status === 200) { var rpcResponse = JSON.parse(httpRequest.responseText); - var address = rpcResponse.result; - console.log("Created contract, address: " + address); - finalizeDeployment(deploymentId, address); + if (rpcResponse.length === requestNames.length) { + var contractAddresses = {}; + for (var r = 0; r < rpcResponse.lenght; r++) + contractAddresses[requestNames[r]] = rpcResponse.result; + finalizeDeployment(deploymentId, contractAddresses); + } } else { var errorText = qsTr("Deployment error: RPC server HTTP status ") + httpRequest.status; console.log(errorText); @@ -298,7 +325,7 @@ function deployProject(force) { httpRequest.send(rpcRequest); } -function finalizeDeployment(deploymentId, address) { +function finalizeDeployment(deploymentId, addresses) { //create a dir for frontend files and copy them var deploymentDir = projectPath + deploymentId + "/"; fileIo.makeDir(deploymentDir); @@ -326,16 +353,18 @@ function finalizeDeployment(deploymentId, address) { fileIo.copyFile(doc.path, deploymentDir + doc.fileName); } //write deployment js - var contractAccessor = "contracts[\"" + codeModel.code.contract.name + "\"]"; var deploymentJs = "// Autogenerated by Mix\n" + "web3 = require(\"web3\");\n" + - "contracts = {};\n" + - contractAccessor + " = {\n" + - "\tinterface: " + codeModel.code.contractInterface + ",\n" + - "\taddress: \"" + address + "\"\n" + + "contracts = {};\n"; + for (var c in codeModel.contracts) { + var contractAccessor = "contracts[\"" + codeModel.contracts[c].contract.name + "\"]"; + deploymentJs += contractAccessor + " = {\n" + + "\tinterface: " + codeModel.contracts[c].contractInterface + ",\n" + + "\taddress: \"" + addresses[c] + "\"\n" + "};\n" + contractAccessor + ".contract = web3.eth.contract(" + contractAccessor + ".address, " + contractAccessor + ".interface);\n"; + } fileIo.writeFile(deploymentDir + "deployment.js", deploymentJs); //copy scripts fileIo.copyFile("qrc:///js/bignumber.min.js", deploymentDir + "bignumber.min.js"); diff --git a/mix/qml/js/TransactionHelper.js b/mix/qml/js/TransactionHelper.js index 87dd74beb..62399161e 100644 --- a/mix/qml/js/TransactionHelper.js +++ b/mix/qml/js/TransactionHelper.js @@ -7,7 +7,6 @@ function defaultTransaction() functionId: "", gas: createBigInt("125000"), gasPrice: createEther("100000", QEther.Wei), - executeConstructor: false, parameters: {} }; } diff --git a/mix/qml/main.qml b/mix/qml/main.qml index 71d8c24bf..a1cf99de6 100644 --- a/mix/qml/main.qml +++ b/mix/qml/main.qml @@ -30,7 +30,7 @@ ApplicationWindow { MenuItem { action: addNewHtmlFileAction } MenuItem { action: addNewCssFileAction } MenuSeparator {} - //MenuItem { action: addNewContractAction } + MenuItem { action: addNewContractAction } MenuItem { action: closeProjectAction } MenuSeparator {} MenuItem { action: exitAppAction } From 37fa06106b651e1a50c2ea3f34164e19622d74df Mon Sep 17 00:00:00 2001 From: arkpar Date: Tue, 17 Feb 2015 09:23:23 +0100 Subject: [PATCH 2/3] fixed cppcheck warnings --- mix/ClientModel.cpp | 3 +-- mix/CodeHighlighter.cpp | 2 +- mix/CodeModel.cpp | 8 ++++---- mix/ContractCallDataEncoder.cpp | 2 +- mix/MixClient.cpp | 4 ++-- mix/QVariableDefinition.h | 4 ++-- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index 91e5c98ab..45198c114 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -216,7 +216,6 @@ void ClientModel::executeSequence(std::vector const& _seque for (TransactionSettings const& transaction: _sequence) { ContractCallDataEncoder encoder; - QFunctionDefinition const* f = nullptr; if (!transaction.stdContractUrl.isEmpty()) { //std contract @@ -229,9 +228,9 @@ void ClientModel::executeSequence(std::vector const& _seque { //encode data CompiledContract const& compilerRes = m_context->codeModel()->contract(transaction.contractId); + QFunctionDefinition const* f = nullptr; bytes contractCode = compilerRes.bytes(); std::shared_ptr contractDef = compilerRes.sharedContract(); - f = nullptr; if (transaction.functionId.isEmpty()) f = contractDef->constructor(); else diff --git a/mix/CodeHighlighter.cpp b/mix/CodeHighlighter.cpp index d76d8b73e..86dfe9e5d 100644 --- a/mix/CodeHighlighter.cpp +++ b/mix/CodeHighlighter.cpp @@ -119,7 +119,7 @@ void CodeHighlighter::processComments(std::string const& _source) //add single line comment int start = i; i += 2; - while (_source[i] != '\n' && i < size) + while (i < size && _source[i] != '\n') ++i; m_formats.push_back(FormatRange(CodeHighlighterSettings::Comment, start, i - start)); } diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index d605f1c6e..7a38594ce 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -108,7 +108,7 @@ void CodeModel::reset(QVariantMap const& _documents) Guard pl(x_pendingContracts); m_pendingContracts.clear(); - for (QVariantMap::const_iterator d = _documents.cbegin(); d != _documents.cend(); d++) + for (QVariantMap::const_iterator d = _documents.cbegin(); d != _documents.cend(); ++d) m_pendingContracts[d.key()] = d.value().toString(); // launch the background thread m_compiling = true; @@ -138,7 +138,7 @@ QVariantMap CodeModel::contracts() const { QVariantMap result; Guard l(x_contractMap); - for (ContractMap::const_iterator c = m_contractMap.cbegin(); c != m_contractMap.cend(); c++) + for (ContractMap::const_iterator c = m_contractMap.cbegin(); c != m_contractMap.cend(); ++c) result.insert(c.key(), QVariant::fromValue(c.value())); return result; } @@ -146,7 +146,7 @@ QVariantMap CodeModel::contracts() const CompiledContract* CodeModel::contractByDocumentId(QString _documentId) const { Guard l(x_contractMap); - for (ContractMap::const_iterator c = m_contractMap.cbegin(); c != m_contractMap.cend(); c++) + for (ContractMap::const_iterator c = m_contractMap.cbegin(); c != m_contractMap.cend(); ++c) if (c.value()->m_documentId == _documentId) return c.value(); return nullptr; @@ -163,7 +163,7 @@ CompiledContract const& CodeModel::contract(QString _name) const void CodeModel::releaseContracts() { - for (ContractMap::iterator c = m_contractMap.begin(); c != m_contractMap.end(); c++) + for (ContractMap::iterator c = m_contractMap.begin(); c != m_contractMap.end(); ++c) c.value()->deleteLater(); m_contractMap.clear(); } diff --git a/mix/ContractCallDataEncoder.cpp b/mix/ContractCallDataEncoder.cpp index 19ece2a4d..e31f79e9f 100644 --- a/mix/ContractCallDataEncoder.cpp +++ b/mix/ContractCallDataEncoder.cpp @@ -56,7 +56,7 @@ QList ContractCallDataEncoder::decode(QList r; for (int k = 0; k <_returnParameters.length(); k++) { - QVariableDeclaration* dec = (QVariableDeclaration*)_returnParameters.at(k); + QVariableDeclaration* dec = static_cast(_returnParameters.at(k)); QVariableDefinition* def = nullptr; if (dec->type().contains("int")) def = new QIntType(dec, QString()); diff --git a/mix/MixClient.cpp b/mix/MixClient.cpp index b2a367929..3b0a2a03b 100644 --- a/mix/MixClient.cpp +++ b/mix/MixClient.cpp @@ -117,8 +117,8 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c 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; + VM& vm = *static_cast(voidVM); + ExtVM const& ext = *static_cast(voidExt); if (lastCode == nullptr || lastCode != &ext.code) { auto const& iter = codeIndexes.find(&ext.code); diff --git a/mix/QVariableDefinition.h b/mix/QVariableDefinition.h index ae9bf9459..1825c2567 100644 --- a/mix/QVariableDefinition.h +++ b/mix/QVariableDefinition.h @@ -135,8 +135,8 @@ class QBoolType: public QVariableDefinition Q_OBJECT public: - QBoolType() {} - QBoolType(QVariableDeclaration* _def, QString _value): QVariableDefinition(_def, _value) {} + QBoolType(): m_boolValue(false) {} + QBoolType(QVariableDeclaration* _def, QString _value): QVariableDefinition(_def, _value), m_boolValue(false) {} dev::bytes encodeValue() override; void decodeValue(dev::bytes const& _rawValue) override; /// @returns the boolean value for the current definition. From 9e87abc136b31ae42a332e104900cc3eaccadf05 Mon Sep 17 00:00:00 2001 From: arkpar Date: Tue, 17 Feb 2015 11:14:53 +0100 Subject: [PATCH 3/3] minor fixes --- mix/qml/StateListModel.qml | 9 ++++++++- mix/qml/WebPreview.qml | 12 +++++++----- mix/qml/main.qml | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/mix/qml/StateListModel.qml b/mix/qml/StateListModel.qml index da02ce5e6..52b0d542d 100644 --- a/mix/qml/StateListModel.qml +++ b/mix/qml/StateListModel.qml @@ -47,6 +47,10 @@ Item { varComponent = Qt.createComponent("qrc:/qml/QHashType.qml"); else if (type.indexOf("bool") !== -1) varComponent = Qt.createComponent("qrc:/qml/QBoolType.qml"); + else { + console.log("Unknown parameter type: " + type); + continue; + } var param = varComponent.createObject(stateListModel); var dec = Qt.createComponent("qrc:/qml/QVariableDeclaration.qml"); @@ -203,7 +207,7 @@ Item { } function debugDefaultState() { - if (defaultStateIndex >= 0) + if (defaultStateIndex >= 0 && defaultStateIndex < stateList.length) runState(defaultStateIndex); } @@ -221,6 +225,9 @@ Item { defaultStateIndex = 0; defaultStateChanged(); } + else if (defaultStateIndex > index) + defaultStateIndex--; + save(); } diff --git a/mix/qml/WebPreview.qml b/mix/qml/WebPreview.qml index 9886a71c6..0d0d70d31 100644 --- a/mix/qml/WebPreview.qml +++ b/mix/qml/WebPreview.qml @@ -24,8 +24,10 @@ Item { } function reload() { - updateContract(); - webView.runJavaScript("reloadPage()"); + if (initialized) { + updateContract(); + webView.runJavaScript("reloadPage()"); + } } function updateContract() { @@ -33,9 +35,9 @@ Item { for (var c in codeModel.contracts) { var contract = codeModel.contracts[c]; contracts[c] = { - name: contract.contract.name, - address: clientModel.contractAddresses[contract.contract.name], - interface: JSON.parse(contract.contractInterface), + name: contract.contract.name, + address: clientModel.contractAddresses[contract.contract.name], + interface: JSON.parse(contract.contractInterface), }; } webView.runJavaScript("updateContracts(" + JSON.stringify(contracts) + ")"); diff --git a/mix/qml/main.qml b/mix/qml/main.qml index a1cf99de6..c9e8c8225 100644 --- a/mix/qml/main.qml +++ b/mix/qml/main.qml @@ -95,7 +95,7 @@ ApplicationWindow { text: qsTr("Mine") shortcut: "Ctrl+M" onTriggered: clientModel.mine(); - enabled: codeModel.hasContract && !clientModel.running &&!clientModel.mining + enabled: codeModel.hasContract && !clientModel.running && !clientModel.mining } StateList {