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 <QClipboard>
#include <QQmlComponent>
#include <QQmlContext>
#include <QQmlApplicationEngine>
@ -94,3 +95,9 @@ void AppContext::displayMessageDialog(QString _title, QString _message)
dialogWin->findChild<QObject*>("messageContent", Qt::FindChildrenRecursively)->setProperty("text", _message);
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(); }
/// Display an alert message.
void displayMessageDialog(QString _title, QString _message);
/// Copy text to clipboard
Q_INVOKABLE void toClipboard(QString _text);
signals:
/// Triggered once components have been loaded

7
mix/CMakeLists.txt

@ -13,7 +13,7 @@ aux_source_directory(. SRC_LIST)
include_directories(..)
find_package (Qt5WebEngine QUIET)
qt5_add_resources(UI_RESOURCES qml.qrc)
qt5_add_resources(UI_RESOURCES res.qrc)
file(GLOB HEADERS "*.h")
@ -62,6 +62,7 @@ eth_install_executable(${EXECUTABLE}
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/*.*")
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 "AppContext.h"
#include "DebuggingStateWrapper.h"
#include "Exceptions.h"
#include "QContractDefinition.h"
#include "QVariableDeclaration.h"
#include "ContractCallDataEncoder.h"
@ -60,8 +61,9 @@ private:
QString m_response;
};
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<QEther*>("QEther*");
@ -71,11 +73,13 @@ ClientModel::ClientModel(AppContext* _context):
qRegisterMetaType<QList<QVariableDeclaration*>>("QList<QVariableDeclaration*>");
qRegisterMetaType<QVariableDeclaration*>("QVariableDeclaration*");
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_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);
}
@ -90,10 +94,15 @@ QString ClientModel::apiCall(QString const& _message)
return m_rpcConnector->response();
}
void ClientModel::mine()
{
m_client->mine();
newBlock();
}
QString ClientModel::contractAddress() const
{
QString address = QString::fromStdString(dev::toJS(m_client->lastContractAddress()));
return address;
return QString::fromStdString(dev::toJS(m_contractAddress));
}
void ClientModel::debugDeployment()
@ -101,13 +110,12 @@ void ClientModel::debugDeployment()
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();
QVariantList transactions = _state.value("transactions").toList();
std::vector<TransactionSettings> transactionSequence;
TransactionSettings constructorTr;
for (auto const& t: transactions)
{
QVariantMap transaction = t.toMap();
@ -115,33 +123,46 @@ void ClientModel::debugState(QVariantMap _state)
u256 gas = (qvariant_cast<QEther*>(transaction.value("gas")))->toU256Wei();
u256 value = (qvariant_cast<QEther*>(transaction.value("value")))->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.parameterValues.insert(std::make_pair(p.key(), boost::get<dev::u256>(param->internalValue())));
TransactionSettings transactionSettings(functionId, transaction.value("url").toString());
transactionSettings.gasPrice = 10000000000000;
transactionSettings.gas = 125000;
transactionSettings.value = 100;
transactionSequence.push_back(transactionSettings);
}
if (transaction.value("executeConstructor").toBool())
constructorTr = transactionSettings;
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);
}
}
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)
throw (std::logic_error("debugging already running"));
auto compilerRes = m_context->codeModel()->code();
BOOST_THROW_EXCEPTION(ExecutionStateException());
CompilationResult* compilerRes = m_context->codeModel()->code();
std::shared_ptr<QContractDefinition> contractDef = compilerRes->sharedContract();
m_running = true;
emit runStarted();
emit stateChanged();
emit runStateChanged();
//run sequence
QtConcurrent::run([=]()
@ -149,60 +170,62 @@ void ClientModel::executeSequence(std::vector<TransactionSettings> const& _seque
try
{
bytes contractCode = compilerRes->bytes();
std::vector<dev::bytes> transactonData;
QFunctionDefinition* f = nullptr;
ContractCallDataEncoder c;
//encode data for all transactions
for (auto const& t: _sequence)
m_client->resetState(_balance);
onStateReset();
for (TransactionSettings const& transaction: _sequence)
{
f = nullptr;
for (int tf = 0; tf < contractDef->functionsList().size(); tf++)
ContractCallDataEncoder encoder;
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);
break;
QVariableDeclaration* var = f->parametersList().at(p);
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);
for (int p = 0; p < f->parametersList().size(); p++)
{
QVariableDeclaration* var = (QVariableDeclaration*)f->parametersList().at(p);
u256 value = 0;
auto v = t.parameterValues.find(var->name());
if (v != t.parameterValues.cend())
value = v->second;
c.encode(var, value);
if (transaction.functionId.isEmpty())
{
Address newAddress = deployContract(contractCode, transaction);
if (newAddress != m_contractAddress)
{
m_contractAddress = newAddress;
contractAddressChanged();
}
}
else
callContract(m_contractAddress, encoder.encodedData(), transaction);
}
transactonData.emplace_back(c.encodedData());
}
//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);
onNewTransaction();
}
//collect states for last transaction
AssemblyDebuggerData code = DebuggingStateWrapper::getHumanReadableCode(debuggingContent.executionCode);
emit dataAvailable(returnParameters, wStates, code);
m_running = false;
emit runComplete();
}
catch(boost::exception const&)
@ -215,51 +238,133 @@ void ClientModel::executeSequence(std::vector<TransactionSettings> const& _seque
emit runFailed(e.what());
}
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));
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(_returnParam)));
//we need to wrap states in a QObject before sending to QML.
QList<QObject*> wStates;
for (unsigned i = 0; i < _t.machineStates.size(); i++)
{
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();
}
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)
{
//TODO: change that to a signal
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;
if (!_ctrTransaction.isEmpty())
newAddress = m_client->transact(m_client->userAccount().secret(), _ctrTransaction.value, _code, _ctrTransaction.gas, _ctrTransaction.gasPrice);
m_client->transact(m_client->userAccount().secret(), _tr.value, _contract, _data, _tr.gas, _tr.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
{
u256 gasPrice = 10000000000000;
u256 gas = 125000;
u256 amount = 100;
newAddress = m_client->transact(m_client->userAccount().secret(), amount, _code, gas, gasPrice);
//call
if (tr.transactionData.size() >= 4)
{
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();
ExecutionResult r = m_client->lastExecutionResult();
if (newAddress != lastAddress)
contractAddressChanged();
return r;
}
if (m_contractAddress != 0 && (tr.address == m_contractAddress || tr.contractAddress == m_contractAddress))
{
auto compilerRes = m_context->codeModel()->code();
QContractDefinition* def = compilerRes->contract();
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)
{
m_client->transact(m_client->userAccount().secret(), _tr.value, _contract, _data, _tr.gas, _tr.gasPrice);
ExecutionResult r = m_client->lastExecutionResult();
r.contractAddress = _contract;
return r;
TransactionLogEntry* log = new TransactionLogEntry(block, index, contract, function, value, address, returned);
QQmlEngine::setObjectOwnership(log, QQmlEngine::JavaScriptOwnership);
emit newTransaction(log);
}
}

79
mix/ClientModel.h

@ -24,6 +24,7 @@
#pragma once
#include <atomic>
#include <map>
#include "DebuggingStateWrapper.h"
#include "MixClient.h"
@ -40,6 +41,7 @@ namespace mix
class AppContext;
class Web3Server;
class RpcConnector;
class QEther;
/// Backend transaction config class
struct TransactionSettings
@ -47,6 +49,10 @@ struct TransactionSettings
TransactionSettings() {}
TransactionSettings(QString const& _functionId, u256 _value, u256 _gas, u256 _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
QString functionId;
@ -58,12 +64,45 @@ struct TransactionSettings
u256 gasPrice;
/// Mapping from contract function parameter name to value
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:
/// @returns true if the functionId has not be set
bool isEmpty() const { return functionId.isNull() || functionId.isEmpty(); }
};
TransactionLogEntry():
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
@ -76,7 +115,7 @@ public:
ClientModel(AppContext* _context);
~ClientModel();
/// @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
Q_PROPERTY(QString contractAddress READ contractAddress NOTIFY contractAddressChanged)
/// ethereum.js RPC request entry point
@ -84,16 +123,21 @@ public:
/// @returns RPC response in Json format
Q_INVOKABLE QString apiCall(QString const& _message);
/// Simulate mining. Creates a new block
Q_INVOKABLE void mine();
public slots:
/// Run the contract constructor and show debugger window.
void debugDeployment();
/// Setup state, run transaction sequence, show debugger for the last transaction
/// @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:
/// 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.
void showDebugError(QString const& _error);
@ -108,27 +152,36 @@ signals:
/// Contract address changed
void contractAddressChanged();
/// Execution state changed
void stateChanged();
void newBlock();
/// Execution state changed
void runStateChanged();
/// Show debugger window request
void showDebuggerWindow();
/// ethereum.js RPC response ready
/// @param _message RPC response in Json format
void apiResponse(QString const& _message);
/// Emited when machine states are available.
void dataAvailable(QList<QVariableDefinition*> const& _returnParams = QList<QVariableDefinition*>(), QList<QObject*> const& _wStates = QList<QObject*>(), AssemblyDebuggerData const& _code = AssemblyDebuggerData());
/// New transaction log entry
void newTransaction(TransactionLogEntry* _tr);
/// State (transaction log) cleared
void stateCleared();
private:
QString contractAddress() const;
void executeSequence(std::vector<TransactionSettings> const& _sequence, u256 _balance, TransactionSettings const& _ctrTransaction = TransactionSettings());
ExecutionResult deployContract(bytes const& _code, TransactionSettings const& _tr = TransactionSettings());
ExecutionResult callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr);
void executeSequence(std::vector<TransactionSettings> const& _sequence, u256 _balance);
dev::Address deployContract(bytes const& _code, TransactionSettings const& _tr = TransactionSettings());
void callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr);
void onNewTransaction();
void onStateReset();
void showDebuggerForTransaction(ExecutionResult const& _t);
AppContext* m_context;
std::atomic<bool> m_running;
std::unique_ptr<MixClient> m_client;
std::unique_ptr<RpcConnector> m_rpcConnector;
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)
{
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)

21
mix/CodeModel.cpp

@ -32,6 +32,7 @@
#include "QFunctionDefinition.h"
#include "QVariableDeclaration.h"
#include "CodeHighlighter.h"
#include "FileIo.h"
#include "CodeModel.h"
using namespace dev::mix;
@ -188,3 +189,23 @@ void CodeModel::updateFormatting(QTextDocument* _document)
{
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 <atomic>
#include <map>
#include <QObject>
#include <QThread>
#include <libdevcore/Common.h>
@ -131,6 +132,8 @@ public:
bool hasContract() const;
/// Apply text document formatting. @todo Move this to editor module
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:
/// Emited on compilation state change
@ -163,6 +166,7 @@ private:
QThread m_backgroundThread;
BackgroundWorker m_backgroundWorker;
int m_backgroundJobId = 0; //protects from starting obsolete compilation job
std::map<QString, dev::bytes> m_compiledContracts; //by name
friend class BackgroundWorker;
};

5
mix/Exceptions.h

@ -35,9 +35,14 @@ namespace mix
struct QmlLoadException: 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 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
{
return this->errorString();
return QTcpServer::errorString();
}
void HttpServer::setListen(bool _listen)

4
mix/HttpServer.h

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

224
mix/MixClient.cpp

@ -29,14 +29,17 @@
#include <libethereum/ExtVM.h>
#include <libevm/VM.h>
#include "Exceptions.h"
#include "MixClient.h"
using namespace dev;
using namespace dev::eth;
using namespace dev::mix;
const Secret c_stdSecret = Secret("cb73d9408c4720e230387d956eb0f829d8a4dd2c1055f96257167e14e7169074");
MixClient::MixClient():
m_userAccount(KeyPair::create())
m_userAccount(c_stdSecret)
{
resetState(10000000 * ether);
}
@ -44,14 +47,22 @@ MixClient::MixClient():
void MixClient::resetState(u256 _balance)
{
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);
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);
execution.setup(_rlp);
execution.setup(&rlp);
bytes code;
bytesConstRef data;
bool firstIteration = true;
@ -86,25 +97,73 @@ void MixClient::executeTransaction(bytesConstRef _rlp, State& _state)
d.machineStates = machineStates;
d.executionCode = code;
d.executionData = data;
d.contentAvailable = true;
d.message = "ok";
d.contractAddress = m_lastExecutionResult.contractAddress;
m_lastExecutionResult = d;
d.transactionData = _t.data();
d.address = _t.receiveAddress();
d.sender = _t.sender();
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
{
//TODO: throw exception here if _block != 0
(void)_block;
if (_block != -1 && _block != 0 && (unsigned)_block >= m_blocks.size() - 1)
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)
{
WriteGuard l(x_state);
u256 n = m_state.transactionsFrom(toAddress(_secret));
Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret);
bytes rlp = t.rlp();
executeTransaction(&rlp, m_state);
executeTransaction(t, m_state);
}
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);
u256 n = m_state.transactionsFrom(toAddress(_secret));
eth::Transaction t(_endowment, _gasPrice, _gas, _init, n, _secret);
bytes rlp = t.rlp();
executeTransaction(&rlp, m_state);
executeTransaction(t, m_state);
Address address = right160(sha3(rlpList(t.sender(), t.nonce())));
m_lastExecutionResult.contractAddress = address;
return address;
}
void MixClient::inject(bytesConstRef _rlp)
{
WriteGuard l(x_state);
executeTransaction(_rlp, m_state);
eth::Transaction t(_rlp, CheckSignature::None);
executeTransaction(t, m_state);
}
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);
bytes rlp = t.rlp();
WriteGuard lw(x_state); //TODO: lock is required only for last executoin state
executeTransaction(&rlp, temp);
return m_lastExecutionResult.returnValue;
WriteGuard lw(x_state); //TODO: lock is required only for last execution state
executeTransaction(t, temp);
return m_blocks.back().transactions.back().returnValue;
}
u256 MixClient::balanceAt(Address _a, int _block) const
{
validateBlock(_block);
ReadGuard l(x_state);
return m_state.balance(_a);
return asOf(_block).balance(_a);
}
u256 MixClient::countAt(Address _a, int _block) const
{
validateBlock(_block);
ReadGuard l(x_state);
return m_state.transactionsFrom(_a);
return asOf(_block).transactionsFrom(_a);
}
u256 MixClient::stateAt(Address _a, u256 _l, int _block) const
{
validateBlock(_block);
ReadGuard l(x_state);
return m_state.storage(_a, _l);
return asOf(_block).storage(_a, _l);
}
bytes MixClient::codeAt(Address _a, int _block) const
{
validateBlock(_block);
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
{
validateBlock(_block);
ReadGuard l(x_state);
return m_state.storage(_a);
return asOf(_block).storage(_a);
}
eth::LocalisedLogEntries MixClient::logs(unsigned _watchId) const
{
(void)_watchId;
return LocalisedLogEntries();
Guard l(m_filterLock);
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;
return LocalisedLogEntries();
LocalisedLogEntries ret;
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;
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::installWatch"));
h256 h = _f.sha3();
{
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;
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::installWatch"));
Guard l(m_filterLock);
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;
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::uninstallWatch"));
for (auto& i: m_watches)
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;
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::peekWatch"));
Guard l(m_filterLock);
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;
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::checkWatch"));
Guard l(m_filterLock);
LocalisedLogEntries ret;
if (_watchId < m_watches.size())
std::swap(ret, m_watches.at(_watchId).changes);
return ret;
}
h256 MixClient::hashFromNumber(unsigned _number) const
{
(void)_number;
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::hashFromNumber"));
validateBlock(_number);
return m_blocks[_number].hash;
}
eth::BlockInfo MixClient::blockInfo(h256 _hash) const
@ -256,7 +376,7 @@ eth::BlockInfo MixClient::uncle(h256 _blockHash, unsigned _i) const
unsigned MixClient::number() const
{
return 0;
return m_blocks.size() - 1;
}
eth::Transactions MixClient::pending() const

41
mix/MixClient.h

@ -23,7 +23,9 @@
#pragma once
#include <vector>
#include <libethereum/Interface.h>
#include <libethereum/Client.h>
namespace dev
{
@ -53,24 +55,42 @@ struct MachineState
*/
struct ExecutionResult
{
ExecutionResult(): receipt(dev::h256(), dev::h256(), dev::eth::LogEntries()) {}
std::vector<MachineState> machineStates;
bytes transactionData;
bytes executionCode;
bytesConstRef executionData;
Address contractAddress;
bool contentAvailable;
std::string message;
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
{
public:
MixClient();
/// Reset state to the empty state with given balance.
void resetState(u256 _balance);
const KeyPair& userAccount() const { return m_userAccount; }
const ExecutionResult lastExecutionResult() const { ReadGuard l(x_state); return m_lastExecutionResult; }
const Address lastContractAddress() const { ReadGuard l(x_state); return m_lastExecutionResult.contractAddress; }
KeyPair const& userAccount() const { return m_userAccount; }
void mine();
Blocks const& record() const { return m_blocks; }
//dev::eth::Interface
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;
private:
void executeTransaction(bytesConstRef _rlp, eth::State& _state);
void executeTransaction(dev::eth::Transaction const& _t, eth::State& _state);
void validateBlock(int _block) const;
void noteChanged(h256Set const& _filters);
dev::eth::State const& asOf(int _block) const;
KeyPair m_userAccount;
eth::State m_state;
OverlayDB m_stateDB;
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())
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.
QFunctionDefinition* constructor() const { return m_constructor; }
QList<QFunctionDefinition*> const& functionsList() const { return m_functions; }
/// Find function by hash, returns nullptr if not found
QFunctionDefinition* getFunction(dev::FixedHash<4> _hash);
private:
QList<QFunctionDefinition*> m_functions;
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);
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 <string>
#include <QObject>
#include <libweb3jsonrpc/WebThreeStubServerBase.h>
namespace dev
@ -32,11 +33,20 @@ namespace dev
namespace mix
{
class Web3Server: public dev::WebThreeStubServerBase, public dev::WebThreeStubDatabaseFace
class Web3Server: public QObject, public dev::WebThreeStubServerBase, public dev::WebThreeStubDatabaseFace
{
Q_OBJECT
public:
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:
dev::eth::Interface* client() override { return m_client; }
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
Layout.fillWidth: true
Layout.fillHeight: true
TransactionLog {
Layout.fillWidth: true
height: 250
}
RowLayout {
// step button + slider
id: buttonRow

115
mix/qml/Ether.qml

@ -10,7 +10,7 @@ import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import QtQuick.Controls.Styles 1.1
Rectangle {
RowLayout {
id: etherEdition
property bool displayFormattedValue;
property bool edit;
@ -32,84 +32,59 @@ Rectangle {
units.currentIndex = unit;
}
RowLayout
TextField
{
anchors.fill: parent;
id: row
width: 200
height: parent.height
Rectangle
implicitWidth: 200
onTextChanged:
{
width : 200
color: edit ? "blue" : "white"
TextField
if (value !== undefined)
{
onTextChanged:
{
if (value !== undefined)
{
value.setValue(text)
formattedValue.text = value.format();
}
}
width: parent.width
readOnly: !edit
visible: edit
id: etherValueEdit;
value.setValue(text)
formattedValue.text = value.format();
}
}
readOnly: !edit
visible: edit
id: etherValueEdit;
}
Rectangle
ComboBox
{
id: units
onCurrentTextChanged:
{
Layout.fillWidth: true
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
if (value !== undefined)
{
anchors.verticalCenter: parent.verticalCenter
anchors.left: units.right
visible: displayFormattedValue
width: 20
Text
{
id: formattedValue
}
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"; }
}
}
Text
{
visible: displayFormattedValue
id: formattedValue
}
}

35
mix/qml/MainContent.qml

@ -35,40 +35,10 @@ Rectangle {
function startQuickDebugging()
{
var item = TransactionHelper.defaultTransaction();
item.executeConstructor = true;
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);
ensureRightView();
projectModel.stateListModel.debugDefaultState();
}
TransactionDialog {
id: transactionDialog
onAccepted: {
ensureRightView();
var item = transactionDialog.getItem();
item.executeConstructor = true;
startF5Debugging(item);
}
useTransactionDefaultValue: true
}
function toggleRightView() {
if (!rightView.visible)
rightView.show();
@ -99,7 +69,6 @@ Rectangle {
codeWebSplitter.orientation = (codeWebSplitter.orientation === Qt.Vertical ? Qt.Horizontal : Qt.Vertical);
}
CodeEditorExtensionManager {
headerView: headerPaneTabs;
rightView: rightPaneTabs;

6
mix/qml/ProjectModel.qml

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

13
mix/qml/StateDialog.qml

@ -17,11 +17,12 @@ Window {
property alias stateTitle: titleField.text
property alias stateBalance: balanceField.value
property alias isDefault: defaultCheckBox.checked
property int stateIndex
property var stateTransactions: []
signal accepted
function open(index, item) {
function open(index, item, setDefault) {
stateIndex = index;
stateTitle = item.title;
balanceField.value = item.balance;
@ -32,6 +33,7 @@ Window {
transactionsModel.append(item.transactions[t]);
stateTransactions.push(item.transactions[t]);
}
isDefault = setDefault;
visible = true;
titleField.focus = true;
}
@ -77,6 +79,14 @@ Window {
Layout.fillWidth: true
}
Label {
text: qsTr("Default")
}
CheckBox {
id: defaultCheckBox
Layout.fillWidth: true
}
Label {
text: qsTr("Transactions")
}
@ -152,6 +162,7 @@ Window {
}
ToolButton {
text: qsTr("Edit");
visible: !stdContract
Layout.fillHeight: true
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.Dialogs 1.1
import QtQuick.Layouts 1.1
import org.ethereum.qml.QEther 1.0
import "js/QEtherHelper.js" as QEtherHelper
Rectangle {
color: "#ededed"
@ -14,32 +12,13 @@ Rectangle {
anchors.left: parent.left
height: parent.height
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 {
id: list
anchors.top: parent.top
height: parent.height
width: parent.width
model: stateListModel
model: projectModel.stateListModel
delegate: renderDelegate
}
@ -48,65 +27,6 @@ Rectangle {
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 {
id: renderDelegate
Item {
@ -125,20 +45,17 @@ Rectangle {
ToolButton {
text: qsTr("Edit");
Layout.fillHeight: true
onClicked: stateListModel.editState(index);
onClicked: list.model.editState(index);
}
ToolButton {
text: qsTr("Delete");
Layout.fillHeight: true
onClicked: stateListModel.deleteState(index);
onClicked: list.model.deleteState(index);
}
ToolButton {
text: qsTr("Run");
Layout.fillHeight: true
onClicked:
{
stateListModel.runState(index)
}
onClicked: list.model.runState(index);
}
}
}
@ -149,7 +66,7 @@ Rectangle {
text: "&Add State"
shortcut: "Ctrl+T"
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;
}
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"
anchors.fill: parent
Rectangle {

8
mix/qml/TransactionDialog.qml

@ -49,7 +49,7 @@ Window {
}
if (functionIndex == -1 && functionsModel.count > 0)
functionIndex = 0; //@todo suggest unused funtion
functionIndex = 0; //@todo suggest unused function
functionComboBox.currentIndex = functionIndex;
paramsModel.clear();
@ -223,17 +223,17 @@ Window {
TableViewColumn {
role: "name"
title: "Name"
title: qsTr("Name")
width: 120
}
TableViewColumn {
role: "type"
title: "Type"
title: qsTr("Type")
width: 120
}
TableViewColumn {
role: "value"
title: "Value"
title: qsTr("Value")
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 {
target: clientModel
onContractAddressChanged: reload();
onRunComplete: reload();
}
Connections {
@ -111,9 +112,14 @@ Item {
accept: true
port: 8893
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);
console.log(response);
if (log)
console.log(response);
_request.setResponse(response);
}
}

2
mix/qml/html/WebContainer.html

@ -38,5 +38,5 @@ body {
</style>
</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>

5
mix/qml/js/ProjectModel.js

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

25
mix/qml/main.qml

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

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

@ -10,6 +10,7 @@
<file>qml/ProjectList.qml</file>
<file>qml/StateDialog.qml</file>
<file>qml/StateList.qml</file>
<file>qml/StateListModel.qml</file>
<file>qml/img/jumpintoback.png</file>
<file>qml/img/jumpintoforward.png</file>
<file>qml/img/jumpoutback.png</file>
@ -44,5 +45,10 @@
<file>qml/js/QEtherHelper.js</file>
<file>qml/js/TransactionHelper.js</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>
</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