diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index 41aa55249..8f8df4f5d 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -216,7 +216,10 @@ void ClientModel::setupState(QVariantMap _state) void ClientModel::executeSequence(vector const& _sequence, map const& _balances) { if (m_running) - BOOST_THROW_EXCEPTION(ExecutionStateException()); + { + qWarning() << "Waiting for current execution to complete"; + m_runFuture.waitForFinished(); + } m_running = true; emit runStarted(); diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index a5a0255d2..e99763be3 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -269,7 +269,10 @@ void CodeModel::runCompilationJob(int _jobId) if (c_predefinedContracts.count(n) != 0) continue; QString name = QString::fromStdString(n); - QString sourceName = QString::fromStdString(*cs.getContractDefinition(n).getLocation().sourceName); + ContractDefinition const& contractDefinition = cs.getContractDefinition(n); + if (!contractDefinition.isFullyImplemented()) + continue; + QString sourceName = QString::fromStdString(*contractDefinition.getLocation().sourceName); auto sourceIter = m_pendingContracts.find(sourceName); QString source = sourceIter != m_pendingContracts.end() ? sourceIter->second : QString(); CompiledContract* contract = new CompiledContract(cs, name, source); diff --git a/mix/qml.qrc b/mix/qml.qrc index 01074edb3..6cbc97a78 100644 --- a/mix/qml.qrc +++ b/mix/qml.qrc @@ -48,8 +48,8 @@ qml/StatusPaneStyle.qml qml/StepActionImage.qml qml/StorageView.qml - qml/StatesComboBox.qml - qml/StructView.qml + qml/StatesComboBox.qml + qml/StructView.qml qml/Style.qml qml/TabStyle.qml qml/TransactionDialog.qml @@ -63,5 +63,6 @@ qml/js/TransactionHelper.js qml/js/Printer.js qml/js/ansi2html.js + qml/js/NetworkDeployment.js diff --git a/mix/qml/DeploymentDialog.qml b/mix/qml/DeploymentDialog.qml index 1e230f088..9235bfaab 100644 --- a/mix/qml/DeploymentDialog.qml +++ b/mix/qml/DeploymentDialog.qml @@ -6,7 +6,7 @@ import QtQuick.Dialogs 1.2 import QtQuick.Controls.Styles 1.3 import org.ethereum.qml.QEther 1.0 import "js/TransactionHelper.js" as TransactionHelper -import "js/ProjectModel.js" as ProjectModelCode +import "js/NetworkDeployment.js" as NetworkDeploymentCode import "js/QEtherHelper.js" as QEtherHelper import "." @@ -356,7 +356,7 @@ Dialog { tooltip: qsTr("Deploy contract(s) and Package resources files.") onTriggered: { var inError = []; - var ethUrl = ProjectModelCode.formatAppUrl(applicationUrlEth.text); + var ethUrl = NetworkDeploymentCode.formatAppUrl(applicationUrlEth.text); for (var k in ethUrl) { if (ethUrl[k].length > 32) @@ -367,7 +367,7 @@ Dialog { if (contractRedeploy.checked) deployWarningDialog.open(); else - ProjectModelCode.startDeployProject(false); + NetworkDeploymentCode.startDeployProject(false); } } } diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index ca08485c3..67f45f973 100644 --- a/mix/qml/MainContent.qml +++ b/mix/qml/MainContent.qml @@ -24,6 +24,7 @@ Rectangle { property alias webViewVisible: webPreview.visible property alias webView: webPreview property alias projectViewVisible: projectList.visible + property alias projectNavigator: projectList property alias runOnProjectLoad: mainSettings.runOnProjectLoad property alias rightPane: rightView property alias codeEditor: codeEditor diff --git a/mix/qml/ProjectList.qml b/mix/qml/ProjectList.qml index a98c2587b..f3f844d71 100644 --- a/mix/qml/ProjectList.qml +++ b/mix/qml/ProjectList.qml @@ -8,6 +8,7 @@ import "." Item { property bool renameMode: false; + property alias sections: sectionRepeater ProjectFilesStyle { id: projectFilesStyle diff --git a/mix/qml/ProjectModel.qml b/mix/qml/ProjectModel.qml index 509ef3719..b15c996fb 100644 --- a/mix/qml/ProjectModel.qml +++ b/mix/qml/ProjectModel.qml @@ -5,6 +5,7 @@ import QtQuick.Controls 1.0 import QtQuick.Dialogs 1.1 import Qt.labs.settings 1.0 import "js/ProjectModel.js" as ProjectModelCode +import "js/NetworkDeployment.js" as NetworkDeploymentCode Item { id: projectModel @@ -69,9 +70,9 @@ Item { function getDocumentIdByName(documentName) { return ProjectModelCode.getDocumentIdByName(documentName); } function getDocumentIndex(documentId) { return ProjectModelCode.getDocumentIndex(documentId); } function addExistingFiles(paths) { ProjectModelCode.doAddExistingFiles(paths); } - function deployProject() { ProjectModelCode.deployProject(false); } - function registerToUrlHint() { ProjectModelCode.registerToUrlHint(); } - function formatAppUrl() { ProjectModelCode.formatAppUrl(url); } + function deployProject() { NetworkDeploymentCode.deployProject(false); } + function registerToUrlHint() { NetworkDeploymentCode.registerToUrlHint(); } + function formatAppUrl() { NetworkDeploymentCode.formatAppUrl(url); } Connections { target: mainApplication @@ -155,7 +156,7 @@ Item { icon: StandardIcon.Question standardButtons: StandardButton.Ok | StandardButton.Abort onAccepted: { - ProjectModelCode.startDeployProject(true); + NetworkDeploymentCode.startDeployProject(true); } } diff --git a/mix/qml/StepActionImage.qml b/mix/qml/StepActionImage.qml index a8c800b64..e2a1ab8e9 100644 --- a/mix/qml/StepActionImage.qml +++ b/mix/qml/StepActionImage.qml @@ -3,8 +3,6 @@ import QtQuick.Controls 1.1 import QtQuick.Layouts 1.0 import QtQuick.Controls.Styles 1.1 - - Rectangle { id: buttonActionContainer property string disableStateImg @@ -15,7 +13,6 @@ Rectangle { property bool buttonRight signal clicked - color: "transparent" width: 35 height: 24 @@ -43,8 +40,8 @@ Rectangle { right: parent.right top: parent.top bottom: parent.bottom - bottomMargin:debugImg.pressed? 0 : 1; - topMargin:debugImg.pressed? 1 : 0; + bottomMargin: debugImg.pressed ? 0 : 1; + topMargin: debugImg.pressed ? 1 : 0; } color: "#FCFBFC" radius: 3 @@ -65,15 +62,14 @@ Rectangle { right: parent.right top: parent.top bottom: parent.bottom - bottomMargin:debugImg.pressed? 0 : 1; - topMargin:debugImg.pressed? 1 : 0; + bottomMargin: debugImg.pressed ? 0 : 1; + topMargin: debugImg.pressed? 1 : 0; } color: "#FCFBFC" radius: 3 } } - Rectangle { id: contentRectangle width: 25 @@ -87,8 +83,8 @@ Rectangle { right: parent.right top: parent.top bottom: parent.bottom - bottomMargin:debugImg.pressed? 0 : 1; - topMargin:debugImg.pressed? 1 : 0; + bottomMargin: debugImg.pressed ? 0 : 1; + topMargin: debugImg.pressed ? 1 : 0; } color: "#FCFBFC" @@ -96,7 +92,7 @@ Rectangle { id: debugImage source: enabledStateImg anchors.centerIn: parent - anchors.topMargin: debugImg.pressed? 1 : 0; + anchors.topMargin: debugImg.pressed ? 1 : 0; fillMode: Image.PreserveAspectFit width: 15 @@ -105,28 +101,24 @@ Rectangle { } - Button { anchors.fill: parent id: debugImg action: buttonAction - style: Rectangle { - color: "transparent" + style: ButtonStyle { + background: Rectangle { + color: "transparent" + } } } - Action { tooltip: buttonTooltip id: buttonAction shortcut: buttonShortcut onTriggered: { - // contentRectangle.anchors.bottomMargin = 0 - // contentRectangle.anchors.topMargin = 1 buttonActionContainer.clicked(); } } } - - } diff --git a/mix/qml/js/NetworkDeployment.js b/mix/qml/js/NetworkDeployment.js new file mode 100644 index 000000000..627277f20 --- /dev/null +++ b/mix/qml/js/NetworkDeployment.js @@ -0,0 +1,351 @@ +/* + 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 NetworkDeployment.js + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @author Yann yann@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ +Qt.include("TransactionHelper.js") + + +var jsonRpcRequestId = 1; +function deployProject(force) { + saveAll(); //TODO: ask user + deploymentDialog.open(); +} + +function startDeployProject(erasePrevious) +{ + var date = new Date(); + var deploymentId = date.toLocaleString(Qt.locale(), "ddMMyyHHmmsszzz"); + if (!erasePrevious) + { + finalizeDeployment(deploymentId, projectModel.deploymentAddresses); + return; + } + + var jsonRpcUrl = "http://127.0.0.1:8080"; + console.log("Deploying " + deploymentId + " to " + jsonRpcUrl); + deploymentStarted(); + + var ctrNames = Object.keys(codeModel.contracts); + var ctrAddresses = {}; + deployContracts(0, ctrAddresses, ctrNames, function (){ + finalizeDeployment(deploymentId, ctrAddresses); + }); +} + +function deployContracts(ctrIndex, ctrAddresses, ctrNames, callBack) +{ + var code = codeModel.contracts[ctrNames[ctrIndex]].codeHex; + var requests = [{ + jsonrpc: "2.0", + method: "eth_sendTransaction", + params: [ { "from": deploymentDialog.currentAccount, "gas": deploymentDialog.gasToUse, "code": code } ], + id: 0 + }]; + rpcCall(requests, function (httpCall, response){ + var txt = qsTr("Please wait while " + ctrNames[ctrIndex] + " is published ...") + deploymentStepChanged(txt); + console.log(txt); + ctrAddresses[ctrNames[ctrIndex]] = JSON.parse(response)[0].result + deploymentDialog.waitForTrCountToIncrement(function(status) { + if (status === -1) + { + trCountIncrementTimeOut(); + return; + } + ctrIndex++; + if (ctrIndex < ctrNames.length) + deployContracts(ctrIndex, ctrAddresses, ctrNames, callBack); + else + callBack(); + }); + }); +} + +function finalizeDeployment(deploymentId, addresses) { + deploymentStepChanged(qsTr("Packaging application ...")); + var deploymentDir = projectPath + deploymentId + "/"; + projectModel.deploymentDir = deploymentDir; + fileIo.makeDir(deploymentDir); + for (var i = 0; i < projectListModel.count; i++) { + var doc = projectListModel.get(i); + if (doc.isContract) + continue; + if (doc.isHtml) { + //inject the script to access contract API + //TODO: use a template + var html = fileIo.readFile(doc.path); + var insertAt = html.indexOf("") + if (insertAt < 0) + insertAt = 0; + else + insertAt += 6; + html = html.substr(0, insertAt) + + "" + + html.substr(insertAt); + fileIo.writeFile(deploymentDir + doc.fileName, html); + } + else + fileIo.copyFile(doc.path, deploymentDir + doc.fileName); + } + //write deployment js + var deploymentJs = + "// Autogenerated by Mix\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 + ".contractClass = web3.eth.contract(" + contractAccessor + ".interface);\n" + + contractAccessor + ".contract = new " + contractAccessor + ".contractClass(" + contractAccessor + ".address);\n"; + } + fileIo.writeFile(deploymentDir + "deployment.js", deploymentJs); + deploymentAddresses = addresses; + saveProject(); + + var packageRet = fileIo.makePackage(deploymentDir); + deploymentDialog.packageHash = packageRet[0]; + deploymentDialog.packageBase64 = packageRet[1]; + deploymentDialog.localPackageUrl = packageRet[2] + "?hash=" + packageRet[0]; + + var applicationUrlEth = deploymentDialog.applicationUrlEth; + + applicationUrlEth = formatAppUrl(applicationUrlEth); + deploymentStepChanged(qsTr("Registering application on the Ethereum network ...")); + checkEthPath(applicationUrlEth, function () { + deploymentComplete(); + deployResourcesDialog.text = qsTr("Register Web Application to finalize deployment."); + deployResourcesDialog.open(); + }); +} + +function checkEthPath(dappUrl, callBack) +{ + if (dappUrl.length === 1) + registerContentHash(deploymentDialog.eth, callBack); // we directly create a dapp under the root registrar. + else + { + // the first owned reigstrar must have been created to follow the path. + var str = createString(dappUrl[0]); + var requests = []; + requests.push({ + //register() + jsonrpc: "2.0", + method: "eth_call", + params: [ { "gas": 150000, "from": deploymentDialog.currentAccount, "to": '0x' + deploymentDialog.eth, "data": "0x6be16bed" + str.encodeValueAsString() } ], + id: jsonRpcRequestId++ + }); + rpcCall(requests, function (httpRequest, response) { + var res = JSON.parse(response); + var addr = normalizeAddress(res[0].result); + if (addr.replace(/0+/g, "") === "") + { + var errorTxt = qsTr("Path does not exists " + JSON.stringify(dappUrl) + ". Please register using Registration Dapp. Aborting."); + deploymentError(errorTxt); + console.log(errorTxt); + } + else + { + dappUrl.splice(0, 1); + checkRegistration(dappUrl, addr, callBack); + } + }); + } +} + +function checkRegistration(dappUrl, addr, callBack) +{ + if (dappUrl.length === 1) + registerContentHash(addr, callBack); // We do not create the register for the last part, just registering the content hash. + else + { + var txt = qsTr("Checking " + JSON.stringify(dappUrl) + " ... in registrar " + addr); + deploymentStepChanged(txt); + console.log(txt); + var requests = []; + var registrar = {} + var str = createString(dappUrl[0]); + requests.push({ + //getOwner() + jsonrpc: "2.0", + method: "eth_call", + params: [ { "gas" : 2000, "from": deploymentDialog.currentAccount, "to": '0x' + addr, "data": "0x893d20e8" } ], + id: jsonRpcRequestId++ + }); + + requests.push({ + //register() + jsonrpc: "2.0", + method: "eth_call", + params: [ { "from": deploymentDialog.currentAccount, "to": '0x' + addr, "data": "0x6be16bed" + str.encodeValueAsString() } ], + id: jsonRpcRequestId++ + }); + + rpcCall(requests, function (httpRequest, response) { + var res = JSON.parse(response); + var nextAddr = normalizeAddress(res[1].result); + var errorTxt; + if (res[1].result === "0x") + { + errorTxt = qsTr("Error when creating new owned regsitrar. Please use the regsitration Dapp. Aborting"); + deploymentError(errorTxt); + console.log(errorTxt); + } + else if (normalizeAddress(deploymentDialog.currentAccount) !== normalizeAddress(res[0].result)) + { + errorTxt = qsTr("You are not the owner of " + dappUrl[0] + ". Aborting"); + deploymentError(errorTxt); + console.log(errorTxt); + } + else if (nextAddr.replace(/0+/g, "") !== "") + { + dappUrl.splice(0, 1); + checkRegistration(dappUrl, nextAddr, callBack); + } + else + { + var txt = qsTr("Registering sub domain " + dappUrl[0] + " ..."); + console.log(txt); + deploymentStepChanged(txt); + //current registrar is owned => ownedregistrar creation and continue. + requests = []; + + requests.push({ + jsonrpc: "2.0", + method: "eth_sendTransaction", + params: [ { "from": deploymentDialog.currentAccount, "gas": 20000, "code": "0x60056013565b61059e8061001d6000396000f35b33600081905550560060003560e060020a90048063019848921461009a578063449c2090146100af5780635d574e32146100cd5780635fd4b08a146100e1578063618242da146100f65780636be16bed1461010b5780636c4489b414610129578063893d20e8146101585780639607730714610173578063c284bc2a14610187578063e50f599a14610198578063e5811b35146101af578063ec7b9200146101cd57005b6100a560043561031b565b8060005260206000f35b6100ba6004356103a0565b80600160a060020a031660005260206000f35b6100db600435602435610537565b60006000f35b6100ec600435610529565b8060005260206000f35b6101016004356103dd565b8060005260206000f35b6101166004356103bd565b80600160a060020a031660005260206000f35b61013460043561034b565b82600160a060020a031660005281600160a060020a03166020528060405260606000f35b610160610341565b80600160a060020a031660005260206000f35b6101816004356024356102b4565b60006000f35b6101926004356103fd565b60006000f35b6101a96004356024356044356101f2565b60006000f35b6101ba6004356101eb565b80600160a060020a031660005260206000f35b6101d8600435610530565b80600160a060020a031660005260206000f35b6000919050565b600054600160a060020a031633600160a060020a031614610212576102af565b8160026000858152602001908152602001600020819055508061023457610287565b81600160a060020a0316837f680ad70765443c2967675ab0fb91a46350c01c6df59bf9a41ff8a8dd097464ec60006000a3826001600084600160a060020a03168152602001908152602001600020819055505b827f18d67da0cd86808336a3aa8912f6ea70c5250f1a98b586d1017ef56fe199d4fc60006000a25b505050565b600054600160a060020a031633600160a060020a0316146102d457610317565b806002600084815260200190815260200160002060010181905550817f18d67da0cd86808336a3aa8912f6ea70c5250f1a98b586d1017ef56fe199d4fc60006000a25b5050565b60006001600083600160a060020a03168152602001908152602001600020549050919050565b6000600054905090565b6000600060006002600085815260200190815260200160002054925060026000858152602001908152602001600020600101549150600260008581526020019081526020016000206002015490509193909250565b600060026000838152602001908152602001600020549050919050565b600060026000838152602001908152602001600020600101549050919050565b600060026000838152602001908152602001600020600201549050919050565b600054600160a060020a031633600160a060020a03161461041d57610526565b80600160006002600085815260200190815260200160002054600160a060020a031681526020019081526020016000205414610458576104d2565b6002600082815260200190815260200160002054600160a060020a0316817f680ad70765443c2967675ab0fb91a46350c01c6df59bf9a41ff8a8dd097464ec60006000a36000600160006002600085815260200190815260200160002054600160a060020a03168152602001908152602001600020819055505b6002600082815260200190815260200160002060008101600090556001810160009055600281016000905550807f18d67da0cd86808336a3aa8912f6ea70c5250f1a98b586d1017ef56fe199d4fc60006000a25b50565b6000919050565b6000919050565b600054600160a060020a031633600160a060020a0316146105575761059a565b806002600084815260200190815260200160002060020181905550817f18d67da0cd86808336a3aa8912f6ea70c5250f1a98b586d1017ef56fe199d4fc60006000a25b505056" } ], + id: jsonRpcRequestId++ + }); + + rpcCall(requests, function(httpRequest, response) { + var newCtrAddress = normalizeAddress(JSON.parse(response)[0].result); + requests = []; + var txt = qsTr("Please wait " + dappUrl[0] + " is registering ..."); + deploymentStepChanged(txt); + console.log(txt); + deploymentDialog.waitForTrCountToIncrement(function(status) { + if (status === -1) + { + trCountIncrementTimeOut(); + return; + } + var crLevel = createString(dappUrl[0]).encodeValueAsString(); + requests.push({ + //setRegister() + jsonrpc: "2.0", + method: "eth_sendTransaction", + params: [ { "from": deploymentDialog.currentAccount, "gas": 30000, "to": '0x' + addr, "data": "0x96077307" + crLevel + deploymentDialog.pad(newCtrAddress) } ], + id: jsonRpcRequestId++ + }); + + rpcCall(requests, function(request, response){ + dappUrl.splice(0, 1); + checkRegistration(dappUrl, newCtrAddress, callBack); + }); + }); + }); + } + }); + } +} + +function trCountIncrementTimeOut() +{ + var error = qsTr("Something went wrong during the deployment. Please verify the amount of gas for this transaction and check your balance.") + console.log(error); + deploymentError(error); +} + +function registerContentHash(registrar, callBack) +{ + var txt = qsTr("Finalizing Dapp registration ..."); + deploymentStepChanged(txt); + console.log(txt); + var requests = []; + var paramTitle = clientModel.encodeAbiString(projectModel.projectTitle); + requests.push({ + //setContent() + jsonrpc: "2.0", + method: "eth_sendTransaction", + params: [ { "from": deploymentDialog.currentAccount, "gas": 30000, "gasPrice": "10", "to": '0x' + registrar, "data": "0x5d574e32" + paramTitle + deploymentDialog.packageHash } ], + id: jsonRpcRequestId++ + }); + rpcCall(requests, function (httpRequest, response) { + callBack(); + }); +} + +function registerToUrlHint() +{ + deploymentStepChanged(qsTr("Registering application Resources (" + deploymentDialog.applicationUrlHttp) + ") ..."); + var requests = []; + var paramUrlHttp = createString(deploymentDialog.applicationUrlHttp); + requests.push({ + //urlHint => suggestUrl + jsonrpc: "2.0", + method: "eth_sendTransaction", + params: [ { "to": '0x' + deploymentDialog.urlHintContract, "gas": 30000, "data": "0x4983e19c" + deploymentDialog.packageHash + paramUrlHttp.encodeValueAsString() } ], + id: jsonRpcRequestId++ + }); + + rpcCall(requests, function (httpRequest, response) { + deploymentComplete(); + }); +} + +function normalizeAddress(addr) +{ + addr = addr.replace('0x', ''); + if (addr.length <= 40) + return addr; + var left = addr.length - 40; + return addr.substring(left); +} + +function formatAppUrl(url) +{ + if (url.toLowerCase().indexOf("eth://") === 0) + url = url.substring(6); + if (url.toLowerCase().indexOf(projectModel.projectTitle + ".") === 0) + url = url.substring(projectModel.projectTitle.length + 1); + if (url === "") + return [projectModel.projectTitle]; + + var ret; + if (url.indexOf("/") === -1) + ret = url.split('.').reverse(); + else + { + var slash = url.indexOf("/"); + var left = url.substring(0, slash); + var leftA = left.split("."); + leftA.reverse(); + + var right = url.substring(slash + 1); + var rightA = right.split('/'); + ret = leftA.concat(rightA); + } + if (ret[0].toLowerCase() === "eth") + ret.splice(0, 1); + ret.push(projectModel.projectTitle); + return ret; +} diff --git a/mix/qml/js/ProjectModel.js b/mix/qml/js/ProjectModel.js index 993eacf3a..6ec906996 100644 --- a/mix/qml/js/ProjectModel.js +++ b/mix/qml/js/ProjectModel.js @@ -19,8 +19,6 @@ * @date 2015 * Ethereum IDE client. */ -Qt.include("QEtherHelper.js") -Qt.include("TransactionHelper.js") var htmlTemplate = "\n\n\n\n\n\n\n"; var contractTemplate = "contract Contract {\n}\n"; @@ -83,7 +81,10 @@ function saveProjectFile() deploymentDir: projectModel.deploymentDir }; for (var i = 0; i < projectListModel.count; i++) - projectData.files.push(projectListModel.get(i).fileName); + projectData.files.push({ + title: projectListModel.get(i).name, + fileName: projectListModel.get(i).fileName, + }); projectFileSaving(projectData); var json = JSON.stringify(projectData, null, "\t"); @@ -122,7 +123,11 @@ function loadProject(path) { projectData.files = []; for(var i = 0; i < projectData.files.length; i++) { - addFile(projectData.files[i]); + var entry = projectData.files[i]; + if (typeof(entry) === "string") + addFile(entry); //TODO: remove old project file support + else + addFile(entry.fileName, entry.title); } if (mainApplication.trackLastProject) projectSettings.lastProjectPath = path; @@ -140,7 +145,7 @@ function loadProject(path) { }); } -function addFile(fileName) { +function addFile(fileName, title) { var p = projectPath + fileName; var extension = fileName.substring(fileName.lastIndexOf("."), fileName.length); var isContract = extension === ".sol"; @@ -154,7 +159,7 @@ function addFile(fileName) { contract: false, path: p, fileName: fileName, - name: fileName, + name: title !== undefined ? title : fileName, documentId: fileName, syntaxMode: syntaxMode, isText: isContract || isHtml || isCss || isJs, @@ -344,331 +349,3 @@ function generateFileName(name, extension) { } while (fileIo.fileExists(filePath)); return fileName } - - -var jsonRpcRequestId = 1; -function deployProject(force) { - saveAll(); //TODO: ask user - deploymentDialog.open(); -} - -function startDeployProject(erasePrevious) -{ - var date = new Date(); - var deploymentId = date.toLocaleString(Qt.locale(), "ddMMyyHHmmsszzz"); - if (!erasePrevious) - { - finalizeDeployment(deploymentId, projectModel.deploymentAddresses); - return; - } - - var jsonRpcUrl = "http://127.0.0.1:8080"; - console.log("Deploying " + deploymentId + " to " + jsonRpcUrl); - deploymentStarted(); - - var ctrNames = Object.keys(codeModel.contracts); - var ctrAddresses = {}; - deployContracts(0, ctrAddresses, ctrNames, function (){ - finalizeDeployment(deploymentId, ctrAddresses); - }); -} - -function deployContracts(ctrIndex, ctrAddresses, ctrNames, callBack) -{ - var code = codeModel.contracts[ctrNames[ctrIndex]].codeHex; - var requests = [{ - jsonrpc: "2.0", - method: "eth_sendTransaction", - params: [ { "from": deploymentDialog.currentAccount, "gas": deploymentDialog.gasToUse, "code": code } ], - id: 0 - }]; - rpcCall(requests, function (httpCall, response){ - var txt = qsTr("Please wait while " + ctrNames[ctrIndex] + " is published ...") - deploymentStepChanged(txt); - console.log(txt); - ctrAddresses[ctrNames[ctrIndex]] = JSON.parse(response)[0].result - deploymentDialog.waitForTrCountToIncrement(function(status) { - if (status === -1) - { - trCountIncrementTimeOut(); - return; - } - ctrIndex++; - if (ctrIndex < ctrNames.length) - deployContracts(ctrIndex, ctrAddresses, ctrNames, callBack); - else - callBack(); - }); - }); -} - -function finalizeDeployment(deploymentId, addresses) { - deploymentStepChanged(qsTr("Packaging application ...")); - var deploymentDir = projectPath + deploymentId + "/"; - projectModel.deploymentDir = deploymentDir; - fileIo.makeDir(deploymentDir); - for (var i = 0; i < projectListModel.count; i++) { - var doc = projectListModel.get(i); - if (doc.isContract) - continue; - if (doc.isHtml) { - //inject the script to access contract API - //TODO: use a template - var html = fileIo.readFile(doc.path); - var insertAt = html.indexOf("") - if (insertAt < 0) - insertAt = 0; - else - insertAt += 6; - html = html.substr(0, insertAt) + - "" + - html.substr(insertAt); - fileIo.writeFile(deploymentDir + doc.fileName, html); - } - else - fileIo.copyFile(doc.path, deploymentDir + doc.fileName); - } - //write deployment js - var deploymentJs = - "// Autogenerated by Mix\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 + ".contractClass = web3.eth.contract(" + contractAccessor + ".interface);\n" + - contractAccessor + ".contract = new " + contractAccessor + ".contractClass(" + contractAccessor + ".address);\n"; - } - fileIo.writeFile(deploymentDir + "deployment.js", deploymentJs); - deploymentAddresses = addresses; - saveProject(); - - var packageRet = fileIo.makePackage(deploymentDir); - deploymentDialog.packageHash = packageRet[0]; - deploymentDialog.packageBase64 = packageRet[1]; - deploymentDialog.localPackageUrl = packageRet[2] + "?hash=" + packageRet[0]; - - var applicationUrlEth = deploymentDialog.applicationUrlEth; - - applicationUrlEth = formatAppUrl(applicationUrlEth); - deploymentStepChanged(qsTr("Registering application on the Ethereum network ...")); - checkEthPath(applicationUrlEth, function () { - deploymentComplete(); - deployResourcesDialog.text = qsTr("Register Web Application to finalize deployment."); - deployResourcesDialog.open(); - }); -} - -function checkEthPath(dappUrl, callBack) -{ - if (dappUrl.length === 1) - registerContentHash(deploymentDialog.eth, callBack); // we directly create a dapp under the root registrar. - else - { - // the first owned reigstrar must have been created to follow the path. - var str = createString(dappUrl[0]); - var requests = []; - requests.push({ - //register() - jsonrpc: "2.0", - method: "eth_call", - params: [ { "gas": 150000, "from": deploymentDialog.currentAccount, "to": '0x' + deploymentDialog.eth, "data": "0x6be16bed" + str.encodeValueAsString() } ], - id: jsonRpcRequestId++ - }); - rpcCall(requests, function (httpRequest, response) { - var res = JSON.parse(response); - var addr = normalizeAddress(res[0].result); - if (addr.replace(/0+/g, "") === "") - { - var errorTxt = qsTr("Path does not exists " + JSON.stringify(dappUrl) + ". Please register using Registration Dapp. Aborting."); - deploymentError(errorTxt); - console.log(errorTxt); - } - else - { - dappUrl.splice(0, 1); - checkRegistration(dappUrl, addr, callBack); - } - }); - } -} - -function checkRegistration(dappUrl, addr, callBack) -{ - if (dappUrl.length === 1) - registerContentHash(addr, callBack); // We do not create the register for the last part, just registering the content hash. - else - { - var txt = qsTr("Checking " + JSON.stringify(dappUrl) + " ... in registrar " + addr); - deploymentStepChanged(txt); - console.log(txt); - var requests = []; - var registrar = {} - var str = createString(dappUrl[0]); - requests.push({ - //getOwner() - jsonrpc: "2.0", - method: "eth_call", - params: [ { "gas" : 2000, "from": deploymentDialog.currentAccount, "to": '0x' + addr, "data": "0x893d20e8" } ], - id: jsonRpcRequestId++ - }); - - requests.push({ - //register() - jsonrpc: "2.0", - method: "eth_call", - params: [ { "from": deploymentDialog.currentAccount, "to": '0x' + addr, "data": "0x6be16bed" + str.encodeValueAsString() } ], - id: jsonRpcRequestId++ - }); - - rpcCall(requests, function (httpRequest, response) { - var res = JSON.parse(response); - var nextAddr = normalizeAddress(res[1].result); - var errorTxt; - if (res[1].result === "0x") - { - errorTxt = qsTr("Error when creating new owned regsitrar. Please use the regsitration Dapp. Aborting"); - deploymentError(errorTxt); - console.log(errorTxt); - } - else if (normalizeAddress(deploymentDialog.currentAccount) !== normalizeAddress(res[0].result)) - { - errorTxt = qsTr("You are not the owner of " + dappUrl[0] + ". Aborting"); - deploymentError(errorTxt); - console.log(errorTxt); - } - else if (nextAddr.replace(/0+/g, "") !== "") - { - dappUrl.splice(0, 1); - checkRegistration(dappUrl, nextAddr, callBack); - } - else - { - var txt = qsTr("Registering sub domain " + dappUrl[0] + " ..."); - console.log(txt); - deploymentStepChanged(txt); - //current registrar is owned => ownedregistrar creation and continue. - requests = []; - - requests.push({ - jsonrpc: "2.0", - method: "eth_sendTransaction", - params: [ { "from": deploymentDialog.currentAccount, "gas": 20000, "code": "0x60056013565b61059e8061001d6000396000f35b33600081905550560060003560e060020a90048063019848921461009a578063449c2090146100af5780635d574e32146100cd5780635fd4b08a146100e1578063618242da146100f65780636be16bed1461010b5780636c4489b414610129578063893d20e8146101585780639607730714610173578063c284bc2a14610187578063e50f599a14610198578063e5811b35146101af578063ec7b9200146101cd57005b6100a560043561031b565b8060005260206000f35b6100ba6004356103a0565b80600160a060020a031660005260206000f35b6100db600435602435610537565b60006000f35b6100ec600435610529565b8060005260206000f35b6101016004356103dd565b8060005260206000f35b6101166004356103bd565b80600160a060020a031660005260206000f35b61013460043561034b565b82600160a060020a031660005281600160a060020a03166020528060405260606000f35b610160610341565b80600160a060020a031660005260206000f35b6101816004356024356102b4565b60006000f35b6101926004356103fd565b60006000f35b6101a96004356024356044356101f2565b60006000f35b6101ba6004356101eb565b80600160a060020a031660005260206000f35b6101d8600435610530565b80600160a060020a031660005260206000f35b6000919050565b600054600160a060020a031633600160a060020a031614610212576102af565b8160026000858152602001908152602001600020819055508061023457610287565b81600160a060020a0316837f680ad70765443c2967675ab0fb91a46350c01c6df59bf9a41ff8a8dd097464ec60006000a3826001600084600160a060020a03168152602001908152602001600020819055505b827f18d67da0cd86808336a3aa8912f6ea70c5250f1a98b586d1017ef56fe199d4fc60006000a25b505050565b600054600160a060020a031633600160a060020a0316146102d457610317565b806002600084815260200190815260200160002060010181905550817f18d67da0cd86808336a3aa8912f6ea70c5250f1a98b586d1017ef56fe199d4fc60006000a25b5050565b60006001600083600160a060020a03168152602001908152602001600020549050919050565b6000600054905090565b6000600060006002600085815260200190815260200160002054925060026000858152602001908152602001600020600101549150600260008581526020019081526020016000206002015490509193909250565b600060026000838152602001908152602001600020549050919050565b600060026000838152602001908152602001600020600101549050919050565b600060026000838152602001908152602001600020600201549050919050565b600054600160a060020a031633600160a060020a03161461041d57610526565b80600160006002600085815260200190815260200160002054600160a060020a031681526020019081526020016000205414610458576104d2565b6002600082815260200190815260200160002054600160a060020a0316817f680ad70765443c2967675ab0fb91a46350c01c6df59bf9a41ff8a8dd097464ec60006000a36000600160006002600085815260200190815260200160002054600160a060020a03168152602001908152602001600020819055505b6002600082815260200190815260200160002060008101600090556001810160009055600281016000905550807f18d67da0cd86808336a3aa8912f6ea70c5250f1a98b586d1017ef56fe199d4fc60006000a25b50565b6000919050565b6000919050565b600054600160a060020a031633600160a060020a0316146105575761059a565b806002600084815260200190815260200160002060020181905550817f18d67da0cd86808336a3aa8912f6ea70c5250f1a98b586d1017ef56fe199d4fc60006000a25b505056" } ], - id: jsonRpcRequestId++ - }); - - rpcCall(requests, function(httpRequest, response) { - var newCtrAddress = normalizeAddress(JSON.parse(response)[0].result); - requests = []; - var txt = qsTr("Please wait " + dappUrl[0] + " is registering ..."); - deploymentStepChanged(txt); - console.log(txt); - deploymentDialog.waitForTrCountToIncrement(function(status) { - if (status === -1) - { - trCountIncrementTimeOut(); - return; - } - var crLevel = createString(dappUrl[0]).encodeValueAsString(); - requests.push({ - //setRegister() - jsonrpc: "2.0", - method: "eth_sendTransaction", - params: [ { "from": deploymentDialog.currentAccount, "gas": 30000, "to": '0x' + addr, "data": "0x96077307" + crLevel + deploymentDialog.pad(newCtrAddress) } ], - id: jsonRpcRequestId++ - }); - - rpcCall(requests, function(request, response){ - dappUrl.splice(0, 1); - checkRegistration(dappUrl, newCtrAddress, callBack); - }); - }); - }); - } - }); - } -} - -function trCountIncrementTimeOut() -{ - var error = qsTr("Something went wrong during the deployment. Please verify the amount of gas for this transaction and check your balance.") - console.log(error); - deploymentError(error); -} - -function registerContentHash(registrar, callBack) -{ - var txt = qsTr("Finalizing Dapp registration ..."); - deploymentStepChanged(txt); - console.log(txt); - var requests = []; - var paramTitle = clientModel.encodeAbiString(projectModel.projectTitle); - requests.push({ - //setContent() - jsonrpc: "2.0", - method: "eth_sendTransaction", - params: [ { "from": deploymentDialog.currentAccount, "gas": 30000, "gasPrice": "10", "to": '0x' + registrar, "data": "0x5d574e32" + paramTitle + deploymentDialog.packageHash } ], - id: jsonRpcRequestId++ - }); - rpcCall(requests, function (httpRequest, response) { - callBack(); - }); -} - -function registerToUrlHint() -{ - deploymentStepChanged(qsTr("Registering application Resources (" + deploymentDialog.applicationUrlHttp) + ") ..."); - var requests = []; - var paramUrlHttp = createString(deploymentDialog.applicationUrlHttp); - requests.push({ - //urlHint => suggestUrl - jsonrpc: "2.0", - method: "eth_sendTransaction", - params: [ { "to": '0x' + deploymentDialog.urlHintContract, "gas": 30000, "data": "0x4983e19c" + deploymentDialog.packageHash + paramUrlHttp.encodeValueAsString() } ], - id: jsonRpcRequestId++ - }); - - rpcCall(requests, function (httpRequest, response) { - deploymentComplete(); - }); -} - -function normalizeAddress(addr) -{ - addr = addr.replace('0x', ''); - if (addr.length <= 40) - return addr; - var left = addr.length - 40; - return addr.substring(left); -} - -function formatAppUrl(url) -{ - if (url.toLowerCase().indexOf("eth://") === 0) - url = url.substring(6); - if (url.toLowerCase().indexOf(projectModel.projectTitle + ".") === 0) - url = url.substring(projectModel.projectTitle.length + 1); - if (url === "") - return [projectModel.projectTitle]; - - var ret; - if (url.indexOf("/") === -1) - ret = url.split('.').reverse(); - else - { - var slash = url.indexOf("/"); - var left = url.substring(0, slash); - var leftA = left.split("."); - leftA.reverse(); - - var right = url.substring(slash + 1); - var rightA = right.split('/'); - ret = leftA.concat(rightA); - } - if (ret[0].toLowerCase() === "eth") - ret.splice(0, 1); - ret.push(projectModel.projectTitle); - return ret; -} diff --git a/mix/test/qml/TestMain.qml b/mix/test/qml/TestMain.qml index bf449d2dd..70b96be00 100644 --- a/mix/test/qml/TestMain.qml +++ b/mix/test/qml/TestMain.qml @@ -4,6 +4,7 @@ import org.ethereum.qml.TestService 1.0 import "../../qml" import "js/TestDebugger.js" as TestDebugger import "js/TestTutorial.js" as TestTutorial +import "js/TestProject.js" as TestProject TestCase { @@ -49,9 +50,9 @@ TestCase function editContract(c) { mainApplication.mainContent.codeEditor.getEditor("contract.sol").setText(c); - ts.keyPressChar(mainApplication, "S", Qt.ControlModifier, 200); //Ctrl+S if (!ts.waitForSignal(mainApplication.codeModel, "compilationComplete()", 5000)) fail("not compiled"); + ts.keyPressChar(mainApplication, "S", Qt.ControlModifier, 200); //Ctrl+S } function editHtml(c) @@ -74,5 +75,6 @@ TestCase function test_dbg_transactionWithParameter() { TestDebugger.test_transactionWithParameter(); } function test_dbg_constructorParameters() { TestDebugger.test_constructorParameters(); } function test_dbg_arrayParametersAndStorage() { TestDebugger.test_arrayParametersAndStorage(); } + function test_project_contractRename() { TestProject.test_contractRename(); } } diff --git a/mix/test/qml/js/TestProject.js b/mix/test/qml/js/TestProject.js new file mode 100644 index 000000000..d81b72942 --- /dev/null +++ b/mix/test/qml/js/TestProject.js @@ -0,0 +1,18 @@ +function test_contractRename() +{ + newProject(); + tryCompare(mainApplication.mainContent.projectNavigator.sections.itemAt(0).model.get(0), "name", "Contract"); + editContract("contract Renamed {}"); + if (!ts.waitForSignal(mainApplication.clientModel, "runComplete()", 5000)) + fail("Error running transaction"); + wait(1000); + tryCompare(mainApplication.mainContent.projectNavigator.sections.itemAt(0).model.get(0), "name", "Renamed"); + mainApplication.projectModel.stateListModel.editState(0); + mainApplication.projectModel.stateDialog.model.editTransaction(2); + var transactionDialog = mainApplication.projectModel.stateDialog.transactionDialog; + tryCompare(transactionDialog, "contractId", "Renamed"); + tryCompare(transactionDialog, "functionId", "Renamed"); + transactionDialog.close(); + mainApplication.projectModel.stateDialog.close(); + tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel.get(2), "contract", "Renamed"); +}