diff --git a/mix/AppContext.cpp b/mix/AppContext.cpp index 551ce1c72..cae6dde57 100644 --- a/mix/AppContext.cpp +++ b/mix/AppContext.cpp @@ -23,6 +23,7 @@ */ #include +#include #include #include #include @@ -94,3 +95,9 @@ void AppContext::displayMessageDialog(QString _title, QString _message) dialogWin->findChild("messageContent", Qt::FindChildrenRecursively)->setProperty("text", _message); QMetaObject::invokeMethod(dialogWin, "open"); } + +void AppContext::toClipboard(QString _text) +{ + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(_text); +} diff --git a/mix/AppContext.h b/mix/AppContext.h index f304e1f73..31ecd3198 100644 --- a/mix/AppContext.h +++ b/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 diff --git a/mix/CMakeLists.txt b/mix/CMakeLists.txt index 02449ff61..f1b837f01 100644 --- a/mix/CMakeLists.txt +++ b/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}) diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index a8bf34f26..251447c79 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -27,6 +27,7 @@ #include #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*"); qRegisterMetaType("QEther*"); @@ -71,11 +73,13 @@ ClientModel::ClientModel(AppContext* _context): qRegisterMetaType>("QList"); qRegisterMetaType("QVariableDeclaration*"); qRegisterMetaType("AssemblyDebuggerData"); + qRegisterMetaType("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 { 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(), 10000000 * ether); } -void ClientModel::debugState(QVariantMap _state) +void ClientModel::setupState(QVariantMap _state) { u256 balance = (qvariant_cast(_state.value("balance")))->toU256Wei(); QVariantList transactions = _state.value("transactions").toList(); std::vector 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(transaction.value("gas")))->toU256Wei(); u256 value = (qvariant_cast(transaction.value("value")))->toU256Wei(); u256 gasPrice = (qvariant_cast(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(p.value()); - transactionSettings.parameterValues.insert(std::make_pair(p.key(), boost::get(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(p.value()); + transactionSettings.parameterValues.insert(std::make_pair(p.key(), boost::get(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 const& _sequence, u256 _balance, TransactionSettings const& ctrTransaction) +void ClientModel::executeSequence(std::vector 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 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 const& _seque try { bytes contractCode = compilerRes->bytes(); - std::vector 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 returnParameters; - - if (f) - returnParameters = c.decode(f->returnParameters(), debuggingContent.returnValue); - - //we need to wrap states in a QObject before sending to QML. - QList wStates; - for (unsigned i = 0; i < debuggingContent.machineStates.size(); i++) - { - QPointer 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 const& _seque emit runFailed(e.what()); } m_running = false; - emit stateChanged(); + emit runStateChanged(); }); } -void ClientModel::showDebugger(QList const& _returnParam, QList 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 wStates; + for (unsigned i = 0; i < _t.machineStates.size(); i++) + { + QPointer s(new DebuggingStateWrapper(_t.executionCode, _t.executionData.toBytes())); + s->setState(_t.machineStates[i]); + wStates.append(s); + } + + QList 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(""); } - 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); } } diff --git a/mix/ClientModel.h b/mix/ClientModel.h index b47a73843..016673ba6 100644 --- a/mix/ClientModel.h +++ b/mix/ClientModel.h @@ -24,6 +24,7 @@ #pragma once #include +#include #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 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 const& _returnParams = QList(), QList const& _wStates = QList(), 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 const& _returnParams = QList(), QList const& _wStates = QList(), 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 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 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 m_running; std::unique_ptr m_client; std::unique_ptr m_rpcConnector; std::unique_ptr m_web3Server; + Address m_contractAddress; + std::map m_stdContractAddresses; + std::map m_stdContractNames; }; } diff --git a/mix/CodeHighlighter.cpp b/mix/CodeHighlighter.cpp index ab8a61ff5..49d01b418 100644 --- a/mix/CodeHighlighter.cpp +++ b/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(_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) diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index cb73efc9d..39edd6d6f 100644 --- a/mix/CodeModel.cpp +++ b/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); +} + diff --git a/mix/CodeModel.h b/mix/CodeModel.h index 365d2d580..5f2add874 100644 --- a/mix/CodeModel.h +++ b/mix/CodeModel.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -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 m_compiledContracts; //by name friend class BackgroundWorker; }; diff --git a/mix/Exceptions.h b/mix/Exceptions.h index 2a3d813eb..ea4cb87b3 100644 --- a/mix/Exceptions.h +++ b/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 QmlErrorInfo; typedef boost::error_info FileError; +typedef boost::error_info BlockIndex; +typedef boost::error_info FunctionName; } } diff --git a/mix/HttpServer.cpp b/mix/HttpServer.cpp index 688c4ac39..cfe5c37f4 100644 --- a/mix/HttpServer.cpp +++ b/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) diff --git a/mix/HttpServer.h b/mix/HttpServer.h index 5b13dd181..00d63a073 100644 --- a/mix/HttpServer.h +++ b/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) diff --git a/mix/MixClient.cpp b/mix/MixClient.cpp index 39e876dfc..fe79e78b6 100644 --- a/mix/MixClient.cpp +++ b/mix/MixClient.cpp @@ -29,14 +29,17 @@ #include #include +#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& 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 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(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 diff --git a/mix/MixClient.h b/mix/MixClient.h index f615d7afc..54dff0f3a 100644 --- a/mix/MixClient.h +++ b/mix/MixClient.h @@ -23,7 +23,9 @@ #pragma once +#include #include +#include namespace dev { @@ -53,24 +55,42 @@ struct MachineState */ struct ExecutionResult { + ExecutionResult(): receipt(dev::h256(), dev::h256(), dev::eth::LogEntries()) {} + std::vector 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; + +struct Block +{ + ExecutionResults transactions; + h256 hash; + dev::eth::State state; + dev::eth::BlockInfo info; +}; + +using Blocks = std::vector; + + 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 m_filters; + std::map m_watches; + Blocks m_blocks; }; } diff --git a/mix/QContractDefinition.cpp b/mix/QContractDefinition.cpp index c94de17c7..488e08ea3 100644 --- a/mix/QContractDefinition.cpp +++ b/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; +} diff --git a/mix/QContractDefinition.h b/mix/QContractDefinition.h index 890b8bc0f..22f913a70 100644 --- a/mix/QContractDefinition.h +++ b/mix/QContractDefinition.h @@ -46,6 +46,8 @@ public: /// Get the constructor of the contract. QFunctionDefinition* constructor() const { return m_constructor; } QList const& functionsList() const { return m_functions; } + /// Find function by hash, returns nullptr if not found + QFunctionDefinition* getFunction(dev::FixedHash<4> _hash); private: QList m_functions; QFunctionDefinition* m_constructor; diff --git a/mix/Web3Server.cpp b/mix/Web3Server.cpp index 469ca907d..4e04669a3 100644 --- a/mix/Web3Server.cpp +++ b/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; +} diff --git a/mix/Web3Server.h b/mix/Web3Server.h index c603b48a2..94db67124 100644 --- a/mix/Web3Server.h +++ b/mix/Web3Server.h @@ -24,6 +24,7 @@ #include #include +#include #include 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 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 face() override; diff --git a/mix/qml/ContractLibrary.qml b/mix/qml/ContractLibrary.qml new file mode 100644 index 000000000..557f1cc53 --- /dev/null +++ b/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 + } +} + diff --git a/mix/qml/Debugger.qml b/mix/qml/Debugger.qml index 3d0fba349..592e7eb26 100644 --- a/mix/qml/Debugger.qml +++ b/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 diff --git a/mix/qml/Ether.qml b/mix/qml/Ether.qml index 9eb9b63e0..a5fd5dba2 100644 --- a/mix/qml/Ether.qml +++ b/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 } } diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index aed1c94fe..1ed1c11d2 100644 --- a/mix/qml/MainContent.qml +++ b/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; diff --git a/mix/qml/ProjectModel.qml b/mix/qml/ProjectModel.qml index 079158963..14716483c 100644 --- a/mix/qml/ProjectModel.qml +++ b/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; diff --git a/mix/qml/StateDialog.qml b/mix/qml/StateDialog.qml index f4e061d76..c00e3226f 100644 --- a/mix/qml/StateDialog.qml +++ b/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) } diff --git a/mix/qml/StateList.qml b/mix/qml/StateList.qml index 007a45d6a..ad14cf30e 100644 --- a/mix/qml/StateList.qml +++ b/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(); } } diff --git a/mix/qml/StateListModel.qml b/mix/qml/StateListModel.qml new file mode 100644 index 000000000..c158112e1 --- /dev/null +++ b/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(); + } + } +} diff --git a/mix/qml/StatusPane.qml b/mix/qml/StatusPane.qml index 07da269ec..57ade7a3a 100644 --- a/mix/qml/StatusPane.qml +++ b/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 { diff --git a/mix/qml/TransactionDialog.qml b/mix/qml/TransactionDialog.qml index f18db08a5..5eb3fbb13 100644 --- a/mix/qml/TransactionDialog.qml +++ b/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 } diff --git a/mix/qml/TransactionLog.qml b/mix/qml/TransactionLog.qml new file mode 100644 index 000000000..6079366bb --- /dev/null +++ b/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); + } + } + +} diff --git a/mix/qml/WebPreview.qml b/mix/qml/WebPreview.qml index 7e5604356..bd48210ef 100644 --- a/mix/qml/WebPreview.qml +++ b/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); } } diff --git a/mix/qml/html/WebContainer.html b/mix/qml/html/WebContainer.html index b37e27afd..c8afdc261 100644 --- a/mix/qml/html/WebContainer.html +++ b/mix/qml/html/WebContainer.html @@ -38,5 +38,5 @@ body { - + diff --git a/mix/qml/js/ProjectModel.js b/mix/qml/js/ProjectModel.js index c044e4676..ed7ebd3ce 100644 --- a/mix/qml/js/ProjectModel.js +++ b/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, ""); 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); } diff --git a/mix/qml/main.qml b/mix/qml/main.qml index b7992f092..a0a4ba423 100644 --- a/mix/qml/main.qml +++ b/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" diff --git a/mix/qml.qrc b/mix/res.qrc similarity index 89% rename from mix/qml.qrc rename to mix/res.qrc index ea10185c6..f009ab923 100644 --- a/mix/qml.qrc +++ b/mix/res.qrc @@ -10,6 +10,7 @@ qml/ProjectList.qml qml/StateDialog.qml qml/StateList.qml + qml/StateListModel.qml qml/img/jumpintoback.png qml/img/jumpintoforward.png qml/img/jumpoutback.png @@ -44,5 +45,10 @@ qml/js/QEtherHelper.js qml/js/TransactionHelper.js qml/Splitter.qml + qml/ContractLibrary.qml + stdc/config.sol + stdc/namereg.sol + stdc/std.sol + qml/TransactionLog.qml diff --git a/mix/stdc/config.sol b/mix/stdc/config.sol new file mode 100644 index 000000000..f05412f25 --- /dev/null +++ b/mix/stdc/config.sol @@ -0,0 +1,45 @@ +//sol Config +// Simple global configuration registrar. +// @authors: +// Gav Wood +#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; }) + +*/ diff --git a/mix/stdc/namereg.sol b/mix/stdc/namereg.sol new file mode 100644 index 000000000..0886f5f71 --- /dev/null +++ b/mix/stdc/namereg.sol @@ -0,0 +1,74 @@ +//sol NameReg +// Simple global name registrar. +// @authors: +// kobigurk (from #ethereum-dev) +// Gav Wood + +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(); + +*/ diff --git a/mix/stdc/std.sol b/mix/stdc/std.sol new file mode 100644 index 000000000..97a74ac56 --- /dev/null +++ b/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 + + +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 + +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(); + +*/