Browse Source

Add ScenarioExecution + ScenarioLoader

cl-refactor
yann300 10 years ago
parent
commit
8404bceb0c
  1. 231
      mix/ClientModel.cpp
  2. 34
      mix/ClientModel.h
  3. 15
      mix/ContractCallDataEncoder.cpp
  4. 4
      mix/ContractCallDataEncoder.h
  5. 1
      mix/MachineStates.h
  6. 17
      mix/MixClient.cpp
  7. 5
      mix/MixClient.h
  8. 4
      mix/main.cpp
  9. 4
      mix/qml.qrc
  10. 21
      mix/qml/Application.qml
  11. 145
      mix/qml/Block.qml
  12. 306
      mix/qml/BlockChain.qml
  13. 84
      mix/qml/Debugger.qml
  14. 72
      mix/qml/MainContent.qml
  15. 32
      mix/qml/QAddressView.qml
  16. 57
      mix/qml/ScenarioExecution.qml
  17. 77
      mix/qml/ScenarioLoader.qml
  18. 129
      mix/qml/StateListModel.qml
  19. 1
      mix/qml/StructView.qml
  20. 14
      mix/qml/TransactionDialog.qml
  21. 2
      mix/qml/js/ProjectModel.js
  22. 3
      mix/qml/js/TransactionHelper.js

231
mix/ClientModel.cpp

@ -82,12 +82,14 @@ ClientModel::ClientModel():
qRegisterMetaType<QCallData*>("QCallData"); qRegisterMetaType<QCallData*>("QCallData");
qRegisterMetaType<RecordLogEntry*>("RecordLogEntry*"); qRegisterMetaType<RecordLogEntry*>("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_client.reset(new MixClient(QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdString()));
m_ethAccounts = make_shared<FixedAccountHolder>([=](){return m_client.get();}, std::vector<KeyPair>()); m_ethAccounts = make_shared<FixedAccountHolder>([=](){return m_client.get();}, std::vector<KeyPair>());
m_web3Server.reset(new Web3Server(*m_rpcConnector.get(), m_ethAccounts, std::vector<KeyPair>(), m_client.get())); m_web3Server.reset(new Web3Server(*m_rpcConnector.get(), m_ethAccounts, std::vector<KeyPair>(), m_client.get()));
connect(m_web3Server.get(), &Web3Server::newTransaction, this, &ClientModel::onNewTransaction, Qt::DirectConnection); connect(m_web3Server.get(), &Web3Server::newTransaction, this, &ClientModel::onNewTransaction, Qt::DirectConnection);
} }
ClientModel::~ClientModel() ClientModel::~ClientModel()
@ -111,7 +113,7 @@ QString ClientModel::apiCall(QString const& _message)
void ClientModel::mine() void ClientModel::mine()
{ {
if (m_running || m_mining) if (m_mining)
BOOST_THROW_EXCEPTION(ExecutionStateException()); BOOST_THROW_EXCEPTION(ExecutionStateException());
m_mining = true; m_mining = true;
emit miningStarted(); emit miningStarted();
@ -206,92 +208,131 @@ QVariantList ClientModel::gasCosts() const
return res; return res;
} }
void ClientModel::setupState(QVariantMap _state) void ClientModel::setupScenario(QVariantMap _scenario)
{ {
QVariantList stateAccounts = _state.value("accounts").toList(); m_queueTransactions.clear();
QVariantList stateContracts = _state.value("contracts").toList(); m_running = true;
QVariantList transactions = _state.value("transactions").toList();
m_currentScenario = _scenario;
QVariantList blocks = _scenario.value("blocks").toList();
QVariantList stateAccounts = _scenario.value("accounts").toList();
m_accounts.clear();
std::vector<KeyPair> 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<QEther*>(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<Address, Account> accounts; void ClientModel::stopExecution()
std::vector<KeyPair> userAccounts; {
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) void ClientModel::finalizeBlock()
{ {
QVariantMap account = b.toMap(); if (m_queueTransactions.size() > 0)
Address address = {}; mine();
if (account.contains("secret")) else
{ {
KeyPair key(Secret(account.value("secret").toString().toStdString())); disconnect(this, &ClientModel::newBlock, this, &ClientModel::processNextBlock);
userAccounts.push_back(key); disconnect(this, &ClientModel::runStateChanged, this, &ClientModel::finalizeBlock);
address = key.address(); disconnect(this, &ClientModel::runFailed, this, &ClientModel::stopExecution);
} m_running = false;
else if (account.contains("address")) emit runComplete();
address = Address(fromHex(account.value("address").toString().toStdString())); }
if (!address) }
continue;
accounts[address] = Account(qvariant_cast<QEther*>(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<QEther*>(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<u256>(fromHex(s.key().toStdString())), fromBigEndian<u256>(fromHex(s.value().toString().toStdString())));
accounts[address] = account;
}
vector<TransactionSettings> transactionSequence; void ClientModel::processNextBlock()
for (auto const& t: transactions) {
{ processNextTransactions();
QVariantMap transaction = t.toMap();
QString contractId = transaction.value("contractId").toString();
QString functionId = transaction.value("functionId").toString();
u256 gas = boost::get<u256>(qvariant_cast<QBigInt*>(transaction.value("gas"))->internalValue());
bool gasAuto = transaction.value("gasAuto").toBool();
u256 value = (qvariant_cast<QEther*>(transaction.value("value")))->toU256Wei();
u256 gasPrice = (qvariant_cast<QEther*>(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::executeSequence(vector<TransactionSettings> const& _sequence, std::unordered_map<Address, Account> const& _accounts, Secret const& _miner) void ClientModel::processNextTransactions()
{
vector<TransactionSettings> 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<u256>(qvariant_cast<QBigInt*>(transaction.value("gas"))->internalValue());
else
gasAuto = true;
u256 value = (qvariant_cast<QEther*>(transaction.value("value")))->toU256Wei();
u256 gasPrice = (qvariant_cast<QEther*>(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<TransactionSettings> const& _sequence)
{ {
if (m_running) if (m_running)
{ {
qWarning() << "Waiting for current execution to complete"; qWarning() << "Waiting for current execution to complete";
m_runFuture.waitForFinished(); m_runFuture.waitForFinished();
} }
m_running = true;
emit runStarted(); emit runStarted();
emit runStateChanged(); //emit runStateChanged();
m_client->resetState(_accounts, _miner);
//run sequence //run sequence
m_runFuture = QtConcurrent::run([=]() m_runFuture = QtConcurrent::run([=]()
{ {
try try
{ {
vector<Address> deployedContracts; vector<Address> deployedContracts;
onStateReset(); //onStateReset();
m_gasCosts.clear(); m_gasCosts.clear();
for (TransactionSettings const& transaction: _sequence) for (TransactionSettings const& transaction: _sequence)
{ {
@ -319,12 +360,7 @@ void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence,
break; break;
} }
if (!f) if (!f)
{ emit runFailed("Function '" + transaction.functionId + tr("' not found. Please check transactions or the contract code."));
emit runFailed("Function '" + transaction.functionId + tr("' not found. Please check transactions or the contract code."));
m_running = false;
emit runStateChanged();
return;
}
if (!transaction.functionId.isEmpty()) if (!transaction.functionId.isEmpty())
encoder.encode(f); encoder.encode(f);
for (QVariableDeclaration const* p: f->parametersList()) for (QVariableDeclaration const* p: f->parametersList())
@ -355,33 +391,34 @@ void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence,
{ {
auto contractAddressIter = m_contractAddresses.find(ctrInstance); auto contractAddressIter = m_contractAddresses.find(ctrInstance);
if (contractAddressIter == m_contractAddresses.end()) if (contractAddressIter == m_contractAddresses.end())
{ emit runFailed("Contract '" + transaction.contractId + tr(" not deployed.") + "' " + tr(" Cannot call ") + transaction.functionId);
emit runFailed("Contract '" + transaction.contractId + tr(" not deployed.") + "' " + tr(" Cannot call ") + transaction.functionId); callAddress(contractAddressIter->second, encoder.encodedData(), transaction);
m_running = false;
emit runStateChanged();
return;
}
callAddress(contractAddressIter->second, encoder.encodedData(), transaction);
} }
m_gasCosts.append(m_client->lastExecution().gasUsed); m_gasCosts.append(m_client->lastExecution().gasUsed);
onNewTransaction(); onNewTransaction();
} emit runComplete();
m_running = false; }
emit runComplete();
} }
catch(boost::exception const&) catch(boost::exception const&)
{ {
cerr << boost::current_exception_diagnostic_information(); cerr << boost::current_exception_diagnostic_information();
emit runFailed(QString::fromStdString(boost::current_exception_diagnostic_information())); emit runFailed(QString::fromStdString(boost::current_exception_diagnostic_information()));
} }
catch(exception const& e) catch(exception const& e)
{ {
cerr << boost::current_exception_diagnostic_information(); cerr << boost::current_exception_diagnostic_information();
emit runFailed(e.what()); emit runFailed(e.what());
} }
m_running = false;
emit runStateChanged(); 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<QString, int> ClientModel::resolvePair(QString const& _contractId)
QString ClientModel::resolveToken(std::pair<QString, int> const& _value, vector<Address> const& _contracts) QString ClientModel::resolveToken(std::pair<QString, int> const& _value, vector<Address> const& _contracts)
{ {
if (_contracts.size() > 0) 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 else
return _value.first; return _value.first;
} }
@ -610,7 +647,7 @@ void ClientModel::emptyRecord()
void ClientModel::debugRecord(unsigned _index) void ClientModel::debugRecord(unsigned _index)
{ {
ExecutionResult e = m_client->execution(_index); ExecutionResult e = m_client->execution(_index);
showDebuggerForTransaction(e); showDebuggerForTransaction(e);
} }
Address ClientModel::deployContract(bytes const& _code, TransactionSettings const& _ctrTransaction) Address ClientModel::deployContract(bytes const& _code, TransactionSettings const& _ctrTransaction)
@ -631,7 +668,7 @@ RecordLogEntry* ClientModel::lastBlock() const
strGas << blockInfo.gasUsed; strGas << blockInfo.gasUsed;
stringstream strNumber; stringstream strNumber;
strNumber << blockInfo.number; 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); QQmlEngine::setObjectOwnership(record, QQmlEngine::JavaScriptOwnership);
return record; return record;
} }
@ -648,7 +685,7 @@ void ClientModel::onStateReset()
void ClientModel::onNewTransaction() void ClientModel::onNewTransaction()
{ {
ExecutionResult const& tr = m_client->lastExecution(); ExecutionResult const& tr = m_client->lastExecution();
unsigned block = m_client->number() + 1; unsigned block = m_client->number() + 1;
unsigned recordIndex = tr.executonIndex; unsigned recordIndex = tr.executonIndex;
QString transactionIndex = tr.isCall() ? QObject::tr("Call") : QString("%1:%2").arg(block).arg(tr.transactionIndex); QString transactionIndex = tr.isCall() ? QObject::tr("Call") : QString("%1:%2").arg(block).arg(tr.transactionIndex);
QString address = QString::fromStdString(toJS(tr.address)); QString address = QString::fromStdString(toJS(tr.address));
@ -690,6 +727,7 @@ void ClientModel::onNewTransaction()
Address contractAddress = (bool)tr.address ? tr.address : tr.contractAddress; Address contractAddress = (bool)tr.address ? tr.address : tr.contractAddress;
auto contractAddressIter = m_contractNames.find(contractAddress); auto contractAddressIter = m_contractNames.find(contractAddress);
QVariantMap inputParameters;
if (contractAddressIter != m_contractNames.end()) if (contractAddressIter != m_contractNames.end())
{ {
CompiledContract const& compilerRes = m_codeModel->contract(contractAddressIter->second); CompiledContract const& compilerRes = m_codeModel->contract(contractAddressIter->second);
@ -706,11 +744,20 @@ void ClientModel::onNewTransaction()
returned += "("; returned += "(";
returned += returnValues.join(", "); returned += returnValues.join(", ");
returned += ")"; 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); QQmlEngine::setObjectOwnership(log, QQmlEngine::JavaScriptOwnership);
emit newRecord(log); emit newRecord(log);
} }

34
mix/ClientModel.h

@ -30,12 +30,13 @@
#include <QVariantMap> #include <QVariantMap>
#include <QFuture> #include <QFuture>
#include <QVariableDeclaration.h> #include <QVariableDeclaration.h>
#include <libethereum/Account.h>
#include "MachineStates.h" #include "MachineStates.h"
namespace dev namespace dev
{ {
namespace eth { class Account; class FixedAccountHolder; } namespace eth { class FixedAccountHolder; }
namespace mix namespace mix
{ {
@ -108,6 +109,12 @@ class RecordLogEntry: public QObject
Q_PROPERTY(RecordType type MEMBER m_type CONSTANT) Q_PROPERTY(RecordType type MEMBER m_type CONSTANT)
/// Gas used /// Gas used
Q_PROPERTY(QString gasUsed MEMBER m_gasUsed CONSTANT) 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: public:
enum RecordType enum RecordType
@ -118,8 +125,10 @@ public:
RecordLogEntry(): RecordLogEntry():
m_recordIndex(0), m_call(false), m_type(RecordType::Transaction) {} 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): 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) {} 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: private:
unsigned m_recordIndex; unsigned m_recordIndex;
@ -132,6 +141,9 @@ private:
bool m_call; bool m_call;
RecordType m_type; RecordType m_type;
QString m_gasUsed; QString m_gasUsed;
QString m_sender;
QString m_label;
QVariantMap m_inputParameters;
}; };
/** /**
@ -172,7 +184,12 @@ public:
public slots: public slots:
/// Setup state, run transaction sequence, show debugger for the last transaction /// Setup state, run transaction sequence, show debugger for the last transaction
/// @param _state JS object with state configuration /// @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 /// Show the debugger for a specified record
Q_INVOKABLE void debugRecord(unsigned _index); Q_INVOKABLE void debugRecord(unsigned _index);
/// Show the debugger for an empty record /// Show the debugger for an empty record
@ -224,7 +241,7 @@ private:
RecordLogEntry* lastBlock() const; RecordLogEntry* lastBlock() const;
QVariantMap contractAddresses() const; QVariantMap contractAddresses() const;
QVariantList gasCosts() const; QVariantList gasCosts() const;
void executeSequence(std::vector<TransactionSettings> const& _sequence, std::unordered_map<Address, dev::eth::Account> const& _accounts, Secret const& _miner); void executeSequence(std::vector<TransactionSettings> const& _sequence);
dev::Address deployContract(bytes const& _code, TransactionSettings const& _tr = TransactionSettings()); dev::Address deployContract(bytes const& _code, TransactionSettings const& _tr = TransactionSettings());
void callAddress(Address const& _contract, bytes const& _data, TransactionSettings const& _tr); void callAddress(Address const& _contract, bytes const& _data, TransactionSettings const& _tr);
void onNewTransaction(); void onNewTransaction();
@ -235,6 +252,10 @@ private:
std::pair<QString, int> retrieveToken(QString const& _value, std::vector<Address> const& _contracts); std::pair<QString, int> retrieveToken(QString const& _value, std::vector<Address> const& _contracts);
std::pair<QString, int> resolvePair(QString const& _contractId); std::pair<QString, int> resolvePair(QString const& _contractId);
QVariant formatStorageValue(SolidityType const& _type, std::unordered_map<dev::u256, dev::u256> const& _storage, unsigned _offset, dev::u256 const& _slot); QVariant formatStorageValue(SolidityType const& _type, std::unordered_map<dev::u256, dev::u256> const& _storage, unsigned _offset, dev::u256 const& _slot);
void processNextTransactions();
void processNextBlock();
void finalizeBlock();
void stopExecution();
std::atomic<bool> m_running; std::atomic<bool> m_running;
std::atomic<bool> m_mining; std::atomic<bool> m_mining;
@ -243,12 +264,15 @@ private:
std::unique_ptr<RpcConnector> m_rpcConnector; std::unique_ptr<RpcConnector> m_rpcConnector;
std::unique_ptr<Web3Server> m_web3Server; std::unique_ptr<Web3Server> m_web3Server;
std::shared_ptr<eth::FixedAccountHolder> m_ethAccounts; std::shared_ptr<eth::FixedAccountHolder> m_ethAccounts;
std::unordered_map<Address, eth::Account> m_accounts;
QList<u256> m_gasCosts; QList<u256> m_gasCosts;
std::map<std::pair<QString, int>, Address> m_contractAddresses; std::map<std::pair<QString, int>, Address> m_contractAddresses;
std::map<Address, QString> m_contractNames; std::map<Address, QString> m_contractNames;
std::map<QString, Address> m_stdContractAddresses; std::map<QString, Address> m_stdContractAddresses;
std::map<Address, QString> m_stdContractNames; std::map<Address, QString> m_stdContractNames;
CodeModel* m_codeModel = nullptr; CodeModel* m_codeModel = nullptr;
QList<QVariantList> m_queueTransactions;
QVariantMap m_currentScenario;
}; };
} }

15
mix/ContractCallDataEncoder.cpp

@ -29,6 +29,7 @@
#include "QVariableDefinition.h" #include "QVariableDefinition.h"
#include "QFunctionDefinition.h" #include "QFunctionDefinition.h"
#include "ContractCallDataEncoder.h" #include "ContractCallDataEncoder.h"
using namespace std;
using namespace dev; using namespace dev;
using namespace dev::solidity; using namespace dev::solidity;
using namespace dev::mix; 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")); BOOST_THROW_EXCEPTION(Exception() << errinfo_comment("Parameter declaration not found"));
} }
QStringList ContractCallDataEncoder::decode(QList<QVariableDeclaration*> const& _returnParameters, vector<bytes> _value)
{
QStringList r;
for (int k = 0; k <_returnParameters.length(); k++)
{
QVariableDeclaration* dec = static_cast<QVariableDeclaration*>(_returnParameters.at(k));
SolidityType const& type = dec->type()->type();
r.append(decode(type, _value.at(k)).toString());
}
return r;
}
QStringList ContractCallDataEncoder::decode(QList<QVariableDeclaration*> const& _returnParameters, bytes _value) QStringList ContractCallDataEncoder::decode(QList<QVariableDeclaration*> const& _returnParameters, bytes _value)
{ {
bytesConstRef value(&_value); bytesConstRef value(&_value);
@ -238,7 +251,7 @@ QStringList ContractCallDataEncoder::decode(QList<QVariableDeclaration*> const&
value.populate(&rawParam); value.populate(&rawParam);
value = value.cropped(32); value = value.cropped(32);
QVariableDeclaration* dec = static_cast<QVariableDeclaration*>(_returnParameters.at(k)); QVariableDeclaration* dec = static_cast<QVariableDeclaration*>(_returnParameters.at(k));
SolidityType const& type = dec->type()->type(); SolidityType const& type = dec->type()->type();
r.append(decode(type, rawParam).toString()); r.append(decode(type, rawParam).toString());
} }
return r; return r;

4
mix/ContractCallDataEncoder.h

@ -48,7 +48,9 @@ public:
void encode(QVariant const& _data, SolidityType const& _type); void encode(QVariant const& _data, SolidityType const& _type);
/// Decode variable in order to be sent to QML view. /// Decode variable in order to be sent to QML view.
QStringList decode(QList<QVariableDeclaration*> const& _dec, bytes _value); QStringList decode(QList<QVariableDeclaration*> const& _dec, bytes _value);
/// Decode single variable /// Decode @param _parameters
QStringList decode(QList<QVariableDeclaration*> const& _parameters, std::vector<bytes> _value);
/// Decode single variable
QVariant decode(SolidityType const& _type, bytes const& _value); QVariant decode(SolidityType const& _type, bytes const& _value);
/// Get all encoded data encoded by encode function. /// Get all encoded data encoded by encode function.
bytes encodedData(); bytes encodedData();

1
mix/MachineStates.h

@ -83,6 +83,7 @@ namespace mix
dev::u256 gasUsed; dev::u256 gasUsed;
unsigned transactionIndex; unsigned transactionIndex;
unsigned executonIndex = 0; unsigned executonIndex = 0;
bytes inputParameters;
bool isCall() const { return transactionIndex == std::numeric_limits<unsigned>::max(); } bool isCall() const { return transactionIndex == std::numeric_limits<unsigned>::max(); }
bool isConstructor() const { return !isCall() && !address; } bool isConstructor() const { return !isCall() && !address; }

17
mix/MixClient.cpp

@ -22,6 +22,7 @@
#include "MixClient.h" #include "MixClient.h"
#include <vector> #include <vector>
#include <utility>
#include <libdevcore/Exceptions.h> #include <libdevcore/Exceptions.h>
#include <libethereum/CanonBlockChain.h> #include <libethereum/CanonBlockChain.h>
#include <libethereum/Transaction.h> #include <libethereum/Transaction.h>
@ -69,19 +70,28 @@ bytes MixBlockChain::createGenesisBlock(h256 _stateRoot)
MixClient::MixClient(std::string const& _dbPath): MixClient::MixClient(std::string const& _dbPath):
m_dbPath(_dbPath) m_dbPath(_dbPath)
{ {
resetState(std::unordered_map<Address, Account>()); resetState(std::unordered_map<Address, Account>());
} }
MixClient::~MixClient() MixClient::~MixClient()
{ {
} }
LocalisedLogEntries MixClient::logs()
{
return m_watches.at(0).changes;
}
void MixClient::resetState(std::unordered_map<Address, Account> const& _accounts, Secret const& _miner) void MixClient::resetState(std::unordered_map<Address, Account> const& _accounts, Secret const& _miner)
{ {
WriteGuard l(x_state); WriteGuard l(x_state);
Guard fl(x_filtersWatches); Guard fl(x_filtersWatches);
m_filters.clear(); m_filters.clear();
m_watches.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(); m_stateDB = OverlayDB();
SecureTrieDB<Address, MemoryDB> accountState(&m_stateDB); SecureTrieDB<Address, MemoryDB> 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) void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _call, bool _gasAuto, Secret const& _secret)
{ {
Transaction t = _gasAuto ? replaceGas(_t, m_state.gasLimitRemaining()) : _t; Transaction t = _gasAuto ? replaceGas(_t, m_state.gasLimitRemaining()) : _t;
// do debugging run first // do debugging run first
LastHashes lastHashes(256); LastHashes lastHashes(256);
lastHashes[0] = bc().numberHash(bc().number()); lastHashes[0] = bc().numberHash(bc().number());
for (unsigned i = 1; i < 256; ++i) for (unsigned i = 1; i < 256; ++i)
@ -213,6 +223,7 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c
}; };
ExecutionResult d; ExecutionResult d;
d.inputParameters = t.data();
d.result = execution.executionResult(); d.result = execution.executionResult();
d.machineStates = machineStates; d.machineStates = machineStates;
d.executionCode = std::move(codes); 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.transactionIndex = m_state.pending().size();
d.executonIndex = m_executions.size(); d.executonIndex = m_executions.size();
// execute on a state // execute on a state
if (!_call) if (!_call)
{ {
t = _gasAuto ? replaceGas(_t, d.gasUsed, _secret) : _t; t = _gasAuto ? replaceGas(_t, d.gasUsed, _secret) : _t;

5
mix/MixClient.h

@ -25,6 +25,7 @@
#include <vector> #include <vector>
#include <string> #include <string>
#include <libethereum/ExtVM.h>
#include <libethereum/ClientBase.h> #include <libethereum/ClientBase.h>
#include <libethereum/Client.h> #include <libethereum/Client.h>
#include "MachineStates.h" #include "MachineStates.h"
@ -76,6 +77,8 @@ public:
using Interface::blockInfo; // to remove warning about hiding virtual function using Interface::blockInfo; // to remove warning about hiding virtual function
eth::BlockInfo blockInfo() const; eth::BlockInfo blockInfo() const;
dev::eth::LocalisedLogEntries logs();
protected: protected:
/// ClientBase methods /// ClientBase methods
using ClientBase::asOf; using ClientBase::asOf;
@ -91,7 +94,7 @@ private:
void noteChanged(h256Set const& _filters); void noteChanged(h256Set const& _filters);
dev::eth::Transaction replaceGas(dev::eth::Transaction const& _t, dev::u256 const& _gas, dev::Secret const& _secret = dev::Secret()); 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; eth::State m_startState;
OverlayDB m_stateDB; OverlayDB m_stateDB;
std::unique_ptr<MixBlockChain> m_bc; std::unique_ptr<MixBlockChain> m_bc;

4
mix/main.cpp

@ -32,9 +32,9 @@ int main(int _argc, char* _argv[])
{ {
try try
{ {
MixApplication::initialize(); MixApplication::initialize();
MixApplication app(_argc, _argv); MixApplication app(_argc, _argv);
return app.exec(); return app.exec();
} }
catch (boost::exception const& _e) catch (boost::exception const& _e)
{ {

4
mix/qml.qrc

@ -64,5 +64,9 @@
<file>qml/js/ansi2html.js</file> <file>qml/js/ansi2html.js</file>
<file>qml/js/NetworkDeployment.js</file> <file>qml/js/NetworkDeployment.js</file>
<file>qml/js/InputValidator.js</file> <file>qml/js/InputValidator.js</file>
<file>qml/Block.qml</file>
<file>qml/BlockChain.qml</file>
<file>qml/ScenarioExecution.qml</file>
<file>qml/ScenarioLoader.qml</file>
</qresource> </qresource>
</RCC> </RCC>

21
mix/qml/Application.qml

@ -128,10 +128,8 @@ ApplicationWindow {
MenuSeparator {} MenuSeparator {}
MenuItem { action: toggleProjectNavigatorAction } MenuItem { action: toggleProjectNavigatorAction }
MenuItem { action: showHideRightPanelAction } MenuItem { action: showHideRightPanelAction }
MenuItem { action: toggleTransactionLogAction }
MenuItem { action: toggleWebPreviewAction } MenuItem { action: toggleWebPreviewAction }
MenuItem { action: toggleWebPreviewOrientationAction } MenuItem { action: toggleWebPreviewOrientationAction }
//MenuItem { action: toggleCallsInLog }
} }
} }
@ -207,14 +205,14 @@ ApplicationWindow {
enabled: codeModel.hasContract && !clientModel.running enabled: codeModel.hasContract && !clientModel.running
} }
Action { Action {
id: toggleAssemblyDebuggingAction id: toggleAssemblyDebuggingAction
text: qsTr("Show VM Code") text: qsTr("Show VM Code")
shortcut: "Ctrl+Alt+V" shortcut: "Ctrl+Alt+V"
onTriggered: mainContent.rightPane.assemblyMode = !mainContent.rightPane.assemblyMode; onTriggered: mainContent.debuggerPanel.assemblyMode = !mainContent.debuggerPanel.assemblyMode;
checked: mainContent.rightPane.assemblyMode; checked: mainContent.debuggerPanel.assemblyMode;
enabled: true enabled: true
} }
Action { Action {
id: toggleWebPreviewAction id: toggleWebPreviewAction
@ -223,16 +221,7 @@ ApplicationWindow {
checkable: true checkable: true
checked: mainContent.webViewVisible checked: mainContent.webViewVisible
onTriggered: mainContent.toggleWebPreview(); 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 { Action {
id: toggleProjectNavigatorAction id: toggleProjectNavigatorAction

145
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
}
}
}
}
}
}

306
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)
}
}
}

84
mix/qml/Debugger.qml

@ -11,7 +11,6 @@ import "."
Rectangle { Rectangle {
id: debugPanel id: debugPanel
property alias transactionLog: transactionLog
property alias debugSlider: statesSlider property alias debugSlider: statesSlider
property alias solLocals: solLocals property alias solLocals: solLocals
property alias solStorage: solStorage property alias solStorage: solStorage
@ -23,7 +22,7 @@ Rectangle {
signal debugExecuteLocation(string documentId, var location) signal debugExecuteLocation(string documentId, var location)
property string compilationErrorMessage property string compilationErrorMessage
property bool assemblyMode: false property bool assemblyMode: false
signal panelClosed
objectName: "debugPanel" objectName: "debugPanel"
color: "#ededed" color: "#ededed"
clip: true clip: true
@ -61,7 +60,6 @@ Rectangle {
{ {
Debugger.init(data); Debugger.init(data);
debugScrollArea.visible = true; debugScrollArea.visible = true;
compilationErrorArea.visible = false;
machineStates.visible = true; machineStates.visible = true;
} }
if (giveFocus) if (giveFocus)
@ -97,85 +95,21 @@ Rectangle {
Settings { Settings {
id: splitSettings id: splitSettings
property alias transactionLogHeight: transactionLog.height
property alias callStackHeight: callStackRect.height property alias callStackHeight: callStackRect.height
property alias storageHeightSettings: storageRect.height property alias storageHeightSettings: storageRect.height
property alias memoryDumpHeightSettings: memoryRect.height property alias memoryDumpHeightSettings: memoryRect.height
property alias callDataHeightSettings: callDataRect.height property alias callDataHeightSettings: callDataRect.height
property alias transactionLogVisible: transactionLog.visible
property alias solCallStackHeightSettings: solStackRect.height property alias solCallStackHeightSettings: solStackRect.height
property alias solStorageHeightSettings: solStorageRect.height property alias solStorageHeightSettings: solStorageRect.height
property alias solLocalsHeightSettings: solLocalsRect.height property alias solLocalsHeightSettings: solLocalsRect.height
} }
Rectangle Splitter {
{
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 {
id: debugScrollArea id: debugScrollArea
anchors.fill: parent anchors.fill: parent
orientation: Qt.Vertical orientation: Qt.Vertical
TransactionLog { /*TransactionLog {
id: transactionLog id: transactionLog
Layout.fillWidth: true Layout.fillWidth: true
Layout.minimumHeight: 130 Layout.minimumHeight: 130
@ -186,7 +120,13 @@ Rectangle {
anchors.leftMargin: machineStates.sideMargin anchors.leftMargin: machineStates.sideMargin
anchors.rightMargin: machineStates.sideMargin anchors.rightMargin: machineStates.sideMargin
anchors.topMargin: machineStates.sideMargin anchors.topMargin: machineStates.sideMargin
} }*/
Button
{
text: qsTr("close")
onClicked: panelClosed()
}
ScrollView ScrollView
{ {
@ -230,7 +170,7 @@ Rectangle {
spacing: 3 spacing: 3
layoutDirection: Qt.LeftToRight layoutDirection: Qt.LeftToRight
StepActionImage /*StepActionImage
{ {
id: playAction id: playAction
enabledStateImg: "qrc:/qml/img/play_button.png" enabledStateImg: "qrc:/qml/img/play_button.png"
@ -254,7 +194,7 @@ Rectangle {
buttonShortcut: "Ctrl+Shift+F9" buttonShortcut: "Ctrl+Shift+F9"
buttonTooltip: qsTr("Stop Debugging") buttonTooltip: qsTr("Stop Debugging")
visible: true visible: true
} }*/
StepActionImage StepActionImage
{ {

72
mix/qml/MainContent.qml

@ -20,13 +20,14 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
id: root id: root
property alias rightViewVisible: rightView.visible property alias rightViewVisible: scenarioExe.visible
property alias webViewVisible: webPreview.visible property alias webViewVisible: webPreview.visible
property alias webView: webPreview property alias webView: webPreview
property alias projectViewVisible: projectList.visible property alias projectViewVisible: projectList.visible
property alias projectNavigator: projectList property alias projectNavigator: projectList
property alias runOnProjectLoad: mainSettings.runOnProjectLoad property alias runOnProjectLoad: mainSettings.runOnProjectLoad
property alias rightPane: rightView property alias rightPane: scenarioExe
property alias debuggerPanel: debugPanel
property alias codeEditor: codeEditor property alias codeEditor: codeEditor
property bool webViewHorizontal: codeWebSplitter.orientation === Qt.Vertical //vertical splitter positions elements vertically, splits screen horizontally property bool webViewHorizontal: codeWebSplitter.orientation === Qt.Vertical //vertical splitter positions elements vertically, splits screen horizontally
property bool firstCompile: true property bool firstCompile: true
@ -42,17 +43,17 @@ Rectangle {
} }
} }
Connections { Connections {
target: rightView target: debugPanel
onDebugExecuteLocation: { onDebugExecuteLocation: {
codeEditor.highlightExecution(documentId, location); codeEditor.highlightExecution(documentId, location);
} }
} }
Connections { Connections {
target: codeEditor target: codeEditor
onBreakpointsChanged: { onBreakpointsChanged: {
rightPane.setBreakpoints(codeEditor.getBreakpoints()); debugPanel.setBreakpoints(codeEditor.getBreakpoints());
} }
} }
@ -63,20 +64,20 @@ Rectangle {
} }
function toggleRightView() { function toggleRightView() {
rightView.visible = !rightView.visible; scenarioExe.visible = !scenarioExe.visible;
} }
function ensureRightView() { function ensureRightView() {
rightView.visible = true; scenarioExe.visible = true;
} }
function rightViewIsVisible() function rightViewIsVisible()
{ {
return rightView.visible; return scenarioExe.visible;
} }
function hideRightView() { function hideRightView() {
rightView.visible = false; scenarioExe.visible = lfalse;
} }
function toggleWebPreview() { function toggleWebPreview() {
@ -98,8 +99,8 @@ Rectangle {
function displayCompilationErrorIfAny() function displayCompilationErrorIfAny()
{ {
rightView.visible = true; scenarioExe.visible = true;
rightView.displayCompilationErrorIfAny(); scenarioExe.displayCompilationErrorIfAny();
} }
Settings { Settings {
@ -153,7 +154,7 @@ Rectangle {
id: splitSettings id: splitSettings
property alias projectWidth: projectList.width property alias projectWidth: projectList.width
property alias contentViewWidth: contentView.width property alias contentViewWidth: contentView.width
property alias rightViewWidth: rightView.width property alias rightViewWidth: scenarioExe.width
} }
Splitter Splitter
@ -198,14 +199,41 @@ Rectangle {
} }
} }
Debugger { ScenarioExecution
visible: false; {
id: rightView; id: scenarioExe;
Layout.fillHeight: true visible: false;
Keys.onEscapePressed: visible = false Layout.fillHeight: true
Layout.minimumWidth: 515 Keys.onEscapePressed: visible = false
anchors.right: parent.right 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
}
}
} }
} }
} }

32
mix/qml/QAddressView.qml

@ -40,20 +40,28 @@ Item
accountRef.clear(); accountRef.clear();
accountRef.append({"itemid": " - "}); accountRef.append({"itemid": " - "});
if (subType === "contract" || subType === "address") console.log(blockIndex)
console.log(transactionIndex)
if (subType === "contract" || subType === "address")
{ {
var trCr = 0; var trCr = 0;
for (var k = 0; k < transactionsModel.count; k++) if (blockChainPanel)
{ for (var k = 0; k < blockChainPanel.model.blocks.length; k++)
if (k >= transactionIndex) {
break; if (k > blockIndex)
var tr = transactionsModel.get(k); break;
if (tr.functionId === tr.contractId /*&& (dec[1] === tr.contractId || item.subType === "address")*/) for (var i = 0; i < blockChainPanel.model.blocks[k].transactions.length; i++)
{ {
accountRef.append({ "itemid": tr.contractId + " - " + trCr, "value": "<" + tr.contractId + " - " + trCr + ">", "type": "contract" }); if (i > transactionIndex)
trCr++; 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") if (subType === "address")
{ {

57
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
}
}
}

77
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)
}
}
}

129
mix/qml/StateListModel.qml

@ -23,7 +23,8 @@ Item {
return { return {
title: s.title, title: s.title,
transactions: s.transactions.filter(function(t) { return !t.stdContract; }).map(fromPlainTransactionItem), //support old projects by filtering std contracts 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), contracts: s.contracts.map(fromPlainAccountItem),
miner: s.miner miner: s.miner
}; };
@ -58,7 +59,8 @@ Item {
sender: t.sender, sender: t.sender,
isContractCreation: t.isContractCreation, isContractCreation: t.isContractCreation,
label: t.label, label: t.label,
isFunctionCall: t.isFunctionCall isFunctionCall: t.isFunctionCall,
saveStatus: t.saveStatus
}; };
if (r.isFunctionCall === undefined) if (r.isFunctionCall === undefined)
@ -76,10 +78,22 @@ Item {
return r; 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) { function toPlainStateItem(s) {
return { return {
title: s.title, title: s.title,
transactions: s.transactions.map(toPlainTransactionItem), blocks: s.blocks.map(toPlainBlockItem),
transactions: s.transactions.map(toPlainTransactionItem),
accounts: s.accounts.map(toPlainAccountItem), accounts: s.accounts.map(toPlainAccountItem),
contracts: s.contracts.map(toPlainAccountItem), contracts: s.contracts.map(toPlainAccountItem),
miner: s.miner miner: s.miner
@ -96,6 +110,17 @@ Item {
return ''; 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) function toPlainAccountItem(t)
{ {
return { return {
@ -112,6 +137,7 @@ Item {
} }
function toPlainTransactionItem(t) { function toPlainTransactionItem(t) {
console.log(JSON.stringify(t));
var r = { var r = {
type: t.type, type: t.type,
contractId: t.contractId, contractId: t.contractId,
@ -125,7 +151,8 @@ Item {
parameters: {}, parameters: {},
isContractCreation: t.isContractCreation, isContractCreation: t.isContractCreation,
label: t.label, label: t.label,
isFunctionCall: t.isFunctionCall isFunctionCall: t.isFunctionCall,
saveStatus: t.saveStatus
}; };
for (var key in t.parameters) for (var key in t.parameters)
r.parameters[key] = t.parameters[key]; r.parameters[key] = t.parameters[key];
@ -146,14 +173,16 @@ Item {
projectData.states.push(toPlainStateItem(stateList[i])); projectData.states.push(toPlainStateItem(stateList[i]));
} }
projectData.defaultStateIndex = stateListModel.defaultStateIndex; projectData.defaultStateIndex = stateListModel.defaultStateIndex;
} stateListModel.data = projectData
}
onNewProject: { onNewProject: {
var state = toPlainStateItem(stateListModel.createDefaultState()); var state = toPlainStateItem(stateListModel.createDefaultState());
state.title = qsTr("Default"); state.title = qsTr("Default");
projectData.states = [ state ]; projectData.states = [ state ];
projectData.defaultStateIndex = 0; projectData.defaultStateIndex = 0;
stateListModel.loadStatesFromProject(projectData); stateListModel.loadStatesFromProject(projectData);
} }
} }
Connections { Connections {
@ -170,34 +199,40 @@ Item {
id: stateDialog id: stateDialog
onAccepted: { onAccepted: {
var item = stateDialog.getItem(); var item = stateDialog.getItem();
if (stateDialog.stateIndex < stateListModel.count) { saveState(item);
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();
} }
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 { ListModel {
id: stateListModel id: stateListModel
property int defaultStateIndex: 0 property int defaultStateIndex: 0
signal defaultStateChanged; property variant data
signal defaultStateChanged;
signal stateListModelReady; signal stateListModelReady;
signal stateRun(int index) signal stateRun(int index)
signal stateDeleted(int index) signal stateDeleted(int index)
function defaultTransactionItem() { function defaultTransactionItem() {
return TransactionHelper.defaultTransaction(); return TransactionHelper.defaultTransaction();
} }
function newAccount(_balance, _unit, _secret) function newAccount(_balance, _unit, _secret)
{ {
@ -208,15 +243,26 @@ Item {
return { name: name, secret: _secret, balance: QEtherHelper.createEther(_balance, _unit), address: address }; return { name: name, secret: _secret, balance: QEtherHelper.createEther(_balance, _unit), address: address };
} }
function createEmptyBlock()
{
return {
hash: "",
number: -1,
transactions: [],
status: "pending"
}
}
function createDefaultState() { function createDefaultState() {
var item = { var item = {
title: "", title: "",
transactions: [], transactions: [],
accounts: [], 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.accounts.push(account);
item.miner = account; item.miner = account;
@ -228,7 +274,8 @@ Item {
ctorTr.label = qsTr("Deploy") + " " + ctorTr.contractId; ctorTr.label = qsTr("Deploy") + " " + ctorTr.contractId;
ctorTr.sender = item.accounts[0].secret; ctorTr.sender = item.accounts[0].secret;
item.transactions.push(ctorTr); item.transactions.push(ctorTr);
} item.blocks[0].transactions.push(ctorTr)
}
return item; return item;
} }
@ -284,10 +331,20 @@ Item {
stateDialog.open(stateListModel.count, item, false); stateDialog.open(stateListModel.count, item, false);
} }
function appendState(item)
{
stateListModel.append(item);
stateList.push(item);
}
function editState(index) { function editState(index) {
stateDialog.open(index, stateList[index], defaultStateIndex === index); stateDialog.open(index, stateList[index], defaultStateIndex === index);
} }
function getState(index) {
return stateList[index];
}
function debugDefaultState() { function debugDefaultState() {
if (defaultStateIndex >= 0 && defaultStateIndex < stateList.length) if (defaultStateIndex >= 0 && defaultStateIndex < stateList.length)
runState(defaultStateIndex); runState(defaultStateIndex);
@ -295,8 +352,8 @@ Item {
function runState(index) { function runState(index) {
var item = stateList[index]; var item = stateList[index];
clientModel.setupState(item); //clientModel.setupState(item);
stateRun(index); //stateRun(index);
} }
function deleteState(index) { function deleteState(index) {
@ -322,8 +379,22 @@ Item {
return stateList[defaultStateIndex].title; 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) function loadStatesFromProject(projectData)
{ {
data = projectData
if (!projectData.states) if (!projectData.states)
projectData.states = []; projectData.states = [];
if (projectData.defaultStateIndex !== undefined) if (projectData.defaultStateIndex !== undefined)

1
mix/qml/StructView.qml

@ -9,6 +9,7 @@ Column
property alias members: repeater.model //js array property alias members: repeater.model //js array
property variant accounts property variant accounts
property var value: ({}) property var value: ({})
property int blockIndex
property int transactionIndex property int transactionIndex
property string context property string context
Layout.fillWidth: true Layout.fillWidth: true

14
mix/qml/TransactionDialog.qml

@ -17,6 +17,7 @@ Dialog {
visible: false visible: false
title: qsTr("Edit Transaction") title: qsTr("Edit Transaction")
property int transactionIndex property int transactionIndex
property int blockIndex
property alias gas: gasValueEdit.gasValue; property alias gas: gasValueEdit.gasValue;
property alias gasAuto: gasAutoCheck.checked; property alias gasAuto: gasAutoCheck.checked;
property alias gasPrice: gasPriceField.value; property alias gasPrice: gasPriceField.value;
@ -27,21 +28,24 @@ Dialog {
property var paramsModel: []; property var paramsModel: [];
property bool useTransactionDefaultValue: false property bool useTransactionDefaultValue: false
property alias stateAccounts: senderComboBox.model property alias stateAccounts: senderComboBox.model
property bool saveStatus
signal accepted; signal accepted;
StateDialogStyle { StateDialogStyle {
id: transactionDialogStyle id: transactionDialogStyle
} }
function open(index, item) { function open(index, blockIdx, item) {
rowFunction.visible = !useTransactionDefaultValue; rowFunction.visible = !useTransactionDefaultValue;
rowValue.visible = !useTransactionDefaultValue; rowValue.visible = !useTransactionDefaultValue;
rowGas.visible = !useTransactionDefaultValue; rowGas.visible = !useTransactionDefaultValue;
rowGasPrice.visible = !useTransactionDefaultValue; rowGasPrice.visible = !useTransactionDefaultValue;
transactionIndex = index; transactionIndex = index
typeLoader.transactionIndex = index; blockIndex = blockIdx
typeLoader.transactionIndex = index
typeLoader.blockIndex = blockIdx
saveStatus = item.saveStatus
gasValueEdit.gasValue = item.gas; gasValueEdit.gasValue = item.gas;
gasAutoCheck.checked = item.gasAuto ? true : false; gasAutoCheck.checked = item.gasAuto ? true : false;
gasPriceField.value = item.gasPrice; gasPriceField.value = item.gasPrice;
@ -229,7 +233,7 @@ Dialog {
item.functionId = item.contractId; item.functionId = item.contractId;
item.label = qsTr("Deploy") + " " + item.contractId; item.label = qsTr("Deploy") + " " + item.contractId;
} }
item.saveStatus = saveStatus
item.sender = senderComboBox.model[senderComboBox.currentIndex].secret; item.sender = senderComboBox.model[senderComboBox.currentIndex].secret;
item.parameters = paramValues; item.parameters = paramValues;
return item; return item;

2
mix/qml/js/ProjectModel.js

@ -101,6 +101,8 @@ function loadProject(path) {
console.log("Loading project at " + path); console.log("Loading project at " + path);
var projectFile = path + projectFileName; var projectFile = path + projectFileName;
var json = fileIo.readFile(projectFile); var json = fileIo.readFile(projectFile);
if (!json)
return;
var projectData = JSON.parse(json); var projectData = JSON.parse(json);
if (projectData.deploymentDir) if (projectData.deploymentDir)
projectModel.deploymentDir = projectData.deploymentDir projectModel.deploymentDir = projectData.deploymentDir

3
mix/qml/js/TransactionHelper.js

@ -12,7 +12,8 @@ function defaultTransaction()
stdContract: false, stdContract: false,
isContractCreation: true, isContractCreation: true,
label: "", label: "",
isFunctionCall: true isFunctionCall: true,
saveStatus: true
}; };
} }

Loading…
Cancel
Save