Browse Source

Merge pull request #886 from arkpar/mix_tx

mix: Transaction recording, standard contracts
cl-refactor
Gav Wood 10 years ago
parent
commit
435397ec8c
  1. 7
      mix/AppContext.cpp
  2. 2
      mix/AppContext.h
  3. 7
      mix/CMakeLists.txt
  4. 289
      mix/ClientModel.cpp
  5. 79
      mix/ClientModel.h
  6. 3
      mix/CodeHighlighter.cpp
  7. 21
      mix/CodeModel.cpp
  8. 4
      mix/CodeModel.h
  9. 5
      mix/Exceptions.h
  10. 2
      mix/HttpServer.cpp
  11. 4
      mix/HttpServer.h
  12. 224
      mix/MixClient.cpp
  13. 41
      mix/MixClient.h
  14. 9
      mix/QContractDefinition.cpp
  15. 2
      mix/QContractDefinition.h
  16. 15
      mix/Web3Server.cpp
  17. 12
      mix/Web3Server.h
  18. 27
      mix/qml/ContractLibrary.qml
  19. 6
      mix/qml/Debugger.qml
  20. 115
      mix/qml/Ether.qml
  21. 35
      mix/qml/MainContent.qml
  22. 6
      mix/qml/ProjectModel.qml
  23. 13
      mix/qml/StateDialog.qml
  24. 95
      mix/qml/StateList.qml
  25. 196
      mix/qml/StateListModel.qml
  26. 16
      mix/qml/StatusPane.qml
  27. 8
      mix/qml/TransactionDialog.qml
  28. 73
      mix/qml/TransactionLog.qml
  29. 10
      mix/qml/WebPreview.qml
  30. 2
      mix/qml/html/WebContainer.html
  31. 5
      mix/qml/js/ProjectModel.js
  32. 25
      mix/qml/main.qml
  33. 6
      mix/res.qrc
  34. 45
      mix/stdc/config.sol
  35. 74
      mix/stdc/namereg.sol
  36. 124
      mix/stdc/std.sol

7
mix/AppContext.cpp

@ -23,6 +23,7 @@
*/ */
#include <QMessageBox> #include <QMessageBox>
#include <QClipboard>
#include <QQmlComponent> #include <QQmlComponent>
#include <QQmlContext> #include <QQmlContext>
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
@ -94,3 +95,9 @@ void AppContext::displayMessageDialog(QString _title, QString _message)
dialogWin->findChild<QObject*>("messageContent", Qt::FindChildrenRecursively)->setProperty("text", _message); dialogWin->findChild<QObject*>("messageContent", Qt::FindChildrenRecursively)->setProperty("text", _message);
QMetaObject::invokeMethod(dialogWin, "open"); QMetaObject::invokeMethod(dialogWin, "open");
} }
void AppContext::toClipboard(QString _text)
{
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(_text);
}

2
mix/AppContext.h

@ -62,6 +62,8 @@ public:
ClientModel* clientModel() { return m_clientModel.get(); } ClientModel* clientModel() { return m_clientModel.get(); }
/// Display an alert message. /// Display an alert message.
void displayMessageDialog(QString _title, QString _message); void displayMessageDialog(QString _title, QString _message);
/// Copy text to clipboard
Q_INVOKABLE void toClipboard(QString _text);
signals: signals:
/// Triggered once components have been loaded /// Triggered once components have been loaded

7
mix/CMakeLists.txt

@ -13,7 +13,7 @@ aux_source_directory(. SRC_LIST)
include_directories(..) include_directories(..)
find_package (Qt5WebEngine QUIET) find_package (Qt5WebEngine QUIET)
qt5_add_resources(UI_RESOURCES qml.qrc) qt5_add_resources(UI_RESOURCES res.qrc)
file(GLOB HEADERS "*.h") file(GLOB HEADERS "*.h")
@ -62,6 +62,7 @@ eth_install_executable(${EXECUTABLE}
QMLDIR ${CMAKE_CURRENT_SOURCE_DIR}/qml QMLDIR ${CMAKE_CURRENT_SOURCE_DIR}/qml
) )
#add qml files to project tree in Qt creator #add qml asnd stdc files to project tree in Qt creator
file(GLOB_RECURSE QMLFILES "qml/*.*") file(GLOB_RECURSE QMLFILES "qml/*.*")
add_custom_target(dummy SOURCES ${QMLFILES}) file(GLOB_RECURSE SOLFILES "stdc/*.*")
add_custom_target(dummy SOURCES ${QMLFILES} ${SOLFILES})

289
mix/ClientModel.cpp

@ -27,6 +27,7 @@
#include <libethereum/Transaction.h> #include <libethereum/Transaction.h>
#include "AppContext.h" #include "AppContext.h"
#include "DebuggingStateWrapper.h" #include "DebuggingStateWrapper.h"
#include "Exceptions.h"
#include "QContractDefinition.h" #include "QContractDefinition.h"
#include "QVariableDeclaration.h" #include "QVariableDeclaration.h"
#include "ContractCallDataEncoder.h" #include "ContractCallDataEncoder.h"
@ -60,8 +61,9 @@ private:
QString m_response; QString m_response;
}; };
ClientModel::ClientModel(AppContext* _context): ClientModel::ClientModel(AppContext* _context):
m_context(_context), m_running(false), m_rpcConnector(new RpcConnector()) m_context(_context), m_running(false), m_rpcConnector(new RpcConnector()), m_contractAddress(Address())
{ {
qRegisterMetaType<QBigInt*>("QBigInt*"); qRegisterMetaType<QBigInt*>("QBigInt*");
qRegisterMetaType<QEther*>("QEther*"); qRegisterMetaType<QEther*>("QEther*");
@ -71,11 +73,13 @@ ClientModel::ClientModel(AppContext* _context):
qRegisterMetaType<QList<QVariableDeclaration*>>("QList<QVariableDeclaration*>"); qRegisterMetaType<QList<QVariableDeclaration*>>("QList<QVariableDeclaration*>");
qRegisterMetaType<QVariableDeclaration*>("QVariableDeclaration*"); qRegisterMetaType<QVariableDeclaration*>("QVariableDeclaration*");
qRegisterMetaType<AssemblyDebuggerData>("AssemblyDebuggerData"); qRegisterMetaType<AssemblyDebuggerData>("AssemblyDebuggerData");
qRegisterMetaType<TransactionLogEntry*>("TransactionLogEntry");
connect(this, &ClientModel::dataAvailable, this, &ClientModel::showDebugger, Qt::QueuedConnection); connect(this, &ClientModel::runComplete, this, &ClientModel::showDebugger, Qt::QueuedConnection);
m_client.reset(new MixClient()); m_client.reset(new MixClient());
m_web3Server.reset(new Web3Server(*m_rpcConnector.get(), std::vector<dev::KeyPair> { m_client->userAccount() }, m_client.get())); m_web3Server.reset(new Web3Server(*m_rpcConnector.get(), std::vector<dev::KeyPair> { m_client->userAccount() }, m_client.get()));
connect(m_web3Server.get(), &Web3Server::newTransaction, this, &ClientModel::onNewTransaction, Qt::DirectConnection);
_context->appEngine()->rootContext()->setContextProperty("clientModel", this); _context->appEngine()->rootContext()->setContextProperty("clientModel", this);
} }
@ -90,10 +94,15 @@ QString ClientModel::apiCall(QString const& _message)
return m_rpcConnector->response(); return m_rpcConnector->response();
} }
void ClientModel::mine()
{
m_client->mine();
newBlock();
}
QString ClientModel::contractAddress() const QString ClientModel::contractAddress() const
{ {
QString address = QString::fromStdString(dev::toJS(m_client->lastContractAddress())); return QString::fromStdString(dev::toJS(m_contractAddress));
return address;
} }
void ClientModel::debugDeployment() void ClientModel::debugDeployment()
@ -101,13 +110,12 @@ void ClientModel::debugDeployment()
executeSequence(std::vector<TransactionSettings>(), 10000000 * ether); executeSequence(std::vector<TransactionSettings>(), 10000000 * ether);
} }
void ClientModel::debugState(QVariantMap _state) void ClientModel::setupState(QVariantMap _state)
{ {
u256 balance = (qvariant_cast<QEther*>(_state.value("balance")))->toU256Wei(); u256 balance = (qvariant_cast<QEther*>(_state.value("balance")))->toU256Wei();
QVariantList transactions = _state.value("transactions").toList(); QVariantList transactions = _state.value("transactions").toList();
std::vector<TransactionSettings> transactionSequence; std::vector<TransactionSettings> transactionSequence;
TransactionSettings constructorTr;
for (auto const& t: transactions) for (auto const& t: transactions)
{ {
QVariantMap transaction = t.toMap(); QVariantMap transaction = t.toMap();
@ -115,33 +123,46 @@ void ClientModel::debugState(QVariantMap _state)
u256 gas = (qvariant_cast<QEther*>(transaction.value("gas")))->toU256Wei(); u256 gas = (qvariant_cast<QEther*>(transaction.value("gas")))->toU256Wei();
u256 value = (qvariant_cast<QEther*>(transaction.value("value")))->toU256Wei(); u256 value = (qvariant_cast<QEther*>(transaction.value("value")))->toU256Wei();
u256 gasPrice = (qvariant_cast<QEther*>(transaction.value("gasPrice")))->toU256Wei(); u256 gasPrice = (qvariant_cast<QEther*>(transaction.value("gasPrice")))->toU256Wei();
QVariantMap params = transaction.value("parameters").toMap();
TransactionSettings transactionSettings(functionId, value, gas, gasPrice);
for (auto p = params.cbegin(); p != params.cend(); ++p) bool isStdContract = (transaction.value("stdContract").toBool());
if (isStdContract)
{ {
QBigInt* param = qvariant_cast<QBigInt*>(p.value()); TransactionSettings transactionSettings(functionId, transaction.value("url").toString());
transactionSettings.parameterValues.insert(std::make_pair(p.key(), boost::get<dev::u256>(param->internalValue()))); transactionSettings.gasPrice = 10000000000000;
transactionSettings.gas = 125000;
transactionSettings.value = 100;
transactionSequence.push_back(transactionSettings);
} }
if (transaction.value("executeConstructor").toBool())
constructorTr = transactionSettings;
else else
{
QVariantMap params = transaction.value("parameters").toMap();
TransactionSettings transactionSettings(functionId, value, gas, gasPrice);
for (auto p = params.cbegin(); p != params.cend(); ++p)
{
QBigInt* param = qvariant_cast<QBigInt*>(p.value());
transactionSettings.parameterValues.insert(std::make_pair(p.key(), boost::get<dev::u256>(param->internalValue())));
}
if (transaction.value("executeConstructor").toBool())
transactionSettings.functionId.clear();
transactionSequence.push_back(transactionSettings); transactionSequence.push_back(transactionSettings);
}
} }
executeSequence(transactionSequence, balance, constructorTr); executeSequence(transactionSequence, balance);
} }
void ClientModel::executeSequence(std::vector<TransactionSettings> const& _sequence, u256 _balance, TransactionSettings const& ctrTransaction) void ClientModel::executeSequence(std::vector<TransactionSettings> const& _sequence, u256 _balance)
{ {
if (m_running) if (m_running)
throw (std::logic_error("debugging already running")); BOOST_THROW_EXCEPTION(ExecutionStateException());
auto compilerRes = m_context->codeModel()->code(); CompilationResult* compilerRes = m_context->codeModel()->code();
std::shared_ptr<QContractDefinition> contractDef = compilerRes->sharedContract(); std::shared_ptr<QContractDefinition> contractDef = compilerRes->sharedContract();
m_running = true; m_running = true;
emit runStarted(); emit runStarted();
emit stateChanged(); emit runStateChanged();
//run sequence //run sequence
QtConcurrent::run([=]() QtConcurrent::run([=]()
@ -149,60 +170,62 @@ void ClientModel::executeSequence(std::vector<TransactionSettings> const& _seque
try try
{ {
bytes contractCode = compilerRes->bytes(); bytes contractCode = compilerRes->bytes();
std::vector<dev::bytes> transactonData; m_client->resetState(_balance);
QFunctionDefinition* f = nullptr; onStateReset();
ContractCallDataEncoder c; for (TransactionSettings const& transaction: _sequence)
//encode data for all transactions
for (auto const& t: _sequence)
{ {
f = nullptr; ContractCallDataEncoder encoder;
for (int tf = 0; tf < contractDef->functionsList().size(); tf++) QFunctionDefinition const* f = nullptr;
if (!transaction.stdContractUrl.isEmpty())
{
//std contract
dev::bytes const& stdContractCode = m_context->codeModel()->getStdContractCode(transaction.functionId, transaction.stdContractUrl);
Address address = deployContract(stdContractCode, transaction);
m_stdContractAddresses[transaction.functionId] = address;
m_stdContractNames[address] = transaction.functionId;
}
else
{ {
if (contractDef->functionsList().at(tf)->name() == t.functionId) //encode data
f = nullptr;
if (transaction.functionId.isEmpty())
f = contractDef->constructor();
else
for (QFunctionDefinition const* tf: contractDef->functionsList())
if (tf->name() == transaction.functionId)
{
f = tf;
break;
}
if (!f)
BOOST_THROW_EXCEPTION(FunctionNotFoundException() << FunctionName(transaction.functionId.toStdString()));
encoder.encode(f);
for (int p = 0; p < f->parametersList().size(); p++)
{ {
f = contractDef->functionsList().at(tf); QVariableDeclaration* var = f->parametersList().at(p);
break; u256 value = 0;
auto v = transaction.parameterValues.find(var->name());
if (v != transaction.parameterValues.cend())
value = v->second;
encoder.encode(var, value);
} }
}
if (!f)
throw std::runtime_error("function " + t.functionId.toStdString() + " not found");
c.encode(f); if (transaction.functionId.isEmpty())
for (int p = 0; p < f->parametersList().size(); p++) {
{ Address newAddress = deployContract(contractCode, transaction);
QVariableDeclaration* var = (QVariableDeclaration*)f->parametersList().at(p); if (newAddress != m_contractAddress)
u256 value = 0; {
auto v = t.parameterValues.find(var->name()); m_contractAddress = newAddress;
if (v != t.parameterValues.cend()) contractAddressChanged();
value = v->second; }
c.encode(var, value); }
else
callContract(m_contractAddress, encoder.encodedData(), transaction);
} }
transactonData.emplace_back(c.encodedData()); onNewTransaction();
}
//run contract creation first
m_client->resetState(_balance);
ExecutionResult debuggingContent = deployContract(contractCode, ctrTransaction);
Address address = debuggingContent.contractAddress;
for (unsigned i = 0; i < _sequence.size(); ++i)
debuggingContent = callContract(address, transactonData.at(i), _sequence.at(i));
QList<QVariableDefinition*> returnParameters;
if (f)
returnParameters = c.decode(f->returnParameters(), debuggingContent.returnValue);
//we need to wrap states in a QObject before sending to QML.
QList<QObject*> wStates;
for (unsigned i = 0; i < debuggingContent.machineStates.size(); i++)
{
QPointer<DebuggingStateWrapper> s(new DebuggingStateWrapper(debuggingContent.executionCode, debuggingContent.executionData.toBytes()));
s->setState(debuggingContent.machineStates[i]);
wStates.append(s);
} }
//collect states for last transaction m_running = false;
AssemblyDebuggerData code = DebuggingStateWrapper::getHumanReadableCode(debuggingContent.executionCode);
emit dataAvailable(returnParameters, wStates, code);
emit runComplete(); emit runComplete();
} }
catch(boost::exception const&) catch(boost::exception const&)
@ -215,51 +238,133 @@ void ClientModel::executeSequence(std::vector<TransactionSettings> const& _seque
emit runFailed(e.what()); emit runFailed(e.what());
} }
m_running = false; m_running = false;
emit stateChanged(); emit runStateChanged();
}); });
} }
void ClientModel::showDebugger(QList<QVariableDefinition*> const& _returnParam, QList<QObject*> const& _wStates, AssemblyDebuggerData const& _code) void ClientModel::showDebugger()
{
ExecutionResult const& last = m_client->record().back().transactions.back();
showDebuggerForTransaction(last);
}
void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t)
{ {
m_context->appEngine()->rootContext()->setContextProperty("debugStates", QVariant::fromValue(_wStates)); //we need to wrap states in a QObject before sending to QML.
m_context->appEngine()->rootContext()->setContextProperty("humanReadableExecutionCode", QVariant::fromValue(std::get<0>(_code))); QList<QObject*> wStates;
m_context->appEngine()->rootContext()->setContextProperty("bytesCodeMapping", QVariant::fromValue(std::get<1>(_code))); for (unsigned i = 0; i < _t.machineStates.size(); i++)
m_context->appEngine()->rootContext()->setContextProperty("contractCallReturnParameters", QVariant::fromValue(new QVariableDefinitionList(_returnParam))); {
QPointer<DebuggingStateWrapper> s(new DebuggingStateWrapper(_t.executionCode, _t.executionData.toBytes()));
s->setState(_t.machineStates[i]);
wStates.append(s);
}
QList<QVariableDefinition*> returnParameters;
//returnParameters = encoder.decode(f->returnParameters(), debuggingContent.returnValue);
//collect states for last transaction
AssemblyDebuggerData code = DebuggingStateWrapper::getHumanReadableCode(_t.executionCode);
m_context->appEngine()->rootContext()->setContextProperty("debugStates", QVariant::fromValue(wStates));
m_context->appEngine()->rootContext()->setContextProperty("humanReadableExecutionCode", QVariant::fromValue(std::get<0>(code)));
m_context->appEngine()->rootContext()->setContextProperty("bytesCodeMapping", QVariant::fromValue(std::get<1>(code)));
m_context->appEngine()->rootContext()->setContextProperty("contractCallReturnParameters", QVariant::fromValue(new QVariableDefinitionList(returnParameters)));
showDebuggerWindow(); showDebuggerWindow();
} }
void ClientModel::debugTransaction(unsigned _block, unsigned _index)
{
auto const& t = m_client->record().at(_block).transactions.at(_index);
showDebuggerForTransaction(t);
}
void ClientModel::showDebugError(QString const& _error) void ClientModel::showDebugError(QString const& _error)
{ {
//TODO: change that to a signal //TODO: change that to a signal
m_context->displayMessageDialog(tr("Debugger"), _error); m_context->displayMessageDialog(tr("Debugger"), _error);
} }
ExecutionResult ClientModel::deployContract(bytes const& _code, TransactionSettings const& _ctrTransaction) Address ClientModel::deployContract(bytes const& _code, TransactionSettings const& _ctrTransaction)
{
Address newAddress = m_client->transact(m_client->userAccount().secret(), _ctrTransaction.value, _code, _ctrTransaction.gas, _ctrTransaction.gasPrice);
return newAddress;
}
void ClientModel::callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr)
{ {
Address newAddress; m_client->transact(m_client->userAccount().secret(), _tr.value, _contract, _data, _tr.gas, _tr.gasPrice);
if (!_ctrTransaction.isEmpty()) }
newAddress = m_client->transact(m_client->userAccount().secret(), _ctrTransaction.value, _code, _ctrTransaction.gas, _ctrTransaction.gasPrice);
void ClientModel::onStateReset()
{
m_contractAddress = dev::Address();
m_stdContractAddresses.clear();
m_stdContractNames.clear();
emit stateCleared();
}
void ClientModel::onNewTransaction()
{
unsigned block = m_client->number();
unsigned index = m_client->record().back().transactions.size() - 1;
ExecutionResult const& tr = m_client->record().back().transactions.back();
QString address = QString::fromStdString(toJS(tr.address));
QString value = QString::fromStdString(dev::toString(tr.value));
QString contract = address;
QString function;
QString returned;
bool creation = tr.contractAddress != 0;
if (creation)
returned = QString::fromStdString(toJS(tr.contractAddress));
else
returned = QString::fromStdString(toJS(tr.returnValue));
//TODO: handle value transfer
FixedHash<4> functionHash;
bool call = false;
if (creation)
{
//contract creation
auto const stdContractName = m_stdContractNames.find(tr.contractAddress);
if (stdContractName != m_stdContractNames.end())
{
function = stdContractName->second;
contract = function;
}
else
function = QObject::tr("Constructor");
}
else else
{ {
u256 gasPrice = 10000000000000; //call
u256 gas = 125000; if (tr.transactionData.size() >= 4)
u256 amount = 100; {
newAddress = m_client->transact(m_client->userAccount().secret(), amount, _code, gas, gasPrice); functionHash = FixedHash<4>(tr.transactionData.data(), FixedHash<4>::ConstructFromPointer);
function = QString::fromStdString(toJS(functionHash));
call = true;
}
else
function = QObject::tr("<none>");
} }
Address lastAddress = m_client->lastContractAddress(); if (m_contractAddress != 0 && (tr.address == m_contractAddress || tr.contractAddress == m_contractAddress))
ExecutionResult r = m_client->lastExecutionResult(); {
if (newAddress != lastAddress) auto compilerRes = m_context->codeModel()->code();
contractAddressChanged(); QContractDefinition* def = compilerRes->contract();
return r; contract = def->name();
} if (call)
{
QFunctionDefinition* funcDef = def->getFunction(functionHash);
if (funcDef)
function = funcDef->name();
}
}
ExecutionResult ClientModel::callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr) TransactionLogEntry* log = new TransactionLogEntry(block, index, contract, function, value, address, returned);
{ QQmlEngine::setObjectOwnership(log, QQmlEngine::JavaScriptOwnership);
m_client->transact(m_client->userAccount().secret(), _tr.value, _contract, _data, _tr.gas, _tr.gasPrice); emit newTransaction(log);
ExecutionResult r = m_client->lastExecutionResult();
r.contractAddress = _contract;
return r;
} }
} }

79
mix/ClientModel.h

@ -24,6 +24,7 @@
#pragma once #pragma once
#include <atomic> #include <atomic>
#include <map>
#include "DebuggingStateWrapper.h" #include "DebuggingStateWrapper.h"
#include "MixClient.h" #include "MixClient.h"
@ -40,6 +41,7 @@ namespace mix
class AppContext; class AppContext;
class Web3Server; class Web3Server;
class RpcConnector; class RpcConnector;
class QEther;
/// Backend transaction config class /// Backend transaction config class
struct TransactionSettings struct TransactionSettings
@ -47,6 +49,10 @@ struct TransactionSettings
TransactionSettings() {} TransactionSettings() {}
TransactionSettings(QString const& _functionId, u256 _value, u256 _gas, u256 _gasPrice): TransactionSettings(QString const& _functionId, u256 _value, u256 _gas, u256 _gasPrice):
functionId(_functionId), value(_value), gas(_gas), gasPrice(_gasPrice) {} functionId(_functionId), value(_value), gas(_gas), gasPrice(_gasPrice) {}
TransactionSettings(u256 _value, u256 _gas, u256 _gasPrice):
value(_value), gas(_gas), gasPrice(_gasPrice) {}
TransactionSettings(QString const& _stdContractName, QString const& _stdContractUrl):
functionId(_stdContractName), stdContractUrl(_stdContractUrl) {}
/// Contract function name /// Contract function name
QString functionId; QString functionId;
@ -58,12 +64,45 @@ struct TransactionSettings
u256 gasPrice; u256 gasPrice;
/// Mapping from contract function parameter name to value /// Mapping from contract function parameter name to value
std::map<QString, u256> parameterValues; std::map<QString, u256> parameterValues;
/// Standard contract url
QString stdContractUrl;
};
/// UI Transaction log record
class TransactionLogEntry: public QObject
{
Q_OBJECT
/// Transaction block number
Q_PROPERTY(unsigned block MEMBER m_block CONSTANT)
/// Transaction index within the block
Q_PROPERTY(unsigned index MEMBER m_index CONSTANT)
/// Contract name if any
Q_PROPERTY(QString contract MEMBER m_contract CONSTANT)
/// Function name if any
Q_PROPERTY(QString function MEMBER m_function CONSTANT)
/// Transaction value
Q_PROPERTY(QString value MEMBER m_value CONSTANT)
/// Receiving address
Q_PROPERTY(QString address MEMBER m_address CONSTANT)
/// Returned value or transaction address in case of creation
Q_PROPERTY(QString returned MEMBER m_returned CONSTANT)
public: public:
/// @returns true if the functionId has not be set TransactionLogEntry():
bool isEmpty() const { return functionId.isNull() || functionId.isEmpty(); } m_block(0), m_index(0) {}
}; TransactionLogEntry(int _block, int _index, QString _contract, QString _function, QString _value, QString _address, QString _returned):
m_block(_block), m_index(_index), m_contract(_contract), m_function(_function), m_value(_value), m_address(_address), m_returned(_returned) {}
private:
unsigned m_block;
unsigned m_index;
QString m_contract;
QString m_function;
QString m_value;
QString m_address;
QString m_returned;
};
/** /**
* @brief Ethereum state control * @brief Ethereum state control
@ -76,7 +115,7 @@ public:
ClientModel(AppContext* _context); ClientModel(AppContext* _context);
~ClientModel(); ~ClientModel();
/// @returns true if currently executing contract code /// @returns true if currently executing contract code
Q_PROPERTY(bool running MEMBER m_running NOTIFY stateChanged) Q_PROPERTY(bool running MEMBER m_running NOTIFY runStateChanged)
/// @returns address of the last executed contract /// @returns address of the last executed contract
Q_PROPERTY(QString contractAddress READ contractAddress NOTIFY contractAddressChanged) Q_PROPERTY(QString contractAddress READ contractAddress NOTIFY contractAddressChanged)
/// ethereum.js RPC request entry point /// ethereum.js RPC request entry point
@ -84,16 +123,21 @@ public:
/// @returns RPC response in Json format /// @returns RPC response in Json format
Q_INVOKABLE QString apiCall(QString const& _message); Q_INVOKABLE QString apiCall(QString const& _message);
/// Simulate mining. Creates a new block
Q_INVOKABLE void mine();
public slots: public slots:
/// Run the contract constructor and show debugger window. /// Run the contract constructor and show debugger window.
void debugDeployment(); void debugDeployment();
/// 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 debugState(QVariantMap _state); void setupState(QVariantMap _state);
/// Show the debugger for a specified transaction
Q_INVOKABLE void debugTransaction(unsigned _block, unsigned _index);
private slots: private slots:
/// Update UI with machine states result. Display a modal dialog. /// Update UI with machine states result. Display a modal dialog.
void showDebugger(QList<QVariableDefinition*> const& _returnParams = QList<QVariableDefinition*>(), QList<QObject*> const& _wStates = QList<QObject*>(), AssemblyDebuggerData const& _code = AssemblyDebuggerData()); void showDebugger();
/// Update UI with transaction run error. /// Update UI with transaction run error.
void showDebugError(QString const& _error); void showDebugError(QString const& _error);
@ -108,27 +152,36 @@ signals:
/// Contract address changed /// Contract address changed
void contractAddressChanged(); void contractAddressChanged();
/// Execution state changed /// Execution state changed
void stateChanged(); void newBlock();
/// Execution state changed
void runStateChanged();
/// Show debugger window request /// Show debugger window request
void showDebuggerWindow(); void showDebuggerWindow();
/// ethereum.js RPC response ready /// ethereum.js RPC response ready
/// @param _message RPC response in Json format /// @param _message RPC response in Json format
void apiResponse(QString const& _message); void apiResponse(QString const& _message);
/// New transaction log entry
/// Emited when machine states are available. void newTransaction(TransactionLogEntry* _tr);
void dataAvailable(QList<QVariableDefinition*> const& _returnParams = QList<QVariableDefinition*>(), QList<QObject*> const& _wStates = QList<QObject*>(), AssemblyDebuggerData const& _code = AssemblyDebuggerData()); /// State (transaction log) cleared
void stateCleared();
private: private:
QString contractAddress() const; QString contractAddress() const;
void executeSequence(std::vector<TransactionSettings> const& _sequence, u256 _balance, TransactionSettings const& _ctrTransaction = TransactionSettings()); void executeSequence(std::vector<TransactionSettings> const& _sequence, u256 _balance);
ExecutionResult deployContract(bytes const& _code, TransactionSettings const& _tr = TransactionSettings()); dev::Address deployContract(bytes const& _code, TransactionSettings const& _tr = TransactionSettings());
ExecutionResult callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr); void callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr);
void onNewTransaction();
void onStateReset();
void showDebuggerForTransaction(ExecutionResult const& _t);
AppContext* m_context; AppContext* m_context;
std::atomic<bool> m_running; std::atomic<bool> m_running;
std::unique_ptr<MixClient> m_client; std::unique_ptr<MixClient> m_client;
std::unique_ptr<RpcConnector> m_rpcConnector; std::unique_ptr<RpcConnector> m_rpcConnector;
std::unique_ptr<Web3Server> m_web3Server; std::unique_ptr<Web3Server> m_web3Server;
Address m_contractAddress;
std::map<QString, Address> m_stdContractAddresses;
std::map<Address, QString> m_stdContractNames;
}; };
} }

3
mix/CodeHighlighter.cpp

@ -102,7 +102,8 @@ void CodeHighlighter::processAST(dev::solidity::ASTNode const& _ast)
void CodeHighlighter::processError(dev::Exception const& _exception) void CodeHighlighter::processError(dev::Exception const& _exception)
{ {
Location const* location = boost::get_error_info<errinfo_sourceLocation>(_exception); Location const* location = boost::get_error_info<errinfo_sourceLocation>(_exception);
m_formats.push_back(FormatRange(CodeHighlighterSettings::CompilationError, *location)); if (location)
m_formats.push_back(FormatRange(CodeHighlighterSettings::CompilationError, *location));
} }
void CodeHighlighter::processComments(std::string const& _source) void CodeHighlighter::processComments(std::string const& _source)

21
mix/CodeModel.cpp

@ -32,6 +32,7 @@
#include "QFunctionDefinition.h" #include "QFunctionDefinition.h"
#include "QVariableDeclaration.h" #include "QVariableDeclaration.h"
#include "CodeHighlighter.h" #include "CodeHighlighter.h"
#include "FileIo.h"
#include "CodeModel.h" #include "CodeModel.h"
using namespace dev::mix; using namespace dev::mix;
@ -188,3 +189,23 @@ void CodeModel::updateFormatting(QTextDocument* _document)
{ {
m_result->codeHighlighter()->updateFormatting(_document, *m_codeHighlighterSettings); m_result->codeHighlighter()->updateFormatting(_document, *m_codeHighlighterSettings);
} }
dev::bytes const& CodeModel::getStdContractCode(const QString& _contractName, const QString& _url)
{
auto cached = m_compiledContracts.find(_contractName);
if (cached != m_compiledContracts.end())
return cached->second;
FileIo fileIo;
std::string source = fileIo.readFile(_url).toStdString();
solidity::CompilerStack cs(false);
cs.setSource(source);
cs.compile(false);
for (std::string const& name: cs.getContractNames())
{
dev::bytes code = cs.getBytecode(name);
m_compiledContracts.insert(std::make_pair(QString::fromStdString(name), std::move(code)));
}
return m_compiledContracts.at(_contractName);
}

4
mix/CodeModel.h

@ -24,6 +24,7 @@
#include <memory> #include <memory>
#include <atomic> #include <atomic>
#include <map>
#include <QObject> #include <QObject>
#include <QThread> #include <QThread>
#include <libdevcore/Common.h> #include <libdevcore/Common.h>
@ -131,6 +132,8 @@ public:
bool hasContract() const; bool hasContract() const;
/// Apply text document formatting. @todo Move this to editor module /// Apply text document formatting. @todo Move this to editor module
void updateFormatting(QTextDocument* _document); void updateFormatting(QTextDocument* _document);
/// Get contract code by url. Contract is compiled on first access and cached
dev::bytes const& getStdContractCode(QString const& _contractName, QString const& _url);
signals: signals:
/// Emited on compilation state change /// Emited on compilation state change
@ -163,6 +166,7 @@ private:
QThread m_backgroundThread; QThread m_backgroundThread;
BackgroundWorker m_backgroundWorker; BackgroundWorker m_backgroundWorker;
int m_backgroundJobId = 0; //protects from starting obsolete compilation job int m_backgroundJobId = 0; //protects from starting obsolete compilation job
std::map<QString, dev::bytes> m_compiledContracts; //by name
friend class BackgroundWorker; friend class BackgroundWorker;
}; };

5
mix/Exceptions.h

@ -35,9 +35,14 @@ namespace mix
struct QmlLoadException: virtual Exception {}; struct QmlLoadException: virtual Exception {};
struct FileIoException: virtual Exception {}; struct FileIoException: virtual Exception {};
struct InvalidBlockException: virtual Exception {};
struct FunctionNotFoundException: virtual Exception {};
struct ExecutionStateException: virtual Exception {};
typedef boost::error_info<struct tagQmlError, QQmlError> QmlErrorInfo; typedef boost::error_info<struct tagQmlError, QQmlError> QmlErrorInfo;
typedef boost::error_info<struct tagFileError, std::string> FileError; typedef boost::error_info<struct tagFileError, std::string> FileError;
typedef boost::error_info<struct tagBlockIndex, unsigned> BlockIndex;
typedef boost::error_info<struct tagFunctionName, std::string> FunctionName;
} }
} }

2
mix/HttpServer.cpp

@ -72,7 +72,7 @@ void HttpServer::setPort(int _port)
QString HttpServer::errorString() const QString HttpServer::errorString() const
{ {
return this->errorString(); return QTcpServer::errorString();
} }
void HttpServer::setListen(bool _listen) void HttpServer::setListen(bool _listen)

4
mix/HttpServer.h

@ -33,7 +33,7 @@ namespace mix
{ {
/// Simple http server for serving jsonrpc requests /// Simple http server for serving jsonrpc requests
class HttpRequest : public QObject class HttpRequest: public QObject
{ {
Q_OBJECT Q_OBJECT
/// Request url /// Request url
@ -59,7 +59,7 @@ private:
friend class HttpServer; friend class HttpServer;
}; };
class HttpServer : public QTcpServer, public QQmlParserStatus class HttpServer: public QTcpServer, public QQmlParserStatus
{ {
Q_OBJECT Q_OBJECT
Q_DISABLE_COPY(HttpServer) Q_DISABLE_COPY(HttpServer)

224
mix/MixClient.cpp

@ -29,14 +29,17 @@
#include <libethereum/ExtVM.h> #include <libethereum/ExtVM.h>
#include <libevm/VM.h> #include <libevm/VM.h>
#include "Exceptions.h"
#include "MixClient.h" #include "MixClient.h"
using namespace dev; using namespace dev;
using namespace dev::eth; using namespace dev::eth;
using namespace dev::mix; using namespace dev::mix;
const Secret c_stdSecret = Secret("cb73d9408c4720e230387d956eb0f829d8a4dd2c1055f96257167e14e7169074");
MixClient::MixClient(): MixClient::MixClient():
m_userAccount(KeyPair::create()) m_userAccount(c_stdSecret)
{ {
resetState(10000000 * ether); resetState(10000000 * ether);
} }
@ -44,14 +47,22 @@ MixClient::MixClient():
void MixClient::resetState(u256 _balance) void MixClient::resetState(u256 _balance)
{ {
WriteGuard l(x_state); WriteGuard l(x_state);
m_state = eth::State(Address(), m_stateDB, BaseState::Empty); Guard fl(m_filterLock);
m_filters.clear();
m_watches.clear();
m_state = eth::State(m_userAccount.address(), m_stateDB, BaseState::Empty);
m_state.addBalance(m_userAccount.address(), _balance); m_state.addBalance(m_userAccount.address(), _balance);
Block genesis;
genesis.state = m_state;
Block open;
m_blocks = Blocks { genesis, open }; //last block contains a list of pending transactions to be finalized
} }
void MixClient::executeTransaction(bytesConstRef _rlp, State& _state) void MixClient::executeTransaction(Transaction const& _t, State& _state)
{ {
bytes rlp = _t.rlp();
Executive execution(_state, LastHashes(), 0); Executive execution(_state, LastHashes(), 0);
execution.setup(_rlp); execution.setup(&rlp);
bytes code; bytes code;
bytesConstRef data; bytesConstRef data;
bool firstIteration = true; bool firstIteration = true;
@ -86,25 +97,73 @@ void MixClient::executeTransaction(bytesConstRef _rlp, State& _state)
d.machineStates = machineStates; d.machineStates = machineStates;
d.executionCode = code; d.executionCode = code;
d.executionData = data; d.executionData = data;
d.contentAvailable = true; d.transactionData = _t.data();
d.message = "ok"; d.address = _t.receiveAddress();
d.contractAddress = m_lastExecutionResult.contractAddress; d.sender = _t.sender();
m_lastExecutionResult = d; d.value = _t.value();
if (_t.isCreation())
d.contractAddress = right160(sha3(rlpList(_t.sender(), _t.nonce())));
d.receipt = TransactionReceipt(m_state.rootHash(), execution.gasUsed(), execution.logs()); //TODO: track gas usage
m_blocks.back().transactions.emplace_back(d);
h256Set changed;
Guard l(m_filterLock);
for (std::pair<h256 const, eth::InstalledFilter>& i: m_filters)
{
if ((unsigned)i.second.filter.latest() > m_blocks.size() - 1)
{
// acceptable number.
auto m = i.second.filter.matches(d.receipt);
if (m.size())
{
// filter catches them
for (LogEntry const& l: m)
i.second.changes.push_back(LocalisedLogEntry(l, m_blocks.size()));
changed.insert(i.first);
}
}
}
changed.insert(dev::eth::PendingChangedFilter);
noteChanged(changed);
} }
void MixClient::validateBlock(int _block) const void MixClient::validateBlock(int _block) const
{ {
//TODO: throw exception here if _block != 0 if (_block != -1 && _block != 0 && (unsigned)_block >= m_blocks.size() - 1)
(void)_block; BOOST_THROW_EXCEPTION(InvalidBlockException() << BlockIndex(_block));
}
void MixClient::mine()
{
WriteGuard l(x_state);
Block& block = m_blocks.back();
m_state.completeMine();
block.state = m_state;
block.info = m_state.info();
block.hash = block.info.hash;
m_blocks.push_back(Block());
h256Set changed { dev::eth::PendingChangedFilter, dev::eth::ChainChangedFilter };
noteChanged(changed);
}
State const& MixClient::asOf(int _block) const
{
validateBlock(_block);
if (_block == 0)
return m_blocks[m_blocks.size() - 2].state;
else if (_block == -1)
return m_state;
else
return m_blocks[_block].state;
} }
void MixClient::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) void MixClient::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice)
{ {
WriteGuard l(x_state); WriteGuard l(x_state);
u256 n = m_state.transactionsFrom(toAddress(_secret)); u256 n = m_state.transactionsFrom(toAddress(_secret));
Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret);
bytes rlp = t.rlp(); executeTransaction(t, m_state);
executeTransaction(&rlp, m_state);
} }
Address MixClient::transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) Address MixClient::transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice)
@ -112,17 +171,16 @@ Address MixClient::transact(Secret _secret, u256 _endowment, bytes const& _init,
WriteGuard l(x_state); WriteGuard l(x_state);
u256 n = m_state.transactionsFrom(toAddress(_secret)); u256 n = m_state.transactionsFrom(toAddress(_secret));
eth::Transaction t(_endowment, _gasPrice, _gas, _init, n, _secret); eth::Transaction t(_endowment, _gasPrice, _gas, _init, n, _secret);
bytes rlp = t.rlp(); executeTransaction(t, m_state);
executeTransaction(&rlp, m_state);
Address address = right160(sha3(rlpList(t.sender(), t.nonce()))); Address address = right160(sha3(rlpList(t.sender(), t.nonce())));
m_lastExecutionResult.contractAddress = address;
return address; return address;
} }
void MixClient::inject(bytesConstRef _rlp) void MixClient::inject(bytesConstRef _rlp)
{ {
WriteGuard l(x_state); WriteGuard l(x_state);
executeTransaction(_rlp, m_state); eth::Transaction t(_rlp, CheckSignature::None);
executeTransaction(t, m_state);
} }
void MixClient::flushTransactions() void MixClient::flushTransactions()
@ -140,92 +198,154 @@ bytes MixClient::call(Secret _secret, u256 _value, Address _dest, bytes const& _
} }
Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret);
bytes rlp = t.rlp(); bytes rlp = t.rlp();
WriteGuard lw(x_state); //TODO: lock is required only for last executoin state WriteGuard lw(x_state); //TODO: lock is required only for last execution state
executeTransaction(&rlp, temp); executeTransaction(t, temp);
return m_lastExecutionResult.returnValue; return m_blocks.back().transactions.back().returnValue;
} }
u256 MixClient::balanceAt(Address _a, int _block) const u256 MixClient::balanceAt(Address _a, int _block) const
{ {
validateBlock(_block);
ReadGuard l(x_state); ReadGuard l(x_state);
return m_state.balance(_a); return asOf(_block).balance(_a);
} }
u256 MixClient::countAt(Address _a, int _block) const u256 MixClient::countAt(Address _a, int _block) const
{ {
validateBlock(_block);
ReadGuard l(x_state); ReadGuard l(x_state);
return m_state.transactionsFrom(_a); return asOf(_block).transactionsFrom(_a);
} }
u256 MixClient::stateAt(Address _a, u256 _l, int _block) const u256 MixClient::stateAt(Address _a, u256 _l, int _block) const
{ {
validateBlock(_block);
ReadGuard l(x_state); ReadGuard l(x_state);
return m_state.storage(_a, _l); return asOf(_block).storage(_a, _l);
} }
bytes MixClient::codeAt(Address _a, int _block) const bytes MixClient::codeAt(Address _a, int _block) const
{ {
validateBlock(_block);
ReadGuard l(x_state); ReadGuard l(x_state);
return m_state.code(_a); return asOf(_block).code(_a);
} }
std::map<u256, u256> MixClient::storageAt(Address _a, int _block) const std::map<u256, u256> MixClient::storageAt(Address _a, int _block) const
{ {
validateBlock(_block);
ReadGuard l(x_state); ReadGuard l(x_state);
return m_state.storage(_a); return asOf(_block).storage(_a);
} }
eth::LocalisedLogEntries MixClient::logs(unsigned _watchId) const eth::LocalisedLogEntries MixClient::logs(unsigned _watchId) const
{ {
(void)_watchId; Guard l(m_filterLock);
return LocalisedLogEntries(); h256 h = m_watches.at(_watchId).id;
auto filterIter = m_filters.find(h);
if (filterIter != m_filters.end())
return logs(filterIter->second.filter);
return eth::LocalisedLogEntries();
} }
eth::LocalisedLogEntries MixClient::logs(eth::LogFilter const& _filter) const eth::LocalisedLogEntries MixClient::logs(eth::LogFilter const& _f) const
{ {
(void)_filter; LocalisedLogEntries ret;
return LocalisedLogEntries(); unsigned lastBlock = m_blocks.size() - 1; //last block contains pending transactions
unsigned block = std::min<unsigned>(lastBlock, (unsigned)_f.latest());
unsigned end = std::min(lastBlock, std::min(block, (unsigned)_f.earliest()));
for (; ret.size() != _f.max() && block != end; block--)
{
bool pendingBlock = (block == lastBlock);
if (pendingBlock || _f.matches(m_blocks[block].info.logBloom))
for (ExecutionResult const& t: m_blocks[block].transactions)
if (pendingBlock || _f.matches(t.receipt.bloom()))
{
LogEntries logEntries = _f.matches(t.receipt);
if (logEntries.size())
{
for (unsigned entry = _f.skip(); entry < logEntries.size() && ret.size() != _f.max(); ++entry)
ret.insert(ret.begin(), LocalisedLogEntry(logEntries[entry], block));
}
}
}
return ret;
}
unsigned MixClient::installWatch(h256 _h)
{
unsigned ret;
{
Guard l(m_filterLock);
ret = m_watches.size() ? m_watches.rbegin()->first + 1 : 0;
m_watches[ret] = ClientWatch(_h);
}
auto ch = logs(ret);
if (ch.empty())
ch.push_back(eth::InitialChange);
{
Guard l(m_filterLock);
swap(m_watches[ret].changes, ch);
}
return ret;
} }
unsigned MixClient::installWatch(eth::LogFilter const& _filter) unsigned MixClient::installWatch(eth::LogFilter const& _f)
{ {
(void)_filter; h256 h = _f.sha3();
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::installWatch")); {
Guard l(m_filterLock);
m_filters.insert(std::make_pair(h, _f));
}
return installWatch(h);
} }
unsigned MixClient::installWatch(h256 _filterId) void MixClient::uninstallWatch(unsigned _i)
{ {
(void)_filterId; Guard l(m_filterLock);
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::installWatch"));
auto it = m_watches.find(_i);
if (it == m_watches.end())
return;
auto id = it->second.id;
m_watches.erase(it);
auto fit = m_filters.find(id);
if (fit != m_filters.end())
if (!--fit->second.refCount)
m_filters.erase(fit);
} }
void MixClient::uninstallWatch(unsigned _watchId) void MixClient::noteChanged(h256Set const& _filters)
{ {
(void)_watchId; for (auto& i: m_watches)
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::uninstallWatch")); if (_filters.count(i.second.id))
{
if (m_filters.count(i.second.id))
i.second.changes += m_filters.at(i.second.id).changes;
else
i.second.changes.push_back(LocalisedLogEntry(SpecialLogEntry, 0));
}
for (auto& i: m_filters)
i.second.changes.clear();
} }
eth::LocalisedLogEntries MixClient::peekWatch(unsigned _watchId) const LocalisedLogEntries MixClient::peekWatch(unsigned _watchId) const
{ {
(void)_watchId; Guard l(m_filterLock);
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::peekWatch")); if (_watchId < m_watches.size())
return m_watches.at(_watchId).changes;
return LocalisedLogEntries();
} }
eth::LocalisedLogEntries MixClient::checkWatch(unsigned _watchId) LocalisedLogEntries MixClient::checkWatch(unsigned _watchId)
{ {
(void)_watchId; Guard l(m_filterLock);
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::checkWatch")); LocalisedLogEntries ret;
if (_watchId < m_watches.size())
std::swap(ret, m_watches.at(_watchId).changes);
return ret;
} }
h256 MixClient::hashFromNumber(unsigned _number) const h256 MixClient::hashFromNumber(unsigned _number) const
{ {
(void)_number; validateBlock(_number);
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::hashFromNumber")); return m_blocks[_number].hash;
} }
eth::BlockInfo MixClient::blockInfo(h256 _hash) const eth::BlockInfo MixClient::blockInfo(h256 _hash) const
@ -256,7 +376,7 @@ eth::BlockInfo MixClient::uncle(h256 _blockHash, unsigned _i) const
unsigned MixClient::number() const unsigned MixClient::number() const
{ {
return 0; return m_blocks.size() - 1;
} }
eth::Transactions MixClient::pending() const eth::Transactions MixClient::pending() const

41
mix/MixClient.h

@ -23,7 +23,9 @@
#pragma once #pragma once
#include <vector>
#include <libethereum/Interface.h> #include <libethereum/Interface.h>
#include <libethereum/Client.h>
namespace dev namespace dev
{ {
@ -53,24 +55,42 @@ struct MachineState
*/ */
struct ExecutionResult struct ExecutionResult
{ {
ExecutionResult(): receipt(dev::h256(), dev::h256(), dev::eth::LogEntries()) {}
std::vector<MachineState> machineStates; std::vector<MachineState> machineStates;
bytes transactionData;
bytes executionCode; bytes executionCode;
bytesConstRef executionData; bytesConstRef executionData;
Address contractAddress;
bool contentAvailable;
std::string message;
bytes returnValue; bytes returnValue;
dev::Address address;
dev::Address sender;
dev::Address contractAddress;
dev::u256 value;
dev::eth::TransactionReceipt receipt;
}; };
using ExecutionResults = std::vector<ExecutionResult>;
struct Block
{
ExecutionResults transactions;
h256 hash;
dev::eth::State state;
dev::eth::BlockInfo info;
};
using Blocks = std::vector<Block>;
class MixClient: public dev::eth::Interface class MixClient: public dev::eth::Interface
{ {
public: public:
MixClient(); MixClient();
/// Reset state to the empty state with given balance. /// Reset state to the empty state with given balance.
void resetState(u256 _balance); void resetState(u256 _balance);
const KeyPair& userAccount() const { return m_userAccount; } KeyPair const& userAccount() const { return m_userAccount; }
const ExecutionResult lastExecutionResult() const { ReadGuard l(x_state); return m_lastExecutionResult; } void mine();
const Address lastContractAddress() const { ReadGuard l(x_state); return m_lastExecutionResult.contractAddress; } Blocks const& record() const { return m_blocks; }
//dev::eth::Interface //dev::eth::Interface
void transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) override; void transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) override;
@ -111,14 +131,19 @@ public:
eth::MineProgress miningProgress() const override; eth::MineProgress miningProgress() const override;
private: private:
void executeTransaction(bytesConstRef _rlp, eth::State& _state); void executeTransaction(dev::eth::Transaction const& _t, eth::State& _state);
void validateBlock(int _block) const; void validateBlock(int _block) const;
void noteChanged(h256Set const& _filters);
dev::eth::State const& asOf(int _block) const;
KeyPair m_userAccount; KeyPair m_userAccount;
eth::State m_state; eth::State m_state;
OverlayDB m_stateDB; OverlayDB m_stateDB;
mutable boost::shared_mutex x_state; mutable boost::shared_mutex x_state;
ExecutionResult m_lastExecutionResult; mutable std::mutex m_filterLock;
std::map<h256, dev::eth::InstalledFilter> m_filters;
std::map<unsigned, dev::eth::ClientWatch> m_watches;
Blocks m_blocks;
}; };
} }

9
mix/QContractDefinition.cpp

@ -41,3 +41,12 @@ QContractDefinition::QContractDefinition(dev::solidity::ContractDefinition const
for (auto const& it: _contract->getInterfaceFunctions()) for (auto const& it: _contract->getInterfaceFunctions())
m_functions.append(new QFunctionDefinition(it.second));} m_functions.append(new QFunctionDefinition(it.second));}
QFunctionDefinition* QContractDefinition::getFunction(dev::FixedHash<4> _hash)
{
for (auto const& f: m_functions)
if (f->hash() == _hash)
return f;
return nullptr;
}

2
mix/QContractDefinition.h

@ -46,6 +46,8 @@ public:
/// Get the constructor of the contract. /// Get the constructor of the contract.
QFunctionDefinition* constructor() const { return m_constructor; } QFunctionDefinition* constructor() const { return m_constructor; }
QList<QFunctionDefinition*> const& functionsList() const { return m_functions; } QList<QFunctionDefinition*> const& functionsList() const { return m_functions; }
/// Find function by hash, returns nullptr if not found
QFunctionDefinition* getFunction(dev::FixedHash<4> _hash);
private: private:
QList<QFunctionDefinition*> m_functions; QList<QFunctionDefinition*> m_functions;
QFunctionDefinition* m_constructor; QFunctionDefinition* m_constructor;

15
mix/Web3Server.cpp

@ -53,3 +53,18 @@ void Web3Server::put(std::string const& _name, std::string const& _key, std::str
std::string k(_name + "/" + _key); std::string k(_name + "/" + _key);
m_db[k] = _value; m_db[k] = _value;
} }
std::string Web3Server::eth_transact(Json::Value const& _json)
{
std::string ret = WebThreeStubServerBase::eth_transact(_json);
emit newTransaction();
return ret;
}
std::string Web3Server::eth_call(Json::Value const& _json)
{
std::string ret = WebThreeStubServerBase::eth_call(_json);
emit newTransaction();
return ret;
}

12
mix/Web3Server.h

@ -24,6 +24,7 @@
#include <map> #include <map>
#include <string> #include <string>
#include <QObject>
#include <libweb3jsonrpc/WebThreeStubServerBase.h> #include <libweb3jsonrpc/WebThreeStubServerBase.h>
namespace dev namespace dev
@ -32,11 +33,20 @@ namespace dev
namespace mix namespace mix
{ {
class Web3Server: public dev::WebThreeStubServerBase, public dev::WebThreeStubDatabaseFace class Web3Server: public QObject, public dev::WebThreeStubServerBase, public dev::WebThreeStubDatabaseFace
{ {
Q_OBJECT
public: public:
Web3Server(jsonrpc::AbstractServerConnector& _conn, std::vector<dev::KeyPair> const& _accounts, dev::eth::Interface* _client); Web3Server(jsonrpc::AbstractServerConnector& _conn, std::vector<dev::KeyPair> const& _accounts, dev::eth::Interface* _client);
signals:
void newTransaction();
protected:
virtual std::string eth_transact(Json::Value const& _json) override;
virtual std::string eth_call(Json::Value const& _json) override;
private: private:
dev::eth::Interface* client() override { return m_client; } dev::eth::Interface* client() override { return m_client; }
std::shared_ptr<dev::shh::Interface> face() override; std::shared_ptr<dev::shh::Interface> face() override;

27
mix/qml/ContractLibrary.qml

@ -0,0 +1,27 @@
import QtQuick 2.2
Item {
id: contractLibrary
property alias model: contractListModel;
Connections {
target: appContext
Component.onCompleted: {
//TODO: load a list, dependencies, ets, from external files
contractListModel.append({
name: "Config",
url: "qrc:///stdc/std.sol",
});
contractListModel.append({
name: "NameReg",
url: "qrc:///stdc/std.sol",
});
}
}
ListModel {
id: contractListModel
}
}

6
mix/qml/Debugger.qml

@ -129,6 +129,12 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
TransactionLog {
Layout.fillWidth: true
height: 250
}
RowLayout { RowLayout {
// step button + slider // step button + slider
id: buttonRow id: buttonRow

115
mix/qml/Ether.qml

@ -10,7 +10,7 @@ import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1 import QtQuick.Controls.Styles 1.1
Rectangle { RowLayout {
id: etherEdition id: etherEdition
property bool displayFormattedValue; property bool displayFormattedValue;
property bool edit; property bool edit;
@ -32,84 +32,59 @@ Rectangle {
units.currentIndex = unit; units.currentIndex = unit;
} }
RowLayout TextField
{ {
anchors.fill: parent; implicitWidth: 200
id: row onTextChanged:
width: 200
height: parent.height
Rectangle
{ {
width : 200 if (value !== undefined)
color: edit ? "blue" : "white"
TextField
{ {
onTextChanged: value.setValue(text)
{ formattedValue.text = value.format();
if (value !== undefined)
{
value.setValue(text)
formattedValue.text = value.format();
}
}
width: parent.width
readOnly: !edit
visible: edit
id: etherValueEdit;
} }
} }
readOnly: !edit
visible: edit
id: etherValueEdit;
}
Rectangle ComboBox
{
id: units
onCurrentTextChanged:
{ {
Layout.fillWidth: true if (value !== undefined)
id: unitContainer
width: 20
anchors.verticalCenter: parent.verticalCenter
ComboBox
{
id: units
onCurrentTextChanged:
{
if (value !== undefined)
{
value.setUnit(currentText);
formattedValue.text = value.format();
}
}
model: ListModel {
id: unitsModel
ListElement { text: "Uether"; }
ListElement { text: "Vether"; }
ListElement { text: "Dether"; }
ListElement { text: "Nether"; }
ListElement { text: "Yether"; }
ListElement { text: "Zether"; }
ListElement { text: "Eether"; }
ListElement { text: "Pether"; }
ListElement { text: "Tether"; }
ListElement { text: "Gether"; }
ListElement { text: "Mether"; }
ListElement { text: "grand"; }
ListElement { text: "ether"; }
ListElement { text: "finney"; }
ListElement { text: "szabo"; }
ListElement { text: "Gwei"; }
ListElement { text: "Mwei"; }
ListElement { text: "Kwei"; }
ListElement { text: "wei"; }
}
}
Rectangle
{ {
anchors.verticalCenter: parent.verticalCenter value.setUnit(currentText);
anchors.left: units.right formattedValue.text = value.format();
visible: displayFormattedValue
width: 20
Text
{
id: formattedValue
}
} }
} }
model: ListModel {
id: unitsModel
ListElement { text: "Uether"; }
ListElement { text: "Vether"; }
ListElement { text: "Dether"; }
ListElement { text: "Nether"; }
ListElement { text: "Yether"; }
ListElement { text: "Zether"; }
ListElement { text: "Eether"; }
ListElement { text: "Pether"; }
ListElement { text: "Tether"; }
ListElement { text: "Gether"; }
ListElement { text: "Mether"; }
ListElement { text: "grand"; }
ListElement { text: "ether"; }
ListElement { text: "finney"; }
ListElement { text: "szabo"; }
ListElement { text: "Gwei"; }
ListElement { text: "Mwei"; }
ListElement { text: "Kwei"; }
ListElement { text: "wei"; }
}
}
Text
{
visible: displayFormattedValue
id: formattedValue
} }
} }

35
mix/qml/MainContent.qml

@ -35,40 +35,10 @@ Rectangle {
function startQuickDebugging() function startQuickDebugging()
{ {
var item = TransactionHelper.defaultTransaction(); ensureRightView();
item.executeConstructor = true; projectModel.stateListModel.debugDefaultState();
if (codeModel.code.contract.constructor.parameters.length === 0)
{
ensureRightView();
startF5Debugging(item);
}
else
transactionDialog.open(0, item);
}
function startF5Debugging(transaction)
{
var ether = QEtherHelper.createEther("100000000000000000000000000", QEther.Wei);
var state = {
title: "",
balance: ether,
transactions: [transaction]
};
clientModel.debugState(state);
} }
TransactionDialog {
id: transactionDialog
onAccepted: {
ensureRightView();
var item = transactionDialog.getItem();
item.executeConstructor = true;
startF5Debugging(item);
}
useTransactionDefaultValue: true
}
function toggleRightView() { function toggleRightView() {
if (!rightView.visible) if (!rightView.visible)
rightView.show(); rightView.show();
@ -99,7 +69,6 @@ Rectangle {
codeWebSplitter.orientation = (codeWebSplitter.orientation === Qt.Vertical ? Qt.Horizontal : Qt.Vertical); codeWebSplitter.orientation = (codeWebSplitter.orientation === Qt.Vertical ? Qt.Horizontal : Qt.Vertical);
} }
CodeEditorExtensionManager { CodeEditorExtensionManager {
headerView: headerPaneTabs; headerView: headerPaneTabs;
rightView: rightPaneTabs; rightView: rightPaneTabs;

6
mix/qml/ProjectModel.qml

@ -17,6 +17,7 @@ Item {
signal documentAdded(var documentId) signal documentAdded(var documentId)
signal projectSaving(var projectData) signal projectSaving(var projectData)
signal projectSaved() signal projectSaved()
signal newProject(var projectData)
signal documentSaved(var documentId) signal documentSaved(var documentId)
property bool isEmpty: (projectPath === "") property bool isEmpty: (projectPath === "")
@ -27,6 +28,7 @@ Item {
property string projectTitle: "" property string projectTitle: ""
property string currentDocumentId: "" property string currentDocumentId: ""
property var listModel: projectListModel property var listModel: projectListModel
property var stateListModel: projectStateListModel.model
//interface //interface
function saveAll() { ProjectModelCode.saveAll(); } function saveAll() { ProjectModelCode.saveAll(); }
@ -84,6 +86,10 @@ Item {
id: projectListModel id: projectListModel
} }
StateListModel {
id: projectStateListModel
}
Settings { Settings {
id: projectSettings id: projectSettings
property string lastProjectPath; property string lastProjectPath;

13
mix/qml/StateDialog.qml

@ -17,11 +17,12 @@ Window {
property alias stateTitle: titleField.text property alias stateTitle: titleField.text
property alias stateBalance: balanceField.value property alias stateBalance: balanceField.value
property alias isDefault: defaultCheckBox.checked
property int stateIndex property int stateIndex
property var stateTransactions: [] property var stateTransactions: []
signal accepted signal accepted
function open(index, item) { function open(index, item, setDefault) {
stateIndex = index; stateIndex = index;
stateTitle = item.title; stateTitle = item.title;
balanceField.value = item.balance; balanceField.value = item.balance;
@ -32,6 +33,7 @@ Window {
transactionsModel.append(item.transactions[t]); transactionsModel.append(item.transactions[t]);
stateTransactions.push(item.transactions[t]); stateTransactions.push(item.transactions[t]);
} }
isDefault = setDefault;
visible = true; visible = true;
titleField.focus = true; titleField.focus = true;
} }
@ -77,6 +79,14 @@ Window {
Layout.fillWidth: true Layout.fillWidth: true
} }
Label {
text: qsTr("Default")
}
CheckBox {
id: defaultCheckBox
Layout.fillWidth: true
}
Label { Label {
text: qsTr("Transactions") text: qsTr("Transactions")
} }
@ -152,6 +162,7 @@ Window {
} }
ToolButton { ToolButton {
text: qsTr("Edit"); text: qsTr("Edit");
visible: !stdContract
Layout.fillHeight: true Layout.fillHeight: true
onClicked: transactionsModel.editTransaction(index) onClicked: transactionsModel.editTransaction(index)
} }

95
mix/qml/StateList.qml

@ -3,8 +3,6 @@ import QtQuick.Controls.Styles 1.1
import QtQuick.Controls 1.1 import QtQuick.Controls 1.1
import QtQuick.Dialogs 1.1 import QtQuick.Dialogs 1.1
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
import org.ethereum.qml.QEther 1.0
import "js/QEtherHelper.js" as QEtherHelper
Rectangle { Rectangle {
color: "#ededed" color: "#ededed"
@ -14,32 +12,13 @@ Rectangle {
anchors.left: parent.left anchors.left: parent.left
height: parent.height height: parent.height
width: parent.width width: parent.width
property var stateList: []
Connections {
target: projectModel
onProjectClosed: {
stateListModel.clear();
}
onProjectLoaded: {
if (!projectData.states)
projectData.states = [];
var items = projectData.states;
for(var i = 0; i < items.length; i++) {
stateListModel.append(items[i]);
stateList.push(items[i])
}
}
onProjectSaving: {
projectData.states = stateList;
}
}
ListView { ListView {
id: list
anchors.top: parent.top anchors.top: parent.top
height: parent.height height: parent.height
width: parent.width width: parent.width
model: stateListModel model: projectModel.stateListModel
delegate: renderDelegate delegate: renderDelegate
} }
@ -48,65 +27,6 @@ Rectangle {
action: addStateAction action: addStateAction
} }
StateDialog {
id: stateDialog
onAccepted: {
var item = stateDialog.getItem();
if (stateDialog.stateIndex < stateListModel.count) {
stateList[stateDialog.stateIndex] = item;
stateListModel.set(stateDialog.stateIndex, item);
} else {
stateList.push(item);
stateListModel.append(item);
}
stateListModel.save();
}
}
ListModel {
id: stateListModel
function addState() {
var ether = QEtherHelper.createEther("100000000000000000000000000", QEther.Wei);
var item = {
title: "",
balance: ether,
transactions: []
};
var ctorTr = {
value: QEtherHelper.createEther("100", QEther.Wei),
functionId: qsTr("Constructor"),
gas: QEtherHelper.createEther("125000", QEther.Wei),
gasPrice: QEtherHelper.createEther("10000000000000", QEther.Wei),
executeConstructor: true
};
item.transactions.push(ctorTr);
stateDialog.open(stateListModel.count, item);
}
function editState(index) {
stateDialog.open(index, stateList[index]);
}
function runState(index) {
var item = stateList[index];
clientModel.debugState(item);
}
function deleteState(index) {
stateListModel.remove(index);
stateList.splice(index, 1);
save();
}
function save() {
projectModel.saveProject();
}
}
Component { Component {
id: renderDelegate id: renderDelegate
Item { Item {
@ -125,20 +45,17 @@ Rectangle {
ToolButton { ToolButton {
text: qsTr("Edit"); text: qsTr("Edit");
Layout.fillHeight: true Layout.fillHeight: true
onClicked: stateListModel.editState(index); onClicked: list.model.editState(index);
} }
ToolButton { ToolButton {
text: qsTr("Delete"); text: qsTr("Delete");
Layout.fillHeight: true Layout.fillHeight: true
onClicked: stateListModel.deleteState(index); onClicked: list.model.deleteState(index);
} }
ToolButton { ToolButton {
text: qsTr("Run"); text: qsTr("Run");
Layout.fillHeight: true Layout.fillHeight: true
onClicked: onClicked: list.model.runState(index);
{
stateListModel.runState(index)
}
} }
} }
} }
@ -149,7 +66,7 @@ Rectangle {
text: "&Add State" text: "&Add State"
shortcut: "Ctrl+T" shortcut: "Ctrl+T"
enabled: codeModel.hasContract && !clientModel.running; enabled: codeModel.hasContract && !clientModel.running;
onTriggered: stateListModel.addState(); onTriggered: list.model.addState();
} }
} }

196
mix/qml/StateListModel.qml

@ -0,0 +1,196 @@
import QtQuick 2.2
import QtQuick.Controls.Styles 1.1
import QtQuick.Controls 1.1
import QtQuick.Dialogs 1.1
import QtQuick.Layouts 1.1
import org.ethereum.qml.QEther 1.0
import "js/QEtherHelper.js" as QEtherHelper
Item {
property int defaultStateIndex: -1
property alias model: stateListModel
property var stateList: []
function fromPlainStateItem(s) {
return {
title: s.title,
balance: QEtherHelper.createEther(s.balance.value, s.balance.unit),
transactions: s.transactions.map(fromPlainTransactionItem)
};
}
function fromPlainTransactionItem(t) {
var r = {
functionId: t.functionId,
url: t.url,
value: QEtherHelper.createEther(t.value.value, t.value.unit),
gas: QEtherHelper.createEther(t.gas.value, t.gas.unit),
gasPrice: QEtherHelper.createEther(t.gasPrice.value, t.gasPrice.unit),
executeConstructor: t.executeConstructor,
stdContract: t.stdContract,
parameters: {}
};
for (var key in t.parameters) {
var intComponent = Qt.createComponent("qrc:/qml/BigIntValue.qml");
var param = intComponent.createObject();
param.setValue(t.parameters[key]);
r.parameters[key] = param;
}
return r;
}
function toPlainStateItem(s) {
return {
title: s.title,
balance: { value: s.balance.value, unit: s.balance.unit },
transactions: s.transactions.map(toPlainTransactionItem)
};
}
function toPlainTransactionItem(t) {
var r = {
functionId: t.functionId,
url: t.url,
value: { value: t.value.value, unit: t.value.unit },
gas: { value: t.gas.value, unit: t.gas.unit },
gasPrice: { value: t.gasPrice.value, unit: t.gasPrice.unit },
executeConstructor: t.executeConstructor,
stdContract: t.stdContract,
parameters: {}
};
for (var key in t.parameters)
r.parameters[key] = t.parameters[key].value();
return r;
}
Connections {
target: projectModel
onProjectClosed: {
stateListModel.clear();
stateList = [];
}
onProjectLoaded: {
if (!projectData.states)
projectData.states = [];
if (projectData.defaultStateIndex !== undefined)
defaultStateIndex = projectData.defaultStateIndex;
else
defaultStateIndex = -1;
var items = projectData.states;
for(var i = 0; i < items.length; i++) {
var item = fromPlainStateItem(items[i]);
stateListModel.append(item);
stateList.push(item);
}
}
onProjectSaving: {
projectData.states = []
for(var i = 0; i < stateListModel.count; i++) {
projectData.states.push(toPlainStateItem(stateList[i]));
}
projectData.defaultStateIndex = defaultStateIndex;
}
onNewProject: {
var state = toPlainStateItem(stateListModel.createDefaultState());
state.title = qsTr("Default");
projectData.states = [ state ];
projectData.defaultStateIndex = 0;
}
}
StateDialog {
id: stateDialog
onAccepted: {
var item = stateDialog.getItem();
if (stateDialog.stateIndex < stateListModel.count) {
if (stateDialog.isDefault)
defaultStateIndex = stateIndex;
stateList[stateDialog.stateIndex] = item;
stateListModel.set(stateDialog.stateIndex, item);
} else {
if (stateDialog.isDefault)
defaultStateIndex = 0;
stateList.push(item);
stateListModel.append(item);
}
stateListModel.save();
}
}
ContractLibrary {
id: contractLibrary;
}
ListModel {
id: stateListModel
function defaultTransactionItem() {
return {
value: QEtherHelper.createEther("100", QEther.Wei),
gas: QEtherHelper.createEther("125000", QEther.Wei),
gasPrice: QEtherHelper.createEther("10000000000000", QEther.Wei),
executeConstructor: false,
stdContract: false
};
}
function createDefaultState() {
var ether = QEtherHelper.createEther("100000000000000000000000000", QEther.Wei);
var item = {
title: "",
balance: ether,
transactions: []
};
//add all stdc contracts
for (var i = 0; i < contractLibrary.model.count; i++) {
var contractTransaction = defaultTransactionItem();
var contractItem = contractLibrary.model.get(i);
contractTransaction.url = contractItem.url;
contractTransaction.functionId = contractItem.name;
contractTransaction.stdContract = true;
item.transactions.push(contractTransaction);
};
//add constructor
var ctorTr = defaultTransactionItem();
ctorTr.executeConstructor = true;
ctorTr.functionId = qsTr("Constructor");
item.transactions.push(ctorTr);
return item;
}
function addState() {
var item = createDefaultState();
stateDialog.open(stateListModel.count, item, defaultStateIndex === -1);
}
function editState(index) {
stateDialog.open(index, stateList[index], defaultStateIndex === index);
}
function debugDefaultState() {
if (defaultStateIndex >= 0)
runState(defaultStateIndex);
}
function runState(index) {
var item = stateList[index];
clientModel.setupState(item);
}
function deleteState(index) {
stateListModel.remove(index);
stateList.splice(index, 1);
if (index === defaultStateIndex)
defaultStateIndex = -1;
save();
}
function save() {
projectModel.saveProject();
}
}
}

16
mix/qml/StatusPane.qml

@ -27,6 +27,22 @@ Rectangle {
debugRunActionIcon.enabled = statusPane.result.successful; debugRunActionIcon.enabled = statusPane.result.successful;
} }
function infoMessage(text)
{
status.state = "";
status.text = text
logslink.visible = false;
}
Connections {
target:clientModel
onRunStarted: infoMessage(qsTr("Running transactions.."));
onRunFailed: infoMessage(qsTr("Error running transactions"));
onRunComplete: infoMessage(qsTr("Run complete"));
onNewBlock: infoMessage(qsTr("New block created"));
}
color: "transparent" color: "transparent"
anchors.fill: parent anchors.fill: parent
Rectangle { Rectangle {

8
mix/qml/TransactionDialog.qml

@ -49,7 +49,7 @@ Window {
} }
if (functionIndex == -1 && functionsModel.count > 0) if (functionIndex == -1 && functionsModel.count > 0)
functionIndex = 0; //@todo suggest unused funtion functionIndex = 0; //@todo suggest unused function
functionComboBox.currentIndex = functionIndex; functionComboBox.currentIndex = functionIndex;
paramsModel.clear(); paramsModel.clear();
@ -223,17 +223,17 @@ Window {
TableViewColumn { TableViewColumn {
role: "name" role: "name"
title: "Name" title: qsTr("Name")
width: 120 width: 120
} }
TableViewColumn { TableViewColumn {
role: "type" role: "type"
title: "Type" title: qsTr("Type")
width: 120 width: 120
} }
TableViewColumn { TableViewColumn {
role: "value" role: "value"
title: "Value" title: qsTr("Value")
width: 120 width: 120
} }

73
mix/qml/TransactionLog.qml

@ -0,0 +1,73 @@
import QtQuick 2.2
import QtQuick.Controls.Styles 1.1
import QtQuick.Controls 1.1
import QtQuick.Dialogs 1.1
import QtQuick.Layouts 1.1
Item {
TableView {
anchors.fill: parent
model: logModel
TableViewColumn {
role: "block"
title: qsTr("Block")
width: 40
}
TableViewColumn {
role: "index"
title: qsTr("Index")
width: 40
}
TableViewColumn {
role: "contract"
title: qsTr("Contract")
width: 120
}
TableViewColumn {
role: "function"
title: qsTr("Function")
width: 120
}
TableViewColumn {
role: "value"
title: qsTr("Value")
width: 120
}
TableViewColumn {
role: "address"
title: qsTr("Address")
width: 120
}
TableViewColumn {
role: "returned"
title: qsTr("Returned")
width: 120
}
onActivated: {
var item = logModel.get(row);
clientModel.debugTransaction(item.block, item.index);
}
Keys.onPressed: {
if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_C && currentRow >=0 && currentRow < logModel.count) {
var item = logModel.get(currentRow);
appContext.toClipboard(item.returned);
}
}
}
ListModel {
id: logModel
}
Connections {
target: clientModel
onStateCleared: {
logModel.clear();
}
onNewTransaction: {
logModel.append(_tr);
}
}
}

10
mix/qml/WebPreview.qml

@ -62,6 +62,7 @@ Item {
Connections { Connections {
target: clientModel target: clientModel
onContractAddressChanged: reload(); onContractAddressChanged: reload();
onRunComplete: reload();
} }
Connections { Connections {
@ -111,9 +112,14 @@ Item {
accept: true accept: true
port: 8893 port: 8893
onClientConnected: { onClientConnected: {
console.log(_request.content); //filter polling spam
//TODO: do it properly
var log = _request.content.indexOf("eth_changed") < 0;
if (log)
console.log(_request.content);
var response = clientModel.apiCall(_request.content); var response = clientModel.apiCall(_request.content);
console.log(response); if (log)
console.log(response);
_request.setResponse(response); _request.setResponse(response);
} }
} }

2
mix/qml/html/WebContainer.html

@ -38,5 +38,5 @@ body {
</style> </style>
</head> </head>
<body><iframe src="" name="preview" id="preview" style="border: 0; width: 100%; height: 100%"></iframe></body> <body><iframe src="" name="preview" id="preview" style="border: 0; position:absolute; top:0; left:0; right:0; bottom:0; width:100%; height:100%"></iframe></body>
</html> </html>

5
mix/qml/js/ProjectModel.js

@ -47,7 +47,7 @@ function saveProject() {
for (var i = 0; i < projectListModel.count; i++) for (var i = 0; i < projectListModel.count; i++)
projectData.files.push(projectListModel.get(i).fileName) projectData.files.push(projectListModel.get(i).fileName)
projectSaving(projectData); projectSaving(projectData);
var json = JSON.stringify(projectData); var json = JSON.stringify(projectData, null, "\t");
var projectFile = projectPath + projectFileName; var projectFile = projectPath + projectFileName;
fileIo.writeFile(projectFile, json); fileIo.writeFile(projectFile, json);
projectSaved(); projectSaved();
@ -173,7 +173,8 @@ function doCreateProject(title, path) {
//TODO: copy from template //TODO: copy from template
fileIo.writeFile(dirPath + indexFile, "<html></html>"); fileIo.writeFile(dirPath + indexFile, "<html></html>");
fileIo.writeFile(dirPath + contractsFile, "contract MyContract {\n}\n"); fileIo.writeFile(dirPath + contractsFile, "contract MyContract {\n}\n");
var json = JSON.stringify(projectData); newProject(projectData);
var json = JSON.stringify(projectData, null, "\t");
fileIo.writeFile(projectFile, json); fileIo.writeFile(projectFile, json);
loadProject(dirPath); loadProject(dirPath);
} }

25
mix/qml/main.qml

@ -4,14 +4,14 @@ import QtQuick.Controls.Styles 1.1
import QtQuick.Dialogs 1.1 import QtQuick.Dialogs 1.1
import QtQuick.Layouts 1.1 import QtQuick.Layouts 1.1
import QtQuick.Window 2.1 import QtQuick.Window 2.1
import CodeEditorExtensionManager 1.0 import Qt.labs.settings 1.0
import org.ethereum.qml.QEther 1.0 import org.ethereum.qml.QEther 1.0
ApplicationWindow { ApplicationWindow {
id: mainApplication id: mainApplication
visible: true visible: true
width: 1200 width: 1200
height: 600 height: 800
minimumWidth: 400 minimumWidth: 400
minimumHeight: 300 minimumHeight: 300
title: qsTr("mix") title: qsTr("mix")
@ -37,6 +37,7 @@ ApplicationWindow {
title: qsTr("Debug") title: qsTr("Debug")
MenuItem { action: debugRunAction } MenuItem { action: debugRunAction }
MenuItem { action: debugResetStateAction } MenuItem { action: debugResetStateAction }
MenuItem { action: mineAction }
} }
Menu { Menu {
title: qsTr("Windows") title: qsTr("Windows")
@ -49,11 +50,6 @@ ApplicationWindow {
} }
} }
Component.onCompleted: {
setX(Screen.width / 2 - width / 2);
setY(Screen.height / 2 - height / 2);
}
MainContent { MainContent {
id: mainContent; id: mainContent;
anchors.fill: parent anchors.fill: parent
@ -69,6 +65,14 @@ ApplicationWindow {
id: messageDialog id: messageDialog
} }
Settings {
id: mainWindowSettings
property alias mainWidth: mainApplication.width
property alias mainHeight: mainApplication.height
property alias mainX: mainApplication.x
property alias mainY: mainApplication.y
}
Action { Action {
id: exitAppAction id: exitAppAction
text: qsTr("Exit") text: qsTr("Exit")
@ -76,6 +80,13 @@ ApplicationWindow {
onTriggered: Qt.quit(); onTriggered: Qt.quit();
} }
Action {
id: mineAction
text: "Mine"
shortcut: "Ctrl+M"
onTriggered: clientModel.mine();
enabled: codeModel.hasContract && !clientModel.running
}
Action { Action {
id: debugRunAction id: debugRunAction
text: "&Run" text: "&Run"

6
mix/qml.qrc → mix/res.qrc

@ -10,6 +10,7 @@
<file>qml/ProjectList.qml</file> <file>qml/ProjectList.qml</file>
<file>qml/StateDialog.qml</file> <file>qml/StateDialog.qml</file>
<file>qml/StateList.qml</file> <file>qml/StateList.qml</file>
<file>qml/StateListModel.qml</file>
<file>qml/img/jumpintoback.png</file> <file>qml/img/jumpintoback.png</file>
<file>qml/img/jumpintoforward.png</file> <file>qml/img/jumpintoforward.png</file>
<file>qml/img/jumpoutback.png</file> <file>qml/img/jumpoutback.png</file>
@ -44,5 +45,10 @@
<file>qml/js/QEtherHelper.js</file> <file>qml/js/QEtherHelper.js</file>
<file>qml/js/TransactionHelper.js</file> <file>qml/js/TransactionHelper.js</file>
<file>qml/Splitter.qml</file> <file>qml/Splitter.qml</file>
<file>qml/ContractLibrary.qml</file>
<file>stdc/config.sol</file>
<file>stdc/namereg.sol</file>
<file>stdc/std.sol</file>
<file>qml/TransactionLog.qml</file>
</qresource> </qresource>
</RCC> </RCC>

45
mix/stdc/config.sol

@ -0,0 +1,45 @@
//sol Config
// Simple global configuration registrar.
// @authors:
// Gav Wood <g@ethdev.com>
#require mortal
contract Config is mortal {
function register(uint id, address service) {
if (tx.origin != owner)
return;
services[id] = service;
log1(0, id);
}
function unregister(uint id) {
if (msg.sender != owner && services[id] != msg.sender)
return;
services[id] = address(0);
log1(0, id);
}
function lookup(uint service) constant returns(address a) {
return services[service];
}
mapping (uint => address) services;
}
/*
// Solidity Interface:
contract Config{function lookup(uint256 service)constant returns(address a){}function kill(){}function unregister(uint256 id){}function register(uint256 id,address service){}}
// Example Solidity use:
address addrConfig = 0xf025d81196b72fba60a1d4dddad12eeb8360d828;
address addrNameReg = Config(addrConfig).lookup(1);
// JS Interface:
var abiConfig = [{"constant":false,"inputs":[],"name":"kill","outputs":[]},{"constant":true,"inputs":[{"name":"service","type":"uint256"}],"name":"lookup","outputs":[{"name":"a","type":"address"}]},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"service","type":"address"}],"name":"register","outputs":[]},{"constant":false,"inputs":[{"name":"id","type":"uint256"}],"name":"unregister","outputs":[]}];
// Example JS use:
var addrConfig = "0x661005d2720d855f1d9976f88bb10c1a3398c77f";
var addrNameReg;
web3.eth.contract(addrConfig, abiConfig).lookup(1).call().then(function(r){ addrNameReg = r; })
*/

74
mix/stdc/namereg.sol

@ -0,0 +1,74 @@
//sol NameReg
// Simple global name registrar.
// @authors:
// kobigurk (from #ethereum-dev)
// Gav Wood <g@ethdev.com>
contract NameRegister {
function getAddress(string32 _name) constant returns (address o_owner) {}
function getName(address _owner) constant returns (string32 o_name) {}
}
#require Config, owned
contract NameReg is owned, NameRegister {
function NameReg() {
address addrConfig = 0xf025d81196b72fba60a1d4dddad12eeb8360d828;
toName[addrConfig] = "Config";
toAddress["Config"] = addrConfig;
toName[this] = "NameReg";
toAddress["NameReg"] = this;
Config(addrConfig).register(1, this);
log1(0, hash256(Config()));
log1(0, hash256(this));
}
function register(string32 name) {
// Don't allow the same name to be overwritten.
if (toAddress[name] != address(0))
return;
// Unregister previous name if there was one.
if (toName[msg.sender] != "")
toAddress[toName[msg.sender]] = 0;
toName[msg.sender] = name;
toAddress[name] = msg.sender;
log1(0, hash256(msg.sender));
}
function unregister() {
string32 n = toName[msg.sender];
if (n == "")
return;
log1(0, hash256(toAddress[n]));
toName[msg.sender] = "";
toAddress[n] = address(0);
}
function addressOf(string32 name) constant returns (address addr) {
return toAddress[name];
}
function nameOf(address addr) constant returns (string32 name) {
return toName[addr];
}
mapping (address => string32) toName;
mapping (string32 => address) toAddress;
}
/*
// Solidity Interface:
contract NameReg{function kill(){}function register(string32 name){}function addressOf(string32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(string32 name){}}
// Example Solidity use:
NameReg(addrNameReg).register("Some Contract");
// JS Interface:
var abiNameReg = [{"constant":true,"inputs":[{"name":"name","type":"string32"}],"name":"addressOf","outputs":[{"name":"addr","type":"address"}]},{"constant":false,"inputs":[],"name":"kill","outputs":[]},{"constant":true,"inputs":[{"name":"addr","type":"address"}],"name":"nameOf","outputs":[{"name":"name","type":"string32"}]},{"constant":false,"inputs":[{"name":"name","type":"string32"}],"name":"register","outputs":[]},{"constant":false,"inputs":[],"name":"unregister","outputs":[]}];
// Example JS use:
web3.eth.contract(addrNameReg, abiNameReg).register("My Name").transact();
*/

124
mix/stdc/std.sol

@ -0,0 +1,124 @@
//TODO: use imports
contract owned{function owned(){owner = msg.sender;}modifier onlyowner(){if(msg.sender==owner)_}address owner;}
contract mortal is owned {function kill() { if (msg.sender == owner) suicide(owner); }}
//sol Config
// Simple global configuration registrar.
// @authors:
// Gav Wood <g@ethdev.com>
contract Config is mortal {
function register(uint id, address service) {
if (tx.origin != owner)
return;
services[id] = service;
log1(0, id);
}
function unregister(uint id) {
if (msg.sender != owner && services[id] != msg.sender)
return;
services[id] = address(0);
log1(0, id);
}
function lookup(uint service) constant returns(address a) {
return services[service];
}
mapping (uint => address) services;
}
/*
// Solidity Interface:
contract Config{function lookup(uint256 service)constant returns(address a){}function kill(){}function unregister(uint256 id){}function register(uint256 id,address service){}}
// Example Solidity use:
address addrConfig = 0xf025d81196b72fba60a1d4dddad12eeb8360d828;
address addrNameReg = Config(addrConfig).lookup(1);
// JS Interface:
var abiConfig = [{"constant":false,"inputs":[],"name":"kill","outputs":[]},{"constant":true,"inputs":[{"name":"service","type":"uint256"}],"name":"lookup","outputs":[{"name":"a","type":"address"}]},{"constant":false,"inputs":[{"name":"id","type":"uint256"},{"name":"service","type":"address"}],"name":"register","outputs":[]},{"constant":false,"inputs":[{"name":"id","type":"uint256"}],"name":"unregister","outputs":[]}];
// Example JS use:
var addrConfig = "0x661005d2720d855f1d9976f88bb10c1a3398c77f";
var addrNameReg;
web3.eth.contract(addrConfig, abiConfig).lookup(1).call().then(function(r){ addrNameReg = r; })
*/
//sol NameReg
// Simple global name registrar.
// @authors:
// kobigurk (from #ethereum-dev)
// Gav Wood <g@ethdev.com>
contract NameRegister {
function getAddress(string32 _name) constant returns (address o_owner) {}
function getName(address _owner) constant returns (string32 o_name) {}
}
contract NameReg is owned, NameRegister {
function NameReg() {
address addrConfig = 0xf025d81196b72fba60a1d4dddad12eeb8360d828;
toName[addrConfig] = "Config";
toAddress["Config"] = addrConfig;
toName[this] = "NameReg";
toAddress["NameReg"] = this;
Config(addrConfig).register(1, this);
log1(0, hash256(addrConfig));
log1(0, hash256(this));
}
function register(string32 name) {
// Don't allow the same name to be overwritten.
if (toAddress[name] != address(0))
return;
// Unregister previous name if there was one.
if (toName[msg.sender] != "")
toAddress[toName[msg.sender]] = 0;
toName[msg.sender] = name;
toAddress[name] = msg.sender;
log1(0, hash256(msg.sender));
}
function unregister() {
string32 n = toName[msg.sender];
if (n == "")
return;
log1(0, hash256(toAddress[n]));
toName[msg.sender] = "";
toAddress[n] = address(0);
}
function addressOf(string32 name) constant returns (address addr) {
return toAddress[name];
}
function nameOf(address addr) constant returns (string32 name) {
return toName[addr];
}
mapping (address => string32) toName;
mapping (string32 => address) toAddress;
}
/*
// Solidity Interface:
contract NameReg{function kill(){}function register(string32 name){}function addressOf(string32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(string32 name){}}
// Example Solidity use:
NameReg(addrNameReg).register("Some Contract");
// JS Interface:
var abiNameReg = [{"constant":true,"inputs":[{"name":"name","type":"string32"}],"name":"addressOf","outputs":[{"name":"addr","type":"address"}]},{"constant":false,"inputs":[],"name":"kill","outputs":[]},{"constant":true,"inputs":[{"name":"addr","type":"address"}],"name":"nameOf","outputs":[{"name":"name","type":"string32"}]},{"constant":false,"inputs":[{"name":"name","type":"string32"}],"name":"register","outputs":[]},{"constant":false,"inputs":[],"name":"unregister","outputs":[]}];
// Example JS use:
web3.eth.contract(addrNameReg, abiNameReg).register("My Name").transact();
*/
Loading…
Cancel
Save