From 8404bceb0c9efd5d69de1918b3530918940a52bf Mon Sep 17 00:00:00 2001 From: yann300 Date: Fri, 5 Jun 2015 14:43:38 +0200 Subject: [PATCH] Add ScenarioExecution + ScenarioLoader --- mix/ClientModel.cpp | 231 ++++++++++++++---------- mix/ClientModel.h | 34 +++- mix/ContractCallDataEncoder.cpp | 15 +- mix/ContractCallDataEncoder.h | 4 +- mix/MachineStates.h | 1 + mix/MixClient.cpp | 17 +- mix/MixClient.h | 5 +- mix/main.cpp | 4 +- mix/qml.qrc | 4 + mix/qml/Application.qml | 21 +-- mix/qml/Block.qml | 145 +++++++++++++++ mix/qml/BlockChain.qml | 306 ++++++++++++++++++++++++++++++++ mix/qml/Debugger.qml | 84 ++------- mix/qml/MainContent.qml | 72 +++++--- mix/qml/QAddressView.qml | 32 ++-- mix/qml/ScenarioExecution.qml | 57 ++++++ mix/qml/ScenarioLoader.qml | 77 ++++++++ mix/qml/StateListModel.qml | 129 +++++++++++--- mix/qml/StructView.qml | 1 + mix/qml/TransactionDialog.qml | 14 +- mix/qml/js/ProjectModel.js | 2 + mix/qml/js/TransactionHelper.js | 3 +- 22 files changed, 996 insertions(+), 262 deletions(-) create mode 100644 mix/qml/Block.qml create mode 100644 mix/qml/BlockChain.qml create mode 100644 mix/qml/ScenarioExecution.qml create mode 100644 mix/qml/ScenarioLoader.qml diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index cad210f1b..3cd401e23 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -82,12 +82,14 @@ ClientModel::ClientModel(): qRegisterMetaType("QCallData"); qRegisterMetaType("RecordLogEntry*"); - connect(this, &ClientModel::runComplete, this, &ClientModel::showDebugger, Qt::QueuedConnection); + //connect(this, &ClientModel::runComplete, this, &ClientModel::showDebugger, Qt::QueuedConnection); m_client.reset(new MixClient(QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdString())); m_ethAccounts = make_shared([=](){return m_client.get();}, std::vector()); m_web3Server.reset(new Web3Server(*m_rpcConnector.get(), m_ethAccounts, std::vector(), m_client.get())); connect(m_web3Server.get(), &Web3Server::newTransaction, this, &ClientModel::onNewTransaction, Qt::DirectConnection); + + } ClientModel::~ClientModel() @@ -111,7 +113,7 @@ QString ClientModel::apiCall(QString const& _message) void ClientModel::mine() { - if (m_running || m_mining) + if (m_mining) BOOST_THROW_EXCEPTION(ExecutionStateException()); m_mining = true; emit miningStarted(); @@ -206,92 +208,131 @@ QVariantList ClientModel::gasCosts() const return res; } -void ClientModel::setupState(QVariantMap _state) +void ClientModel::setupScenario(QVariantMap _scenario) { - QVariantList stateAccounts = _state.value("accounts").toList(); - QVariantList stateContracts = _state.value("contracts").toList(); - QVariantList transactions = _state.value("transactions").toList(); + m_queueTransactions.clear(); + m_running = true; + + m_currentScenario = _scenario; + QVariantList blocks = _scenario.value("blocks").toList(); + QVariantList stateAccounts = _scenario.value("accounts").toList(); + + m_accounts.clear(); + std::vector userAccounts; + + for (auto const& b: stateAccounts) + { + QVariantMap account = b.toMap(); + Address address = {}; + if (account.contains("secret")) + { + KeyPair key(Secret(account.value("secret").toString().toStdString())); + userAccounts.push_back(key); + address = key.address(); + } + else if (account.contains("address")) + address = Address(fromHex(account.value("address").toString().toStdString())); + if (!address) + continue; + + m_accounts[address] = Account(qvariant_cast(account.value("balance"))->toU256Wei(), Account::NormalCreation); + } + m_ethAccounts->setAccounts(userAccounts); + for (auto const& b: blocks) + { + QVariantList transactions = b.toMap().value("transactions").toList(); + m_queueTransactions.push_back(transactions); + } + + m_client->resetState(m_accounts, Secret(m_currentScenario.value("miner").toMap().value("secret").toString().toStdString())); + + connect(this, &ClientModel::newBlock, this, &ClientModel::processNextBlock, Qt::QueuedConnection); + connect(this, &ClientModel::runFailed, this, &ClientModel::stopExecution, Qt::QueuedConnection); + connect(this, &ClientModel::runStateChanged, this, &ClientModel::finalizeBlock, Qt::QueuedConnection); + processNextBlock(); +} - unordered_map accounts; - std::vector userAccounts; +void ClientModel::stopExecution() +{ + disconnect(this, &ClientModel::newBlock, this, &ClientModel::processNextBlock); + disconnect(this, &ClientModel::runStateChanged, this, &ClientModel::finalizeBlock); + disconnect(this, &ClientModel::runFailed, this, &ClientModel::stopExecution); + m_running = false; +} - for (auto const& b: stateAccounts) - { - QVariantMap account = b.toMap(); - Address address = {}; - if (account.contains("secret")) - { - KeyPair key(Secret(account.value("secret").toString().toStdString())); - userAccounts.push_back(key); - address = key.address(); - } - else if (account.contains("address")) - address = Address(fromHex(account.value("address").toString().toStdString())); - if (!address) - continue; +void ClientModel::finalizeBlock() +{ + if (m_queueTransactions.size() > 0) + mine(); + else + { + disconnect(this, &ClientModel::newBlock, this, &ClientModel::processNextBlock); + disconnect(this, &ClientModel::runStateChanged, this, &ClientModel::finalizeBlock); + disconnect(this, &ClientModel::runFailed, this, &ClientModel::stopExecution); + m_running = false; + emit runComplete(); + } +} - accounts[address] = Account(qvariant_cast(account.value("balance"))->toU256Wei(), Account::NormalCreation); - } - for (auto const& c: stateContracts) - { - QVariantMap contract = c.toMap(); - Address address = Address(fromHex(contract.value("address").toString().toStdString())); - Account account(qvariant_cast(contract.value("balance"))->toU256Wei(), Account::ContractConception); - bytes code = fromHex(contract.value("code").toString().toStdString()); - account.setCode(code); - QVariantMap storageMap = contract.value("storage").toMap(); - for(auto s = storageMap.cbegin(); s != storageMap.cend(); ++s) - account.setStorage(fromBigEndian(fromHex(s.key().toStdString())), fromBigEndian(fromHex(s.value().toString().toStdString()))); - accounts[address] = account; - } - vector transactionSequence; - 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()); - bool gasAuto = transaction.value("gasAuto").toBool(); - u256 value = (qvariant_cast(transaction.value("value")))->toU256Wei(); - u256 gasPrice = (qvariant_cast(transaction.value("gasPrice")))->toU256Wei(); - QString sender = transaction.value("sender").toString(); - bool isContractCreation = transaction.value("isContractCreation").toBool(); - bool isFunctionCall = transaction.value("isFunctionCall").toBool(); - if (contractId.isEmpty() && m_codeModel->hasContract()) //TODO: This is to support old project files, remove later - contractId = m_codeModel->contracts().keys()[0]; - TransactionSettings transactionSettings(contractId, functionId, value, gas, gasAuto, gasPrice, Secret(sender.toStdString()), isContractCreation, isFunctionCall); - transactionSettings.parameterValues = transaction.value("parameters").toMap(); - - if (contractId == functionId || functionId == "Constructor") - transactionSettings.functionId.clear(); - - transactionSequence.push_back(transactionSettings); - } - m_ethAccounts->setAccounts(userAccounts); - executeSequence(transactionSequence, accounts, Secret(_state.value("miner").toMap().value("secret").toString().toStdString())); +void ClientModel::processNextBlock() +{ + processNextTransactions(); } -void ClientModel::executeSequence(vector const& _sequence, std::unordered_map const& _accounts, Secret const& _miner) +void ClientModel::processNextTransactions() +{ + vector transactionSequence; + for (auto const& t: m_queueTransactions.front()) + { + QVariantMap transaction = t.toMap(); + QString contractId = transaction.value("contractId").toString(); + QString functionId = transaction.value("functionId").toString(); + bool gasAuto = transaction.value("gasAuto").toBool(); + u256 gas = 0; + if (transaction.value("gas").data()) + gas = boost::get(qvariant_cast(transaction.value("gas"))->internalValue()); + else + gasAuto = true; + + u256 value = (qvariant_cast(transaction.value("value")))->toU256Wei(); + u256 gasPrice = (qvariant_cast(transaction.value("gasPrice")))->toU256Wei(); + QString sender = transaction.value("sender").toString(); + bool isContractCreation = transaction.value("isContractCreation").toBool(); + bool isFunctionCall = transaction.value("isFunctionCall").toBool(); + if (contractId.isEmpty() && m_codeModel->hasContract()) //TODO: This is to support old project files, remove later + contractId = m_codeModel->contracts().keys()[0]; + TransactionSettings transactionSettings(contractId, functionId, value, gas, gasAuto, gasPrice, Secret(sender.toStdString()), isContractCreation, isFunctionCall); + transactionSettings.parameterValues = transaction.value("parameters").toMap(); + + if (contractId == functionId || functionId == "Constructor") + transactionSettings.functionId.clear(); + + transactionSequence.push_back(transactionSettings); + } + m_queueTransactions.pop_front(); + executeSequence(transactionSequence); +} + +void ClientModel::executeSequence(vector const& _sequence) { if (m_running) { qWarning() << "Waiting for current execution to complete"; m_runFuture.waitForFinished(); } - m_running = true; emit runStarted(); - emit runStateChanged(); + //emit runStateChanged(); + - m_client->resetState(_accounts, _miner); //run sequence m_runFuture = QtConcurrent::run([=]() { try { vector
deployedContracts; - onStateReset(); + //onStateReset(); m_gasCosts.clear(); for (TransactionSettings const& transaction: _sequence) { @@ -319,12 +360,7 @@ void ClientModel::executeSequence(vector const& _sequence, break; } if (!f) - { - emit runFailed("Function '" + transaction.functionId + tr("' not found. Please check transactions or the contract code.")); - m_running = false; - emit runStateChanged(); - return; - } + emit runFailed("Function '" + transaction.functionId + tr("' not found. Please check transactions or the contract code.")); if (!transaction.functionId.isEmpty()) encoder.encode(f); for (QVariableDeclaration const* p: f->parametersList()) @@ -355,33 +391,34 @@ void ClientModel::executeSequence(vector const& _sequence, { auto contractAddressIter = m_contractAddresses.find(ctrInstance); if (contractAddressIter == m_contractAddresses.end()) - { - emit runFailed("Contract '" + transaction.contractId + tr(" not deployed.") + "' " + tr(" Cannot call ") + transaction.functionId); - m_running = false; - emit runStateChanged(); - return; - } - callAddress(contractAddressIter->second, encoder.encodedData(), transaction); + emit runFailed("Contract '" + transaction.contractId + tr(" not deployed.") + "' " + tr(" Cannot call ") + transaction.functionId); + callAddress(contractAddressIter->second, encoder.encodedData(), transaction); } m_gasCosts.append(m_client->lastExecution().gasUsed); onNewTransaction(); - } - m_running = false; - emit runComplete(); + emit runComplete(); + } } catch(boost::exception const&) { cerr << boost::current_exception_diagnostic_information(); emit runFailed(QString::fromStdString(boost::current_exception_diagnostic_information())); - } + } catch(exception const& e) { cerr << boost::current_exception_diagnostic_information(); emit runFailed(e.what()); - } - m_running = false; + } emit runStateChanged(); - }); + }); +} + +void ClientModel::executeTr(QVariantMap _tr) +{ + QVariantList trs; + trs.push_back(_tr); + m_queueTransactions.push_back(trs); + processNextTransactions(); } @@ -399,7 +436,7 @@ std::pair ClientModel::resolvePair(QString const& _contractId) QString ClientModel::resolveToken(std::pair const& _value, vector
const& _contracts) { if (_contracts.size() > 0) - return QString::fromStdString("0x" + dev::toHex(_contracts.at(_value.second).ref())); + return QString::fromStdString("0x" + dev::toHex(m_contractAddresses[_value].ref())); //dev::toHex(_contracts.at(_value.second).ref())); else return _value.first; } @@ -610,7 +647,7 @@ void ClientModel::emptyRecord() void ClientModel::debugRecord(unsigned _index) { ExecutionResult e = m_client->execution(_index); - showDebuggerForTransaction(e); + showDebuggerForTransaction(e); } Address ClientModel::deployContract(bytes const& _code, TransactionSettings const& _ctrTransaction) @@ -631,7 +668,7 @@ RecordLogEntry* ClientModel::lastBlock() const strGas << blockInfo.gasUsed; stringstream strNumber; strNumber << blockInfo.number; - RecordLogEntry* record = new RecordLogEntry(0, QString::fromStdString(strNumber.str()), tr(" - Block - "), tr("Hash: ") + QString(QString::fromStdString(dev::toHex(blockInfo.hash().ref()))), QString(), QString(), QString(), false, RecordLogEntry::RecordType::Block, QString::fromStdString(strGas.str())); + RecordLogEntry* record = new RecordLogEntry(0, QString::fromStdString(strNumber.str()), tr(" - Block - "), tr("Hash: ") + QString(QString::fromStdString(dev::toHex(blockInfo.hash().ref()))), QString(), QString(), QString(), false, RecordLogEntry::RecordType::Block, QString::fromStdString(strGas.str()), QString(), tr("Block"), QVariantMap()); QQmlEngine::setObjectOwnership(record, QQmlEngine::JavaScriptOwnership); return record; } @@ -648,7 +685,7 @@ void ClientModel::onStateReset() void ClientModel::onNewTransaction() { ExecutionResult const& tr = m_client->lastExecution(); - unsigned block = m_client->number() + 1; + unsigned block = m_client->number() + 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)); @@ -690,6 +727,7 @@ void ClientModel::onNewTransaction() Address contractAddress = (bool)tr.address ? tr.address : tr.contractAddress; auto contractAddressIter = m_contractNames.find(contractAddress); + QVariantMap inputParameters; if (contractAddressIter != m_contractNames.end()) { CompiledContract const& compilerRes = m_codeModel->contract(contractAddressIter->second); @@ -706,11 +744,20 @@ void ClientModel::onNewTransaction() returned += "("; returned += returnValues.join(", "); returned += ")"; - } + bytes data = tr.inputParameters; + data.erase(data.begin(), data.begin() + 4); + QStringList parameters = encoder.decode(funcDef->parametersList(), data); + for (int k = 0; k < parameters.length(); ++k) + inputParameters.insert(funcDef->parametersList().at(k)->name(), parameters.at(k)); + } } } - RecordLogEntry* log = new RecordLogEntry(recordIndex, transactionIndex, contract, function, value, address, returned, tr.isCall(), RecordLogEntry::RecordType::Transaction, gasUsed); + LocalisedLogEntries logs = m_client->logs(); + QString sender = QString::fromStdString(dev::toHex(tr.sender.ref())); + QString label = contract + "." + function + "()"; + RecordLogEntry* log = new RecordLogEntry(recordIndex, transactionIndex, contract, function, value, address, returned, tr.isCall(), RecordLogEntry::RecordType::Transaction, + gasUsed, sender, label, inputParameters); QQmlEngine::setObjectOwnership(log, QQmlEngine::JavaScriptOwnership); emit newRecord(log); } diff --git a/mix/ClientModel.h b/mix/ClientModel.h index e1648b78d..503b4189f 100644 --- a/mix/ClientModel.h +++ b/mix/ClientModel.h @@ -30,12 +30,13 @@ #include #include #include +#include #include "MachineStates.h" namespace dev { -namespace eth { class Account; class FixedAccountHolder; } +namespace eth { class FixedAccountHolder; } namespace mix { @@ -108,6 +109,12 @@ class RecordLogEntry: public QObject Q_PROPERTY(RecordType type MEMBER m_type CONSTANT) /// Gas used Q_PROPERTY(QString gasUsed MEMBER m_gasUsed CONSTANT) + /// Sender + Q_PROPERTY(QString sender MEMBER m_sender CONSTANT) + /// label + Q_PROPERTY(QString label MEMBER m_label CONSTANT) + /// input parameters + Q_PROPERTY(QVariantMap parameters MEMBER m_inputParameters CONSTANT) public: enum RecordType @@ -118,8 +125,10 @@ public: RecordLogEntry(): m_recordIndex(0), m_call(false), m_type(RecordType::Transaction) {} - RecordLogEntry(unsigned _recordIndex, QString _transactionIndex, QString _contract, QString _function, QString _value, QString _address, QString _returned, bool _call, RecordType _type, QString _gasUsed): - m_recordIndex(_recordIndex), m_transactionIndex(_transactionIndex), m_contract(_contract), m_function(_function), m_value(_value), m_address(_address), m_returned(_returned), m_call(_call), m_type(_type), m_gasUsed(_gasUsed) {} + RecordLogEntry(unsigned _recordIndex, QString _transactionIndex, QString _contract, QString _function, QString _value, QString _address, QString _returned, bool _call, RecordType _type, QString _gasUsed, + QString _sender, QString _label, QVariantMap _inputParameters): + m_recordIndex(_recordIndex), m_transactionIndex(_transactionIndex), m_contract(_contract), m_function(_function), m_value(_value), m_address(_address), m_returned(_returned), m_call(_call), m_type(_type), m_gasUsed(_gasUsed), + m_sender(_sender), m_label(_label), m_inputParameters(_inputParameters) {} private: unsigned m_recordIndex; @@ -132,6 +141,9 @@ private: bool m_call; RecordType m_type; QString m_gasUsed; + QString m_sender; + QString m_label; + QVariantMap m_inputParameters; }; /** @@ -172,7 +184,12 @@ public: public slots: /// Setup state, run transaction sequence, show debugger for the last transaction /// @param _state JS object with state configuration - void setupState(QVariantMap _state); + //void setupState(QVariantMap _state); + /// Setup scenario, run transaction sequence, show debugger for the last transaction + /// @param _state JS object with state configuration + void setupScenario(QVariantMap _scenario); + /// Execute the given @param _tr on the curret state + void executeTr(QVariantMap _tr); /// Show the debugger for a specified record Q_INVOKABLE void debugRecord(unsigned _index); /// Show the debugger for an empty record @@ -224,7 +241,7 @@ private: RecordLogEntry* lastBlock() const; QVariantMap contractAddresses() const; QVariantList gasCosts() const; - void executeSequence(std::vector const& _sequence, std::unordered_map const& _accounts, Secret const& _miner); + void executeSequence(std::vector const& _sequence); dev::Address deployContract(bytes const& _code, TransactionSettings const& _tr = TransactionSettings()); void callAddress(Address const& _contract, bytes const& _data, TransactionSettings const& _tr); void onNewTransaction(); @@ -235,6 +252,10 @@ private: std::pair retrieveToken(QString const& _value, std::vector
const& _contracts); std::pair resolvePair(QString const& _contractId); QVariant formatStorageValue(SolidityType const& _type, std::unordered_map const& _storage, unsigned _offset, dev::u256 const& _slot); + void processNextTransactions(); + void processNextBlock(); + void finalizeBlock(); + void stopExecution(); std::atomic m_running; std::atomic m_mining; @@ -243,12 +264,15 @@ private: std::unique_ptr m_rpcConnector; std::unique_ptr m_web3Server; std::shared_ptr m_ethAccounts; + std::unordered_map m_accounts; QList m_gasCosts; std::map, Address> m_contractAddresses; std::map m_contractNames; std::map m_stdContractAddresses; std::map m_stdContractNames; CodeModel* m_codeModel = nullptr; + QList m_queueTransactions; + QVariantMap m_currentScenario; }; } diff --git a/mix/ContractCallDataEncoder.cpp b/mix/ContractCallDataEncoder.cpp index 07ab8dd73..70c1e6068 100644 --- a/mix/ContractCallDataEncoder.cpp +++ b/mix/ContractCallDataEncoder.cpp @@ -29,6 +29,7 @@ #include "QVariableDefinition.h" #include "QFunctionDefinition.h" #include "ContractCallDataEncoder.h" +using namespace std; using namespace dev; using namespace dev::solidity; using namespace dev::mix; @@ -227,6 +228,18 @@ QVariant ContractCallDataEncoder::decode(SolidityType const& _type, bytes const& BOOST_THROW_EXCEPTION(Exception() << errinfo_comment("Parameter declaration not found")); } +QStringList ContractCallDataEncoder::decode(QList const& _returnParameters, vector _value) +{ + QStringList r; + for (int k = 0; k <_returnParameters.length(); k++) + { + QVariableDeclaration* dec = static_cast(_returnParameters.at(k)); + SolidityType const& type = dec->type()->type(); + r.append(decode(type, _value.at(k)).toString()); + } + return r; +} + QStringList ContractCallDataEncoder::decode(QList const& _returnParameters, bytes _value) { bytesConstRef value(&_value); @@ -238,7 +251,7 @@ QStringList ContractCallDataEncoder::decode(QList const& value.populate(&rawParam); value = value.cropped(32); QVariableDeclaration* dec = static_cast(_returnParameters.at(k)); - SolidityType const& type = dec->type()->type(); + SolidityType const& type = dec->type()->type(); r.append(decode(type, rawParam).toString()); } return r; diff --git a/mix/ContractCallDataEncoder.h b/mix/ContractCallDataEncoder.h index 2707845ae..57b364b49 100644 --- a/mix/ContractCallDataEncoder.h +++ b/mix/ContractCallDataEncoder.h @@ -48,7 +48,9 @@ public: void encode(QVariant const& _data, SolidityType const& _type); /// Decode variable in order to be sent to QML view. QStringList decode(QList const& _dec, bytes _value); - /// Decode single variable + /// Decode @param _parameters + QStringList decode(QList const& _parameters, std::vector _value); + /// Decode single variable QVariant decode(SolidityType const& _type, bytes const& _value); /// Get all encoded data encoded by encode function. bytes encodedData(); diff --git a/mix/MachineStates.h b/mix/MachineStates.h index 2a88d83bf..ae9804f7f 100644 --- a/mix/MachineStates.h +++ b/mix/MachineStates.h @@ -83,6 +83,7 @@ namespace mix dev::u256 gasUsed; unsigned transactionIndex; unsigned executonIndex = 0; + bytes inputParameters; 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 e3ed2eead..fcc72db9d 100644 --- a/mix/MixClient.cpp +++ b/mix/MixClient.cpp @@ -22,6 +22,7 @@ #include "MixClient.h" #include +#include #include #include #include @@ -69,19 +70,28 @@ bytes MixBlockChain::createGenesisBlock(h256 _stateRoot) MixClient::MixClient(std::string const& _dbPath): m_dbPath(_dbPath) { - resetState(std::unordered_map()); + resetState(std::unordered_map()); } MixClient::~MixClient() { } +LocalisedLogEntries MixClient::logs() +{ + return m_watches.at(0).changes; +} + void MixClient::resetState(std::unordered_map const& _accounts, Secret const& _miner) { + WriteGuard l(x_state); Guard fl(x_filtersWatches); m_filters.clear(); m_watches.clear(); + LogFilter filter; + m_filters.insert(std::make_pair(filter.sha3(), filter)); + m_watches.insert(std::make_pair(0, ClientWatch(filter.sha3(), Reaping::Automatic))); m_stateDB = OverlayDB(); SecureTrieDB accountState(&m_stateDB); @@ -122,7 +132,7 @@ Transaction MixClient::replaceGas(Transaction const& _t, u256 const& _gas, Secre void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _call, bool _gasAuto, Secret const& _secret) { Transaction t = _gasAuto ? replaceGas(_t, m_state.gasLimitRemaining()) : _t; - // do debugging run first + // do debugging run first LastHashes lastHashes(256); lastHashes[0] = bc().numberHash(bc().number()); for (unsigned i = 1; i < 256; ++i) @@ -213,6 +223,7 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c }; ExecutionResult d; + d.inputParameters = t.data(); d.result = execution.executionResult(); d.machineStates = machineStates; d.executionCode = std::move(codes); @@ -227,7 +238,7 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c d.transactionIndex = m_state.pending().size(); d.executonIndex = m_executions.size(); - // execute on a state + // execute on a state if (!_call) { t = _gasAuto ? replaceGas(_t, d.gasUsed, _secret) : _t; diff --git a/mix/MixClient.h b/mix/MixClient.h index 4c5b51a09..852937634 100644 --- a/mix/MixClient.h +++ b/mix/MixClient.h @@ -25,6 +25,7 @@ #include #include +#include #include #include #include "MachineStates.h" @@ -76,6 +77,8 @@ public: using Interface::blockInfo; // to remove warning about hiding virtual function eth::BlockInfo blockInfo() const; + dev::eth::LocalisedLogEntries logs(); + protected: /// ClientBase methods using ClientBase::asOf; @@ -91,7 +94,7 @@ private: void noteChanged(h256Set const& _filters); dev::eth::Transaction replaceGas(dev::eth::Transaction const& _t, dev::u256 const& _gas, dev::Secret const& _secret = dev::Secret()); - eth::State m_state; + eth::State m_state; eth::State m_startState; OverlayDB m_stateDB; std::unique_ptr m_bc; diff --git a/mix/main.cpp b/mix/main.cpp index 78b5261ac..b4206ec51 100644 --- a/mix/main.cpp +++ b/mix/main.cpp @@ -32,9 +32,9 @@ int main(int _argc, char* _argv[]) { try { - MixApplication::initialize(); + MixApplication::initialize(); MixApplication app(_argc, _argv); - return app.exec(); + return app.exec(); } catch (boost::exception const& _e) { diff --git a/mix/qml.qrc b/mix/qml.qrc index 784404270..0227b4efd 100644 --- a/mix/qml.qrc +++ b/mix/qml.qrc @@ -64,5 +64,9 @@ qml/js/ansi2html.js qml/js/NetworkDeployment.js qml/js/InputValidator.js + qml/Block.qml + qml/BlockChain.qml + qml/ScenarioExecution.qml + qml/ScenarioLoader.qml diff --git a/mix/qml/Application.qml b/mix/qml/Application.qml index d041f421e..509e2e178 100644 --- a/mix/qml/Application.qml +++ b/mix/qml/Application.qml @@ -128,10 +128,8 @@ ApplicationWindow { MenuSeparator {} MenuItem { action: toggleProjectNavigatorAction } MenuItem { action: showHideRightPanelAction } - MenuItem { action: toggleTransactionLogAction } MenuItem { action: toggleWebPreviewAction } MenuItem { action: toggleWebPreviewOrientationAction } - //MenuItem { action: toggleCallsInLog } } } @@ -207,14 +205,14 @@ ApplicationWindow { enabled: codeModel.hasContract && !clientModel.running } - Action { + Action { id: toggleAssemblyDebuggingAction text: qsTr("Show VM Code") shortcut: "Ctrl+Alt+V" - onTriggered: mainContent.rightPane.assemblyMode = !mainContent.rightPane.assemblyMode; - checked: mainContent.rightPane.assemblyMode; + onTriggered: mainContent.debuggerPanel.assemblyMode = !mainContent.debuggerPanel.assemblyMode; + checked: mainContent.debuggerPanel.assemblyMode; enabled: true - } + } Action { id: toggleWebPreviewAction @@ -223,16 +221,7 @@ ApplicationWindow { checkable: true checked: mainContent.webViewVisible onTriggered: mainContent.toggleWebPreview(); - } - - Action { - id: toggleTransactionLogAction - text: qsTr("Show States and Transactions") - shortcut: "Alt+1" - checkable: true - checked: mainContent.rightPane.transactionLog.visible - onTriggered: mainContent.rightPane.transactionLog.visible = !mainContent.rightPane.transactionLog.visible - } + } Action { id: toggleProjectNavigatorAction diff --git a/mix/qml/Block.qml b/mix/qml/Block.qml new file mode 100644 index 000000000..e331dc6a6 --- /dev/null +++ b/mix/qml/Block.qml @@ -0,0 +1,145 @@ +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Layouts 1.1 +import Qt.labs.settings 1.0 +import "js/Debugger.js" as Debugger +import "js/ErrorLocationFormater.js" as ErrorLocationFormater +import "." + +ColumnLayout +{ + property variant transactions + property string status + property int number + Rectangle + { + width: parent.width + height: 50 + anchors.left: parent.left + anchors.leftMargin: statusWidth + Label { + text: + { + if (status === "mined") + return qsTr("BLOCK") + " " + number + else + return qsTr("BLOCK") + " pending" + } + + anchors.left: parent.left + } + } + + Repeater // List of transactions + { + id: transactionRepeater + model: transactions + Row + { + height: 50 + Rectangle + { + id: trSaveStatus + color: "transparent" + CheckBox + { + id: saveStatus + checked: { + if (index >= 0) + return transactions.get(index).saveStatus + else + return true + } + onCheckedChanged: + { + if (index >= 0) + transactions.get(index).saveStatus = checked + } + } + } + + Rectangle + { + width: parent.width + height: 50 + color: "#cccccc" + radius: 4 + Row + { + Label + { + id: status + width: statusWidth + } + Label + { + id: hash + width: fromWidth + text: { + if (index >= 0) + return transactions.get(index).sender + else + return "" + } + + clip: true + } + Label + { + id: func + text: { + if (index >= 0) + parent.userFrienldyToken(transactions.get(index).label) + else + return "" + } + + width: toWidth + clip: true + } + + function userFrienldyToken(value) + { + if (value && value.indexOf("<") === 0) + return value.split(" - ")[0].replace("<", "") + "." + value.split("> ")[1] + "()"; + else + return value + } + + Label + { + id: returnValue + width: valueWidth + text: { + if (index >= 0 && transactions.get(index).returned) + return transactions.get(index).returned + else + return "" + } + clip: true + } + + Button + { + id: debug + width: logsWidth + text: "debug" + onClicked: + { + clientModel.debugRecord(transactions.get(index).recordIndex); + } + } + + Label + { + id: logs + width: logsWidth + } + } + } + } + } +} + diff --git a/mix/qml/BlockChain.qml b/mix/qml/BlockChain.qml new file mode 100644 index 000000000..6e87ed52d --- /dev/null +++ b/mix/qml/BlockChain.qml @@ -0,0 +1,306 @@ +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Layouts 1.1 +import Qt.labs.settings 1.0 +import org.ethereum.qml.QEther 1.0 +import "js/Debugger.js" as Debugger +import "js/ErrorLocationFormater.js" as ErrorLocationFormater +import "js/TransactionHelper.js" as TransactionHelper +import "js/QEtherHelper.js" as QEtherHelper +import "." + +Column { + id: blockChainPanel + property variant model + spacing: 5 + + function load(scenario) + { + + if (!scenario) + return; + model = scenario + blockModel.clear() + for (var b in model.blocks) + blockModel.append(model.blocks[b]) + } + + property int statusWidth: 50 + property int fromWidth: 100 + property int toWidth: 250 + property int valueWidth: 100 + property int logsWidth: 50 + + Row + { + id: header + width: parent.width + Label + { + text: "Status" + width: statusWidth + } + Label + { + text: "From" + width: fromWidth + } + Label + { + text: "To" + width: toWidth + } + Label + { + text: "Value" + width: valueWidth + } + Label + { + text: "Logs" + width: logsWidth + } + } + + Rectangle + { + width: parent.width + height: 500 + border.color: "#cccccc" + border.width: 2 + color: "white" + ScrollView + { + width: parent.width + height: parent.height + ColumnLayout + { + Repeater // List of blocks + { + id: blockChainRepeater + width: parent.width + model: blockModel + Block + { + height: + { + if (index >= 0) + return 50 + 50 * blockModel.get(index).transactions.length + else + return 0 + } + + transactions: + { + if (index >= 0) + return blockModel.get(index).transactions + else + return [] + } + + status: + { + if (index >= 0) + return blockModel.get(index).status + else + return "" + } + + number: + { + if (index >= 0) + return blockModel.get(index).number + else + return 0 + } + } + } + } + } + } + + ListModel + { + id: blockModel + + function appendBlock(block) + { + blockModel.append(block); + } + + function appendTransaction(tr) + { + blockModel.get(blockModel.count - 1).transactions.append(tr) + } + + function removeTransaction(blockIndex, trIndex) + { + console.log(blockIndex) + console.log(trIndex) + blockModel.get(blockIndex).transactions.remove(trIndex) + } + + function removeLastBlock() + { + blockModel.remove(blockModel.count - 1) + } + + function removeBlock(index) + { + blockModel.remove(index) + } + + function getTransaction(block, tr) + { + return blockModel.get(block - 1).transactions.get(tr) + } + } + + RowLayout + { + width: parent.width + Button { + id: rebuild + text: qsTr("Rebuild") + onClicked: + { + for (var j = 0; j < model.blocks.length; j++) + { + for (var k = 0; k < model.blocks[j].transactions.length; k++) + { + if (!blockModel.get(j).transactions.get(k).saveStatus) + { + model.blocks[j].transactions.splice(k, 1) + blockModel.removeTransaction(j, k) + if (model.blocks[j].transactions.length === 0) + { + model.blocks[j].splice(j, 1); + blockModel.removeBlock(j); + } + } + } + } + clientModel.setupScenario(model); + } + } + + Button { + id: addTransaction + text: qsTr("Add Transaction") + onClicked: + { + var lastBlock = model.blocks[model.blocks.length - 1]; + if (lastBlock.status === "mined") + model.blocks.push(projectModel.stateListModel.createEmptyBlock()); + var item = TransactionHelper.defaultTransaction() + transactionDialog.stateAccounts = model.accounts + transactionDialog.open(model.blocks[model.blocks.length - 1].transactions.length, model.blocks.length - 1, item) + } + } + + Button { + id: addBlockBtn + text: qsTr("Add Block") + onClicked: + { + var lastBlock = model.blocks[model.blocks.length - 1] + if (lastBlock.status === "pending") + clientModel.mine() + else + addNewBlock() + } + + function addNewBlock() + { + var block = projectModel.stateListModel.createEmptyBlock() + model.blocks.push(block) + blockModel.appendBlock(block) + } + } + + Connections + { + target: clientModel + onNewBlock: + { + if (!clientModel.running) + { + var lastBlock = model.blocks[model.blocks.length - 1] + lastBlock.status = "mined" + lastBlock.number = model.blocks.length + var lastB = blockModel.get(model.blocks.length - 1) + lastB.status = "mined" + lastB.number = model.blocks.length + addBlockBtn.addNewBlock() + } + } + onStateCleared: + { + } + onNewRecord: + { + var blockIndex = _r.transactionIndex.split(":")[0] + var trIndex = _r.transactionIndex.split(":")[1] + if (parseInt(blockIndex) <= model.blocks.length) + { + var item = model.blocks[parseInt(blockIndex) - 1]; + if (parseInt(trIndex) <= item.transactions.length) + { + var tr = item.transactions[parseInt(trIndex)]; + tr.returned = _r.returned; + blockModel.getTransaction(blockIndex, trIndex).returned = _r.returned; + tr.recordIndex = _r.recordIndex; + blockModel.getTransaction(blockIndex, trIndex).recordIndex = _r.recordIndex; + return; + } + } + + // tr is not in the list. coming from JavaScript + var itemTr = TransactionHelper.defaultTransaction() + itemTr.functionId = _r.function + itemTr.contractId = _r.contract + itemTr.gasAuto = true + itemTr.parameters = _r.parameters + itemTr.isContractCreation = itemTr.functionId === itemTr.contractId + itemTr.label = _r.label + itemTr.isFunctionCall = itemTr.functionId !== "" + itemTr.returned = _r.returned + itemTr.value = QEtherHelper.createEther(_r.value, QEther.Wei) + itemTr.sender = _r.sender + itemTr.recordIndex = _r.recordIndex + + model.blocks[model.blocks.length - 1].transactions.push(itemTr) + blockModel.appendTransaction(itemTr) + } + onMiningComplete: + { + } + } + + Button { + id: newAccount + text: qsTr("New Account") + onClicked: { + model.accounts.push(projectModel.stateListModel.newAccount("1000000", QEther.Ether)) + } + } + } + + TransactionDialog { + id: transactionDialog + onAccepted: { + var item = transactionDialog.getItem() + var lastBlock = model.blocks[model.blocks.length - 1]; + if (lastBlock.status === "mined") + model.blocks.push(projectModel.stateListModel.createEmptyBlock()); + model.blocks[model.blocks.length - 1].transactions.push(item) + blockModel.appendTransaction(item) + if (!clientModel.running) + clientModel.executeTr(item) + } + } +} + + diff --git a/mix/qml/Debugger.qml b/mix/qml/Debugger.qml index 9decc91ae..7827931b0 100644 --- a/mix/qml/Debugger.qml +++ b/mix/qml/Debugger.qml @@ -11,7 +11,6 @@ import "." Rectangle { id: debugPanel - property alias transactionLog: transactionLog property alias debugSlider: statesSlider property alias solLocals: solLocals property alias solStorage: solStorage @@ -23,7 +22,7 @@ Rectangle { signal debugExecuteLocation(string documentId, var location) property string compilationErrorMessage property bool assemblyMode: false - + signal panelClosed objectName: "debugPanel" color: "#ededed" clip: true @@ -61,7 +60,6 @@ Rectangle { { Debugger.init(data); debugScrollArea.visible = true; - compilationErrorArea.visible = false; machineStates.visible = true; } if (giveFocus) @@ -97,85 +95,21 @@ Rectangle { Settings { id: splitSettings - property alias transactionLogHeight: transactionLog.height property alias callStackHeight: callStackRect.height property alias storageHeightSettings: storageRect.height property alias memoryDumpHeightSettings: memoryRect.height property alias callDataHeightSettings: callDataRect.height - property alias transactionLogVisible: transactionLog.visible property alias solCallStackHeightSettings: solStackRect.height property alias solStorageHeightSettings: solStorageRect.height property alias solLocalsHeightSettings: solLocalsRect.height } - Rectangle - { - visible: false; - id: compilationErrorArea - width: parent.width - 20 - height: 600 - color: "#ededed" - anchors.left: parent.left - anchors.top: parent.top - anchors.margins: 10 - ColumnLayout - { - width: parent.width - anchors.top: parent.top - spacing: 15 - Rectangle - { - height: 15 - Button { - text: qsTr("Back to Debugger") - onClicked: { - debugScrollArea.visible = true; - compilationErrorArea.visible = false; - machineStates.visible = true; - } - } - } - - RowLayout - { - height: 100 - ColumnLayout - { - Text { - color: "red" - id: errorLocation - } - Text { - color: "#4a4a4a" - id: errorDetail - } - } - } - - Rectangle - { - width: parent.width - 6 - height: 2 - color: "#d0d0d0" - } - - RowLayout - { - Text - { - color: "#4a4a4a" - id: errorLine - } - } - } - } - - Splitter { + Splitter { id: debugScrollArea anchors.fill: parent orientation: Qt.Vertical - TransactionLog { + /*TransactionLog { id: transactionLog Layout.fillWidth: true Layout.minimumHeight: 130 @@ -186,7 +120,13 @@ Rectangle { anchors.leftMargin: machineStates.sideMargin anchors.rightMargin: machineStates.sideMargin anchors.topMargin: machineStates.sideMargin - } + }*/ + + Button + { + text: qsTr("close") + onClicked: panelClosed() + } ScrollView { @@ -230,7 +170,7 @@ Rectangle { spacing: 3 layoutDirection: Qt.LeftToRight - StepActionImage + /*StepActionImage { id: playAction enabledStateImg: "qrc:/qml/img/play_button.png" @@ -254,7 +194,7 @@ Rectangle { buttonShortcut: "Ctrl+Shift+F9" buttonTooltip: qsTr("Stop Debugging") visible: true - } + }*/ StepActionImage { diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index 67f45f973..4af8ece46 100644 --- a/mix/qml/MainContent.qml +++ b/mix/qml/MainContent.qml @@ -20,13 +20,14 @@ Rectangle { anchors.fill: parent id: root - property alias rightViewVisible: rightView.visible + property alias rightViewVisible: scenarioExe.visible 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 rightPane: scenarioExe + property alias debuggerPanel: debugPanel property alias codeEditor: codeEditor property bool webViewHorizontal: codeWebSplitter.orientation === Qt.Vertical //vertical splitter positions elements vertically, splits screen horizontally property bool firstCompile: true @@ -42,17 +43,17 @@ Rectangle { } } - Connections { - target: rightView - onDebugExecuteLocation: { + Connections { + target: debugPanel + onDebugExecuteLocation: { codeEditor.highlightExecution(documentId, location); } - } + } Connections { target: codeEditor onBreakpointsChanged: { - rightPane.setBreakpoints(codeEditor.getBreakpoints()); + debugPanel.setBreakpoints(codeEditor.getBreakpoints()); } } @@ -63,20 +64,20 @@ Rectangle { } function toggleRightView() { - rightView.visible = !rightView.visible; + scenarioExe.visible = !scenarioExe.visible; } function ensureRightView() { - rightView.visible = true; + scenarioExe.visible = true; } function rightViewIsVisible() { - return rightView.visible; + return scenarioExe.visible; } function hideRightView() { - rightView.visible = false; + scenarioExe.visible = lfalse; } function toggleWebPreview() { @@ -98,8 +99,8 @@ Rectangle { function displayCompilationErrorIfAny() { - rightView.visible = true; - rightView.displayCompilationErrorIfAny(); + scenarioExe.visible = true; + scenarioExe.displayCompilationErrorIfAny(); } Settings { @@ -153,7 +154,7 @@ Rectangle { id: splitSettings property alias projectWidth: projectList.width property alias contentViewWidth: contentView.width - property alias rightViewWidth: rightView.width + property alias rightViewWidth: scenarioExe.width } Splitter @@ -198,14 +199,41 @@ Rectangle { } } - Debugger { - visible: false; - id: rightView; - Layout.fillHeight: true - Keys.onEscapePressed: visible = false - Layout.minimumWidth: 515 - anchors.right: parent.right - } + ScenarioExecution + { + id: scenarioExe; + visible: false; + Layout.fillHeight: true + Keys.onEscapePressed: visible = false + Layout.minimumWidth: 650 + anchors.right: parent.right + } + + Debugger + { + id: debugPanel + visible: false + Layout.fillHeight: true + Keys.onEscapePressed: visible = false + Layout.minimumWidth: 650 + anchors.right: parent.right + } + + Connections { + target: clientModel + onDebugDataReady: { + scenarioExe.visible = false + debugPanel.visible = true + } + } + + Connections { + target: debugPanel + onPanelClosed: { + scenarioExe.visible = true + debugPanel.visible = false + } + } } } } diff --git a/mix/qml/QAddressView.qml b/mix/qml/QAddressView.qml index c880f0904..3c34d45eb 100644 --- a/mix/qml/QAddressView.qml +++ b/mix/qml/QAddressView.qml @@ -40,20 +40,28 @@ Item accountRef.clear(); accountRef.append({"itemid": " - "}); - if (subType === "contract" || subType === "address") + console.log(blockIndex) + console.log(transactionIndex) + if (subType === "contract" || subType === "address") { var trCr = 0; - for (var k = 0; k < transactionsModel.count; k++) - { - if (k >= transactionIndex) - break; - var tr = transactionsModel.get(k); - if (tr.functionId === tr.contractId /*&& (dec[1] === tr.contractId || item.subType === "address")*/) - { - accountRef.append({ "itemid": tr.contractId + " - " + trCr, "value": "<" + tr.contractId + " - " + trCr + ">", "type": "contract" }); - trCr++; - } - } + if (blockChainPanel) + for (var k = 0; k < blockChainPanel.model.blocks.length; k++) + { + if (k > blockIndex) + break; + for (var i = 0; i < blockChainPanel.model.blocks[k].transactions.length; i++) + { + if (i > transactionIndex) + break; + var tr = blockChainPanel.model.blocks[k].transactions[i] + if (tr.functionId === tr.contractId /*&& (dec[1] === tr.contractId || item.subType === "address")*/) + { + accountRef.append({ "itemid": tr.contractId + " - " + trCr, "value": "<" + tr.contractId + " - " + trCr + ">", "type": "contract" }); + trCr++; + } + } + } } if (subType === "address") { diff --git a/mix/qml/ScenarioExecution.qml b/mix/qml/ScenarioExecution.qml new file mode 100644 index 000000000..5fcbfab3e --- /dev/null +++ b/mix/qml/ScenarioExecution.qml @@ -0,0 +1,57 @@ +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Layouts 1.1 +import Qt.labs.settings 1.0 +import "js/Debugger.js" as Debugger +import "js/ErrorLocationFormater.js" as ErrorLocationFormater +import "." + +Rectangle { + + border.color: "red" + border.width: 1 + + Connections + { + target: projectModel + onProjectLoaded: { + loader.init() + } + + } + + Column + { + anchors.margins: 10 + anchors.fill: parent + spacing: 5 + ScenarioLoader + { + width: parent.width + id: loader + } + + Rectangle + { + width: parent.width + height: 1 + color: "#cccccc" + } + + Connections + { + target: loader + onLoaded: { + blockChain.load(scenario) + } + } + + BlockChain + { + id: blockChain + width: parent.width + } + } +} diff --git a/mix/qml/ScenarioLoader.qml b/mix/qml/ScenarioLoader.qml new file mode 100644 index 000000000..0b703c860 --- /dev/null +++ b/mix/qml/ScenarioLoader.qml @@ -0,0 +1,77 @@ +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.1 +import QtQuick.Dialogs 1.1 +import QtQuick.Layouts 1.1 +import Qt.labs.settings 1.0 +import "js/Debugger.js" as Debugger +import "js/ErrorLocationFormater.js" as ErrorLocationFormater +import "." + +RowLayout +{ + signal restored(variant scenario) + signal saved(variant scenario) + signal duplicated(variant scenario) + signal loaded(variant scenario) + + function init() + { + scenarioList.load() + } + + id: blockChainSelector + ComboBox + { + id: scenarioList + model: projectModel.stateListModel + textRole: "title" + onCurrentIndexChanged: + { + restoreScenario.restore() + } + + function load() + { + var state = projectModel.stateListModel.getState(currentIndex) + loaded(state) + } + } + Button + { + id: restoreScenario + text: qsTr("Restore") + onClicked: { + restore() + } + + function restore() + { + var state = projectModel.stateListModel.reloadStateFromFromProject(scenarioList.currentIndex) + restored(state) + loaded(state) + } + + } + Button + { + id: saveScenario + text: qsTr("Save") + onClicked: { + projectModel.saveProjectFile() + saved(state) + } + } + Button + { + id: duplicateScenario + text: qsTr("Duplicate") + onClicked: { + var state = JSON.parse(JSON.stringify(projectModel.stateListModel.getState(scenarioList.currentIndex))) + state.title = qsTr("Copy of ") + state.title; + projectModel.stateListModel.appendState(state) + projectModel.stateListModel.save() + duplicated(state) + } + } +} diff --git a/mix/qml/StateListModel.qml b/mix/qml/StateListModel.qml index d3062af9e..d08ed668d 100644 --- a/mix/qml/StateListModel.qml +++ b/mix/qml/StateListModel.qml @@ -23,7 +23,8 @@ Item { return { title: s.title, transactions: s.transactions.filter(function(t) { return !t.stdContract; }).map(fromPlainTransactionItem), //support old projects by filtering std contracts - accounts: s.accounts.map(fromPlainAccountItem), + blocks: s.blocks.map(fromPlainBlockItem), + accounts: s.accounts.map(fromPlainAccountItem), contracts: s.contracts.map(fromPlainAccountItem), miner: s.miner }; @@ -58,7 +59,8 @@ Item { sender: t.sender, isContractCreation: t.isContractCreation, label: t.label, - isFunctionCall: t.isFunctionCall + isFunctionCall: t.isFunctionCall, + saveStatus: t.saveStatus }; if (r.isFunctionCall === undefined) @@ -76,10 +78,22 @@ Item { return r; } + function fromPlainBlockItem(b) + { + var r = { + hash: b.hash, + number: b.number, + transactions: b.transactions.filter(function(t) { return !t.stdContract; }).map(fromPlainTransactionItem), //support old projects by filtering std contracts + status: b.status + } + return r; + } + function toPlainStateItem(s) { return { title: s.title, - transactions: s.transactions.map(toPlainTransactionItem), + blocks: s.blocks.map(toPlainBlockItem), + transactions: s.transactions.map(toPlainTransactionItem), accounts: s.accounts.map(toPlainAccountItem), contracts: s.contracts.map(toPlainAccountItem), miner: s.miner @@ -96,6 +110,17 @@ Item { return ''; } + function toPlainBlockItem(b) + { + var r = { + hash: b.hash, + number: b.number, + transactions: b.transactions.map(toPlainTransactionItem), + status: b.status + } + return r; + } + function toPlainAccountItem(t) { return { @@ -112,6 +137,7 @@ Item { } function toPlainTransactionItem(t) { + console.log(JSON.stringify(t)); var r = { type: t.type, contractId: t.contractId, @@ -125,7 +151,8 @@ Item { parameters: {}, isContractCreation: t.isContractCreation, label: t.label, - isFunctionCall: t.isFunctionCall + isFunctionCall: t.isFunctionCall, + saveStatus: t.saveStatus }; for (var key in t.parameters) r.parameters[key] = t.parameters[key]; @@ -146,14 +173,16 @@ Item { projectData.states.push(toPlainStateItem(stateList[i])); } projectData.defaultStateIndex = stateListModel.defaultStateIndex; - } + stateListModel.data = projectData + + } onNewProject: { var state = toPlainStateItem(stateListModel.createDefaultState()); state.title = qsTr("Default"); projectData.states = [ state ]; projectData.defaultStateIndex = 0; - stateListModel.loadStatesFromProject(projectData); - } + stateListModel.loadStatesFromProject(projectData); + } } Connections { @@ -170,34 +199,40 @@ Item { id: stateDialog onAccepted: { var item = stateDialog.getItem(); - if (stateDialog.stateIndex < stateListModel.count) { - if (stateDialog.isDefault) - stateListModel.defaultStateIndex = stateIndex; - stateList[stateDialog.stateIndex] = item; - stateListModel.set(stateDialog.stateIndex, item); - } else { - if (stateDialog.isDefault) - stateListModel.defaultStateIndex = 0; - stateList.push(item); - stateListModel.append(item); - } - if (stateDialog.isDefault) - stateListModel.defaultStateChanged(); - stateListModel.save(); + saveState(item); } + + function saveState(item) + { + if (stateDialog.stateIndex < stateListModel.count) { + if (stateDialog.isDefault) + stateListModel.defaultStateIndex = stateIndex; + stateList[stateDialog.stateIndex] = item; + stateListModel.set(stateDialog.stateIndex, item); + } else { + if (stateDialog.isDefault) + stateListModel.defaultStateIndex = 0; + stateList.push(item); + stateListModel.append(item); + } + if (stateDialog.isDefault) + stateListModel.defaultStateChanged(); + stateListModel.save(); + } } ListModel { id: stateListModel property int defaultStateIndex: 0 - signal defaultStateChanged; + property variant data + signal defaultStateChanged; signal stateListModelReady; signal stateRun(int index) signal stateDeleted(int index) function defaultTransactionItem() { return TransactionHelper.defaultTransaction(); - } + } function newAccount(_balance, _unit, _secret) { @@ -208,15 +243,26 @@ Item { return { name: name, secret: _secret, balance: QEtherHelper.createEther(_balance, _unit), address: address }; } + function createEmptyBlock() + { + return { + hash: "", + number: -1, + transactions: [], + status: "pending" + } + } + function createDefaultState() { var item = { title: "", transactions: [], accounts: [], - contracts: [] - }; + contracts: [], + blocks: [{ status: "pending", number: -1, hash: "", transactions: []}] + }; - var account = newAccount("1000000", QEther.Ether, defaultAccount) + var account = newAccount("1000000", QEther.Ether, defaultAccount) item.accounts.push(account); item.miner = account; @@ -228,7 +274,8 @@ Item { ctorTr.label = qsTr("Deploy") + " " + ctorTr.contractId; ctorTr.sender = item.accounts[0].secret; item.transactions.push(ctorTr); - } + item.blocks[0].transactions.push(ctorTr) + } return item; } @@ -284,10 +331,20 @@ Item { stateDialog.open(stateListModel.count, item, false); } + function appendState(item) + { + stateListModel.append(item); + stateList.push(item); + } + function editState(index) { stateDialog.open(index, stateList[index], defaultStateIndex === index); } + function getState(index) { + return stateList[index]; + } + function debugDefaultState() { if (defaultStateIndex >= 0 && defaultStateIndex < stateList.length) runState(defaultStateIndex); @@ -295,8 +352,8 @@ Item { function runState(index) { var item = stateList[index]; - clientModel.setupState(item); - stateRun(index); + //clientModel.setupState(item); + //stateRun(index); } function deleteState(index) { @@ -322,8 +379,22 @@ Item { return stateList[defaultStateIndex].title; } + function reloadStateFromFromProject(index) + { + console.log(JSON.stringify(data)) + if (data) + { + var item = fromPlainStateItem(data.states[index]) + + stateListModel.set(index, item) + stateList[index] = item + return item + } + } + function loadStatesFromProject(projectData) { + data = projectData if (!projectData.states) projectData.states = []; if (projectData.defaultStateIndex !== undefined) diff --git a/mix/qml/StructView.qml b/mix/qml/StructView.qml index 029fd162d..9cb461204 100644 --- a/mix/qml/StructView.qml +++ b/mix/qml/StructView.qml @@ -9,6 +9,7 @@ Column property alias members: repeater.model //js array property variant accounts property var value: ({}) + property int blockIndex property int transactionIndex property string context Layout.fillWidth: true diff --git a/mix/qml/TransactionDialog.qml b/mix/qml/TransactionDialog.qml index df5ad781b..b53c7c787 100644 --- a/mix/qml/TransactionDialog.qml +++ b/mix/qml/TransactionDialog.qml @@ -17,6 +17,7 @@ Dialog { visible: false title: qsTr("Edit Transaction") property int transactionIndex + property int blockIndex property alias gas: gasValueEdit.gasValue; property alias gasAuto: gasAutoCheck.checked; property alias gasPrice: gasPriceField.value; @@ -27,21 +28,24 @@ Dialog { property var paramsModel: []; property bool useTransactionDefaultValue: false property alias stateAccounts: senderComboBox.model + property bool saveStatus signal accepted; StateDialogStyle { id: transactionDialogStyle } - function open(index, item) { + function open(index, blockIdx, item) { rowFunction.visible = !useTransactionDefaultValue; rowValue.visible = !useTransactionDefaultValue; rowGas.visible = !useTransactionDefaultValue; rowGasPrice.visible = !useTransactionDefaultValue; - transactionIndex = index; - typeLoader.transactionIndex = index; - + transactionIndex = index + blockIndex = blockIdx + typeLoader.transactionIndex = index + typeLoader.blockIndex = blockIdx + saveStatus = item.saveStatus gasValueEdit.gasValue = item.gas; gasAutoCheck.checked = item.gasAuto ? true : false; gasPriceField.value = item.gasPrice; @@ -229,7 +233,7 @@ Dialog { item.functionId = item.contractId; item.label = qsTr("Deploy") + " " + item.contractId; } - + item.saveStatus = saveStatus item.sender = senderComboBox.model[senderComboBox.currentIndex].secret; item.parameters = paramValues; return item; diff --git a/mix/qml/js/ProjectModel.js b/mix/qml/js/ProjectModel.js index 6fce7686d..1e6169e5d 100644 --- a/mix/qml/js/ProjectModel.js +++ b/mix/qml/js/ProjectModel.js @@ -101,6 +101,8 @@ function loadProject(path) { console.log("Loading project at " + path); var projectFile = path + projectFileName; var json = fileIo.readFile(projectFile); + if (!json) + return; var projectData = JSON.parse(json); if (projectData.deploymentDir) projectModel.deploymentDir = projectData.deploymentDir diff --git a/mix/qml/js/TransactionHelper.js b/mix/qml/js/TransactionHelper.js index b9a011b66..8a7c5f5b8 100644 --- a/mix/qml/js/TransactionHelper.js +++ b/mix/qml/js/TransactionHelper.js @@ -12,7 +12,8 @@ function defaultTransaction() stdContract: false, isContractCreation: true, label: "", - isFunctionCall: true + isFunctionCall: true, + saveStatus: true }; }