diff --git a/libjsqrc/ethereumjs/dist/ethereum.js b/libjsqrc/ethereumjs/dist/ethereum.js index fb9101aef..e9fb14f87 100644 --- a/libjsqrc/ethereumjs/dist/ethereum.js +++ b/libjsqrc/ethereumjs/dist/ethereum.js @@ -1465,10 +1465,8 @@ var WebSocketProvider = function(host) { /// Response for the call will be received by ws.onmessage /// @param payload is inner message object WebSocketProvider.prototype.send = function(payload) { - console.log("wsSend"); if (this.ready) { var data = JSON.stringify(payload); - console.log(payload); this.ws.send(data); } else { diff --git a/mix/CMakeLists.txt b/mix/CMakeLists.txt index 92638d5f4..3ba87999b 100644 --- a/mix/CMakeLists.txt +++ b/mix/CMakeLists.txt @@ -20,10 +20,10 @@ file(GLOB HEADERS "*.h") set(EXECUTABLE mix) if ("${Qt5WebEngine_VERSION_STRING}" VERSION_GREATER "5.3.0") - set (ETH_HAVE_WEBENGINE TRUE) - qt5_add_resources(UI_RESOURCES web.qrc) + set (ETH_HAVE_WEBENGINE TRUE) + qt5_add_resources(UI_RESOURCES web.qrc) else() - qt5_add_resources(UI_RESOURCES noweb.qrc) + qt5_add_resources(UI_RESOURCES noweb.qrc) endif() @@ -47,12 +47,12 @@ target_link_libraries(${EXECUTABLE} lll) target_link_libraries(${EXECUTABLE} solidity) target_link_libraries(${EXECUTABLE} evmcore) target_link_libraries(${EXECUTABLE} devcore) +target_link_libraries(${EXECUTABLE} jsqrc) +target_link_libraries(${EXECUTABLE} web3jsonrpc) if (${ETH_HAVE_WEBENGINE}) - add_definitions(-DETH_HAVE_WEBENGINE) - target_link_libraries(${EXECUTABLE} Qt5::WebEngine) - target_link_libraries(${EXECUTABLE} jsqrc) - target_link_libraries(${EXECUTABLE} web3jsonrpc) + add_definitions(-DETH_HAVE_WEBENGINE) + target_link_libraries(${EXECUTABLE} Qt5::WebEngine) endif() # eth_install_executable is defined in cmake/EthExecutableHelper.cmake diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index eb61c8554..6412e347c 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -24,7 +24,7 @@ #include #include #include -#include "ClientModel.h" +#include #include "AppContext.h" #include "DebuggingStateWrapper.h" #include "QContractDefinition.h" @@ -33,13 +33,15 @@ #include "CodeModel.h" #include "ClientModel.h" #include "QEther.h" +#include "Web3Server.h" +#include "ClientModel.h" using namespace dev; using namespace dev::eth; using namespace dev::mix; ClientModel::ClientModel(AppContext* _context): - m_context(_context), m_running(false) + m_context(_context), m_running(false), m_qWebThree(nullptr) { qRegisterMetaType("QBigInt*"); qRegisterMetaType("QEther*"); @@ -53,9 +55,30 @@ ClientModel::ClientModel(AppContext* _context): connect(this, &ClientModel::dataAvailable, 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); + _context->appEngine()->rootContext()->setContextProperty("clientModel", this); } +ClientModel::~ClientModel() +{ +} + +void ClientModel::apiRequest(const QString& _message) +{ + m_qWebThree->postMessage(_message); +} + +QString ClientModel::contractAddress() const +{ + QString address = QString::fromStdString(dev::toJS(m_client->lastContractAddress())); + return address; +} + void ClientModel::debugDeployment() { executeSequence(std::vector(), 10000000 * ether); @@ -195,9 +218,11 @@ ExecutionResult ClientModel::deployContract(bytes const& _code) u256 gas = 125000; u256 amount = 100; - Address contractAddress = m_client->transact(m_client->userAccount().secret(), amount, _code, gas, gasPrice); + Address lastAddress = m_client->lastContractAddress(); + Address newAddress = m_client->transact(m_client->userAccount().secret(), amount, _code, gas, gasPrice); ExecutionResult r = m_client->lastExecutionResult(); - r.contractAddress = contractAddress; + if (newAddress != lastAddress) + contractAddressChanged(); return r; } diff --git a/mix/ClientModel.h b/mix/ClientModel.h index 0e7f9c092..f74206245 100644 --- a/mix/ClientModel.h +++ b/mix/ClientModel.h @@ -32,12 +32,16 @@ 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 { class AppContext; +class Web3Server; /// Backend transaction config class struct TransactionSettings @@ -67,10 +71,16 @@ class ClientModel: public QObject public: ClientModel(AppContext* _context); - + ~ClientModel(); + /// @returns true if currently executing contract code Q_PROPERTY(bool running MEMBER m_running NOTIFY stateChanged) + /// @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); /// Run the contract constructor and show debugger window. void debugDeployment(); /// Setup state, run transaction sequence, show debugger for the last transaction @@ -91,15 +101,21 @@ signals: /// Transaction execution completed with error /// @param _message Error message void runFailed(QString const& _message); + /// Contract address changed + void contractAddressChanged(); /// Execution state changed void stateChanged(); /// Show debugger window request void showDebuggerWindow(); + /// 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()); 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); @@ -107,6 +123,9 @@ private: AppContext* m_context; std::atomic m_running; std::unique_ptr m_client; + QWebThree* m_qWebThree; + std::unique_ptr m_qWebThreeConnector; + std::unique_ptr m_web3Server; }; } diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index 699e0753d..90326601c 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "QContractDefinition.h" #include "QFunctionDefinition.h" @@ -55,9 +56,12 @@ CompilationResult::CompilationResult(const dev::solidity::CompilerStack& _compil { if (!_compiler.getContractNames().empty()) { - m_contract.reset(new QContractDefinition(&_compiler.getContractDefinition(std::string()))); + auto const& contractDefinition = _compiler.getContractDefinition(std::string()); + m_contract.reset(new QContractDefinition(&contractDefinition)); m_bytes = _compiler.getBytecode(); m_assemblyCode = QString::fromStdString(dev::eth::disassemble(m_bytes)); + dev::solidity::InterfaceHandler interfaceHandler; + m_contractDefinition = QString::fromStdString(*interfaceHandler.getABIInterface(contractDefinition)); } else m_contract.reset(new QContractDefinition()); @@ -71,6 +75,7 @@ CompilationResult::CompilationResult(CompilationResult const& _prev, QString con m_compilerMessage(_compilerMessage), m_bytes(_prev.m_bytes), m_assemblyCode(_prev.m_assemblyCode), + m_contractDefinition(_prev.m_contractDefinition), m_codeHighlighter(_prev.m_codeHighlighter) {} @@ -156,14 +161,19 @@ void CodeModel::runCompilationJob(int _jobId, QString const& _code) emit compilationCompleteInternal(result.release()); } -void CodeModel::onCompilationComplete(CompilationResult*_newResult) +void CodeModel::onCompilationComplete(CompilationResult* _newResult) { m_compiling = false; + bool contractChanged = m_result->contractDefinition() != _newResult->contractDefinition(); m_result.reset(_newResult); emit compilationComplete(); emit stateChanged(); if (m_result->successful()) + { emit codeChanged(); + if (contractChanged) + emit contractDefinitionChanged(); + } } bool CodeModel::hasContract() const diff --git a/mix/CodeModel.h b/mix/CodeModel.h index 0c1de2a90..d747c530b 100644 --- a/mix/CodeModel.h +++ b/mix/CodeModel.h @@ -67,6 +67,7 @@ class CompilationResult: public QObject Q_PROPERTY(QContractDefinition* contract READ contract) Q_PROPERTY(QString compilerMessage READ compilerMessage CONSTANT) Q_PROPERTY(bool successful READ successful CONSTANT) + Q_PROPERTY(QString contractDefinition READ contractDefinition CONSTANT) public: /// Empty compilation result constructor @@ -88,6 +89,8 @@ public: dev::bytes const& bytes() const { return m_bytes; } /// @returns contract bytecode in human-readable form QString assemblyCode() const { return m_assemblyCode; } + /// @returns contract definition in JSON format + QString contractDefinition() const { return m_contractDefinition; } /// Get code highlighter std::shared_ptr codeHighlighter() { return m_codeHighlighter; } @@ -98,6 +101,7 @@ private: QString m_compilerMessage; ///< @todo: use some structure here dev::bytes m_bytes; QString m_assemblyCode; + QString m_contractDefinition; std::shared_ptr m_codeHighlighter; friend class CodeModel; @@ -137,6 +141,8 @@ signals: void scheduleCompilationJob(int _jobId, QString const& _content); /// Emitted if there are any changes in the code model void codeChanged(); + /// Emitted if there are any changes in the contract interface + void contractDefinitionChanged(); /// Emitted on compilation complete. Internal void compilationCompleteInternal(CompilationResult* _newResult); diff --git a/mix/MixClient.cpp b/mix/MixClient.cpp index dde6b7b89..39e876dfc 100644 --- a/mix/MixClient.cpp +++ b/mix/MixClient.cpp @@ -88,6 +88,7 @@ void MixClient::executeTransaction(bytesConstRef _rlp, State& _state) d.executionData = data; d.contentAvailable = true; d.message = "ok"; + d.contractAddress = m_lastExecutionResult.contractAddress; m_lastExecutionResult = d; } @@ -113,7 +114,9 @@ Address MixClient::transact(Secret _secret, u256 _endowment, bytes const& _init, eth::Transaction t(_endowment, _gasPrice, _gas, _init, n, _secret); bytes rlp = t.rlp(); executeTransaction(&rlp, m_state); - return right160(sha3(rlpList(t.sender(), t.nonce()))); + Address address = right160(sha3(rlpList(t.sender(), t.nonce()))); + m_lastExecutionResult.contractAddress = address; + return address; } void MixClient::inject(bytesConstRef _rlp) @@ -128,7 +131,6 @@ void MixClient::flushTransactions() bytes MixClient::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) { - bytes out; u256 n; State temp; { diff --git a/mix/MixClient.h b/mix/MixClient.h index c22d845d7..f615d7afc 100644 --- a/mix/MixClient.h +++ b/mix/MixClient.h @@ -70,6 +70,7 @@ public: 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; } //dev::eth::Interface void transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) override; diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index 42a780288..145a38cea 100644 --- a/mix/qml/MainContent.qml +++ b/mix/qml/MainContent.qml @@ -17,26 +17,30 @@ Rectangle { anchors.fill: parent id: root - function toggleRightView() - { + property alias rightViewVisible : rightView.visible + property alias webViewVisible : webPreview.visible + + function toggleRightView() { if (!rightView.visible) rightView.show(); else rightView.hide(); } - function ensureRightView() - { + function ensureRightView() { if (!rightView.visible) rightView.show(); } - function hideRightView() - { + function hideRightView() { if (rightView.visible) rightView.hide(); } + function toggleWebPreview() { + webPreview.visible = !webPreview.visible; + } + CodeEditorExtensionManager { headerView: headerPaneTabs; rightView: rightPaneTabs; @@ -102,11 +106,13 @@ Rectangle { CodeEditorView { height: parent.height * 0.6 anchors.top: parent.top - width: parent.width + Layout.fillWidth: true + Layout.fillHeight: true } WebPreview { + id: webPreview height: parent.height * 0.4 - width: parent.width + Layout.fillWidth: true } } } diff --git a/mix/qml/WebPreview.qml b/mix/qml/WebPreview.qml index 80d0f7e43..bf5d32adc 100644 --- a/mix/qml/WebPreview.qml +++ b/mix/qml/WebPreview.qml @@ -5,7 +5,7 @@ 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 QtWebEngine.experimental 1.0 Item { id: webPreview @@ -17,14 +17,20 @@ Item { pendingPageUrl = url; else { pendingPageUrl = ""; + updateContract(); webView.runJavaScript("loadPage(\"" + url + "\")"); } } function reload() { + updateContract(); webView.runJavaScript("reloadPage()"); } + function updateContract() { + webView.runJavaScript("updateContract(\"" + clientModel.contractAddress + "\", " + codeModel.code.contractDefinition + ")"); + } + function reloadOnSave() { if (autoReloadOnSave.checked) reload(); @@ -53,13 +59,21 @@ Item { } } + Connections { + target: clientModel + onContractAddressChanged: reload(); + } + + Connections { + target: codeModel + onContractDefinitionChanged: reload(); + } + Connections { target: projectModel onProjectSaved : reloadOnSave(); onDocumentSaved: reloadOnSave(); onDocumentAdded: { - console.log("added") - console.log(documentId) var document = projectModel.getDocument(documentId) if (document.isHtml) pageListModel.append(document); @@ -98,7 +112,12 @@ Item { onClientConnected: { webSocket.onTextMessageReceived.connect(function(message) { - console.log("rpc: " + message); + console.log("rpc_request: " + message); + clientModel.apiRequest(message); + }); + clientModel.onApiResponse.connect(function(message) { + console.log("rpc_response: " + message); + webSocket.sendTextMessage(message); }); } } @@ -134,8 +153,8 @@ Item { Layout.fillWidth: true Layout.fillHeight: true id: webView - experimental.settings.localContentCanAccessFileUrls: true - experimental.settings.localContentCanAccessRemoteUrls: true + //experimental.settings.localContentCanAccessFileUrls: true + //experimental.settings.localContentCanAccessRemoteUrls: true onJavaScriptConsoleMessage: { console.log(sourceID + ":" + lineNumber + ":" + message); } diff --git a/mix/qml/html/WebContainer.html b/mix/qml/html/WebContainer.html index 2a81985bc..cd8d2614a 100644 --- a/mix/qml/html/WebContainer.html +++ b/mix/qml/html/WebContainer.html @@ -16,11 +16,19 @@ reloadPage = function() { preview.contentWindow.location.reload(); }; +updateContract = function(address, definition) { + if (window.web3) { + window.contractAddress = address; + window.contractDefinition = definition; + window.contract = window.web3.eth.contract(address, definition); + } +}; + init = function(url) { web3 = require('web3'); web3.setProvider(new web3.providers.WebSocketProvider(url)); window.web3 = web3; -} +}; diff --git a/mix/qml/main.qml b/mix/qml/main.qml index e234d6344..6baa6fa76 100644 --- a/mix/qml/main.qml +++ b/mix/qml/main.qml @@ -88,10 +88,21 @@ ApplicationWindow { onTriggered: clientModel.resetState(); } + Action { + id: toggleWebPreview + text: "Show/Hide web view" + shortcut: "F2" + checkable: true + checked: mainContent.webViewVisible + onTriggered: mainContent.toggleWebPreview(); + } + Action { id: showHideRightPanel - text: "Show/Hide right view" + text: "Show right view" shortcut: "F7" + checkable: true + checked: mainContent.rightViewVisible onTriggered: mainContent.toggleRightView(); }