Browse Source

Add ScenarioExecution + ScenarioLoader

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

133
mix/ClientModel.cpp

@ -82,12 +82,14 @@ ClientModel::ClientModel():
qRegisterMetaType<QCallData*>("QCallData");
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_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()));
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,13 +208,16 @@ 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;
unordered_map<Address, Account> accounts;
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)
@ -230,29 +235,66 @@ void ClientModel::setupState(QVariantMap _state)
if (!address)
continue;
accounts[address] = Account(qvariant_cast<QEther*>(account.value("balance"))->toU256Wei(), Account::NormalCreation);
m_accounts[address] = Account(qvariant_cast<QEther*>(account.value("balance"))->toU256Wei(), Account::NormalCreation);
}
for (auto const& c: stateContracts)
m_ethAccounts->setAccounts(userAccounts);
for (auto const& b: blocks)
{
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;
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();
}
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;
}
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();
}
}
void ClientModel::processNextBlock()
{
processNextTransactions();
}
void ClientModel::processNextTransactions()
{
vector<TransactionSettings> transactionSequence;
for (auto const& t: transactions)
for (auto const& t: m_queueTransactions.front())
{
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 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();
@ -268,30 +310,29 @@ void ClientModel::setupState(QVariantMap _state)
transactionSequence.push_back(transactionSettings);
}
m_ethAccounts->setAccounts(userAccounts);
executeSequence(transactionSequence, accounts, Secret(_state.value("miner").toMap().value("secret").toString().toStdString()));
m_queueTransactions.pop_front();
executeSequence(transactionSequence);
}
void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence, std::unordered_map<Address, Account> const& _accounts, Secret const& _miner)
void ClientModel::executeSequence(vector<TransactionSettings> 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<Address> deployedContracts;
onStateReset();
//onStateReset();
m_gasCosts.clear();
for (TransactionSettings const& transaction: _sequence)
{
@ -319,12 +360,7 @@ void ClientModel::executeSequence(vector<TransactionSettings> 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;
}
if (!transaction.functionId.isEmpty())
encoder.encode(f);
for (QVariableDeclaration const* p: f->parametersList())
@ -355,19 +391,13 @@ void ClientModel::executeSequence(vector<TransactionSettings> 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);
}
m_gasCosts.append(m_client->lastExecution().gasUsed);
onNewTransaction();
}
m_running = false;
emit runComplete();
}
}
catch(boost::exception const&)
{
@ -379,11 +409,18 @@ void ClientModel::executeSequence(vector<TransactionSettings> const& _sequence,
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();
}
std::pair<QString, int> ClientModel::resolvePair(QString const& _contractId)
{
@ -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)
{
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;
}
@ -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;
}
@ -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);
}

34
mix/ClientModel.h

@ -30,12 +30,13 @@
#include <QVariantMap>
#include <QFuture>
#include <QVariableDeclaration.h>
#include <libethereum/Account.h>
#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<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());
void callAddress(Address const& _contract, bytes const& _data, TransactionSettings const& _tr);
void onNewTransaction();
@ -235,6 +252,10 @@ private:
std::pair<QString, int> retrieveToken(QString const& _value, std::vector<Address> const& _contracts);
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);
void processNextTransactions();
void processNextBlock();
void finalizeBlock();
void stopExecution();
std::atomic<bool> m_running;
std::atomic<bool> m_mining;
@ -243,12 +264,15 @@ private:
std::unique_ptr<RpcConnector> m_rpcConnector;
std::unique_ptr<Web3Server> m_web3Server;
std::shared_ptr<eth::FixedAccountHolder> m_ethAccounts;
std::unordered_map<Address, eth::Account> m_accounts;
QList<u256> m_gasCosts;
std::map<std::pair<QString, int>, Address> m_contractAddresses;
std::map<Address, QString> m_contractNames;
std::map<QString, Address> m_stdContractAddresses;
std::map<Address, QString> m_stdContractNames;
CodeModel* m_codeModel = nullptr;
QList<QVariantList> m_queueTransactions;
QVariantMap m_currentScenario;
};
}

13
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<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)
{
bytesConstRef value(&_value);

2
mix/ContractCallDataEncoder.h

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

1
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<unsigned>::max(); }
bool isConstructor() const { return !isCall() && !address; }

11
mix/MixClient.cpp

@ -22,6 +22,7 @@
#include "MixClient.h"
#include <vector>
#include <utility>
#include <libdevcore/Exceptions.h>
#include <libethereum/CanonBlockChain.h>
#include <libethereum/Transaction.h>
@ -76,12 +77,21 @@ MixClient::~MixClient()
{
}
LocalisedLogEntries MixClient::logs()
{
return m_watches.at(0).changes;
}
void MixClient::resetState(std::unordered_map<Address, Account> 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<Address, MemoryDB> accountState(&m_stateDB);
@ -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);

3
mix/MixClient.h

@ -25,6 +25,7 @@
#include <vector>
#include <string>
#include <libethereum/ExtVM.h>
#include <libethereum/ClientBase.h>
#include <libethereum/Client.h>
#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;

4
mix/qml.qrc

@ -64,5 +64,9 @@
<file>qml/js/ansi2html.js</file>
<file>qml/js/NetworkDeployment.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>
</RCC>

15
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 }
}
}
@ -211,8 +209,8 @@ ApplicationWindow {
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
}
@ -225,15 +223,6 @@ ApplicationWindow {
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
text: qsTr("Show Project Navigator")

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

80
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 {
id: debugScrollArea
anchors.fill: parent
orientation: Qt.Vertical
TransactionLog {
/*TransactionLog {
id: transactionLog
Layout.fillWidth: true
Layout.minimumHeight: 130
@ -186,6 +120,12 @@ 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
{

56
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
@ -43,7 +44,7 @@ Rectangle {
}
Connections {
target: rightView
target: debugPanel
onDebugExecuteLocation: {
codeEditor.highlightExecution(documentId, location);
}
@ -52,7 +53,7 @@ Rectangle {
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 {
ScenarioExecution
{
id: scenarioExe;
visible: false;
id: rightView;
Layout.fillHeight: true
Keys.onEscapePressed: visible = false
Layout.minimumWidth: 515
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
}
}
}
}
}

14
mix/qml/QAddressView.qml

@ -40,14 +40,21 @@ Item
accountRef.clear();
accountRef.append({"itemid": " - "});
console.log(blockIndex)
console.log(transactionIndex)
if (subType === "contract" || subType === "address")
{
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)
if (k > blockIndex)
break;
var tr = transactionsModel.get(k);
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" });
@ -55,6 +62,7 @@ Item
}
}
}
}
if (subType === "address")
{
for (k = 0; k < accounts.length; k++)

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

81
mix/qml/StateListModel.qml

@ -23,6 +23,7 @@ Item {
return {
title: s.title,
transactions: s.transactions.filter(function(t) { return !t.stdContract; }).map(fromPlainTransactionItem), //support old projects by filtering std contracts
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,9 +78,21 @@ 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,
blocks: s.blocks.map(toPlainBlockItem),
transactions: s.transactions.map(toPlainTransactionItem),
accounts: s.accounts.map(toPlainAccountItem),
contracts: s.contracts.map(toPlainAccountItem),
@ -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,6 +173,8 @@ Item {
projectData.states.push(toPlainStateItem(stateList[i]));
}
projectData.defaultStateIndex = stateListModel.defaultStateIndex;
stateListModel.data = projectData
}
onNewProject: {
var state = toPlainStateItem(stateListModel.createDefaultState());
@ -170,6 +199,11 @@ Item {
id: stateDialog
onAccepted: {
var item = stateDialog.getItem();
saveState(item);
}
function saveState(item)
{
if (stateDialog.stateIndex < stateListModel.count) {
if (stateDialog.isDefault)
stateListModel.defaultStateIndex = stateIndex;
@ -190,6 +224,7 @@ Item {
ListModel {
id: stateListModel
property int defaultStateIndex: 0
property variant data
signal defaultStateChanged;
signal stateListModelReady;
signal stateRun(int index)
@ -208,12 +243,23 @@ 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)
@ -228,6 +274,7 @@ 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)

1
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

14
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;

2
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

3
mix/qml/js/TransactionHelper.js

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

Loading…
Cancel
Save