diff --git a/alethzero/CMakeLists.txt b/alethzero/CMakeLists.txt index d9e8dff6f..9abb3f1a4 100644 --- a/alethzero/CMakeLists.txt +++ b/alethzero/CMakeLists.txt @@ -22,6 +22,7 @@ qt5_wrap_ui(ui_Main.h Main.ui) qt5_wrap_ui(ui_Connect.h Connect.ui) qt5_wrap_ui(ui_Debugger.h Debugger.ui) qt5_wrap_ui(ui_Transact.h Transact.ui) +qt5_wrap_ui(ui_ExportState.h ExportState.ui) file(GLOB HEADERS "*.h") @@ -34,7 +35,7 @@ endif () # eth_add_executable is defined in cmake/EthExecutableHelper.cmake eth_add_executable(${EXECUTABLE} ICON alethzero - UI_RESOURCES alethzero.icns Main.ui Connect.ui Debugger.ui Transact.ui + UI_RESOURCES alethzero.icns Main.ui Connect.ui Debugger.ui Transact.ui ExportState.ui WIN_RESOURCES alethzero.rc ) diff --git a/alethzero/ExportState.cpp b/alethzero/ExportState.cpp new file mode 100644 index 000000000..e39b74b76 --- /dev/null +++ b/alethzero/ExportState.cpp @@ -0,0 +1,183 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file ExportState.cpp + * @author Arkadiy Paronyan + * @date 2015 + */ + +#include "ExportState.h" +#include +#include +#include +#include "MainWin.h" +#include "ui_ExportState.h" + +using namespace std; +using namespace dev; +using namespace dev::eth; + +ExportStateDialog::ExportStateDialog(Main* _parent): + QDialog(_parent), + ui(new Ui::ExportState), + m_main(_parent) +{ + ui->setupUi(this); + connect(ui->close, &QPushButton::clicked, this, &ExportStateDialog::close); + connect(ui->accounts, &QListWidget::itemSelectionChanged, this, &ExportStateDialog::generateJSON); + connect(ui->contracts, &QListWidget::itemSelectionChanged, this, &ExportStateDialog::generateJSON); + fillBlocks(); +} + +ExportStateDialog::~ExportStateDialog() +{ +} + +dev::eth::Client* ExportStateDialog::ethereum() const +{ + return m_main->ethereum(); +} + +void ExportStateDialog::on_block_editTextChanged() +{ + QString text = ui->block->currentText(); + int i = ui->block->count(); + while (i-- >= 0) + if (ui->block->itemText(i) == text) + return; + fillBlocks(); +} + +void ExportStateDialog::on_block_currentIndexChanged(int _index) +{ + m_block = ui->block->itemData(_index).toUInt(); + fillContracts(); +} + +void ExportStateDialog::fillBlocks() +{ + BlockChain const& bc = ethereum()->blockChain(); + QStringList filters = ui->block->currentText().toLower().split(QRegExp("\\s+"), QString::SkipEmptyParts); + const unsigned numLastBlocks = 10; + if (ui->block->count() == 0) + { + unsigned i = numLastBlocks; + for (auto h = bc.currentHash(); bc.details(h) && i; h = bc.details(h).parent, --i) + { + auto d = bc.details(h); + ui->block->addItem(QString("#%1 %2").arg(d.number).arg(h.abridged().c_str()), d.number); + if (h == bc.genesisHash()) + break; + } + if (ui->block->currentIndex() < 0) + ui->block->setCurrentIndex(0); + m_recentBlocks = numLastBlocks - i; + } + + int i = ui->block->count(); + while (i > 0 && i >= m_recentBlocks) + ui->block->removeItem(i--); + + h256Set blocks; + for (QString f: filters) + { + if (f.startsWith("#")) + f = f.remove(0, 1); + if (f.size() == 64) + { + h256 h(f.toStdString()); + if (bc.isKnown(h)) + blocks.insert(h); + for (auto const& b: bc.withBlockBloom(LogBloom().shiftBloom<3>(sha3(h)), 0, -1)) + blocks.insert(bc.numberHash(b)); + } + else if (f.toLongLong() <= bc.number()) + blocks.insert(bc.numberHash((unsigned)f.toLongLong())); + else if (f.size() == 40) + { + Address h(f.toStdString()); + for (auto const& b: bc.withBlockBloom(LogBloom().shiftBloom<3>(sha3(h)), 0, -1)) + blocks.insert(bc.numberHash(b)); + } + } + + for (auto const& h: blocks) + { + auto d = bc.details(h); + ui->block->addItem(QString("#%1 %2").arg(d.number).arg(h.abridged().c_str()), d.number); + } +} + +void ExportStateDialog::fillContracts() +{ + ui->accounts->clear(); + ui->contracts->clear(); + ui->accounts->setEnabled(true); + ui->contracts->setEnabled(true); + for (auto i: ethereum()->addresses(m_block)) + { + QString r = m_main->render(i); + (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(ethereum()->balanceAt(i)).c_str()).arg(r).arg((unsigned)ethereum()->countAt(i)), ethereum()->codeAt(i).empty() ? ui->accounts : ui->contracts)) + ->setData(Qt::UserRole, QByteArray((char const*)i.data(), Address::size)); + } +} + +void ExportStateDialog::generateJSON() +{ + std::stringstream json; + json << "{\n"; + std::string prefix; + for(QListWidgetItem* item: ui->accounts->selectedItems()) + { + auto hba = item->data(Qt::UserRole).toByteArray(); + auto address = Address((byte const*)hba.data(), Address::ConstructFromPointer); + json << prefix << "\t\"" << toHex(address.ref()) << "\": { \"wei\": \"" << ethereum()->balanceAt(address, m_block) << "\" }"; + prefix = ",\n"; + } + for(QListWidgetItem* item: ui->contracts->selectedItems()) + { + auto hba = item->data(Qt::UserRole).toByteArray(); + auto address = Address((byte const*)hba.data(), Address::ConstructFromPointer); + json << prefix << "\t\"" << toHex(address.ref()) << "\":\n\t{\n\t\t\"wei\": \"" << ethereum()->balanceAt(address, m_block) << "\",\n"; + json << "\t\t\"code\": \"" << toHex(ethereum()->codeAt(address, m_block)) << "\",\n"; + std::map storage = ethereum()->storageAt(address, m_block); + if (!storage.empty()) + { + json << "\t\t\"storage\":\n\t\t{\n"; + for (auto s: storage) + json << "\t\t\t\"" << toHex(s.first) << "\": \"" << toHex(s.second) << "\"" << (s.first == storage.rbegin()->first ? "" : ",") <<"\n"; + json << "\t\t}\n"; + } + json << "\t}"; + prefix = ",\n"; + } + json << "\n}"; + json.flush(); + + ui->json->setEnabled(true); + ui->json->setText(QString::fromStdString(json.str())); + ui->saveButton->setEnabled(true); +} + +void ExportStateDialog::on_saveButton_clicked() +{ + QString fn = QFileDialog::getSaveFileName(this, "Save state", QString(), "JSON Files (*.json)"); + if (!fn.endsWith(".json")) + fn = fn.append(".json"); + ofstream file(fn.toStdString()); + if (file.is_open()) + file << ui->json->toPlainText().toStdString(); +} diff --git a/alethzero/ExportState.h b/alethzero/ExportState.h new file mode 100644 index 000000000..e8e045855 --- /dev/null +++ b/alethzero/ExportState.h @@ -0,0 +1,57 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file ExportState.h + * @author Arkadiy Paronyan + * @date 2015 + */ + +#pragma once + +#include +#include +#include + +namespace Ui { class ExportState; } +namespace dev { namespace eth { class Client; } } + +class Main; + +class ExportStateDialog: public QDialog +{ + Q_OBJECT + +public: + explicit ExportStateDialog(Main* _parent = 0); + virtual ~ExportStateDialog(); + +private slots: + void on_block_editTextChanged(); + void on_block_currentIndexChanged(int _index); + void on_saveButton_clicked(); + +private: + dev::eth::Client* ethereum() const; + void fillBlocks(); + void fillContracts(); + void generateJSON(); + +private: + std::unique_ptr ui; + Main* m_main; + int m_recentBlocks = 0; + dev::eth::BlockNumber m_block = dev::eth::LatestBlock; +}; diff --git a/alethzero/ExportState.ui b/alethzero/ExportState.ui new file mode 100644 index 000000000..96256bc7f --- /dev/null +++ b/alethzero/ExportState.ui @@ -0,0 +1,183 @@ + + + ExportState + + + + 0 + 0 + 490 + 522 + + + + Export State + + + true + + + + + + + 0 + 0 + + + + &Block + + + block + + + + + + + true + + + + + + + + + + &Accounts + + + accounts + + + + + + + false + + + + 0 + 1 + + + + QAbstractItemView::MultiSelection + + + + + + + &Contracts + + + contracts + + + + + + + false + + + + 0 + 1 + + + + QAbstractItemView::MultiSelection + + + + + + + + 0 + 0 + + + + &JSON + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + json + + + + + + + false + + + + 0 + 2 + + + + true + + + + + + + + + false + + + + 0 + 0 + + + + &Save... + + + Esc + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + &Close + + + Esc + + + + + + + + + + diff --git a/alethzero/Main.ui b/alethzero/Main.ui index 1fd9669e9..ee46017f5 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -160,6 +160,8 @@ + + @@ -1478,6 +1480,11 @@ font-size: 14pt &Load Javascript... + + + &Export State... + + false diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 247e7fde3..023351361 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -72,6 +72,7 @@ #include "DappLoader.h" #include "DappHost.h" #include "WebPage.h" +#include "ExportState.h" #include "ui_Main.h" using namespace std; using namespace dev; @@ -820,6 +821,12 @@ void Main::on_exportKey_triggered() } } +void Main::on_exportState_triggered() +{ + ExportStateDialog dialog(this); + dialog.exec(); +} + void Main::on_usePrivate_triggered() { if (ui->usePrivate->isChecked()) diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 4ef1e8d54..2a0c1401d 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -134,6 +134,7 @@ private slots: // Tools void on_newTransaction_triggered(); void on_loadJS_triggered(); + void on_exportState_triggered(); // Stuff concerning the blocks/transactions/accounts panels void ourAccountsRowsMoved(); diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index c0ce36ab5..7863eb31d 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -85,7 +85,7 @@ ClientModel::ClientModel(): connect(this, &ClientModel::runComplete, this, &ClientModel::showDebugger, Qt::QueuedConnection); m_client.reset(new MixClient(QStandardPaths::writableLocation(QStandardPaths::TempLocation).toStdString())); - m_web3Server.reset(new Web3Server(*m_rpcConnector.get(), m_client->userAccounts(), m_client.get())); + m_web3Server.reset(new Web3Server(*m_rpcConnector.get(), std::vector(), m_client.get())); connect(m_web3Server.get(), &Web3Server::newTransaction, this, &ClientModel::onNewTransaction, Qt::DirectConnection); } @@ -169,14 +169,41 @@ QVariantMap ClientModel::gasCosts() const void ClientModel::setupState(QVariantMap _state) { - QVariantList balances = _state.value("accounts").toList(); + QVariantList stateAccounts = _state.value("accounts").toList(); + QVariantList stateContracts = _state.value("contracts").toList(); QVariantList transactions = _state.value("transactions").toList(); - map accounts; - for (auto const& b: balances) + map accounts; + std::vector userAccounts; + + for (auto const& b: stateAccounts) + { + QVariantMap account = b.toMap(); + Address address = {}; + if (account.contains("secret")) + { + KeyPair key(Secret(account.value("secret").toString().toStdString())); + userAccounts.push_back(key); + address = key.address(); + } + else if (account.contains("address")) + address = Address(fromHex(account.value("address").toString().toStdString())); + if (!address) + continue; + + accounts[address] = Account(qvariant_cast(account.value("balance"))->toU256Wei(), Account::NormalCreation); + } + for (auto const& c: stateContracts) { - QVariantMap address = b.toMap(); - accounts.insert(make_pair(Secret(address.value("secret").toString().toStdString()), (qvariant_cast(address.value("balance")))->toU256Wei())); + QVariantMap contract = c.toMap(); + Address address = Address(fromHex(contract.value("address").toString().toStdString())); + Account account(qvariant_cast(contract.value("balance"))->toU256Wei(), Account::ContractConception); + bytes code = fromHex(contract.value("code").toString().toStdString()); + account.setCode(code); + QVariantMap storageMap = contract.value("storage").toMap(); + for(auto s = storageMap.cbegin(); s != storageMap.cend(); ++s) + account.setStorage(fromBigEndian(fromHex(s.key().toStdString())), fromBigEndian(fromHex(s.value().toString().toStdString()))); + accounts[address] = account; } vector transactionSequence; @@ -215,10 +242,11 @@ void ClientModel::setupState(QVariantMap _state) transactionSequence.push_back(transactionSettings); } } + m_web3Server->setAccounts(userAccounts); executeSequence(transactionSequence, accounts, Secret(_state.value("miner").toMap().value("secret").toString().toStdString())); } -void ClientModel::executeSequence(vector const& _sequence, map const& _balances, Secret const& _miner) +void ClientModel::executeSequence(vector const& _sequence, std::map const& _accounts, Secret const& _miner) { if (m_running) { @@ -230,8 +258,7 @@ void ClientModel::executeSequence(vector const& _sequence, emit runStarted(); emit runStateChanged(); - m_client->resetState(_balances, _miner); - m_web3Server->setAccounts(m_client->userAccounts()); + m_client->resetState(_accounts, _miner); //run sequence m_runFuture = QtConcurrent::run([=]() { diff --git a/mix/ClientModel.h b/mix/ClientModel.h index dcb62f55d..714b81431 100644 --- a/mix/ClientModel.h +++ b/mix/ClientModel.h @@ -32,6 +32,9 @@ namespace dev { + +namespace eth { class Account; } + namespace mix { @@ -209,7 +212,7 @@ private: RecordLogEntry* lastBlock() const; QVariantMap contractAddresses() const; QVariantMap gasCosts() const; - void executeSequence(std::vector const& _sequence, std::map const& _balances, Secret const& _miner); + void executeSequence(std::vector const& _sequence, std::map const& _accounts, Secret const& _miner); dev::Address deployContract(bytes const& _code, TransactionSettings const& _tr = TransactionSettings()); void callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr); void onNewTransaction(); diff --git a/mix/MixClient.cpp b/mix/MixClient.cpp index 0b7d22f3f..3fb325bd4 100644 --- a/mix/MixClient.cpp +++ b/mix/MixClient.cpp @@ -39,7 +39,6 @@ namespace dev namespace mix { -Secret const c_defaultUserAccountSecret = Secret("cb73d9408c4720e230387d956eb0f829d8a4dd2c1055f96257167e14e7169074"); u256 const c_mixGenesisDifficulty = 131072; //TODO: make it lower for Mix somehow namespace @@ -69,16 +68,14 @@ bytes MixBlockChain::createGenesisBlock(h256 _stateRoot) MixClient::MixClient(std::string const& _dbPath): m_dbPath(_dbPath) { - std::map account; - account.insert(std::make_pair(c_defaultUserAccountSecret, 1000000 * ether)); - resetState(account); + resetState(std::map()); } MixClient::~MixClient() { } -void MixClient::resetState(std::map _accounts, Secret _miner) +void MixClient::resetState(std::map const& _accounts, Secret const& _miner) { WriteGuard l(x_state); Guard fl(x_filtersWatches); @@ -89,16 +86,7 @@ void MixClient::resetState(std::map _accounts, Secret _miner) SecureTrieDB accountState(&m_stateDB); accountState.init(); - m_userAccounts.clear(); - std::map genesisState; - for (auto account: _accounts) - { - KeyPair a = KeyPair(account.first); - m_userAccounts.push_back(a); - genesisState.insert(std::make_pair(a.address(), Account(account.second, Account::NormalCreation))); - } - - dev::eth::commit(genesisState, static_cast(m_stateDB), accountState); + dev::eth::commit(_accounts, static_cast(m_stateDB), accountState); h256 stateRoot = accountState.root(); m_bc.reset(); m_bc.reset(new MixBlockChain(m_dbPath, stateRoot)); diff --git a/mix/MixClient.h b/mix/MixClient.h index eec58413b..182e333c2 100644 --- a/mix/MixClient.h +++ b/mix/MixClient.h @@ -48,7 +48,7 @@ public: MixClient(std::string const& _dbPath); virtual ~MixClient(); /// Reset state to the empty state with given balance. - void resetState(std::map _accounts, Secret _miner = Secret()); + void resetState(std::map const& _accounts, Secret const& _miner = Secret()); void mine(); ExecutionResult lastExecution() const; ExecutionResult execution(unsigned _index) const; @@ -75,7 +75,6 @@ public: /// @returns the last mined block information using Interface::blockInfo; // to remove warning about hiding virtual function eth::BlockInfo blockInfo() const; - std::vector userAccounts() { return m_userAccounts; } protected: /// ClientBase methods @@ -92,7 +91,6 @@ private: void noteChanged(h256Set const& _filters); dev::eth::Transaction replaceGas(dev::eth::Transaction const& _t, dev::Secret const& _secret, dev::u256 const& _gas); - std::vector m_userAccounts; eth::State m_state; eth::State m_startState; OverlayDB m_stateDB; diff --git a/mix/qml/StateDialog.qml b/mix/qml/StateDialog.qml index b85996b29..3a3a060c3 100644 --- a/mix/qml/StateDialog.qml +++ b/mix/qml/StateDialog.qml @@ -14,7 +14,7 @@ Dialog { modality: Qt.ApplicationModal width: 630 - height: 500 + height: 660 title: qsTr("Edit State") visible: false @@ -27,6 +27,7 @@ Dialog { property int stateIndex property var stateTransactions: [] property var stateAccounts: [] + property var stateContracts: [] signal accepted StateDialogStyle { @@ -34,63 +35,67 @@ Dialog { } function open(index, item, setDefault) { - stateIndex = index; - stateTitle = item.title; - transactionsModel.clear(); + stateIndex = index + stateTitle = item.title + transactionsModel.clear() - stateTransactions = []; - var transactions = item.transactions; + stateTransactions = [] + var transactions = item.transactions for (var t = 0; t < transactions.length; t++) { - transactionsModel.append(item.transactions[t]); - stateTransactions.push(item.transactions[t]); + transactionsModel.append(item.transactions[t]) + stateTransactions.push(item.transactions[t]) } - accountsModel.clear(); - stateAccounts = []; - var miner = 0; - for (var k = 0; k < item.accounts.length; k++) - { - accountsModel.append(item.accounts[k]); - stateAccounts.push(item.accounts[k]); + accountsModel.clear() + stateAccounts = [] + var miner = 0 + for (var k = 0; k < item.accounts.length; k++) { + accountsModel.append(item.accounts[k]) + stateAccounts.push(item.accounts[k]) if (item.miner && item.accounts[k].name === item.miner.name) - miner = k; + miner = k } - visible = true; - isDefault = setDefault; - titleField.focus = true; - defaultCheckBox.enabled = !isDefault; - comboMiner.model = stateAccounts; - comboMiner.currentIndex = miner; - forceActiveFocus(); + stateContracts = [] + if (item.contracts) { + for (k = 0; k < item.contracts.length; k++) { + contractsModel.append(item.contracts[k]) + stateContracts.push(item.contracts[k]) + } + } + + visible = true + isDefault = setDefault + titleField.focus = true + defaultCheckBox.enabled = !isDefault + comboMiner.model = stateAccounts + comboMiner.currentIndex = miner + forceActiveFocus() } function acceptAndClose() { - close(); - accepted(); + close() + accepted() } function close() { - visible = false; + visible = false } function getItem() { var item = { title: stateDialog.stateTitle, - transactions: [], - accounts: [] + transactions: stateTransactions, + accounts: stateAccounts, + contracts: stateContracts } - item.transactions = stateTransactions; - item.accounts = stateAccounts; - for (var k = 0; k < stateAccounts.length; k++) - { - if (stateAccounts[k].name === comboMiner.currentText) - { - item.miner = stateAccounts[k]; - break; + for (var k = 0; k < stateAccounts.length; k++) { + if (stateAccounts[k].name === comboMiner.currentText) { + item.miner = stateAccounts[k] + break } } - return item; + return item } contentItem: Rectangle { @@ -106,31 +111,147 @@ Dialog { ColumnLayout { id: dialogContent anchors.top: parent.top - RowLayout - { + RowLayout { Layout.fillWidth: true DefaultLabel { Layout.preferredWidth: 85 text: qsTr("Title") } - DefaultTextField - { + DefaultTextField { id: titleField Layout.fillWidth: true } } - CommonSeparator - { + CommonSeparator { + Layout.fillWidth: true + } + + RowLayout { + Layout.fillWidth: true + + Rectangle { + Layout.preferredWidth: 85 + DefaultLabel { + id: contractsLabel + Layout.preferredWidth: 85 + wrapMode: Text.WrapAnywhere + text: qsTr("Genesis\nContracts") + } + + Button { + id: importStateButton + anchors.top: contractsLabel.bottom + anchors.topMargin: 10 + action: importStateAction + } + + Action { + id: importStateAction + tooltip: qsTr("Import genesis state from JSON file") + text: qsTr("Import...") + onTriggered: { + importJsonFileDialog.open() + } + } + FileDialog { + id: importJsonFileDialog + visible: false + title: qsTr("Select State File") + nameFilters: [qsTr("JSON files (*.json)", "All files (*)")] + onAccepted: { + var path = importJsonFileDialog.fileUrl.toString() + var jsonData = fileIo.readFile(path) + if (jsonData) { + var json = JSON.parse(jsonData) + for (var address in json) { + var account = { + address: address, + name: (json[address].name ? json[address].name : address), + balance: QEtherHelper.createEther(json[address].wei, QEther.Wei), + code: json[address].code, + storage: json[address].storage + } + if (account.code) { + contractsModel.append(account) + stateContracts.push(account) + } else { + accountsModel.append(account) + stateAccounts.push(account) + } + } + } + } + } + } + + TableView { + id: genesisContractsView + Layout.fillWidth: true + model: contractsModel + headerVisible: false + TableViewColumn { + role: "name" + title: qsTr("Name") + width: 230 + delegate: Item { + RowLayout { + height: 25 + width: parent.width + anchors.verticalCenter: parent.verticalCenter + Button { + iconSource: "qrc:/qml/img/delete_sign.png" + action: deleteContractAction + } + + Action { + id: deleteContractAction + tooltip: qsTr("Delete Contract") + onTriggered: { + stateContracts.splice(styleData.row, 1) + contractsModel.remove(styleData.row) + } + } + + DefaultTextField { + anchors.verticalCenter: parent.verticalCenter + onTextChanged: { + if (styleData.row > -1) + stateContracts[styleData.row].name = text + } + text: styleData.value + } + } + } + } + + TableViewColumn { + role: "balance" + title: qsTr("Balance") + width: 200 + delegate: Item { + Ether { + edit: true + displayFormattedValue: false + value: styleData.value + } + } + } + rowDelegate: Rectangle { + color: styleData.alternate ? "transparent" : "#f0f0f0" + height: 30 + } + } + } + + CommonSeparator { Layout.fillWidth: true } - RowLayout - { + RowLayout { Layout.fillWidth: true - Rectangle - { + Rectangle { Layout.preferredWidth: 85 DefaultLabel { id: accountsLabel @@ -138,8 +259,7 @@ Dialog { text: qsTr("Accounts") } - Button - { + Button { id: newAccountButton anchors.top: accountsLabel.bottom anchors.topMargin: 10 @@ -150,31 +270,28 @@ Dialog { Action { id: newAccountAction tooltip: qsTr("Add new Account") - onTriggered: - { - add(); + onTriggered: { + add() } - function add() - { - var account = stateListModel.newAccount("1000000", QEther.Ether); - stateAccounts.push(account); - accountsModel.append(account); - return account; + function add() { + var account = stateListModel.newAccount( + "1000000", QEther.Ether) + stateAccounts.push(account) + accountsModel.append(account) + return account } } } - MessageDialog - { + MessageDialog { id: alertAlreadyUsed text: qsTr("This account is in use. You cannot remove it. The first account is used to deploy config contract and cannot be removed.") icon: StandardIcon.Warning standardButtons: StandardButton.Ok } - TableView - { + TableView { id: accountsView Layout.fillWidth: true model: accountsModel @@ -184,12 +301,10 @@ Dialog { title: qsTr("Name") width: 230 delegate: Item { - RowLayout - { + RowLayout { height: 25 width: parent.width - Button - { + Button { iconSource: "qrc:/qml/img/delete_sign.png" action: deleteAccountAction } @@ -197,18 +312,21 @@ Dialog { Action { id: deleteAccountAction tooltip: qsTr("Delete Account") - onTriggered: - { - if (transactionsModel.isUsed(stateAccounts[styleData.row].secret)) - alertAlreadyUsed.open(); - else - { - if (stateAccounts[styleData.row].name === comboMiner.currentText) - comboMiner.currentIndex = 0; - stateAccounts.splice(styleData.row, 1); - accountsModel.remove(styleData.row); - comboMiner.model = stateAccounts; - comboMiner.update(); + onTriggered: { + if (transactionsModel.isUsed( + stateAccounts[styleData.row].secret)) + alertAlreadyUsed.open() + else { + if (stateAccounts[styleData.row].name + === comboMiner.currentText) + comboMiner.currentIndex = 0 + stateAccounts.splice( + styleData.row, + 1) + accountsModel.remove( + styleData.row) + comboMiner.model = stateAccounts //TODO: filter accounts wo private keys + comboMiner.update() } } } @@ -216,15 +334,14 @@ Dialog { DefaultTextField { anchors.verticalCenter: parent.verticalCenter onTextChanged: { - if (styleData.row > -1) - { + if (styleData.row > -1) { stateAccounts[styleData.row].name = text - var index = comboMiner.currentIndex; - comboMiner.model = stateAccounts; - comboMiner.currentIndex = index; + var index = comboMiner.currentIndex + comboMiner.model = stateAccounts + comboMiner.currentIndex = index } } - text: { + text: { return styleData.value } } @@ -238,28 +355,24 @@ Dialog { width: 200 delegate: Item { Ether { - id: balanceField edit: true displayFormattedValue: false value: styleData.value } } } - rowDelegate: - Rectangle { + rowDelegate: Rectangle { color: styleData.alternate ? "transparent" : "#f0f0f0" - height: 30; + height: 30 } } } - CommonSeparator - { + CommonSeparator { Layout.fillWidth: true } - RowLayout - { + RowLayout { Layout.fillWidth: true DefaultLabel { Layout.preferredWidth: 85 @@ -272,13 +385,11 @@ Dialog { } } - CommonSeparator - { + CommonSeparator { Layout.fillWidth: true } - RowLayout - { + RowLayout { Layout.fillWidth: true DefaultLabel { Layout.preferredWidth: 85 @@ -290,17 +401,14 @@ Dialog { } } - CommonSeparator - { + CommonSeparator { Layout.fillWidth: true } - RowLayout - { + RowLayout { Layout.fillWidth: true - Rectangle - { + Rectangle { Layout.preferredWidth: 85 DefaultLabel { id: transactionsLabel @@ -308,8 +416,7 @@ Dialog { text: qsTr("Transactions") } - Button - { + Button { anchors.top: transactionsLabel.bottom anchors.topMargin: 10 iconSource: "qrc:/qml/img/plus.png" @@ -323,8 +430,7 @@ Dialog { } } - TableView - { + TableView { id: transactionsView Layout.fillWidth: true model: transactionsModel @@ -334,12 +440,10 @@ Dialog { title: qsTr("Name") width: 150 delegate: Item { - RowLayout - { + RowLayout { height: 30 width: parent.width - Button - { + Button { iconSource: "qrc:/qml/img/delete_sign.png" action: deleteTransactionAction } @@ -347,20 +451,23 @@ Dialog { Action { id: deleteTransactionAction tooltip: qsTr("Delete") - onTriggered: transactionsModel.deleteTransaction(styleData.row) + onTriggered: transactionsModel.deleteTransaction( + styleData.row) } - Button - { + Button { iconSource: "qrc:/qml/img/edit.png" action: editAction - visible: styleData.row >= 0 ? !transactionsModel.get(styleData.row).stdContract : false + visible: styleData.row + >= 0 ? !transactionsModel.get( + styleData.row).stdContract : false width: 10 height: 10 Action { id: editAction tooltip: qsTr("Edit") - onTriggered: transactionsModel.editTransaction(styleData.row) + onTriggered: transactionsModel.editTransaction( + styleData.row) } } @@ -368,56 +475,63 @@ Dialog { Layout.preferredWidth: 150 text: { if (styleData.row >= 0) - return transactionsModel.get(styleData.row).functionId; + return transactionsModel.get( + styleData.row).functionId else - return ""; + return "" } } } } } - rowDelegate: - Rectangle { + rowDelegate: Rectangle { color: styleData.alternate ? "transparent" : "#f0f0f0" - height: 30; + height: 30 } } } } - RowLayout - { + RowLayout { anchors.bottom: parent.bottom - anchors.right: parent.right; + anchors.right: parent.right Button { - text: qsTr("Delete"); + text: qsTr("Delete") enabled: !modalStateDialog.isDefault onClicked: { - projectModel.stateListModel.deleteState(stateIndex); - close(); + projectModel.stateListModel.deleteState(stateIndex) + close() } } Button { - text: qsTr("OK"); + text: qsTr("OK") onClicked: { - close(); - accepted(); + close() + accepted() } } Button { - text: qsTr("Cancel"); - onClicked: close(); + text: qsTr("Cancel") + onClicked: close() } } ListModel { id: accountsModel - function removeAccount(_i) - { - accountsModel.remove(_i); - stateAccounts.splice(_i, 1); + function removeAccount(_i) { + accountsModel.remove(_i) + stateAccounts.splice(_i, 1) + } + } + + ListModel { + id: contractsModel + + function removeContract(_i) { + contractsModel.remove(_i) + stateContracts.splice(_i, 1) } } @@ -425,47 +539,46 @@ Dialog { id: transactionsModel function editTransaction(index) { - transactionDialog.stateAccounts = stateAccounts; - transactionDialog.open(index, transactionsModel.get(index)); + transactionDialog.stateAccounts = stateAccounts + transactionDialog.open(index, + transactionsModel.get(index)) } function addTransaction() { // Set next id here to work around Qt bug // https://bugreports.qt-project.org/browse/QTBUG-41327 // Second call to signal handler would just edit the item that was just created, no harm done - var item = TransactionHelper.defaultTransaction(); - transactionDialog.stateAccounts = stateAccounts; - transactionDialog.open(transactionsModel.count, item); + var item = TransactionHelper.defaultTransaction() + transactionDialog.stateAccounts = stateAccounts + transactionDialog.open(transactionsModel.count, item) } function deleteTransaction(index) { - stateTransactions.splice(index, 1); - transactionsModel.remove(index); + stateTransactions.splice(index, 1) + transactionsModel.remove(index) } - function isUsed(secret) - { - for (var i in stateTransactions) - { + function isUsed(secret) { + for (var i in stateTransactions) { if (stateTransactions[i].sender === secret) - return true; + return true } - return false; + return false } } - TransactionDialog - { + TransactionDialog { id: transactionDialog - onAccepted: - { - var item = transactionDialog.getItem(); + onAccepted: { + var item = transactionDialog.getItem() if (transactionDialog.transactionIndex < transactionsModel.count) { - transactionsModel.set(transactionDialog.transactionIndex, item); - stateTransactions[transactionDialog.transactionIndex] = item; + transactionsModel.set( + transactionDialog.transactionIndex, + item) + stateTransactions[transactionDialog.transactionIndex] = item } else { - transactionsModel.append(item); - stateTransactions.push(item); + transactionsModel.append(item) + stateTransactions.push(item) } } } diff --git a/mix/qml/StateListModel.qml b/mix/qml/StateListModel.qml index 075a049b6..51df3b1a8 100644 --- a/mix/qml/StateListModel.qml +++ b/mix/qml/StateListModel.qml @@ -18,10 +18,13 @@ Item { function fromPlainStateItem(s) { if (!s.accounts) s.accounts = [stateListModel.newAccount("1000000", QEther.Ether, defaultAccount)]; //support for old project + if (!s.contracts) + s.contracts = []; return { title: s.title, transactions: s.transactions.map(fromPlainTransactionItem), accounts: s.accounts.map(fromPlainAccountItem), + contracts: s.contracts.map(fromPlainAccountItem), miner: s.miner }; } @@ -30,8 +33,11 @@ Item { { return { name: t.name, + address: t.address, secret: t.secret, - balance: QEtherHelper.createEther(t.balance.value, t.balance.unit) + balance: QEtherHelper.createEther(t.balance.value, t.balance.unit), + storage: t.storage, + code: t.code, }; } @@ -62,6 +68,7 @@ Item { title: s.title, transactions: s.transactions.map(toPlainTransactionItem), accounts: s.accounts.map(toPlainAccountItem), + contracts: s.contracts.map(toPlainAccountItem), miner: s.miner }; } @@ -85,6 +92,9 @@ Item { value: t.balance.value, unit: t.balance.unit }, + address: t.address, + storage: t.storage, + code: t.code, }; } @@ -190,7 +200,8 @@ Item { var item = { title: "", transactions: [], - accounts: [] + accounts: [], + contracts: [] }; var account = newAccount("1000000", QEther.Ether, defaultAccount) diff --git a/test/libweb3jsonrpc/webthreestubclient.h b/test/libweb3jsonrpc/webthreestubclient.h index fd71bfb5d..51d556eec 100644 --- a/test/libweb3jsonrpc/webthreestubclient.h +++ b/test/libweb3jsonrpc/webthreestubclient.h @@ -476,6 +476,36 @@ class WebThreeStubClient : public jsonrpc::Client else throw jsonrpc::JsonRpcException(jsonrpc::Errors::ERROR_CLIENT_INVALID_RESPONSE, result.toStyledString()); } + std::string eth_signTransaction(const Json::Value& param1) throw (jsonrpc::JsonRpcException) + { + Json::Value p; + p.append(param1); + Json::Value result = this->CallMethod("eth_signTransaction",p); + if (result.isString()) + return result.asString(); + else + throw jsonrpc::JsonRpcException(jsonrpc::Errors::ERROR_CLIENT_INVALID_RESPONSE, result.toStyledString()); + } + Json::Value eth_inspectTransaction(const std::string& param1) throw (jsonrpc::JsonRpcException) + { + Json::Value p; + p.append(param1); + Json::Value result = this->CallMethod("eth_inspectTransaction",p); + if (result.isObject()) + return result; + else + throw jsonrpc::JsonRpcException(jsonrpc::Errors::ERROR_CLIENT_INVALID_RESPONSE, result.toStyledString()); + } + bool eth_injectTransaction(const std::string& param1) throw (jsonrpc::JsonRpcException) + { + Json::Value p; + p.append(param1); + Json::Value result = this->CallMethod("eth_injectTransaction",p); + if (result.isBool()) + return result.asBool(); + else + throw jsonrpc::JsonRpcException(jsonrpc::Errors::ERROR_CLIENT_INVALID_RESPONSE, result.toStyledString()); + } bool db_put(const std::string& param1, const std::string& param2, const std::string& param3) throw (jsonrpc::JsonRpcException) { Json::Value p;