diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index f12bcc1d8..7fdba2720 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -221,9 +221,11 @@ void ClientModel::executeSequence(std::vector const& _seque { //std contract dev::bytes const& stdContractCode = m_context->codeModel()->getStdContractCode(transaction.contractId, transaction.stdContractUrl); - Address address = deployContract(stdContractCode, transaction); - m_stdContractAddresses[transaction.contractId] = address; - m_stdContractNames[address] = transaction.contractId; + TransactionSettings stdTransaction = transaction; + stdTransaction.gas = 500000;// TODO: get this from std contracts library + Address address = deployContract(stdContractCode, stdTransaction); + m_stdContractAddresses[stdTransaction.contractId] = address; + m_stdContractNames[address] = stdTransaction.contractId; } else { @@ -296,7 +298,7 @@ void ClientModel::executeSequence(std::vector const& _seque void ClientModel::showDebugger() { - ExecutionResult const& last = m_client->lastExecution(); + ExecutionResult last = m_client->lastExecution(); showDebuggerForTransaction(last); } @@ -433,7 +435,7 @@ void ClientModel::emptyRecord() void ClientModel::debugRecord(unsigned _index) { - ExecutionResult const& e = m_client->executions().at(_index); + ExecutionResult e = m_client->execution(_index); showDebuggerForTransaction(e); } @@ -479,7 +481,7 @@ void ClientModel::onNewTransaction() { ExecutionResult const& tr = m_client->lastExecution(); unsigned block = m_client->number() + 1; - unsigned recordIndex = m_client->executions().size() - 1; + unsigned recordIndex = tr.executonIndex; QString transactionIndex = tr.isCall() ? QObject::tr("Call") : QString("%1:%2").arg(block).arg(tr.transactionIndex); QString address = QString::fromStdString(toJS(tr.address)); QString value = QString::fromStdString(dev::toString(tr.value)); @@ -503,6 +505,7 @@ void ClientModel::onNewTransaction() } else function = QObject::tr("Constructor"); + address = QObject::tr("(Create contract)"); } else { diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index 51c91c3bf..fb55c4fc7 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -262,6 +262,8 @@ void CodeModel::runCompilationJob(int _jobId) CompiledContract* prevContract = m_contractMap.value(name); if (prevContract != nullptr && prevContract->contractInterface() != result[name]->contractInterface()) emit contractInterfaceChanged(name); + if (prevContract == nullptr) + emit newContractCompiled(name); } releaseContracts(); m_contractMap.swap(result); diff --git a/mix/CodeModel.h b/mix/CodeModel.h index 8f84b103d..07e93498a 100644 --- a/mix/CodeModel.h +++ b/mix/CodeModel.h @@ -163,6 +163,8 @@ signals: void codeChanged(); /// Emitted if there are any changes in the contract interface void contractInterfaceChanged(QString _documentId); + /// Emitted if there is a new contract compiled for the first time + void newContractCompiled(QString _documentId); public slots: /// Update code model on source code change diff --git a/mix/Exceptions.h b/mix/Exceptions.h index 46178bf96..02e31760f 100644 --- a/mix/Exceptions.h +++ b/mix/Exceptions.h @@ -39,6 +39,7 @@ struct InvalidBlockException: virtual Exception {}; struct FunctionNotFoundException: virtual Exception {}; struct ExecutionStateException: virtual Exception {}; struct ParameterChangedException: virtual Exception {}; +struct OutOfGasException: virtual Exception {}; using QmlErrorInfo = boost::error_info; using FileError = boost::error_info; diff --git a/mix/MachineStates.h b/mix/MachineStates.h index 310d5cacd..e949db8b2 100644 --- a/mix/MachineStates.h +++ b/mix/MachineStates.h @@ -80,6 +80,7 @@ namespace mix dev::Address contractAddress; dev::u256 value; unsigned transactionIndex; + unsigned executonIndex = 0; bool isCall() const { return transactionIndex == std::numeric_limits::max(); } bool isConstructor() const { return !isCall() && !address; } diff --git a/mix/MixClient.cpp b/mix/MixClient.cpp index e088a093b..3df6d7c2e 100644 --- a/mix/MixClient.cpp +++ b/mix/MixClient.cpp @@ -104,6 +104,7 @@ void MixClient::resetState(std::map _accounts) m_state = eth::State(genesisState.begin()->first , m_stateDB, BaseState::Empty); m_state.sync(bc()); m_startState = m_state; + WriteGuard lx(x_executions); m_executions.clear(); } @@ -186,12 +187,14 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c d.contractAddress = right160(sha3(rlpList(_t.sender(), _t.nonce()))); if (!_call) d.transactionIndex = m_state.pending().size(); - m_executions.emplace_back(std::move(d)); + d.executonIndex = m_executions.size(); // execute on a state if (!_call) { _state.execute(lastHashes, rlp, nullptr, true); + if (_t.isCreation() && _state.code(d.contractAddress).empty()) + BOOST_THROW_EXCEPTION(OutOfGas() << errinfo_comment("Not enough gas for contract deployment")); // collect watches h256Set changed; Guard l(m_filterLock); @@ -211,6 +214,8 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c changed.insert(dev::eth::PendingChangedFilter); noteChanged(changed); } + WriteGuard l(x_executions); + m_executions.emplace_back(std::move(d)); } void MixClient::mine() @@ -226,14 +231,16 @@ void MixClient::mine() noteChanged(changed); } -ExecutionResult const& MixClient::lastExecution() const +ExecutionResult MixClient::lastExecution() const { - return m_executions.back(); + ReadGuard l(x_executions); + return m_executions.empty() ? ExecutionResult() : m_executions.back(); } -ExecutionResults const& MixClient::executions() const +ExecutionResult MixClient::execution(unsigned _index) const { - return m_executions; + ReadGuard l(x_executions); + return m_executions.at(_index); } State MixClient::asOf(int _block) const @@ -441,32 +448,38 @@ LocalisedLogEntries MixClient::checkWatch(unsigned _watchId) h256 MixClient::hashFromNumber(unsigned _number) const { + ReadGuard l(x_state); return bc().numberHash(_number); } eth::BlockInfo MixClient::blockInfo(h256 _hash) const { + ReadGuard l(x_state); return BlockInfo(bc().block(_hash)); } eth::BlockInfo MixClient::blockInfo() const { + ReadGuard l(x_state); return BlockInfo(bc().block()); } eth::BlockDetails MixClient::blockDetails(h256 _hash) const { + ReadGuard l(x_state); return bc().details(_hash); } Transaction MixClient::transaction(h256 _transactionHash) const { + ReadGuard l(x_state); return Transaction(bc().transaction(_transactionHash), CheckSignature::Range); } eth::Transaction MixClient::transaction(h256 _blockHash, unsigned _i) const { + ReadGuard l(x_state); auto bl = bc().block(_blockHash); RLP b(bl); if (_i < b[1].itemCount()) @@ -477,6 +490,7 @@ eth::Transaction MixClient::transaction(h256 _blockHash, unsigned _i) const eth::BlockInfo MixClient::uncle(h256 _blockHash, unsigned _i) const { + ReadGuard l(x_state); auto bl = bc().block(_blockHash); RLP b(bl); if (_i < b[2].itemCount()) @@ -487,6 +501,7 @@ eth::BlockInfo MixClient::uncle(h256 _blockHash, unsigned _i) const unsigned MixClient::transactionCount(h256 _blockHash) const { + ReadGuard l(x_state); auto bl = bc().block(_blockHash); RLP b(bl); return b[1].itemCount(); @@ -494,6 +509,7 @@ unsigned MixClient::transactionCount(h256 _blockHash) const unsigned MixClient::uncleCount(h256 _blockHash) const { + ReadGuard l(x_state); auto bl = bc().block(_blockHash); RLP b(bl); return b[2].itemCount(); @@ -501,6 +517,7 @@ unsigned MixClient::uncleCount(h256 _blockHash) const Transactions MixClient::transactions(h256 _blockHash) const { + ReadGuard l(x_state); auto bl = bc().block(_blockHash); RLP b(bl); Transactions res; @@ -511,21 +528,25 @@ Transactions MixClient::transactions(h256 _blockHash) const TransactionHashes MixClient::transactionHashes(h256 _blockHash) const { + ReadGuard l(x_state); return bc().transactionHashes(_blockHash); } unsigned MixClient::number() const { + ReadGuard l(x_state); return bc().number(); } eth::Transactions MixClient::pending() const { + ReadGuard l(x_state); return m_state.pending(); } eth::StateDiff MixClient::diff(unsigned _txi, h256 _block) const { + ReadGuard l(x_state); State st(m_stateDB, bc(), _block); return st.fromPending(_txi).diff(st.fromPending(_txi + 1)); } diff --git a/mix/MixClient.h b/mix/MixClient.h index af5c8ec2b..4c08dd35c 100644 --- a/mix/MixClient.h +++ b/mix/MixClient.h @@ -44,8 +44,8 @@ public: /// Reset state to the empty state with given balance. void resetState(std::map _accounts); void mine(); - ExecutionResult const& lastExecution() const; - ExecutionResults const& executions() const; + ExecutionResult lastExecution() const; + ExecutionResult execution(unsigned _index) const; //dev::eth::Interface void transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) override; @@ -108,6 +108,7 @@ private: OverlayDB m_stateDB; std::auto_ptr m_bc; mutable boost::shared_mutex x_state; + mutable boost::shared_mutex x_executions; mutable std::mutex m_filterLock; std::map m_filters; std::map m_watches; diff --git a/mix/qml/CodeEditorView.qml b/mix/qml/CodeEditorView.qml index 0410382a5..a24fd094f 100644 --- a/mix/qml/CodeEditorView.qml +++ b/mix/qml/CodeEditorView.qml @@ -116,7 +116,9 @@ Item { for (var i = 0; i < editorListModel.count; i++) { var doc = editorListModel.get(i); - fileIo.writeFile(doc.path, editors.itemAt(i).item.getText()); + var editor = editors.itemAt(i).item; + if (editor) + fileIo.writeFile(doc.path, item.getText()); } } diff --git a/mix/qml/DebugInfoList.qml b/mix/qml/DebugInfoList.qml index 721d5540b..17b97166e 100644 --- a/mix/qml/DebugInfoList.qml +++ b/mix/qml/DebugInfoList.qml @@ -8,6 +8,7 @@ ColumnLayout { property string title property variant listModel; property bool collapsible; + property bool collapsed; property bool enableSelection: false; property real storedHeight: 0; property Component itemDelegate @@ -19,18 +20,20 @@ ColumnLayout { function collapse() { storedHeight = childrenRect.height; - storageContainer.state = "collapsed"; + storageContainer.collapse(); } function show() { - storageContainer.state = ""; + storageContainer.expand(); } Component.onCompleted: { if (storageContainer.parent.parent.height === 25) - storageContainer.state = "collapsed"; + storageContainer.collapse(); + else + storageContainer.expand(); } RowLayout { @@ -59,15 +62,15 @@ ColumnLayout { onClicked: { if (collapsible) { - if (storageContainer.state == "collapsed") + if (collapsed) { - storageContainer.state = ""; + storageContainer.expand(); storageContainer.parent.parent.height = storedHeight; } else { storedHeight = root.childrenRect.height; - storageContainer.state = "collapsed"; + storageContainer.collapse(); } } } @@ -80,19 +83,19 @@ ColumnLayout { border.color: "#deddd9" Layout.fillWidth: true Layout.fillHeight: true - states: [ - State { - name: "collapsed" - PropertyChanges { - target: storageImgArrow - source: "qrc:/qml/img/closedtriangleindicator.png" - } - PropertyChanges { - target: storageContainer.parent.parent - height: 25 - } - } - ] + + function collapse() { + storageImgArrow.source = "qrc:/qml/img/closedtriangleindicator.png"; + if (storageContainer.parent.parent.height > 25) + storageContainer.parent.parent.height = 25; + collapsed = true; + } + + function expand() { + storageImgArrow.source = "qrc:/qml/img/opentriangleindicator.png"; + collapsed = false; + } + Loader { id: loader @@ -102,6 +105,17 @@ ColumnLayout { anchors.leftMargin: 3 width: parent.width - 3 height: parent.height - 6 + onHeightChanged: { + if (height <= 0 && collapsible) { + if (storedHeight <= 0) + storedHeight = 200; + storageContainer.collapse(); + } + else if (height > 0 && collapsed) { + storageContainer.expand(); + } + } + sourceComponent: componentDelegate ? componentDelegate : table } Component @@ -116,17 +130,6 @@ ColumnLayout { selectionMode: enableSelection ? SelectionMode.SingleSelection : SelectionMode.NoSelection headerDelegate: null itemDelegate: root.itemDelegate - onHeightChanged: { - if (height <= 0 && collapsible) { - if (storedHeight <= 0) - storedHeight = 200; - storageContainer.state = "collapsed"; - } - else if (height > 0 && storageContainer.state == "collapsed") { - //TODO: fix increasing size - //storageContainer.state = ""; - } - } onActivated: rowActivated(row); Keys.onPressed: { if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_C && currentRow >=0 && currentRow < listModel.length) { diff --git a/mix/qml/Debugger.qml b/mix/qml/Debugger.qml index e73f8d668..c4b6502b0 100644 --- a/mix/qml/Debugger.qml +++ b/mix/qml/Debugger.qml @@ -170,7 +170,7 @@ Rectangle { TransactionLog { id: transactionLog Layout.fillWidth: true - Layout.minimumHeight: 60 + Layout.minimumHeight: 120 height: 250 anchors.top: parent.top anchors.left: parent.left @@ -233,6 +233,7 @@ Rectangle { height: 30 buttonShortcut: "Ctrl+Shift+F5" buttonTooltip: qsTr("Run Back") + visible: false } StepActionImage @@ -245,6 +246,7 @@ Rectangle { height: 30 buttonShortcut: "Ctrl+Shift+F11" buttonTooltip: qsTr("Step Out Back") + visible: false } StepActionImage diff --git a/mix/qml/ProjectModel.qml b/mix/qml/ProjectModel.qml index a47d77ddb..7c9fdf4ac 100644 --- a/mix/qml/ProjectModel.qml +++ b/mix/qml/ProjectModel.qml @@ -18,8 +18,10 @@ Item { signal documentRemoved(var documentId) signal documentUpdated(var documentId) //renamed signal documentAdded(var documentId) - signal projectSaving(var projectData) + signal projectSaving() + signal projectFileSaving(var projectData) signal projectSaved() + signal projectFileSaved() signal newProject(var projectData) signal documentSaved(var documentId) signal contractSaved(var documentId) diff --git a/mix/qml/StateListModel.qml b/mix/qml/StateListModel.qml index faa0af26f..50ecd16f3 100644 --- a/mix/qml/StateListModel.qml +++ b/mix/qml/StateListModel.qml @@ -6,12 +6,14 @@ import QtQuick.Window 2.2 import QtQuick.Layouts 1.1 import org.ethereum.qml.QEther 1.0 import "js/QEtherHelper.js" as QEtherHelper +import "js/TransactionHelper.js" as TransactionHelper Item { property alias model: stateListModel property var stateList: [] property string defaultAccount: "cb73d9408c4720e230387d956eb0f829d8a4dd2c1055f96257167e14e7169074" //support for old project + function fromPlainStateItem(s) { if (!s.accounts) s.accounts = [stateListModel.newAccount("1000000", QEther.Ether, defaultAccount)]; //support for old project @@ -105,7 +107,7 @@ Item { codeModel.reset(); } onProjectLoading: stateListModel.loadStatesFromProject(projectData); - onProjectSaving: { + onProjectFileSaving: { projectData.states = [] for(var i = 0; i < stateListModel.count; i++) { projectData.states.push(toPlainStateItem(stateList[i])); @@ -121,6 +123,13 @@ Item { } } + Connections { + target: codeModel + onNewContractCompiled: { + stateListModel.addNewContracts(); + } + } + StateDialog { id: stateDialog onAccepted: { @@ -154,12 +163,7 @@ Item { signal stateRun(int index) function defaultTransactionItem() { - return { - value: QEtherHelper.createEther("100", QEther.Wei), - gas: QEtherHelper.createBigInt("125000"), - gasPrice: QEtherHelper.createEther("10000000000000", QEther.Wei), - stdContract: false - }; + return TransactionHelper.defaultTransaction(); } function newAccount(_balance, _unit, _secret) @@ -202,6 +206,32 @@ Item { return item; } + function addNewContracts() { + //add new contracts for all states + for(var c in codeModel.contracts) { + for (var s = 0; s < stateListModel.count; s++) { + var state = stateList[s];//toPlainStateItem(stateListModel.get(s)); + for (var t = 0; t < state.transactions.length; t++) { + var transaction = state.transactions[t]; + if (transaction.functionId === c && transaction.contractId === c) + break; + } + if (t === state.transactions.length) { + //append this contract + var ctorTr = defaultTransactionItem(); + ctorTr.functionId = c; + ctorTr.contractId = c; + ctorTr.sender = state.accounts[0].secret; + state.transactions.push(ctorTr); + var item = state;//fromPlainStateItem(state); + stateListModel.set(s, item); + stateList[s] = item; + } + } + } + save(); + } + function addState() { var item = createDefaultState(); stateDialog.open(stateListModel.count, item, false); diff --git a/mix/qml/StructView.qml b/mix/qml/StructView.qml index 2935e6c72..312a24804 100644 --- a/mix/qml/StructView.qml +++ b/mix/qml/StructView.qml @@ -7,7 +7,7 @@ Column { id: root property alias members: repeater.model //js array - property var value : { } + property var value: ({}) Layout.fillWidth: true Repeater @@ -24,13 +24,13 @@ Column height: 20 id: typeLabel text: modelData.type.name - Layout.preferredWidth: 50 + Layout.preferredWidth: 60 } DefaultLabel { id: nameLabel text: modelData.name - Layout.preferredWidth: 80 + Layout.preferredWidth: 100 } DefaultLabel { diff --git a/mix/qml/TransactionLog.qml b/mix/qml/TransactionLog.qml index 86d48a829..315028c74 100644 --- a/mix/qml/TransactionLog.qml +++ b/mix/qml/TransactionLog.qml @@ -125,7 +125,7 @@ Item { TableViewColumn { role: "transactionIndex" - title: qsTr("Index") + title: qsTr("#") width: 40 } TableViewColumn { @@ -145,8 +145,8 @@ Item { } TableViewColumn { role: "address" - title: qsTr("Address") - width: 120 + title: qsTr("Destination") + width: 130 } TableViewColumn { role: "returned" diff --git a/mix/qml/WebCodeEditor.qml b/mix/qml/WebCodeEditor.qml index 97cbcd30c..c424eeddd 100644 --- a/mix/qml/WebCodeEditor.qml +++ b/mix/qml/WebCodeEditor.qml @@ -40,7 +40,8 @@ Item { } function highlightExecution(location) { - editorBrowser.runJavaScript("highlightExecution(" + location.start + "," + location.end + ")"); + if (initialized) + editorBrowser.runJavaScript("highlightExecution(" + location.start + "," + location.end + ")"); } function getBreakpoints() { @@ -48,11 +49,13 @@ Item { } function toggleBreakpoint() { - editorBrowser.runJavaScript("toggleBreakpoint()"); + if (initialized) + editorBrowser.runJavaScript("toggleBreakpoint()"); } function changeGeneration() { - editorBrowser.runJavaScript("changeGeneration()", function(result) {}); + if (initialized) + editorBrowser.runJavaScript("changeGeneration()", function(result) {}); } Connections { diff --git a/mix/qml/js/ProjectModel.js b/mix/qml/js/ProjectModel.js index e969ea46a..37e95762d 100644 --- a/mix/qml/js/ProjectModel.js +++ b/mix/qml/js/ProjectModel.js @@ -61,10 +61,10 @@ function closeProject(callBack) { function saveProject() { if (!isEmpty) { + projectSaving(); var projectData = saveProjectFile(); if (projectData !== null) { - projectSaving(projectData); projectSaved(); } } @@ -86,9 +86,11 @@ function saveProjectFile() for (var i = 0; i < projectListModel.count; i++) projectData.files.push(projectListModel.get(i).fileName); + projectFileSaving(projectData); var json = JSON.stringify(projectData, null, "\t"); var projectFile = projectPath + projectFileName; fileIo.writeFile(projectFile, json); + projectFileSaved(projectData); return projectData; } return null; diff --git a/mix/qml/js/TransactionHelper.js b/mix/qml/js/TransactionHelper.js index 95208a82d..5668030be 100644 --- a/mix/qml/js/TransactionHelper.js +++ b/mix/qml/js/TransactionHelper.js @@ -5,9 +5,10 @@ function defaultTransaction() return { value: createEther("0", QEther.Wei), functionId: "", - gas: createBigInt("125000"), + gas: createBigInt("250000"), gasPrice: createEther("100000", QEther.Wei), - parameters: {} + parameters: {}, + stdContract: false }; }