Browse Source

allow more than one contract

cl-refactor
arkpar 10 years ago
parent
commit
7af5ac1252
  1. 2
      libsolidity/Compiler.h
  2. 2
      mix/CMakeLists.txt
  3. 67
      mix/ClientModel.cpp
  4. 21
      mix/ClientModel.h
  5. 213
      mix/CodeModel.cpp
  6. 76
      mix/CodeModel.h
  7. 2
      mix/QContractDefinition.cpp
  8. 2
      mix/QContractDefinition.h
  9. 11
      mix/StatusPane.cpp
  10. 4
      mix/StatusPane.h
  11. 2
      mix/qml/CodeEditorView.qml
  12. 2
      mix/qml/Debugger.qml
  13. 2
      mix/qml/MainContent.qml
  14. 21
      mix/qml/ProjectList.qml
  15. 2
      mix/qml/ProjectModel.qml
  16. 18
      mix/qml/StateListModel.qml
  17. 14
      mix/qml/StatusPane.qml
  18. 107
      mix/qml/TransactionDialog.qml
  19. 12
      mix/qml/WebPreview.qml
  20. 16
      mix/qml/html/WebContainer.html
  21. 77
      mix/qml/js/ProjectModel.js
  22. 1
      mix/qml/js/TransactionHelper.js
  23. 2
      mix/qml/main.qml

2
libsolidity/Compiler.h

@ -20,6 +20,8 @@
* Solidity AST to EVM bytecode compiler. * Solidity AST to EVM bytecode compiler.
*/ */
#pragma once
#include <ostream> #include <ostream>
#include <functional> #include <functional>
#include <libsolidity/ASTVisitor.h> #include <libsolidity/ASTVisitor.h>

2
mix/CMakeLists.txt

@ -71,4 +71,4 @@ eth_install_executable(${EXECUTABLE}
#add qml asnd stdc files to project tree in Qt creator #add qml asnd stdc files to project tree in Qt creator
file(GLOB_RECURSE QMLFILES "qml/*.*") file(GLOB_RECURSE QMLFILES "qml/*.*")
file(GLOB_RECURSE SOLFILES "stdc/*.*") file(GLOB_RECURSE SOLFILES "stdc/*.*")
add_custom_target(dummy SOURCES ${QMLFILES} ${SOLFILES}) add_custom_target(mix_qml SOURCES ${QMLFILES} ${SOLFILES})

67
mix/ClientModel.cpp

@ -65,7 +65,7 @@ private:
ClientModel::ClientModel(AppContext* _context): ClientModel::ClientModel(AppContext* _context):
m_context(_context), m_running(false), m_rpcConnector(new RpcConnector()), m_contractAddress(Address()) m_context(_context), m_running(false), m_rpcConnector(new RpcConnector())
{ {
qRegisterMetaType<QBigInt*>("QBigInt*"); qRegisterMetaType<QBigInt*>("QBigInt*");
qRegisterMetaType<QIntType*>("QIntType*"); qRegisterMetaType<QIntType*>("QIntType*");
@ -136,9 +136,12 @@ void ClientModel::mine()
}); });
} }
QString ClientModel::contractAddress() const QVariantMap ClientModel::contractAddresses() const
{ {
return QString::fromStdString(dev::toJS(m_contractAddress)); QVariantMap res;
for (auto const& c: m_contractAddresses)
res.insert(c.first, QString::fromStdString(dev::toJS(c.second)));
return res;
} }
void ClientModel::debugDeployment() void ClientModel::debugDeployment()
@ -155,8 +158,8 @@ void ClientModel::setupState(QVariantMap _state)
for (auto const& t: transactions) for (auto const& t: transactions)
{ {
QVariantMap transaction = t.toMap(); QVariantMap transaction = t.toMap();
QString contractId = transaction.value("contractId").toString();
QString functionId = transaction.value("functionId").toString(); QString functionId = transaction.value("functionId").toString();
u256 gas = boost::get<u256>(qvariant_cast<QBigInt*>(transaction.value("gas"))->internalValue()); u256 gas = boost::get<u256>(qvariant_cast<QBigInt*>(transaction.value("gas"))->internalValue());
u256 value = (qvariant_cast<QEther*>(transaction.value("value")))->toU256Wei(); u256 value = (qvariant_cast<QEther*>(transaction.value("value")))->toU256Wei();
u256 gasPrice = (qvariant_cast<QEther*>(transaction.value("gasPrice")))->toU256Wei(); u256 gasPrice = (qvariant_cast<QEther*>(transaction.value("gasPrice")))->toU256Wei();
@ -164,7 +167,9 @@ void ClientModel::setupState(QVariantMap _state)
bool isStdContract = (transaction.value("stdContract").toBool()); bool isStdContract = (transaction.value("stdContract").toBool());
if (isStdContract) if (isStdContract)
{ {
TransactionSettings transactionSettings(functionId, transaction.value("url").toString()); if (contractId.isEmpty()) //TODO: This is to support old project files, remove later
contractId = functionId;
TransactionSettings transactionSettings(contractId, transaction.value("url").toString());
transactionSettings.gasPrice = 10000000000000; transactionSettings.gasPrice = 10000000000000;
transactionSettings.gas = 125000; transactionSettings.gas = 125000;
transactionSettings.value = 0; transactionSettings.value = 0;
@ -172,8 +177,10 @@ void ClientModel::setupState(QVariantMap _state)
} }
else else
{ {
if (contractId.isEmpty() && m_context->codeModel()->hasContract()) //TODO: This is to support old project files, remove later
contractId = m_context->codeModel()->contracts().keys()[0];
QVariantList qParams = transaction.value("qType").toList(); QVariantList qParams = transaction.value("qType").toList();
TransactionSettings transactionSettings(functionId, value, gas, gasPrice); TransactionSettings transactionSettings(contractId, functionId, value, gas, gasPrice);
for (QVariant const& variant: qParams) for (QVariant const& variant: qParams)
{ {
@ -181,7 +188,7 @@ void ClientModel::setupState(QVariantMap _state)
transactionSettings.parameterValues.push_back(param); transactionSettings.parameterValues.push_back(param);
} }
if (transaction.value("executeConstructor").toBool()) if (contractId == functionId || functionId == "Constructor")
transactionSettings.functionId.clear(); transactionSettings.functionId.clear();
transactionSequence.push_back(transactionSettings); transactionSequence.push_back(transactionSettings);
@ -194,8 +201,6 @@ void ClientModel::executeSequence(std::vector<TransactionSettings> const& _seque
{ {
if (m_running) if (m_running)
BOOST_THROW_EXCEPTION(ExecutionStateException()); BOOST_THROW_EXCEPTION(ExecutionStateException());
CompilationResult* compilerRes = m_context->codeModel()->code();
std::shared_ptr<QContractDefinition> contractDef = compilerRes->sharedContract();
m_running = true; m_running = true;
emit runStarted(); emit runStarted();
@ -206,7 +211,6 @@ void ClientModel::executeSequence(std::vector<TransactionSettings> const& _seque
{ {
try try
{ {
bytes contractCode = compilerRes->bytes();
m_client->resetState(_balance); m_client->resetState(_balance);
onStateReset(); onStateReset();
for (TransactionSettings const& transaction: _sequence) for (TransactionSettings const& transaction: _sequence)
@ -216,14 +220,17 @@ void ClientModel::executeSequence(std::vector<TransactionSettings> const& _seque
if (!transaction.stdContractUrl.isEmpty()) if (!transaction.stdContractUrl.isEmpty())
{ {
//std contract //std contract
dev::bytes const& stdContractCode = m_context->codeModel()->getStdContractCode(transaction.functionId, transaction.stdContractUrl); dev::bytes const& stdContractCode = m_context->codeModel()->getStdContractCode(transaction.contractId, transaction.stdContractUrl);
Address address = deployContract(stdContractCode, transaction); Address address = deployContract(stdContractCode, transaction);
m_stdContractAddresses[transaction.functionId] = address; m_stdContractAddresses[transaction.contractId] = address;
m_stdContractNames[address] = transaction.functionId; m_stdContractNames[address] = transaction.contractId;
} }
else else
{ {
//encode data //encode data
CompiledContract const& compilerRes = m_context->codeModel()->contract(transaction.contractId);
bytes contractCode = compilerRes.bytes();
std::shared_ptr<QContractDefinition> contractDef = compilerRes.sharedContract();
f = nullptr; f = nullptr;
if (transaction.functionId.isEmpty()) if (transaction.functionId.isEmpty())
f = contractDef->constructor(); f = contractDef->constructor();
@ -240,24 +247,31 @@ void ClientModel::executeSequence(std::vector<TransactionSettings> const& _seque
encoder.encode(f); encoder.encode(f);
for (int p = 0; p < transaction.parameterValues.size(); p++) for (int p = 0; p < transaction.parameterValues.size(); p++)
{ {
if (f->parametersList().at(p)->type() != transaction.parameterValues.at(p)->declaration()->type()) if (f->parametersList().size() <= p || f->parametersList().at(p)->type() != transaction.parameterValues.at(p)->declaration()->type())
BOOST_THROW_EXCEPTION(ParameterChangedException() << FunctionName(f->parametersList().at(p)->type().toStdString())); BOOST_THROW_EXCEPTION(ParameterChangedException() << FunctionName(transaction.functionId.toStdString()));
encoder.push(transaction.parameterValues.at(p)->encodeValue()); encoder.push(transaction.parameterValues.at(p)->encodeValue());
} }
if (transaction.functionId.isEmpty()) if (transaction.functionId.isEmpty() || transaction.functionId == transaction.contractId)
{ {
bytes param = encoder.encodedData(); bytes param = encoder.encodedData();
contractCode.insert(contractCode.end(), param.begin(), param.end()); contractCode.insert(contractCode.end(), param.begin(), param.end());
Address newAddress = deployContract(contractCode, transaction); Address newAddress = deployContract(contractCode, transaction);
if (newAddress != m_contractAddress) auto contractAddressIter = m_contractAddresses.find(transaction.contractId);
if (contractAddressIter == m_contractAddresses.end() || newAddress != contractAddressIter->second)
{ {
m_contractAddress = newAddress; m_contractAddresses[transaction.contractId] = newAddress;
contractAddressChanged(); m_contractNames[newAddress] = transaction.contractId;
contractAddressesChanged();
} }
} }
else else
callContract(m_contractAddress, encoder.encodedData(), transaction); {
auto contractAddressIter = m_contractAddresses.find(transaction.contractId);
if (contractAddressIter == m_contractAddresses.end())
BOOST_THROW_EXCEPTION(dev::Exception() << dev::errinfo_comment("Contract not deployed: " + transaction.contractId.toStdString()));
callContract(contractAddressIter->second, encoder.encodedData(), transaction);
}
} }
onNewTransaction(); onNewTransaction();
} }
@ -338,7 +352,8 @@ void ClientModel::callContract(Address const& _contract, bytes const& _data, Tra
void ClientModel::onStateReset() void ClientModel::onStateReset()
{ {
m_contractAddress = dev::Address(); m_contractAddresses.clear();
m_contractNames.clear();
m_stdContractAddresses.clear(); m_stdContractAddresses.clear();
m_stdContractNames.clear(); m_stdContractNames.clear();
emit stateCleared(); emit stateCleared();
@ -389,14 +404,16 @@ void ClientModel::onNewTransaction()
if (creation) if (creation)
returned = QString::fromStdString(toJS(tr.contractAddress)); returned = QString::fromStdString(toJS(tr.contractAddress));
if (m_contractAddress != 0 && (tr.address == m_contractAddress || tr.contractAddress == m_contractAddress)) Address contractAddress = tr.address != 0 ? tr.address : tr.contractAddress;
auto contractAddressIter = m_contractNames.find(contractAddress);
if (contractAddressIter != m_contractNames.end())
{ {
auto compilerRes = m_context->codeModel()->code(); CompiledContract const& compilerRes = m_context->codeModel()->contract(contractAddressIter->second);
QContractDefinition* def = compilerRes->contract(); const QContractDefinition* def = compilerRes.contract();
contract = def->name(); contract = def->name();
if (abi) if (abi)
{ {
QFunctionDefinition* funcDef = def->getFunction(functionHash); QFunctionDefinition const* funcDef = def->getFunction(functionHash);
if (funcDef) if (funcDef)
{ {
function = funcDef->name(); function = funcDef->name();

21
mix/ClientModel.h

@ -46,13 +46,13 @@ class QVariableDefinition;
struct TransactionSettings struct TransactionSettings
{ {
TransactionSettings() {} TransactionSettings() {}
TransactionSettings(QString const& _functionId, u256 _value, u256 _gas, u256 _gasPrice): TransactionSettings(QString const& _contractId, QString const& _functionId, u256 _value, u256 _gas, u256 _gasPrice):
functionId(_functionId), value(_value), gas(_gas), gasPrice(_gasPrice) {} contractId(_contractId), 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): TransactionSettings(QString const& _stdContractName, QString const& _stdContractUrl):
functionId(_stdContractName), stdContractUrl(_stdContractUrl) {} contractId(_stdContractName), stdContractUrl(_stdContractUrl) {}
/// Contract name
QString contractId;
/// Contract function name /// Contract function name
QString functionId; QString functionId;
/// Transaction value /// Transaction value
@ -121,8 +121,8 @@ public:
Q_PROPERTY(bool running MEMBER m_running NOTIFY runStateChanged) Q_PROPERTY(bool running MEMBER m_running NOTIFY runStateChanged)
/// @returns true if currently mining /// @returns true if currently mining
Q_PROPERTY(bool mining MEMBER m_mining NOTIFY miningStateChanged) Q_PROPERTY(bool mining MEMBER m_mining NOTIFY miningStateChanged)
/// @returns address of the last executed contract /// @returns deployed contracts addresses
Q_PROPERTY(QString contractAddress READ contractAddress NOTIFY contractAddressChanged) Q_PROPERTY(QVariantMap contractAddresses READ contractAddresses NOTIFY contractAddressesChanged)
/// ethereum.js RPC request entry point /// ethereum.js RPC request entry point
/// @param _message RPC request in Json format /// @param _message RPC request in Json format
/// @returns RPC response in Json format /// @returns RPC response in Json format
@ -161,7 +161,7 @@ signals:
/// @param _message Error message /// @param _message Error message
void runFailed(QString const& _message); void runFailed(QString const& _message);
/// Contract address changed /// Contract address changed
void contractAddressChanged(); void contractAddressesChanged();
/// Execution state changed /// Execution state changed
void newBlock(); void newBlock();
/// Execution state changed /// Execution state changed
@ -177,7 +177,7 @@ signals:
void stateCleared(); void stateCleared();
private: private:
QString contractAddress() const; QVariantMap contractAddresses() const;
void executeSequence(std::vector<TransactionSettings> const& _sequence, u256 _balance); void executeSequence(std::vector<TransactionSettings> const& _sequence, u256 _balance);
dev::Address deployContract(bytes const& _code, TransactionSettings const& _tr = TransactionSettings()); dev::Address deployContract(bytes const& _code, TransactionSettings const& _tr = TransactionSettings());
void callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr); void callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr);
@ -191,7 +191,8 @@ private:
std::unique_ptr<MixClient> m_client; std::unique_ptr<MixClient> m_client;
std::unique_ptr<RpcConnector> m_rpcConnector; std::unique_ptr<RpcConnector> m_rpcConnector;
std::unique_ptr<Web3Server> m_web3Server; std::unique_ptr<Web3Server> m_web3Server;
Address m_contractAddress; std::map<QString, Address> m_contractAddresses;
std::map<Address, QString> m_contractNames;
std::map<QString, Address> m_stdContractAddresses; std::map<QString, Address> m_stdContractAddresses;
std::map<Address, QString> m_stdContractNames; std::map<Address, QString> m_stdContractNames;
}; };

213
mix/CodeModel.cpp

@ -21,6 +21,7 @@
*/ */
#include <sstream> #include <sstream>
#include <memory>
#include <QDebug> #include <QDebug>
#include <QApplication> #include <QApplication>
#include <QtQml> #include <QtQml>
@ -38,51 +39,31 @@
using namespace dev::mix; using namespace dev::mix;
void BackgroundWorker::queueCodeChange(int _jobId, QString const& _content) const std::set<std::string> c_predefinedContracts =
{ "Config", "Coin", "CoinReg", "coin", "service", "owned", "mortal", "NameReg", "named", "std", "configUser" };
void BackgroundWorker::queueCodeChange(int _jobId)
{ {
m_model->runCompilationJob(_jobId, _content); m_model->runCompilationJob(_jobId);
} }
CompilationResult::CompilationResult(): CompiledContract::CompiledContract(const dev::solidity::CompilerStack& _compiler, QString const& _contractName, QString const& _source):
QObject(nullptr),
m_successful(false),
m_codeHash(qHash(QString())),
m_contract(new QContractDefinition()),
m_contractInterface("[]"),
m_codeHighlighter(new CodeHighlighter())
{}
CompilationResult::CompilationResult(const dev::solidity::CompilerStack& _compiler):
QObject(nullptr), QObject(nullptr),
m_successful(true), m_sourceHash(qHash(_source))
m_codeHash(qHash(QString()))
{ {
if (!_compiler.getContractNames().empty()) auto const& contractDefinition = _compiler.getContractDefinition(_contractName.toStdString());
{ m_contract.reset(new QContractDefinition(&contractDefinition));
auto const& contractDefinition = _compiler.getContractDefinition(std::string()); QQmlEngine::setObjectOwnership(m_contract.get(), QQmlEngine::CppOwnership);
m_contract.reset(new QContractDefinition(&contractDefinition)); m_bytes = _compiler.getBytecode(_contractName.toStdString());
m_bytes = _compiler.getBytecode(); dev::solidity::InterfaceHandler interfaceHandler;
dev::solidity::InterfaceHandler interfaceHandler; m_contractInterface = QString::fromStdString(*interfaceHandler.getABIInterface(contractDefinition));
m_contractInterface = QString::fromStdString(*interfaceHandler.getABIInterface(contractDefinition)); if (m_contractInterface.isEmpty())
if (m_contractInterface.isEmpty()) m_contractInterface = "[]";
m_contractInterface = "[]"; if (contractDefinition.getLocation().sourceName.get())
} m_documentId = QString::fromStdString(*contractDefinition.getLocation().sourceName);
else
m_contract.reset(new QContractDefinition());
} }
CompilationResult::CompilationResult(CompilationResult const& _prev, QString const& _compilerMessage): QString CompiledContract::codeHex() const
QObject(nullptr),
m_successful(false),
m_codeHash(qHash(QString())),
m_contract(_prev.m_contract),
m_compilerMessage(_compilerMessage),
m_bytes(_prev.m_bytes),
m_contractInterface(_prev.m_contractInterface),
m_codeHighlighter(_prev.m_codeHighlighter)
{}
QString CompilationResult::codeHex() const
{ {
return QString::fromStdString(toJS(m_bytes)); return QString::fromStdString(toJS(m_bytes));
} }
@ -90,27 +71,26 @@ QString CompilationResult::codeHex() const
CodeModel::CodeModel(QObject* _parent): CodeModel::CodeModel(QObject* _parent):
QObject(_parent), QObject(_parent),
m_compiling(false), m_compiling(false),
m_result(new CompilationResult()),
m_codeHighlighterSettings(new CodeHighlighterSettings()), m_codeHighlighterSettings(new CodeHighlighterSettings()),
m_backgroundWorker(this), m_backgroundWorker(this),
m_backgroundJobId(0) m_backgroundJobId(0)
{ {
m_backgroundThread.start();
m_backgroundWorker.moveToThread(&m_backgroundThread); m_backgroundWorker.moveToThread(&m_backgroundThread);
connect(this, &CodeModel::scheduleCompilationJob, &m_backgroundWorker, &BackgroundWorker::queueCodeChange, Qt::QueuedConnection); connect(this, &CodeModel::scheduleCompilationJob, &m_backgroundWorker, &BackgroundWorker::queueCodeChange, Qt::QueuedConnection);
connect(this, &CodeModel::compilationCompleteInternal, this, &CodeModel::onCompilationComplete, Qt::QueuedConnection); qRegisterMetaType<CompiledContract*>("CompiledContract*");
qRegisterMetaType<CompilationResult*>("CompilationResult*");
qRegisterMetaType<QContractDefinition*>("QContractDefinition*"); qRegisterMetaType<QContractDefinition*>("QContractDefinition*");
qRegisterMetaType<QFunctionDefinition*>("QFunctionDefinition*"); qRegisterMetaType<QFunctionDefinition*>("QFunctionDefinition*");
qRegisterMetaType<QVariableDeclaration*>("QVariableDeclaration*"); qRegisterMetaType<QVariableDeclaration*>("QVariableDeclaration*");
qmlRegisterType<QFunctionDefinition>("org.ethereum.qml", 1, 0, "QFunctionDefinition"); qmlRegisterType<QFunctionDefinition>("org.ethereum.qml", 1, 0, "QFunctionDefinition");
qmlRegisterType<QVariableDeclaration>("org.ethereum.qml", 1, 0, "QVariableDeclaration"); qmlRegisterType<QVariableDeclaration>("org.ethereum.qml", 1, 0, "QVariableDeclaration");
m_backgroundThread.start();
} }
CodeModel::~CodeModel() CodeModel::~CodeModel()
{ {
stop(); stop();
disconnect(this); disconnect(this);
releaseContracts();
} }
void CodeModel::stop() void CodeModel::stop()
@ -120,80 +100,133 @@ void CodeModel::stop()
m_backgroundThread.wait(); m_backgroundThread.wait();
} }
void CodeModel::registerCodeChange(QString const& _code) void CodeModel::reset(QVariantMap const& _documents)
{ {
///@todo: cancel bg job
Guard l(x_contractMap);
releaseContracts();
Guard pl(x_pendingContracts);
m_pendingContracts.clear();
for (QVariantMap::const_iterator d = _documents.cbegin(); d != _documents.cend(); d++)
m_pendingContracts[d.key()] = d.value().toString();
// launch the background thread // launch the background thread
uint hash = qHash(_code);
if (m_result->m_codeHash == hash)
return;
m_backgroundJobId++;
m_compiling = true; m_compiling = true;
emit stateChanged(); emit stateChanged();
emit scheduleCompilationJob(m_backgroundJobId, _code); emit scheduleCompilationJob(++m_backgroundJobId);
} }
void CodeModel::runCompilationJob(int _jobId, QString const& _code) void CodeModel::registerCodeChange(QString const& _documentId, QString const& _code)
{ {
if (_jobId != m_backgroundJobId) {
return; //obsolete job Guard l(x_contractMap);
CompiledContract* contract = m_contractMap.value(_documentId);
if (contract != nullptr && contract->m_sourceHash == qHash(_code))
return;
solidity::CompilerStack cs(true); Guard pl(x_pendingContracts);
std::unique_ptr<CompilationResult> result; m_pendingContracts[_documentId] = _code;
}
// launch the background thread
m_compiling = true;
emit stateChanged();
emit scheduleCompilationJob(++m_backgroundJobId);
}
QVariantMap CodeModel::contracts() const
{
QVariantMap result;
Guard l(x_contractMap);
for (ContractMap::const_iterator c = m_contractMap.cbegin(); c != m_contractMap.cend(); c++)
result.insert(c.key(), QVariant::fromValue(c.value()));
return result;
}
std::string source = _code.toStdString(); CompiledContract* CodeModel::contractByDocumentId(QString _documentId) const
// run syntax highlighting first {
// @todo combine this with compilation step Guard l(x_contractMap);
auto codeHighlighter = std::make_shared<CodeHighlighter>(); for (ContractMap::const_iterator c = m_contractMap.cbegin(); c != m_contractMap.cend(); c++)
codeHighlighter->processSource(source); if (c.value()->m_documentId == _documentId)
return c.value();
return nullptr;
}
CompiledContract const& CodeModel::contract(QString _name) const
{
Guard l(x_contractMap);
CompiledContract* res = m_contractMap.value(_name);
if (res == nullptr)
BOOST_THROW_EXCEPTION(dev::Exception() << dev::errinfo_comment("Contract not found: " + _name.toStdString()));
return *res;
}
cs.addSource("configUser", R"(contract configUser{function configAddr()constant returns(address a){ return 0xf025d81196b72fba60a1d4dddad12eeb8360d828;}})"); void CodeModel::releaseContracts()
{
for (ContractMap::iterator c = m_contractMap.begin(); c != m_contractMap.end(); c++)
c.value()->deleteLater();
m_contractMap.clear();
}
void CodeModel::runCompilationJob(int _jobId)
{
if (_jobId != m_backgroundJobId)
return; //obsolete job
// run compilation ContractMap result;
solidity::CompilerStack cs(true);
try try
{ {
cs.addSource("", source); cs.addSource("configUser", R"(contract configUser{function configAddr()constant returns(address a){ return 0xf025d81196b72fba60a1d4dddad12eeb8360d828;}})");
{
Guard l(x_pendingContracts);
for (auto const& c: m_pendingContracts)
cs.addSource(c.first.toStdString(), c.second.toStdString());
}
cs.compile(false); cs.compile(false);
codeHighlighter->processAST(cs.getAST());
result.reset(new CompilationResult(cs)); {
qDebug() << QString(QApplication::tr("compilation succeeded")); Guard pl(x_pendingContracts);
Guard l(x_contractMap);
for (std::string n: cs.getContractNames())
{
if (c_predefinedContracts.count(n) != 0)
continue;
QString name = QString::fromStdString(n);
auto sourceIter = m_pendingContracts.find(name);
QString source = sourceIter != m_pendingContracts.end() ? sourceIter->second : QString();
CompiledContract* contract = new CompiledContract(cs, name, source);
QQmlEngine::setObjectOwnership(contract, QQmlEngine::CppOwnership);
result[name] = contract;
CompiledContract* prevContract = m_contractMap.value(name);
if (prevContract != nullptr && prevContract->contractInterface() != result[name]->contractInterface())
emit contractInterfaceChanged(name);
}
releaseContracts();
m_contractMap.swap(result);
emit codeChanged();
emit compilationComplete();
}
} }
catch (dev::Exception const& _exception) catch (dev::Exception const& _exception)
{ {
std::ostringstream error; std::ostringstream error;
solidity::SourceReferenceFormatter::printExceptionInformation(error, _exception, "Error", cs); solidity::SourceReferenceFormatter::printExceptionInformation(error, _exception, "Error", cs);
result.reset(new CompilationResult(*m_result, QString::fromStdString(error.str()))); solidity::Location const* location = boost::get_error_info<solidity::errinfo_sourceLocation>(_exception);
codeHighlighter->processError(_exception); QString message = QString::fromStdString(error.str());
qDebug() << QString(QApplication::tr("compilation failed:") + " " + result->compilerMessage()); CompiledContract* contract = nullptr;
if (location && location->sourceName.get() && (contract = contractByDocumentId(QString::fromStdString(*location->sourceName))))
message = message.replace(QString::fromStdString(*location->sourceName), contract->contract()->name()); //substitute the location to match our contract names
compilationError(message);
} }
result->m_codeHighlighter = codeHighlighter;
result->m_codeHash = qHash(_code);
emit compilationCompleteInternal(result.release());
}
void CodeModel::onCompilationComplete(CompilationResult* _newResult)
{
m_compiling = false; m_compiling = false;
bool contractChanged = m_result->contractInterface() != _newResult->contractInterface();
m_result.reset(_newResult);
emit compilationComplete();
emit stateChanged(); emit stateChanged();
if (m_result->successful())
{
emit codeChanged();
if (contractChanged)
emit contractInterfaceChanged();
}
} }
bool CodeModel::hasContract() const bool CodeModel::hasContract() const
{ {
return m_result->successful(); Guard l(x_contractMap);
} return m_contractMap.size() != 0;
void CodeModel::updateFormatting(QTextDocument* _document)
{
m_result->codeHighlighter()->updateFormatting(_document, *m_codeHighlighterSettings);
} }
dev::bytes const& CodeModel::getStdContractCode(const QString& _contractName, const QString& _url) dev::bytes const& CodeModel::getStdContractCode(const QString& _contractName, const QString& _url)

76
mix/CodeModel.h

@ -27,7 +27,9 @@
#include <map> #include <map>
#include <QObject> #include <QObject>
#include <QThread> #include <QThread>
#include <QHash>
#include <libdevcore/Common.h> #include <libdevcore/Common.h>
#include <libdevcore/Guards.h>
class QTextDocument; class QTextDocument;
@ -56,59 +58,50 @@ public:
BackgroundWorker(CodeModel* _model): QObject(), m_model(_model) {} BackgroundWorker(CodeModel* _model): QObject(), m_model(_model) {}
public slots: public slots:
void queueCodeChange(int _jobId, QString const& _content); void queueCodeChange(int _jobId);
private: private:
CodeModel* m_model; CodeModel* m_model;
}; };
///Compilation result model. Contains all the compiled contract data required by UI ///Compilation result model. Contains all the compiled contract data required by UI
class CompilationResult: public QObject class CompiledContract: public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QContractDefinition* contract READ contract) Q_PROPERTY(QContractDefinition* contract READ contract)
Q_PROPERTY(QString compilerMessage READ compilerMessage CONSTANT)
Q_PROPERTY(bool successful READ successful CONSTANT)
Q_PROPERTY(QString contractInterface READ contractInterface CONSTANT) Q_PROPERTY(QString contractInterface READ contractInterface CONSTANT)
Q_PROPERTY(QString codeHex READ codeHex CONSTANT) Q_PROPERTY(QString codeHex READ codeHex CONSTANT)
Q_PROPERTY(QString documentId MEMBER m_documentId CONSTANT)
public: public:
/// Empty compilation result constructor
CompilationResult();
/// Successful compilation result constructor /// Successful compilation result constructor
CompilationResult(solidity::CompilerStack const& _compiler); CompiledContract(solidity::CompilerStack const& _compiler, QString const& _contractName, QString const& _source);
/// Failed compilation result constructor
CompilationResult(CompilationResult const& _prev, QString const& _compilerMessage);
/// @returns contract definition for QML property /// @returns contract definition for QML property
QContractDefinition* contract() { return m_contract.get(); } QContractDefinition* contract() const { return m_contract.get(); }
/// @returns contract definition /// @returns contract definition
std::shared_ptr<QContractDefinition> sharedContract() { return m_contract; } std::shared_ptr<QContractDefinition> sharedContract() const { return m_contract; }
/// Indicates if the compilation was successful
bool successful() const { return m_successful; }
/// @returns compiler error message in case of unsuccessful compilation
QString compilerMessage() const { return m_compilerMessage; }
/// @returns contract bytecode /// @returns contract bytecode
dev::bytes const& bytes() const { return m_bytes; } dev::bytes const& bytes() const { return m_bytes; }
/// @returns contract bytecode as hex string /// @returns contract bytecode as hex string
QString codeHex() const; QString codeHex() const;
/// @returns contract definition in JSON format /// @returns contract definition in JSON format
QString contractInterface() const { return m_contractInterface; } QString contractInterface() const { return m_contractInterface; }
/// Get code highlighter
std::shared_ptr<CodeHighlighter> codeHighlighter() { return m_codeHighlighter; }
private: private:
bool m_successful; uint m_sourceHash;
uint m_codeHash;
std::shared_ptr<QContractDefinition> m_contract; std::shared_ptr<QContractDefinition> m_contract;
QString m_compilerMessage; ///< @todo: use some structure here QString m_compilerMessage; ///< @todo: use some structure here
dev::bytes m_bytes; dev::bytes m_bytes;
QString m_contractInterface; QString m_contractInterface;
std::shared_ptr<CodeHighlighter> m_codeHighlighter; QString m_documentId;
friend class CodeModel; friend class CodeModel;
}; };
/// Background code compiler
using ContractMap = QHash<QString, CompiledContract*>;
/// Code compilation model. Compiles contracts in background an provides compiled contract data
class CodeModel: public QObject class CodeModel: public QObject
{ {
Q_OBJECT Q_OBJECT
@ -117,56 +110,59 @@ public:
CodeModel(QObject* _parent); CodeModel(QObject* _parent);
~CodeModel(); ~CodeModel();
/// @returns latest compilation result Q_PROPERTY(QVariantMap contracts READ contracts NOTIFY codeChanged)
CompilationResult* code() { return m_result.get(); }
/// @returns latest compilation resul
CompilationResult const* code() const { return m_result.get(); }
Q_PROPERTY(CompilationResult* code READ code NOTIFY codeChanged)
Q_PROPERTY(bool compiling READ isCompiling NOTIFY stateChanged) Q_PROPERTY(bool compiling READ isCompiling NOTIFY stateChanged)
Q_PROPERTY(bool hasContract READ hasContract NOTIFY codeChanged) Q_PROPERTY(bool hasContract READ hasContract NOTIFY codeChanged)
/// @returns latest compilation results for contracts
QVariantMap contracts() const;
/// @returns compilation status /// @returns compilation status
bool isCompiling() const { return m_compiling; } bool isCompiling() const { return m_compiling; }
/// @returns true if contract has at least one function /// @returns true there is a contract which has at least one function
bool hasContract() const; 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 /// Get contract code by url. Contract is compiled on first access and cached
dev::bytes const& getStdContractCode(QString const& _contractName, QString const& _url); dev::bytes const& getStdContractCode(QString const& _contractName, QString const& _url);
/// Get contract by name
CompiledContract const& contract(QString _name) const;
/// Find a contract by document id
/// @returns CompiledContract object or null if not found
Q_INVOKABLE CompiledContract* contractByDocumentId(QString _documentId) const;
signals: signals:
/// Emited on compilation state change /// Emited on compilation state change
void stateChanged(); void stateChanged();
/// Emitted on compilation complete /// Emitted on compilation complete
void compilationComplete(); void compilationComplete();
/// Emitted on compilation error
void compilationError(QString _error);
/// Internal signal used to transfer compilation job to background thread /// Internal signal used to transfer compilation job to background thread
void scheduleCompilationJob(int _jobId, QString const& _content); void scheduleCompilationJob(int _jobId);
/// Emitted if there are any changes in the code model /// Emitted if there are any changes in the code model
void codeChanged(); void codeChanged();
/// Emitted if there are any changes in the contract interface /// Emitted if there are any changes in the contract interface
void contractInterfaceChanged(); void contractInterfaceChanged(QString _documentId);
/// Emitted on compilation complete. Internal
void compilationCompleteInternal(CompilationResult* _newResult);
private slots:
void onCompilationComplete(CompilationResult* _newResult);
public slots: public slots:
/// Update code model on source code change /// Update code model on source code change
void registerCodeChange(QString const& _code); void registerCodeChange(QString const& _documentId, QString const& _code);
/// Reset code model for a new project
void reset(QVariantMap const& _documents);
private: private:
void runCompilationJob(int _jobId, QString const& _content); void runCompilationJob(int _jobId);
void stop(); void stop();
void releaseContracts();
std::atomic<bool> m_compiling; std::atomic<bool> m_compiling;
std::unique_ptr<CompilationResult> m_result; mutable dev::Mutex x_contractMap;
ContractMap m_contractMap;
std::unique_ptr<CodeHighlighterSettings> m_codeHighlighterSettings; std::unique_ptr<CodeHighlighterSettings> m_codeHighlighterSettings;
QThread m_backgroundThread; QThread m_backgroundThread;
BackgroundWorker m_backgroundWorker; BackgroundWorker m_backgroundWorker;
int m_backgroundJobId = 0; //protects from starting obsolete compilation job int m_backgroundJobId = 0; //protects from starting obsolete compilation job
std::map<QString, dev::bytes> m_compiledContracts; //by name std::map<QString, dev::bytes> m_compiledContracts; //by name
dev::Mutex x_pendingContracts;
std::map<QString, QString> m_pendingContracts; //name to source
friend class BackgroundWorker; friend class BackgroundWorker;
}; };

2
mix/QContractDefinition.cpp

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

2
mix/QContractDefinition.h

@ -47,7 +47,7 @@ public:
QFunctionDefinition* constructor() const { return m_constructor; } QFunctionDefinition* constructor() const { return m_constructor; }
QList<QFunctionDefinition*> const& functionsList() const { return m_functions; } QList<QFunctionDefinition*> const& functionsList() const { return m_functions; }
/// Find function by hash, returns nullptr if not found /// Find function by hash, returns nullptr if not found
QFunctionDefinition* getFunction(dev::FixedHash<4> _hash); QFunctionDefinition const* getFunction(dev::FixedHash<4> _hash) const;
private: private:
QList<QFunctionDefinition*> m_functions; QList<QFunctionDefinition*> m_functions;
QFunctionDefinition* m_constructor; QFunctionDefinition* m_constructor;

11
mix/StatusPane.cpp

@ -36,7 +36,6 @@ using namespace dev::mix;
StatusPane::StatusPane(AppContext* _context): Extension(_context, ExtensionDisplayBehavior::HeaderView) StatusPane::StatusPane(AppContext* _context): Extension(_context, ExtensionDisplayBehavior::HeaderView)
{ {
connect(_context->codeModel(), &CodeModel::compilationComplete, this, &StatusPane::update);
_context->appEngine()->rootContext()->setContextProperty("statusPane", this); _context->appEngine()->rootContext()->setContextProperty("statusPane", this);
} }
@ -54,13 +53,3 @@ void StatusPane::start() const
{ {
} }
CompilationResult* StatusPane::result() const
{
return m_ctx->codeModel()->code();
}
void StatusPane::update()
{
QObject* ctrl = m_view->findChild<QObject*>("statusPane", Qt::FindChildrenRecursively);
QMetaObject::invokeMethod(ctrl, "updateStatus");
}

4
mix/StatusPane.h

@ -20,7 +20,6 @@
#pragma once #pragma once
#include "Extension.h" #include "Extension.h"
#include "CodeModel.h"
namespace dev namespace dev
{ {
@ -33,7 +32,6 @@ namespace mix
class StatusPane: public Extension class StatusPane: public Extension
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(CompilationResult* result READ result CONSTANT)
public: public:
StatusPane(AppContext* _appContext); StatusPane(AppContext* _appContext);
@ -41,10 +39,8 @@ public:
void start() const override; void start() const override;
QString title() const override; QString title() const override;
QString contentUrl() const override; QString contentUrl() const override;
CompilationResult* result() const;
public slots: public slots:
void update();
}; };
} }

2
mix/qml/CodeEditorView.qml

@ -43,7 +43,7 @@ Item {
editor.onEditorTextChanged.connect(function() { editor.onEditorTextChanged.connect(function() {
documentEdit(document.documentId); documentEdit(document.documentId);
if (document.isContract) if (document.isContract)
codeModel.registerCodeChange(editor.getText()); codeModel.registerCodeChange(document.documentId, editor.getText());
}); });
editor.setText(data, document.syntaxMode); editor.setText(data, document.syntaxMode);
} }

2
mix/qml/Debugger.qml

@ -25,7 +25,7 @@ Rectangle {
function update(data, giveFocus) function update(data, giveFocus)
{ {
if (statusPane && statusPane.result.successful) if (statusPane && codeModel.hasContract)
{ {
Debugger.init(data); Debugger.init(data);
debugScrollArea.visible = true; debugScrollArea.visible = true;

2
mix/qml/MainContent.qml

@ -34,7 +34,7 @@ Rectangle {
onCompilationComplete: { onCompilationComplete: {
if (firstCompile) { if (firstCompile) {
firstCompile = false; firstCompile = false;
if (codeModel.code.successful && runOnProjectLoad) if (runOnProjectLoad)
startQuickDebugging(); startQuickDebugging();
} }
} }

21
mix/qml/ProjectList.qml

@ -101,14 +101,19 @@ Item {
Connections { Connections {
target: codeModel target: codeModel
onCompilationComplete: { onCompilationComplete: {
if (modelData === "Contracts") if (modelData === "Contracts") {
{ var ci = 0;
var ctr = projectModel.listModel.get(0); for (var si = 0; si < projectModel.listModel.count; si++) {
if (codeModel.code.contract.name !== ctr.name) var document = projectModel.listModel.get(si);
{ if (document.isContract) {
ctr.name = codeModel.code.contract.name; var compiledDoc = codeModel.contractByDocumentId(document.documentId);
projectModel.listModel.set(0, ctr); if (compiledDoc && compiledDoc.documentId === document.documentId && compiledDoc.contract.name !== document.name) {
sectionModel.set(0, ctr); document.name = compiledDoc.contract.name;
projectModel.listModel.set(si, document);
sectionModel.set(ci, document);
}
ci++;
}
} }
} }
} }

2
mix/qml/ProjectModel.qml

@ -45,7 +45,7 @@ Item {
function newHtmlFile() { ProjectModelCode.newHtmlFile(); } function newHtmlFile() { ProjectModelCode.newHtmlFile(); }
function newJsFile() { ProjectModelCode.newJsFile(); } function newJsFile() { ProjectModelCode.newJsFile(); }
function newCssFile() { ProjectModelCode.newCssFile(); } function newCssFile() { ProjectModelCode.newCssFile(); }
//function newContract() { ProjectModelCode.newContract(); } function newContract() { ProjectModelCode.newContract(); }
function openDocument(documentId) { ProjectModelCode.openDocument(documentId); } function openDocument(documentId) { ProjectModelCode.openDocument(documentId); }
function openNextDocument() { ProjectModelCode.openNextDocument(); } function openNextDocument() { ProjectModelCode.openNextDocument(); }
function openPrevDocument() { ProjectModelCode.openPrevDocument(); } function openPrevDocument() { ProjectModelCode.openPrevDocument(); }

18
mix/qml/StateListModel.qml

@ -22,12 +22,12 @@ Item {
function fromPlainTransactionItem(t) { function fromPlainTransactionItem(t) {
var r = { var r = {
contractId: t.contractId,
functionId: t.functionId, functionId: t.functionId,
url: t.url, url: t.url,
value: QEtherHelper.createEther(t.value.value, t.value.unit), value: QEtherHelper.createEther(t.value.value, t.value.unit),
gas: QEtherHelper.createBigInt(t.gas.value), //t.gas,//QEtherHelper.createEther(t.gas.value, t.gas.unit), gas: QEtherHelper.createBigInt(t.gas.value), //t.gas,//QEtherHelper.createEther(t.gas.value, t.gas.unit),
gasPrice: QEtherHelper.createEther(t.gasPrice.value, t.gasPrice.unit), gasPrice: QEtherHelper.createEther(t.gasPrice.value, t.gasPrice.unit),
executeConstructor: t.executeConstructor,
stdContract: t.stdContract, stdContract: t.stdContract,
parameters: {} parameters: {}
}; };
@ -78,12 +78,12 @@ Item {
function toPlainTransactionItem(t) { function toPlainTransactionItem(t) {
var r = { var r = {
contractId: t.contractId,
functionId: t.functionId, functionId: t.functionId,
url: t.url, url: t.url,
value: { value: t.value.value, unit: t.value.unit }, value: { value: t.value.value, unit: t.value.unit },
gas: { value: t.gas.value() }, gas: { value: t.gas.value() },
gasPrice: { value: t.gasPrice.value, unit: t.gasPrice.unit }, gasPrice: { value: t.gasPrice.value, unit: t.gasPrice.unit },
executeConstructor: t.executeConstructor,
stdContract: t.stdContract, stdContract: t.stdContract,
parameters: {} parameters: {}
}; };
@ -159,7 +159,6 @@ Item {
value: QEtherHelper.createEther("100", QEther.Wei), value: QEtherHelper.createEther("100", QEther.Wei),
gas: QEtherHelper.createBigInt("125000"), gas: QEtherHelper.createBigInt("125000"),
gasPrice: QEtherHelper.createEther("10000000000000", QEther.Wei), gasPrice: QEtherHelper.createEther("10000000000000", QEther.Wei),
executeConstructor: false,
stdContract: false stdContract: false
}; };
} }
@ -178,16 +177,19 @@ Item {
var contractTransaction = defaultTransactionItem(); var contractTransaction = defaultTransactionItem();
var contractItem = contractLibrary.model.get(i); var contractItem = contractLibrary.model.get(i);
contractTransaction.url = contractItem.url; contractTransaction.url = contractItem.url;
contractTransaction.contractId = contractItem.name;
contractTransaction.functionId = contractItem.name; contractTransaction.functionId = contractItem.name;
contractTransaction.stdContract = true; contractTransaction.stdContract = true;
item.transactions.push(contractTransaction); item.transactions.push(contractTransaction);
}; };
//add constructor //add constructors, //TODO: order by dependencies
var ctorTr = defaultTransactionItem(); for(var c in codeModel.contracts) {
ctorTr.executeConstructor = true; var ctorTr = defaultTransactionItem();
ctorTr.functionId = qsTr("Constructor"); ctorTr.functionId = c;
item.transactions.push(ctorTr); ctorTr.contractId = c;
item.transactions.push(ctorTr);
}
return item; return item;
} }

14
mix/qml/StatusPane.qml

@ -8,9 +8,9 @@ Rectangle {
id: statusHeader id: statusHeader
objectName: "statusPane" objectName: "statusPane"
function updateStatus() function updateStatus(message)
{ {
if (statusPane.result.successful) if (!message)
{ {
status.state = ""; status.state = "";
status.text = qsTr("Compile without errors."); status.text = qsTr("Compile without errors.");
@ -20,12 +20,12 @@ Rectangle {
else else
{ {
status.state = "error"; status.state = "error";
var errorInfo = ErrorLocationFormater.extractErrorInfo(statusPane.result.compilerMessage, true); var errorInfo = ErrorLocationFormater.extractErrorInfo(message, true);
status.text = errorInfo.errorLocation + " " + errorInfo.errorDetail; status.text = errorInfo.errorLocation + " " + errorInfo.errorDetail;
logslink.visible = true; logslink.visible = true;
debugImg.state = ""; debugImg.state = "";
} }
debugRunActionIcon.enabled = statusPane.result.successful; debugRunActionIcon.enabled = codeModel.hasContract;
} }
function infoMessage(text) function infoMessage(text)
@ -35,7 +35,6 @@ Rectangle {
logslink.visible = false; logslink.visible = false;
} }
Connections { Connections {
target:clientModel target:clientModel
onRunStarted: infoMessage(qsTr("Running transactions...")); onRunStarted: infoMessage(qsTr("Running transactions..."));
@ -49,6 +48,11 @@ Rectangle {
onDeploymentError: infoMessage(error); onDeploymentError: infoMessage(error);
onDeploymentComplete: infoMessage(qsTr("Deployment complete")); onDeploymentComplete: infoMessage(qsTr("Deployment complete"));
} }
Connections {
target: codeModel
onCompilationComplete: updateStatus();
onCompilationError: updateStatus(_error);
}
color: "transparent" color: "transparent"
anchors.fill: parent anchors.fill: parent

107
mix/qml/TransactionDialog.qml

@ -20,9 +20,9 @@ Window {
property alias gas: gasValueEdit.gasValue; property alias gas: gasValueEdit.gasValue;
property alias gasPrice: gasPriceField.value; property alias gasPrice: gasPriceField.value;
property alias transactionValue: valueField.value; property alias transactionValue: valueField.value;
property string contractId: contractComboBox.currentValue();
property alias functionId: functionComboBox.currentText; property alias functionId: functionComboBox.currentText;
property var itemParams; property var itemParams;
property bool isConstructorTransaction;
property bool useTransactionDefaultValue: false property bool useTransactionDefaultValue: false
property var qType; property var qType;
@ -39,32 +39,47 @@ Window {
gasValueEdit.gasValue = item.gas; gasValueEdit.gasValue = item.gas;
gasPriceField.value = item.gasPrice; gasPriceField.value = item.gasPrice;
valueField.value = item.value; valueField.value = item.value;
var contractId = item.contractId;
var functionId = item.functionId; var functionId = item.functionId;
isConstructorTransaction = item.executeConstructor; rowFunction.visible = true;
rowFunction.visible = !item.executeConstructor;
itemParams = item.parameters !== undefined ? item.parameters : {}; itemParams = item.parameters !== undefined ? item.parameters : {};
functionsModel.clear();
contractsModel.clear();
var contractIndex = -1;
var contracts = codeModel.contracts;
for (var c in contracts) {
contractsModel.append({ cid: c, text: contracts[c].contract.name });
if (contracts[c].contract.name === contractId)
contractIndex = contractsModel.count - 1;
}
if (contractIndex == -1 && contractsModel.count > 0)
contractIndex = 0; //@todo suggest unused contract
contractComboBox.currentIndex = contractIndex;
loadFunctions(contractComboBox.currentValue());
var functionIndex = -1; var functionIndex = -1;
var functions = codeModel.code.contract.functions; for (var f = 0; f < functionsModel.count; f++)
for (var f = 0; f < functions.length; f++) { if (functionsModel.get(f).text === item.functionId)
functionsModel.append({ text: functions[f].name });
if (functions[f].name === item.functionId)
functionIndex = f; functionIndex = f;
}
if (functionIndex == -1 && functionsModel.count > 0) if (functionIndex == -1 && functionsModel.count > 0)
functionIndex = 0; //@todo suggest unused function functionIndex = 0; //@todo suggest unused function
functionComboBox.currentIndex = functionIndex; functionComboBox.currentIndex = functionIndex;
paramsModel.clear(); paramsModel.clear();
if (!item.executeConstructor) if (functionId !== contractComboBox.currentValue())
loadParameters(); loadParameters();
else else {
{ var contract = codeModel.contracts[contractId];
var parameters = codeModel.code.contract.constructor.parameters; if (contract) {
for (var p = 0; p < parameters.length; p++) var parameters = contract.contract.constructor.parameters;
loadParameter(parameters[p]); for (var p = 0; p < parameters.length; p++)
loadParameter(parameters[p]);
}
} }
modalTransactionDialog.setX((Screen.width - width) / 2); modalTransactionDialog.setX((Screen.width - width) / 2);
modalTransactionDialog.setY((Screen.height - height) / 2); modalTransactionDialog.setY((Screen.height - height) / 2);
@ -73,6 +88,21 @@ Window {
valueField.focus = true; valueField.focus = true;
} }
function loadFunctions(contractId)
{
functionsModel.clear();
var contract = codeModel.contracts[contractId];
if (contract) {
var functions = codeModel.contracts[contractId].contract.functions;
for (var f = 0; f < functions.length; f++) {
functionsModel.append({ text: functions[f].name });
}
}
//append constructor
functionsModel.append({ text: contractId });
}
function loadParameter(parameter) function loadParameter(parameter)
{ {
var type = parameter.type; var type = parameter.type;
@ -104,10 +134,15 @@ Window {
if (!paramsModel) if (!paramsModel)
return; return;
if (functionComboBox.currentIndex >= 0 && functionComboBox.currentIndex < functionsModel.count) { if (functionComboBox.currentIndex >= 0 && functionComboBox.currentIndex < functionsModel.count) {
var func = codeModel.code.contract.functions[functionComboBox.currentIndex]; var contract = codeModel.contracts[contractComboBox.currentValue()];
var parameters = func.parameters; if (contract) {
for (var p = 0; p < parameters.length; p++) var func = contract.contract.functions[functionComboBox.currentIndex];
loadParameter(parameters[p]); if (func) {
var parameters = func.parameters;
for (var p = 0; p < parameters.length; p++)
loadParameter(parameters[p]);
}
}
} }
} }
@ -140,24 +175,21 @@ Window {
if (!useTransactionDefaultValue) if (!useTransactionDefaultValue)
{ {
item = { item = {
contractId: transactionDialog.contractId,
functionId: transactionDialog.functionId, functionId: transactionDialog.functionId,
gas: transactionDialog.gas, gas: transactionDialog.gas,
gasPrice: transactionDialog.gasPrice, gasPrice: transactionDialog.gasPrice,
value: transactionDialog.transactionValue, value: transactionDialog.transactionValue,
parameters: {}, parameters: {},
executeConstructor: isConstructorTransaction
}; };
} }
else else
{ {
item = TransactionHelper.defaultTransaction(); item = TransactionHelper.defaultTransaction();
item.contractId = transactionDialog.contractId;
item.functionId = transactionDialog.functionId; item.functionId = transactionDialog.functionId;
item.executeConstructor = isConstructorTransaction;
} }
if (isConstructorTransaction)
item.functionId = qsTr("Constructor");
var orderedQType = []; var orderedQType = [];
for (var p = 0; p < transactionDialog.transactionParams.count; p++) { for (var p = 0; p < transactionDialog.transactionParams.count; p++) {
var parameter = transactionDialog.transactionParams.get(p); var parameter = transactionDialog.transactionParams.get(p);
@ -178,6 +210,33 @@ Window {
id: dialogContent id: dialogContent
anchors.top: parent.top anchors.top: parent.top
spacing: 10 spacing: 10
RowLayout
{
id: rowContract
Layout.fillWidth: true
height: 150
DefaultLabel {
Layout.preferredWidth: 75
text: qsTr("Contract")
}
ComboBox {
id: contractComboBox
function currentValue() {
return (currentIndex >=0 && currentIndex < contractsModel.count) ? contractsModel.get(currentIndex).cid : "";
}
Layout.preferredWidth: 350
currentIndex: -1
textRole: "text"
editable: false
model: ListModel {
id: contractsModel
}
onCurrentIndexChanged: {
loadFunctions(currentValue());
}
}
}
RowLayout RowLayout
{ {
id: rowFunction id: rowFunction

12
mix/qml/WebPreview.qml

@ -29,7 +29,16 @@ Item {
} }
function updateContract() { function updateContract() {
webView.runJavaScript("updateContract(\"" + codeModel.code.contract.name + "\", \"" + clientModel.contractAddress + "\", " + codeModel.code.contractInterface + ")"); var contracts = {};
for (var c in codeModel.contracts) {
var contract = codeModel.contracts[c];
contracts[c] = {
name: contract.contract.name,
address: clientModel.contractAddresses[contract.contract.name],
interface: JSON.parse(contract.contractInterface),
};
}
webView.runJavaScript("updateContracts(" + JSON.stringify(contracts) + ")");
} }
function reloadOnSave() { function reloadOnSave() {
@ -62,7 +71,6 @@ Item {
Connections { Connections {
target: clientModel target: clientModel
onContractAddressChanged: reload();
onRunComplete: reload(); onRunComplete: reload();
} }

16
mix/qml/html/WebContainer.html

@ -15,16 +15,18 @@ reloadPage = function() {
preview.contentWindow.location.reload(); preview.contentWindow.location.reload();
}; };
updateContract = function(name, address, contractFace) { updateContracts = function(contracts) {
if (window.web3) { if (window.web3) {
window.web3.provider.polls = []; window.web3.provider.polls = [];
var contract = window.web3.eth.contract(address, contractFace);
window.contracts = {}; window.contracts = {};
window.contracts[name] = { for (var c in contracts) {
address: address, var contract = window.web3.eth.contract(contracts[c].address, contracts[c].interface);
interface: contractFace, window.contracts[c] = {
contract: contract, address: c.address,
}; interface: c.interface,
contract: contract,
};
}
} }
}; };

77
mix/qml/js/ProjectModel.js

@ -20,6 +20,9 @@
* Ethereum IDE client. * Ethereum IDE client.
*/ */
var htmlTemplate = "<html>\n<head>\n<script>\n</script>\n</head>\n<body>\n<script>\n</script>\n</body>\n</html>";
var contractTemplate = "contract Contract {\n}\n";
function saveAll() { function saveAll() {
saveProject(); saveProject();
} }
@ -76,6 +79,16 @@ function loadProject(path) {
projectSettings.lastProjectPath = path; projectSettings.lastProjectPath = path;
projectLoading(projectData); projectLoading(projectData);
projectLoaded() projectLoaded()
//TODO: move this to codemodel
var contractSources = {};
for (var d = 0; d < listModel.count; d++) {
var doc = listModel.get(d);
if (doc.isContract)
contractSources[doc.documentId] = fileIo.readFile(doc.path);
}
codeModel.reset(contractSources);
} }
function addFile(fileName) { function addFile(fileName) {
@ -92,7 +105,7 @@ function addFile(fileName) {
contract: false, contract: false,
path: p, path: p,
fileName: fileName, fileName: fileName,
name: isContract ? "Contract" : fileName, name: fileName,
documentId: fileName, documentId: fileName,
syntaxMode: syntaxMode, syntaxMode: syntaxMode,
isText: isContract || isHtml || isCss || isJs, isText: isContract || isHtml || isCss || isJs,
@ -150,7 +163,7 @@ function openPrevDocument() {
} }
function doCloseProject() { function doCloseProject() {
console.log("closing project"); console.log("Closing project");
projectListModel.clear(); projectListModel.clear();
projectPath = ""; projectPath = "";
currentDocumentId = ""; currentDocumentId = "";
@ -167,14 +180,14 @@ function doCreateProject(title, path) {
var projectFile = dirPath + projectFileName; var projectFile = dirPath + projectFileName;
var indexFile = "index.html"; var indexFile = "index.html";
var contractsFile = "contracts.sol"; var contractsFile = "contract.sol";
var projectData = { var projectData = {
title: title, title: title,
files: [ contractsFile, indexFile ] files: [ contractsFile, indexFile ]
}; };
//TODO: copy from template //TODO: copy from template
fileIo.writeFile(dirPath + indexFile, "<html>\n<head>\n<script>\nvar web3 = parent.web3;\nvar theContract = parent.contract;\n</script>\n</head>\n<body>\n<script>\n</script>\n</body>\n</html>"); fileIo.writeFile(dirPath + indexFile, htmlTemplate);
fileIo.writeFile(dirPath + contractsFile, "contract Contract {\n}\n"); fileIo.writeFile(dirPath + contractsFile, contractTemplate);
newProject(projectData); newProject(projectData);
var json = JSON.stringify(projectData, null, "\t"); var json = JSON.stringify(projectData, null, "\t");
fileIo.writeFile(projectFile, json); fileIo.writeFile(projectFile, json);
@ -222,7 +235,7 @@ function removeDocument(documentId) {
} }
function newHtmlFile() { function newHtmlFile() {
createAndAddFile("page", "html", "<html>\n</html>"); createAndAddFile("page", "html", htmlTemplate);
} }
function newCssFile() { function newCssFile() {
@ -233,6 +246,11 @@ function newJsFile() {
createAndAddFile("script", "js", "function foo() {\n}\n"); createAndAddFile("script", "js", "function foo() {\n}\n");
} }
function newContract() {
createAndAddFile("contract", "sol", contractTemplate);
}
function createAndAddFile(name, extension, content) { function createAndAddFile(name, extension, content) {
var fileName = generateFileName(name, extension); var fileName = generateFileName(name, extension);
var filePath = projectPath + fileName; var filePath = projectPath + fileName;
@ -267,15 +285,21 @@ function deployProject(force) {
var jsonRpcUrl = "http://localhost:8080"; var jsonRpcUrl = "http://localhost:8080";
console.log("Deploying " + deploymentId + " to " + jsonRpcUrl); console.log("Deploying " + deploymentId + " to " + jsonRpcUrl);
deploymentStarted(); deploymentStarted();
var code = codeModel.codeHex
var rpcRequest = JSON.stringify({ var requests = [];
jsonrpc: "2.0", var requestNames = [];
method: "eth_transact", for (var c in codeModel.contracts) { //TODO: order based on dependencies
params: [ { var code = codeModel.contracts[c].codeHex;
"code": code requests.push({
} ], jsonrpc: "2.0",
id: jsonRpcRequestId++ method: "eth_transact",
}); params: [ { "code": code } ],
id: jsonRpcRequestId++
});
requestNames.push(c);
}
var rpcRequest = JSON.stringify(requests);;
var httpRequest = new XMLHttpRequest() var httpRequest = new XMLHttpRequest()
httpRequest.open("POST", jsonRpcUrl, true); httpRequest.open("POST", jsonRpcUrl, true);
httpRequest.setRequestHeader("Content-type", "application/json"); httpRequest.setRequestHeader("Content-type", "application/json");
@ -285,9 +309,12 @@ function deployProject(force) {
if (httpRequest.readyState === XMLHttpRequest.DONE) { if (httpRequest.readyState === XMLHttpRequest.DONE) {
if (httpRequest.status === 200) { if (httpRequest.status === 200) {
var rpcResponse = JSON.parse(httpRequest.responseText); var rpcResponse = JSON.parse(httpRequest.responseText);
var address = rpcResponse.result; if (rpcResponse.length === requestNames.length) {
console.log("Created contract, address: " + address); var contractAddresses = {};
finalizeDeployment(deploymentId, address); for (var r = 0; r < rpcResponse.lenght; r++)
contractAddresses[requestNames[r]] = rpcResponse.result;
finalizeDeployment(deploymentId, contractAddresses);
}
} else { } else {
var errorText = qsTr("Deployment error: RPC server HTTP status ") + httpRequest.status; var errorText = qsTr("Deployment error: RPC server HTTP status ") + httpRequest.status;
console.log(errorText); console.log(errorText);
@ -298,7 +325,7 @@ function deployProject(force) {
httpRequest.send(rpcRequest); httpRequest.send(rpcRequest);
} }
function finalizeDeployment(deploymentId, address) { function finalizeDeployment(deploymentId, addresses) {
//create a dir for frontend files and copy them //create a dir for frontend files and copy them
var deploymentDir = projectPath + deploymentId + "/"; var deploymentDir = projectPath + deploymentId + "/";
fileIo.makeDir(deploymentDir); fileIo.makeDir(deploymentDir);
@ -326,16 +353,18 @@ function finalizeDeployment(deploymentId, address) {
fileIo.copyFile(doc.path, deploymentDir + doc.fileName); fileIo.copyFile(doc.path, deploymentDir + doc.fileName);
} }
//write deployment js //write deployment js
var contractAccessor = "contracts[\"" + codeModel.code.contract.name + "\"]";
var deploymentJs = var deploymentJs =
"// Autogenerated by Mix\n" + "// Autogenerated by Mix\n" +
"web3 = require(\"web3\");\n" + "web3 = require(\"web3\");\n" +
"contracts = {};\n" + "contracts = {};\n";
contractAccessor + " = {\n" + for (var c in codeModel.contracts) {
"\tinterface: " + codeModel.code.contractInterface + ",\n" + var contractAccessor = "contracts[\"" + codeModel.contracts[c].contract.name + "\"]";
"\taddress: \"" + address + "\"\n" + deploymentJs += contractAccessor + " = {\n" +
"\tinterface: " + codeModel.contracts[c].contractInterface + ",\n" +
"\taddress: \"" + addresses[c] + "\"\n" +
"};\n" + "};\n" +
contractAccessor + ".contract = web3.eth.contract(" + contractAccessor + ".address, " + contractAccessor + ".interface);\n"; contractAccessor + ".contract = web3.eth.contract(" + contractAccessor + ".address, " + contractAccessor + ".interface);\n";
}
fileIo.writeFile(deploymentDir + "deployment.js", deploymentJs); fileIo.writeFile(deploymentDir + "deployment.js", deploymentJs);
//copy scripts //copy scripts
fileIo.copyFile("qrc:///js/bignumber.min.js", deploymentDir + "bignumber.min.js"); fileIo.copyFile("qrc:///js/bignumber.min.js", deploymentDir + "bignumber.min.js");

1
mix/qml/js/TransactionHelper.js

@ -7,7 +7,6 @@ function defaultTransaction()
functionId: "", functionId: "",
gas: createBigInt("125000"), gas: createBigInt("125000"),
gasPrice: createEther("100000", QEther.Wei), gasPrice: createEther("100000", QEther.Wei),
executeConstructor: false,
parameters: {} parameters: {}
}; };
} }

2
mix/qml/main.qml

@ -30,7 +30,7 @@ ApplicationWindow {
MenuItem { action: addNewHtmlFileAction } MenuItem { action: addNewHtmlFileAction }
MenuItem { action: addNewCssFileAction } MenuItem { action: addNewCssFileAction }
MenuSeparator {} MenuSeparator {}
//MenuItem { action: addNewContractAction } MenuItem { action: addNewContractAction }
MenuItem { action: closeProjectAction } MenuItem { action: closeProjectAction }
MenuSeparator {} MenuSeparator {}
MenuItem { action: exitAppAction } MenuItem { action: exitAppAction }

Loading…
Cancel
Save