Marek Kotewicz
10 years ago
57 changed files with 3029 additions and 1447 deletions
@ -0,0 +1,7 @@ |
|||
#!/bin/bash |
|||
|
|||
cd ethereumjs |
|||
export PATH=$PATH:$1:$2 |
|||
npm install |
|||
npm run-script build |
|||
|
@ -0,0 +1,665 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file WebThreeStubServerBase.cpp
|
|||
* @authors: |
|||
* Gav Wood <i@gavwood.com> |
|||
* Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#include <libsolidity/CompilerStack.h> |
|||
#include <libsolidity/Scanner.h> |
|||
#include <libsolidity/SourceReferenceFormatter.h> |
|||
#include <libevmcore/Instruction.h> |
|||
#include <liblll/Compiler.h> |
|||
#include <libethereum/Client.h> |
|||
#include <libwebthree/WebThree.h> |
|||
#include <libdevcore/CommonJS.h> |
|||
#include <libwhisper/Message.h> |
|||
#include <libwhisper/WhisperHost.h> |
|||
#include <libserpent/funcs.h> |
|||
#include "WebThreeStubServerBase.h" |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
|||
static Json::Value toJson(dev::eth::BlockInfo const& _bi) |
|||
{ |
|||
Json::Value res; |
|||
res["hash"] = boost::lexical_cast<string>(_bi.hash); |
|||
res["parentHash"] = toJS(_bi.parentHash); |
|||
res["sha3Uncles"] = toJS(_bi.sha3Uncles); |
|||
res["miner"] = toJS(_bi.coinbaseAddress); |
|||
res["stateRoot"] = toJS(_bi.stateRoot); |
|||
res["transactionsRoot"] = toJS(_bi.transactionsRoot); |
|||
res["difficulty"] = toJS(_bi.difficulty); |
|||
res["number"] = (int)_bi.number; |
|||
res["gasLimit"] = (int)_bi.gasLimit; |
|||
res["timestamp"] = (int)_bi.timestamp; |
|||
res["extraData"] = jsFromBinary(_bi.extraData); |
|||
res["nonce"] = toJS(_bi.nonce); |
|||
return res; |
|||
} |
|||
|
|||
static Json::Value toJson(dev::eth::Transaction const& _t) |
|||
{ |
|||
Json::Value res; |
|||
res["hash"] = toJS(_t.sha3()); |
|||
res["input"] = jsFromBinary(_t.data()); |
|||
res["to"] = toJS(_t.receiveAddress()); |
|||
res["from"] = toJS(_t.sender()); |
|||
res["gas"] = (int)_t.gas(); |
|||
res["gasPrice"] = toJS(_t.gasPrice()); |
|||
res["nonce"] = toJS(_t.nonce()); |
|||
res["value"] = toJS(_t.value()); |
|||
return res; |
|||
} |
|||
|
|||
static Json::Value toJson(dev::eth::LocalisedLogEntry const& _e) |
|||
{ |
|||
Json::Value res; |
|||
|
|||
res["data"] = jsFromBinary(_e.data); |
|||
res["address"] = toJS(_e.address); |
|||
for (auto const& t: _e.topics) |
|||
res["topics"].append(toJS(t)); |
|||
res["number"] = _e.number; |
|||
return res; |
|||
} |
|||
|
|||
static Json::Value toJson(dev::eth::LocalisedLogEntries const& _es) // commented to avoid warning. Uncomment once in use @ poC-7.
|
|||
{ |
|||
Json::Value res; |
|||
for (dev::eth::LocalisedLogEntry const& e: _es) |
|||
res.append(toJson(e)); |
|||
return res; |
|||
} |
|||
|
|||
static Json::Value toJson(std::map<u256, u256> const& _storage) |
|||
{ |
|||
Json::Value res(Json::objectValue); |
|||
for (auto i: _storage) |
|||
res[toJS(i.first)] = toJS(i.second); |
|||
return res; |
|||
} |
|||
|
|||
static dev::eth::LogFilter toLogFilter(Json::Value const& _json) // commented to avoid warning. Uncomment once in use @ PoC-7.
|
|||
{ |
|||
dev::eth::LogFilter filter; |
|||
if (!_json.isObject() || _json.empty()) |
|||
return filter; |
|||
|
|||
if (_json["earliest"].isInt()) |
|||
filter.withEarliest(_json["earliest"].asInt()); |
|||
if (_json["latest"].isInt()) |
|||
filter.withLatest(_json["lastest"].asInt()); |
|||
if (_json["max"].isInt()) |
|||
filter.withMax(_json["max"].asInt()); |
|||
if (_json["skip"].isInt()) |
|||
filter.withSkip(_json["skip"].asInt()); |
|||
if (!_json["address"].empty()) |
|||
{ |
|||
if (_json["address"].isArray()) |
|||
{ |
|||
for (auto i : _json["address"]) |
|||
if (i.isString()) |
|||
filter.address(jsToAddress(i.asString())); |
|||
} |
|||
else if (_json["address"].isString()) |
|||
filter.address(jsToAddress(_json["address"].asString())); |
|||
} |
|||
if (!_json["topics"].empty()) |
|||
{ |
|||
if (_json["topics"].isArray()) |
|||
{ |
|||
for (auto i: _json["topics"]) |
|||
if (i.isString()) |
|||
filter.topic(jsToU256(i.asString())); |
|||
} |
|||
else if(_json["topics"].isString()) |
|||
filter.topic(jsToU256(_json["topics"].asString())); |
|||
} |
|||
return filter; |
|||
} |
|||
|
|||
static shh::Message toMessage(Json::Value const& _json) |
|||
{ |
|||
shh::Message ret; |
|||
if (_json["from"].isString()) |
|||
ret.setFrom(jsToPublic(_json["from"].asString())); |
|||
if (_json["to"].isString()) |
|||
ret.setTo(jsToPublic(_json["to"].asString())); |
|||
if (_json["payload"].isString()) |
|||
ret.setPayload(jsToBytes(_json["payload"].asString())); |
|||
return ret; |
|||
} |
|||
|
|||
static shh::Envelope toSealed(Json::Value const& _json, shh::Message const& _m, Secret _from) |
|||
{ |
|||
unsigned ttl = 50; |
|||
unsigned workToProve = 50; |
|||
shh::BuildTopic bt; |
|||
|
|||
if (_json["ttl"].isInt()) |
|||
ttl = _json["ttl"].asInt(); |
|||
if (_json["workToProve"].isInt()) |
|||
workToProve = _json["workToProve"].asInt(); |
|||
if (!_json["topic"].empty()) |
|||
{ |
|||
if (_json["topic"].isString()) |
|||
bt.shift(jsToBytes(_json["topic"].asString())); |
|||
else if (_json["topic"].isArray()) |
|||
for (auto i: _json["topic"]) |
|||
if (i.isString()) |
|||
bt.shift(jsToBytes(i.asString())); |
|||
} |
|||
return _m.seal(_from, bt, ttl, workToProve); |
|||
} |
|||
|
|||
static pair<shh::TopicMask, Public> toWatch(Json::Value const& _json) |
|||
{ |
|||
shh::BuildTopicMask bt; |
|||
Public to; |
|||
|
|||
if (_json["to"].isString()) |
|||
to = jsToPublic(_json["to"].asString()); |
|||
|
|||
if (!_json["topic"].empty()) |
|||
{ |
|||
if (_json["topic"].isString()) |
|||
bt.shift(jsToBytes(_json["topic"].asString())); |
|||
else if (_json["topic"].isArray()) |
|||
for (auto i: _json["topic"]) |
|||
if (i.isString()) |
|||
bt.shift(jsToBytes(i.asString())); |
|||
} |
|||
return make_pair(bt.toTopicMask(), to); |
|||
} |
|||
|
|||
static Json::Value toJson(h256 const& _h, shh::Envelope const& _e, shh::Message const& _m) |
|||
{ |
|||
Json::Value res; |
|||
res["hash"] = toJS(_h); |
|||
res["expiry"] = (int)_e.expiry(); |
|||
res["sent"] = (int)_e.sent(); |
|||
res["ttl"] = (int)_e.ttl(); |
|||
res["workProved"] = (int)_e.workProved(); |
|||
for (auto const& t: _e.topics()) |
|||
res["topics"].append(toJS(t)); |
|||
res["payload"] = toJS(_m.payload()); |
|||
res["from"] = toJS(_m.from()); |
|||
res["to"] = toJS(_m.to()); |
|||
return res; |
|||
} |
|||
|
|||
WebThreeStubServerBase::WebThreeStubServerBase(jsonrpc::AbstractServerConnector& _conn, std::vector<dev::KeyPair> const& _accounts): |
|||
AbstractWebThreeStubServer(_conn) |
|||
{ |
|||
setAccounts(_accounts); |
|||
} |
|||
|
|||
void WebThreeStubServerBase::setAccounts(std::vector<dev::KeyPair> const& _accounts) |
|||
{ |
|||
m_accounts.clear(); |
|||
for (auto i: _accounts) |
|||
m_accounts[i.address()] = i.secret(); |
|||
} |
|||
|
|||
void WebThreeStubServerBase::setIdentities(std::vector<dev::KeyPair> const& _ids) |
|||
{ |
|||
m_ids.clear(); |
|||
for (auto i: _ids) |
|||
m_ids[i.pub()] = i.secret(); |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::web3_sha3(std::string const& _param1) |
|||
{ |
|||
return toJS(sha3(jsToBytes(_param1))); |
|||
} |
|||
|
|||
Json::Value WebThreeStubServerBase::eth_accounts() |
|||
{ |
|||
Json::Value ret(Json::arrayValue); |
|||
for (auto i: m_accounts) |
|||
ret.append(toJS(i.first)); |
|||
return ret; |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::shh_addToGroup(std::string const& _group, std::string const& _who) |
|||
{ |
|||
(void)_group; |
|||
(void)_who; |
|||
return ""; |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::eth_balanceAt(string const& _address) |
|||
{ |
|||
return toJS(client()->balanceAt(jsToAddress(_address), client()->getDefault())); |
|||
} |
|||
|
|||
Json::Value WebThreeStubServerBase::eth_blockByHash(std::string const& _hash) |
|||
{ |
|||
return toJson(client()->blockInfo(jsToFixed<32>(_hash))); |
|||
} |
|||
|
|||
Json::Value WebThreeStubServerBase::eth_blockByNumber(int const& _number) |
|||
{ |
|||
return toJson(client()->blockInfo(client()->hashFromNumber(_number))); |
|||
} |
|||
|
|||
static TransactionSkeleton toTransaction(Json::Value const& _json) |
|||
{ |
|||
TransactionSkeleton ret; |
|||
if (!_json.isObject() || _json.empty()) |
|||
return ret; |
|||
|
|||
if (_json["from"].isString()) |
|||
ret.from = jsToAddress(_json["from"].asString()); |
|||
if (_json["to"].isString()) |
|||
ret.to = jsToAddress(_json["to"].asString()); |
|||
if (!_json["value"].empty()) |
|||
{ |
|||
if (_json["value"].isString()) |
|||
ret.value = jsToU256(_json["value"].asString()); |
|||
else if (_json["value"].isInt()) |
|||
ret.value = u256(_json["value"].asInt()); |
|||
} |
|||
if (!_json["gas"].empty()) |
|||
{ |
|||
if (_json["gas"].isString()) |
|||
ret.gas = jsToU256(_json["gas"].asString()); |
|||
else if (_json["gas"].isInt()) |
|||
ret.gas = u256(_json["gas"].asInt()); |
|||
} |
|||
if (!_json["gasPrice"].empty()) |
|||
{ |
|||
if (_json["gasPrice"].isString()) |
|||
ret.gasPrice = jsToU256(_json["gasPrice"].asString()); |
|||
else if (_json["gasPrice"].isInt()) |
|||
ret.gas = u256(_json["gas"].asInt()); |
|||
} |
|||
if (!_json["data"].empty()) |
|||
{ |
|||
if (_json["data"].isString()) // ethereum.js has preconstructed the data array
|
|||
ret.data = jsToBytes(_json["data"].asString()); |
|||
else if (_json["data"].isArray()) // old style: array of 32-byte-padded values. TODO: remove PoC-8
|
|||
for (auto i: _json["data"]) |
|||
dev::operator +=(ret.data, padded(jsToBytes(i.asString()), 32)); |
|||
} |
|||
|
|||
if (_json["code"].isString()) |
|||
ret.data = jsToBytes(_json["code"].asString()); |
|||
return ret; |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::eth_call(Json::Value const& _json) |
|||
{ |
|||
std::string ret; |
|||
TransactionSkeleton t = toTransaction(_json); |
|||
if (!t.from && m_accounts.size()) |
|||
{ |
|||
auto b = m_accounts.begin()->first; |
|||
for (auto a: m_accounts) |
|||
if (client()->balanceAt(a.first) > client()->balanceAt(b)) |
|||
b = a.first; |
|||
t.from = b; |
|||
} |
|||
if (!m_accounts.count(t.from)) |
|||
return ret; |
|||
if (!t.gasPrice) |
|||
t.gasPrice = 10 * dev::eth::szabo; |
|||
if (!t.gas) |
|||
t.gas = min<u256>(client()->gasLimitRemaining(), client()->balanceAt(t.from) / t.gasPrice); |
|||
ret = toJS(client()->call(m_accounts[t.from].secret(), t.value, t.to, t.data, t.gas, t.gasPrice)); |
|||
return ret; |
|||
} |
|||
|
|||
Json::Value WebThreeStubServerBase::eth_changed(int const& _id) |
|||
{ |
|||
return toJson(client()->checkWatch(_id)); |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::eth_codeAt(string const& _address) |
|||
{ |
|||
return jsFromBinary(client()->codeAt(jsToAddress(_address), client()->getDefault())); |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::eth_coinbase() |
|||
{ |
|||
return toJS(client()->address()); |
|||
} |
|||
|
|||
double WebThreeStubServerBase::eth_countAt(string const& _address) |
|||
{ |
|||
return (double)(uint64_t)client()->countAt(jsToAddress(_address), client()->getDefault()); |
|||
} |
|||
|
|||
int WebThreeStubServerBase::eth_defaultBlock() |
|||
{ |
|||
return client()->getDefault(); |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::eth_gasPrice() |
|||
{ |
|||
return toJS(10 * dev::eth::szabo); |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::db_get(std::string const& _name, std::string const& _key) |
|||
{ |
|||
string ret = db()->get(_name, _key); |
|||
return toJS(dev::asBytes(ret)); |
|||
} |
|||
|
|||
Json::Value WebThreeStubServerBase::eth_filterLogs(int const& _id) |
|||
{ |
|||
return toJson(client()->logs(_id)); |
|||
} |
|||
|
|||
Json::Value WebThreeStubServerBase::eth_logs(Json::Value const& _json) |
|||
{ |
|||
return toJson(client()->logs(toLogFilter(_json))); |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::db_getString(std::string const& _name, std::string const& _key) |
|||
{ |
|||
return db()->get(_name, _key);; |
|||
} |
|||
|
|||
bool WebThreeStubServerBase::shh_haveIdentity(std::string const& _id) |
|||
{ |
|||
return m_ids.count(jsToPublic(_id)) > 0; |
|||
} |
|||
|
|||
bool WebThreeStubServerBase::eth_listening() |
|||
{ |
|||
return network()->isNetworkStarted(); |
|||
} |
|||
|
|||
bool WebThreeStubServerBase::eth_mining() |
|||
{ |
|||
return client()->isMining(); |
|||
} |
|||
|
|||
int WebThreeStubServerBase::eth_newFilter(Json::Value const& _json) |
|||
{ |
|||
unsigned ret = -1; |
|||
ret = client()->installWatch(toLogFilter(_json)); |
|||
return ret; |
|||
} |
|||
|
|||
int WebThreeStubServerBase::eth_newFilterString(std::string const& _filter) |
|||
{ |
|||
unsigned ret = -1; |
|||
if (_filter.compare("chain") == 0) |
|||
ret = client()->installWatch(dev::eth::ChainChangedFilter); |
|||
else if (_filter.compare("pending") == 0) |
|||
ret = client()->installWatch(dev::eth::PendingChangedFilter); |
|||
return ret; |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::shh_newGroup(std::string const& _id, std::string const& _who) |
|||
{ |
|||
(void)_id; |
|||
(void)_who; |
|||
return ""; |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::shh_newIdentity() |
|||
{ |
|||
// cnote << this << m_ids;
|
|||
KeyPair kp = KeyPair::create(); |
|||
m_ids[kp.pub()] = kp.secret(); |
|||
return toJS(kp.pub()); |
|||
} |
|||
|
|||
Json::Value WebThreeStubServerBase::eth_compilers() |
|||
{ |
|||
Json::Value ret(Json::arrayValue); |
|||
ret.append("lll"); |
|||
ret.append("solidity"); |
|||
ret.append("serpent"); |
|||
return ret; |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::eth_lll(std::string const& _code) |
|||
{ |
|||
string res; |
|||
vector<string> errors; |
|||
res = toJS(dev::eth::compileLLL(_code, true, &errors)); |
|||
cwarn << "LLL compilation errors: " << errors; |
|||
return res; |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::eth_serpent(std::string const& _code) |
|||
{ |
|||
string res; |
|||
try |
|||
{ |
|||
res = toJS(dev::asBytes(::compile(_code))); |
|||
} |
|||
catch (string err) |
|||
{ |
|||
cwarn << "Solidity compilation error: " << err; |
|||
} |
|||
catch (...) |
|||
{ |
|||
cwarn << "Uncought serpent compilation exception"; |
|||
} |
|||
return res; |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::eth_solidity(std::string const& _code) |
|||
{ |
|||
string res; |
|||
dev::solidity::CompilerStack compiler; |
|||
try |
|||
{ |
|||
res = toJS(compiler.compile(_code, true)); |
|||
} |
|||
catch (dev::Exception const& exception) |
|||
{ |
|||
ostringstream error; |
|||
solidity::SourceReferenceFormatter::printExceptionInformation(error, exception, "Error", compiler); |
|||
cwarn << "Solidity compilation error: " << error.str(); |
|||
} |
|||
catch (...) |
|||
{ |
|||
cwarn << "Uncought solidity compilation exception"; |
|||
} |
|||
return res; |
|||
} |
|||
|
|||
int WebThreeStubServerBase::eth_number() |
|||
{ |
|||
return client()->number() + 1; |
|||
} |
|||
|
|||
int WebThreeStubServerBase::eth_peerCount() |
|||
{ |
|||
return network()->peerCount(); |
|||
} |
|||
|
|||
bool WebThreeStubServerBase::shh_post(Json::Value const& _json) |
|||
{ |
|||
shh::Message m = toMessage(_json); |
|||
Secret from; |
|||
|
|||
if (m.from() && m_ids.count(m.from())) |
|||
{ |
|||
cwarn << "Silently signing message from identity" << m.from().abridged() << ": User validation hook goes here."; |
|||
// TODO: insert validification hook here.
|
|||
from = m_ids[m.from()]; |
|||
} |
|||
|
|||
face()->inject(toSealed(_json, m, from)); |
|||
return true; |
|||
} |
|||
|
|||
bool WebThreeStubServerBase::db_put(std::string const& _name, std::string const& _key, std::string const& _value) |
|||
{ |
|||
string v = asString(jsToBytes(_value)); |
|||
db()->put(_name, _key, v); |
|||
return true; |
|||
} |
|||
|
|||
bool WebThreeStubServerBase::db_putString(std::string const& _name, std::string const& _key, std::string const& _value) |
|||
{ |
|||
db()->put(_name, _key,_value); |
|||
return true; |
|||
} |
|||
|
|||
bool WebThreeStubServerBase::eth_setCoinbase(std::string const& _address) |
|||
{ |
|||
client()->setAddress(jsToAddress(_address)); |
|||
return true; |
|||
} |
|||
|
|||
bool WebThreeStubServerBase::eth_setDefaultBlock(int const& _block) |
|||
{ |
|||
client()->setDefault(_block); |
|||
return true; |
|||
} |
|||
|
|||
bool WebThreeStubServerBase::eth_setListening(bool const& _listening) |
|||
{ |
|||
if (_listening) |
|||
network()->startNetwork(); |
|||
else |
|||
network()->stopNetwork(); |
|||
return true; |
|||
} |
|||
|
|||
bool WebThreeStubServerBase::eth_setMining(bool const& _mining) |
|||
{ |
|||
if (_mining) |
|||
client()->startMining(); |
|||
else |
|||
client()->stopMining(); |
|||
return true; |
|||
} |
|||
|
|||
Json::Value WebThreeStubServerBase::shh_changed(int const& _id) |
|||
{ |
|||
Json::Value ret(Json::arrayValue); |
|||
auto pub = m_shhWatches[_id]; |
|||
if (!pub || m_ids.count(pub)) |
|||
for (h256 const& h: face()->checkWatch(_id)) |
|||
{ |
|||
auto e = face()->envelope(h); |
|||
shh::Message m; |
|||
if (pub) |
|||
{ |
|||
cwarn << "Silently decrypting message from identity" << pub.abridged() << ": User validation hook goes here."; |
|||
m = e.open(m_ids[pub]); |
|||
if (!m) |
|||
continue; |
|||
} |
|||
else |
|||
m = e.open(); |
|||
ret.append(toJson(h, e, m)); |
|||
} |
|||
|
|||
return ret; |
|||
} |
|||
|
|||
int WebThreeStubServerBase::shh_newFilter(Json::Value const& _json) |
|||
{ |
|||
auto w = toWatch(_json); |
|||
auto ret = face()->installWatch(w.first); |
|||
m_shhWatches.insert(make_pair(ret, w.second)); |
|||
return ret; |
|||
} |
|||
|
|||
bool WebThreeStubServerBase::shh_uninstallFilter(int const& _id) |
|||
{ |
|||
face()->uninstallWatch(_id); |
|||
return true; |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::eth_stateAt(string const& _address, string const& _storage) |
|||
{ |
|||
return toJS(client()->stateAt(jsToAddress(_address), jsToU256(_storage), client()->getDefault())); |
|||
} |
|||
|
|||
Json::Value WebThreeStubServerBase::eth_storageAt(string const& _address) |
|||
{ |
|||
return toJson(client()->storageAt(jsToAddress(_address))); |
|||
} |
|||
|
|||
std::string WebThreeStubServerBase::eth_transact(Json::Value const& _json) |
|||
{ |
|||
std::string ret; |
|||
TransactionSkeleton t = toTransaction(_json); |
|||
if (!t.from && m_accounts.size()) |
|||
{ |
|||
auto b = m_accounts.begin()->first; |
|||
for (auto a: m_accounts) |
|||
if (client()->balanceAt(a.first) > client()->balanceAt(b)) |
|||
b = a.first; |
|||
t.from = b; |
|||
} |
|||
if (!m_accounts.count(t.from)) |
|||
return ret; |
|||
if (!t.gasPrice) |
|||
t.gasPrice = 10 * dev::eth::szabo; |
|||
if (!t.gas) |
|||
t.gas = min<u256>(client()->gasLimitRemaining(), client()->balanceAt(t.from) / t.gasPrice); |
|||
if (authenticate(t)) |
|||
{ |
|||
if (t.to) |
|||
// TODO: from qethereum, insert validification hook here.
|
|||
client()->transact(m_accounts[t.from].secret(), t.value, t.to, t.data, t.gas, t.gasPrice); |
|||
else |
|||
ret = toJS(client()->transact(m_accounts[t.from].secret(), t.value, t.data, t.gas, t.gasPrice)); |
|||
client()->flushTransactions(); |
|||
} |
|||
return ret; |
|||
} |
|||
|
|||
bool WebThreeStubServerBase::authenticate(TransactionSkeleton const& _t) const |
|||
{ |
|||
cwarn << "Silently signing transaction from address" << _t.from.abridged() << ": User validation hook goes here."; |
|||
return true; |
|||
} |
|||
|
|||
Json::Value WebThreeStubServerBase::eth_transactionByHash(std::string const& _hash, int const& _i) |
|||
{ |
|||
return toJson(client()->transaction(jsToFixed<32>(_hash), _i)); |
|||
} |
|||
|
|||
Json::Value WebThreeStubServerBase::eth_transactionByNumber(int const& _number, int const& _i) |
|||
{ |
|||
return toJson(client()->transaction(client()->hashFromNumber(_number), _i)); |
|||
} |
|||
|
|||
Json::Value WebThreeStubServerBase::eth_uncleByHash(std::string const& _hash, int const& _i) |
|||
{ |
|||
return toJson(client()->uncle(jsToFixed<32>(_hash), _i)); |
|||
} |
|||
|
|||
Json::Value WebThreeStubServerBase::eth_uncleByNumber(int const& _number, int const& _i) |
|||
{ |
|||
return toJson(client()->uncle(client()->hashFromNumber(_number), _i)); |
|||
} |
|||
|
|||
bool WebThreeStubServerBase::eth_uninstallFilter(int const& _id) |
|||
{ |
|||
client()->uninstallWatch(_id); |
|||
return true; |
|||
} |
|||
|
@ -0,0 +1,138 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file WebThreeStubServer.h
|
|||
* @authors: |
|||
* Gav Wood <i@gavwood.com> |
|||
* Marek Kotewicz <marek@ethdev.com> |
|||
* @date 2014 |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <iostream> |
|||
#include <jsonrpccpp/server.h> |
|||
#include <libdevcrypto/Common.h> |
|||
#pragma GCC diagnostic push |
|||
#pragma GCC diagnostic ignored "-Wunused-parameter" |
|||
#include "abstractwebthreestubserver.h" |
|||
#pragma GCC diagnostic pop |
|||
|
|||
|
|||
namespace dev |
|||
{ |
|||
class WebThreeNetworkFace; |
|||
class KeyPair; |
|||
struct TransactionSkeleton; |
|||
namespace eth |
|||
{ |
|||
class Interface; |
|||
} |
|||
namespace shh |
|||
{ |
|||
class Interface; |
|||
} |
|||
|
|||
class WebThreeStubDatabaseFace |
|||
{ |
|||
public: |
|||
virtual std::string get(std::string const& _name, std::string const& _key) = 0; |
|||
virtual void put(std::string const& _name, std::string const& _key, std::string const& _value) = 0; |
|||
}; |
|||
|
|||
/**
|
|||
* @brief JSON-RPC api implementation |
|||
* @todo filters should work on unsigned instead of int |
|||
* unsigned are not supported in json-rpc-cpp and there are bugs with double in json-rpc-cpp version 0.2.1 |
|||
* @todo split these up according to subprotocol (eth, shh, db, p2p, web3) and make it /very/ clear about how to add other subprotocols. |
|||
* @todo modularise everything so additional subprotocols don't need to change this file. |
|||
*/ |
|||
class WebThreeStubServerBase: public AbstractWebThreeStubServer |
|||
{ |
|||
public: |
|||
WebThreeStubServerBase(jsonrpc::AbstractServerConnector& _conn, std::vector<dev::KeyPair> const& _accounts); |
|||
|
|||
virtual std::string web3_sha3(std::string const& _param1); |
|||
virtual Json::Value eth_accounts(); |
|||
virtual std::string eth_balanceAt(std::string const& _address); |
|||
virtual Json::Value eth_blockByHash(std::string const& _hash); |
|||
virtual Json::Value eth_blockByNumber(int const& _number); |
|||
virtual std::string eth_call(Json::Value const& _json); |
|||
virtual Json::Value eth_changed(int const& _id); |
|||
virtual std::string eth_codeAt(std::string const& _address); |
|||
virtual std::string eth_coinbase(); |
|||
virtual Json::Value eth_compilers(); |
|||
virtual double eth_countAt(std::string const& _address); |
|||
virtual int eth_defaultBlock(); |
|||
virtual std::string eth_gasPrice(); |
|||
virtual Json::Value eth_filterLogs(int const& _id); |
|||
virtual Json::Value eth_logs(Json::Value const& _json); |
|||
virtual bool eth_listening(); |
|||
virtual bool eth_mining(); |
|||
virtual int eth_newFilter(Json::Value const& _json); |
|||
virtual int eth_newFilterString(std::string const& _filter); |
|||
virtual int eth_number(); |
|||
virtual int eth_peerCount(); |
|||
virtual bool eth_setCoinbase(std::string const& _address); |
|||
virtual bool eth_setDefaultBlock(int const& _block); |
|||
virtual bool eth_setListening(bool const& _listening); |
|||
virtual std::string eth_lll(std::string const& _s); |
|||
virtual std::string eth_serpent(std::string const& _s); |
|||
virtual bool eth_setMining(bool const& _mining); |
|||
virtual std::string eth_solidity(std::string const& _code); |
|||
virtual std::string eth_stateAt(std::string const& _address, std::string const& _storage); |
|||
virtual Json::Value eth_storageAt(std::string const& _address); |
|||
virtual std::string eth_transact(Json::Value const& _json); |
|||
virtual Json::Value eth_transactionByHash(std::string const& _hash, int const& _i); |
|||
virtual Json::Value eth_transactionByNumber(int const& _number, int const& _i); |
|||
virtual Json::Value eth_uncleByHash(std::string const& _hash, int const& _i); |
|||
virtual Json::Value eth_uncleByNumber(int const& _number, int const& _i); |
|||
virtual bool eth_uninstallFilter(int const& _id); |
|||
|
|||
virtual std::string db_get(std::string const& _name, std::string const& _key); |
|||
virtual std::string db_getString(std::string const& _name, std::string const& _key); |
|||
virtual bool db_put(std::string const& _name, std::string const& _key, std::string const& _value); |
|||
virtual bool db_putString(std::string const& _name, std::string const& _key, std::string const& _value); |
|||
|
|||
virtual std::string shh_addToGroup(std::string const& _group, std::string const& _who); |
|||
virtual Json::Value shh_changed(int const& _id); |
|||
virtual bool shh_haveIdentity(std::string const& _id); |
|||
virtual int shh_newFilter(Json::Value const& _json); |
|||
virtual std::string shh_newGroup(std::string const& _id, std::string const& _who); |
|||
virtual std::string shh_newIdentity(); |
|||
virtual bool shh_post(Json::Value const& _json); |
|||
virtual bool shh_uninstallFilter(int const& _id); |
|||
|
|||
void setAccounts(std::vector<dev::KeyPair> const& _accounts); |
|||
void setIdentities(std::vector<dev::KeyPair> const& _ids); |
|||
std::map<dev::Public, dev::Secret> const& ids() const { return m_ids; } |
|||
|
|||
protected: |
|||
virtual bool authenticate(dev::TransactionSkeleton const& _t) const; |
|||
|
|||
protected: |
|||
virtual dev::eth::Interface* client() = 0; |
|||
virtual std::shared_ptr<dev::shh::Interface> face() = 0; |
|||
virtual dev::WebThreeNetworkFace* network() = 0; |
|||
virtual dev::WebThreeStubDatabaseFace* db() = 0; |
|||
|
|||
std::map<dev::Address, dev::KeyPair> m_accounts; |
|||
|
|||
std::map<dev::Public, dev::Secret> m_ids; |
|||
std::map<unsigned, dev::Public> m_shhWatches; |
|||
}; |
|||
|
|||
} //namespace dev
|
@ -1,119 +0,0 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file AssemblyDebuggerModel.cpp
|
|||
* @author Yann yann@ethdev.com |
|||
* @date 2014 |
|||
* used as a model to debug contract assembly code. |
|||
*/ |
|||
|
|||
#include <QApplication> |
|||
#include <libdevcore/Common.h> |
|||
#include <libevm/VM.h> |
|||
#include <libethereum/Executive.h> |
|||
#include <libethereum/Transaction.h> |
|||
#include <libethereum/ExtVM.h> |
|||
#include "AssemblyDebuggerModel.h" |
|||
#include "AppContext.h" |
|||
#include "DebuggingStateWrapper.h" |
|||
|
|||
using namespace std; |
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace mix |
|||
{ |
|||
|
|||
AssemblyDebuggerModel::AssemblyDebuggerModel(): |
|||
m_userAccount(KeyPair::create()) |
|||
{ |
|||
resetState(10000000 * ether); |
|||
} |
|||
|
|||
DebuggingContent AssemblyDebuggerModel::executeTransaction(bytesConstRef const& _rawTransaction) |
|||
{ |
|||
QList<DebuggingState> machineStates; |
|||
eth::Executive execution(m_executiveState, LastHashes(), 0); |
|||
execution.setup(_rawTransaction); |
|||
std::vector<DebuggingState const*> levels; |
|||
bytes code; |
|||
bytesConstRef data; |
|||
bool firstIteration = true; |
|||
auto onOp = [&](uint64_t steps, Instruction inst, dev::bigint newMemSize, dev::bigint gasCost, void* voidVM, void const* voidExt) |
|||
{ |
|||
VM& vm = *(VM*)voidVM; |
|||
ExtVM const& ext = *(ExtVM const*)voidExt; |
|||
|
|||
if (firstIteration) |
|||
{ |
|||
code = ext.code; |
|||
data = ext.data; |
|||
firstIteration = false; |
|||
} |
|||
|
|||
if (levels.size() < ext.depth) |
|||
levels.push_back(&machineStates.back()); |
|||
else |
|||
levels.resize(ext.depth); |
|||
|
|||
machineStates.append(DebuggingState({steps, ext.myAddress, vm.curPC(), inst, newMemSize, vm.gas(), |
|||
vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels})); |
|||
}; |
|||
|
|||
execution.go(onOp); |
|||
execution.finalize(); |
|||
|
|||
DebuggingContent d; |
|||
d.returnValue = execution.out().toVector(); |
|||
d.machineStates = machineStates; |
|||
d.executionCode = code; |
|||
d.executionData = data; |
|||
d.contentAvailable = true; |
|||
d.message = "ok"; |
|||
return d; |
|||
} |
|||
|
|||
DebuggingContent AssemblyDebuggerModel::deployContract(bytes const& _code) |
|||
{ |
|||
u256 gasPrice = 10000000000000; |
|||
u256 gas = 1000000; |
|||
u256 amount = 100; |
|||
Transaction _tr(amount, gasPrice, min(gas, m_executiveState.gasLimitRemaining()), _code, m_executiveState.transactionsFrom(dev::toAddress(m_userAccount.secret())), m_userAccount.secret()); |
|||
bytes b = _tr.rlp(); |
|||
dev::bytesConstRef bytesRef = &b; |
|||
DebuggingContent d = executeTransaction(bytesRef); |
|||
h256 th = sha3(rlpList(_tr.sender(), _tr.nonce())); |
|||
d.contractAddress = right160(th); |
|||
return d; |
|||
} |
|||
|
|||
DebuggingContent AssemblyDebuggerModel::callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr) |
|||
{ |
|||
Transaction tr = Transaction(_tr.value, _tr.gasPrice, min(_tr.gas, m_executiveState.gasLimitRemaining()), _contract, _data, m_executiveState.transactionsFrom(dev::toAddress(m_userAccount.secret())), m_userAccount.secret()); |
|||
bytes b = tr.rlp(); |
|||
dev::bytesConstRef bytesRef = &b; |
|||
DebuggingContent d = executeTransaction(bytesRef); |
|||
d.contractAddress = tr.receiveAddress(); |
|||
return d; |
|||
} |
|||
|
|||
void AssemblyDebuggerModel::resetState(u256 _balance) |
|||
{ |
|||
m_executiveState = eth::State(Address(), m_overlayDB, BaseState::Empty); |
|||
m_executiveState.addBalance(m_userAccount.address(), _balance); |
|||
} |
|||
|
|||
} |
|||
} |
@ -1,76 +0,0 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file AssemblyDebuggerModel.h
|
|||
* @author Yann yann@ethdev.com |
|||
* @date 2014 |
|||
* Used as a model to debug contract assembly code. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <QObject> |
|||
#include <QList> |
|||
#include <libdevcore/Common.h> |
|||
#include <libdevcrypto/Common.h> |
|||
#include <libethereum/State.h> |
|||
#include <libethereum/Executive.h> |
|||
#include "DebuggingStateWrapper.h" |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace mix |
|||
{ |
|||
|
|||
/// Backend transaction config class
|
|||
struct TransactionSettings |
|||
{ |
|||
TransactionSettings(QString const& _functionId, u256 _value, u256 _gas, u256 _gasPrice): |
|||
functionId(_functionId), value(_value), gas(_gas), gasPrice(_gasPrice) {} |
|||
|
|||
/// Contract function name
|
|||
QString functionId; |
|||
/// Transaction value
|
|||
u256 value; |
|||
/// Gas
|
|||
u256 gas; |
|||
/// Gas price
|
|||
u256 gasPrice; |
|||
/// Mapping from contract function parameter name to value
|
|||
std::map<QString, u256> parameterValues; |
|||
}; |
|||
|
|||
|
|||
/**
|
|||
* @brief Long-life object for managing all executions. |
|||
*/ |
|||
class AssemblyDebuggerModel |
|||
{ |
|||
public: |
|||
AssemblyDebuggerModel(); |
|||
/// Call function in a already deployed contract.
|
|||
DebuggingContent callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr); |
|||
/// Deploy the contract described by _code.
|
|||
DebuggingContent deployContract(bytes const& _code); |
|||
/// Reset state to the empty state with given balance.
|
|||
void resetState(u256 _balance); |
|||
|
|||
private: |
|||
KeyPair m_userAccount; |
|||
OverlayDB m_overlayDB; |
|||
eth::State m_executiveState; |
|||
DebuggingContent executeTransaction(dev::bytesConstRef const& _rawTransaction); |
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,222 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file ClientModel.cpp
|
|||
* @author Yann yann@ethdev.com |
|||
* @author Arkadiy Paronyan arkadiy@ethdev.com |
|||
* @date 2015 |
|||
* Ethereum IDE client. |
|||
*/ |
|||
|
|||
#include <QtConcurrent/QtConcurrent> |
|||
#include <QDebug> |
|||
#include <QQmlContext> |
|||
#include <QQmlApplicationEngine> |
|||
#include <libdevcore/CommonJS.h> |
|||
#include <libethereum/Transaction.h> |
|||
#include "ClientModel.h" |
|||
#include "AppContext.h" |
|||
#include "DebuggingStateWrapper.h" |
|||
#include "QContractDefinition.h" |
|||
#include "QVariableDeclaration.h" |
|||
#include "ContractCallDataEncoder.h" |
|||
#include "CodeModel.h" |
|||
#include "ClientModel.h" |
|||
|
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
using namespace dev::mix; |
|||
|
|||
/// @todo Move this to QML
|
|||
dev::u256 fromQString(QString const& _s) |
|||
{ |
|||
return dev::jsToU256(_s.toStdString()); |
|||
} |
|||
|
|||
/// @todo Move this to QML
|
|||
QString toQString(dev::u256 _value) |
|||
{ |
|||
std::ostringstream s; |
|||
s << _value; |
|||
return QString::fromStdString(s.str()); |
|||
} |
|||
|
|||
ClientModel::ClientModel(AppContext* _context): |
|||
m_context(_context), m_running(false) |
|||
{ |
|||
qRegisterMetaType<QVariableDefinition*>("QVariableDefinition*"); |
|||
qRegisterMetaType<QVariableDefinitionList*>("QVariableDefinitionList*"); |
|||
qRegisterMetaType<QList<QVariableDefinition*>>("QList<QVariableDefinition*>"); |
|||
qRegisterMetaType<QList<QVariableDeclaration*>>("QList<QVariableDeclaration*>"); |
|||
qRegisterMetaType<QVariableDeclaration*>("QVariableDeclaration*"); |
|||
qRegisterMetaType<AssemblyDebuggerData>("AssemblyDebuggerData"); |
|||
|
|||
connect(this, &ClientModel::dataAvailable, this, &ClientModel::showDebugger, Qt::QueuedConnection); |
|||
m_client.reset(new MixClient()); |
|||
|
|||
_context->appEngine()->rootContext()->setContextProperty("clientModel", this); |
|||
} |
|||
|
|||
void ClientModel::debugDeployment() |
|||
{ |
|||
executeSequence(std::vector<TransactionSettings>(), 10000000 * ether); |
|||
} |
|||
|
|||
void ClientModel::debugState(QVariantMap _state) |
|||
{ |
|||
u256 balance = fromQString(_state.value("balance").toString()); |
|||
QVariantList transactions = _state.value("transactions").toList(); |
|||
|
|||
std::vector<TransactionSettings> transactionSequence; |
|||
|
|||
for (auto const& t: transactions) |
|||
{ |
|||
QVariantMap transaction = t.toMap(); |
|||
|
|||
QString functionId = transaction.value("functionId").toString(); |
|||
u256 value = fromQString(transaction.value("value").toString()); |
|||
u256 gas = fromQString(transaction.value("gas").toString()); |
|||
u256 gasPrice = fromQString(transaction.value("gasPrice").toString()); |
|||
QVariantMap params = transaction.value("parameters").toMap(); |
|||
TransactionSettings transactionSettings(functionId, value, gas, gasPrice); |
|||
|
|||
for (auto p = params.cbegin(); p != params.cend(); ++p) |
|||
transactionSettings.parameterValues.insert(std::make_pair(p.key(), fromQString(p.value().toString()))); |
|||
|
|||
transactionSequence.push_back(transactionSettings); |
|||
} |
|||
executeSequence(transactionSequence, balance); |
|||
} |
|||
|
|||
void ClientModel::executeSequence(std::vector<TransactionSettings> const& _sequence, u256 _balance) |
|||
{ |
|||
if (m_running) |
|||
throw (std::logic_error("debugging already running")); |
|||
auto compilerRes = m_context->codeModel()->code(); |
|||
std::shared_ptr<QContractDefinition> contractDef = compilerRes->sharedContract(); |
|||
m_running = true; |
|||
|
|||
emit runStarted(); |
|||
emit stateChanged(); |
|||
|
|||
//run sequence
|
|||
QtConcurrent::run([=]() |
|||
{ |
|||
try |
|||
{ |
|||
bytes contractCode = compilerRes->bytes(); |
|||
std::vector<dev::bytes> transactonData; |
|||
QFunctionDefinition* f; |
|||
ContractCallDataEncoder c; |
|||
//encode data for all transactions
|
|||
for (auto const& t: _sequence) |
|||
{ |
|||
f = nullptr; |
|||
for (int tf = 0; tf < contractDef->functionsList().size(); tf++) |
|||
{ |
|||
if (contractDef->functionsList().at(tf)->name() == t.functionId) |
|||
{ |
|||
f = contractDef->functionsList().at(tf); |
|||
break; |
|||
} |
|||
} |
|||
if (!f) |
|||
throw std::runtime_error("function " + t.functionId.toStdString() + " not found"); |
|||
|
|||
c.encode(f); |
|||
for (int p = 0; p < f->parametersList().size(); p++) |
|||
{ |
|||
QVariableDeclaration* var = (QVariableDeclaration*)f->parametersList().at(p); |
|||
u256 value = 0; |
|||
auto v = t.parameterValues.find(var->name()); |
|||
if (v != t.parameterValues.cend()) |
|||
value = v->second; |
|||
c.encode(var, value); |
|||
} |
|||
transactonData.emplace_back(c.encodedData()); |
|||
} |
|||
|
|||
//run contract creation first
|
|||
m_client->resetState(_balance); |
|||
ExecutionResult debuggingContent = deployContract(contractCode); |
|||
Address address = debuggingContent.contractAddress; |
|||
for (unsigned i = 0; i < _sequence.size(); ++i) |
|||
debuggingContent = callContract(address, transactonData.at(i), _sequence.at(i)); |
|||
|
|||
QList<QVariableDefinition*> returnParameters; |
|||
|
|||
if (f) |
|||
returnParameters = c.decode(f->returnParameters(), debuggingContent.returnValue); |
|||
|
|||
//we need to wrap states in a QObject before sending to QML.
|
|||
QList<QObject*> wStates; |
|||
for (unsigned i = 0; i < debuggingContent.machineStates.size(); i++) |
|||
{ |
|||
QPointer<DebuggingStateWrapper> s(new DebuggingStateWrapper(debuggingContent.executionCode, debuggingContent.executionData.toBytes())); |
|||
s->setState(debuggingContent.machineStates[i]); |
|||
wStates.append(s); |
|||
} |
|||
//collect states for last transaction
|
|||
AssemblyDebuggerData code = DebuggingStateWrapper::getHumanReadableCode(debuggingContent.executionCode); |
|||
emit dataAvailable(returnParameters, wStates, code); |
|||
emit runComplete(); |
|||
} |
|||
catch(boost::exception const&) |
|||
{ |
|||
emit runFailed(QString::fromStdString(boost::current_exception_diagnostic_information())); |
|||
} |
|||
|
|||
catch(std::exception const& e) |
|||
{ |
|||
emit runFailed(e.what()); |
|||
} |
|||
m_running = false; |
|||
emit stateChanged(); |
|||
}); |
|||
} |
|||
|
|||
void ClientModel::showDebugger(QList<QVariableDefinition*> const& _returnParam, QList<QObject*> const& _wStates, AssemblyDebuggerData const& _code) |
|||
{ |
|||
m_context->appEngine()->rootContext()->setContextProperty("debugStates", QVariant::fromValue(_wStates)); |
|||
m_context->appEngine()->rootContext()->setContextProperty("humanReadableExecutionCode", QVariant::fromValue(std::get<0>(_code))); |
|||
m_context->appEngine()->rootContext()->setContextProperty("bytesCodeMapping", QVariant::fromValue(std::get<1>(_code))); |
|||
m_context->appEngine()->rootContext()->setContextProperty("contractCallReturnParameters", QVariant::fromValue(new QVariableDefinitionList(_returnParam))); |
|||
showDebuggerWindow(); |
|||
} |
|||
|
|||
void ClientModel::showDebugError(QString const& _error) |
|||
{ |
|||
//TODO: change that to a signal
|
|||
m_context->displayMessageDialog(tr("Debugger"), _error); |
|||
} |
|||
|
|||
ExecutionResult ClientModel::deployContract(bytes const& _code) |
|||
{ |
|||
u256 gasPrice = 10000000000000; |
|||
u256 gas = 125000; |
|||
u256 amount = 100; |
|||
|
|||
Address contractAddress = m_client->transact(m_client->userAccount().secret(), amount, _code, gas, gasPrice); |
|||
ExecutionResult r = m_client->lastExecutionResult(); |
|||
r.contractAddress = contractAddress; |
|||
return r; |
|||
} |
|||
|
|||
ExecutionResult ClientModel::callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr) |
|||
{ |
|||
m_client->transact(m_client->userAccount().secret(), _tr.value, _contract, _data, _tr.gas, _tr.gasPrice); |
|||
ExecutionResult r = m_client->lastExecutionResult(); |
|||
r.contractAddress = _contract; |
|||
return r; |
|||
} |
|||
|
@ -0,0 +1,113 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file ClientModel.h
|
|||
* @author Yann yann@ethdev.com |
|||
* @author Arkadiy Paronyan arkadiy@ethdev.com |
|||
* @date 2015 |
|||
* Ethereum IDE client. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <atomic> |
|||
#include "DebuggingStateWrapper.h" |
|||
#include "MixClient.h" |
|||
|
|||
using AssemblyDebuggerData = std::tuple<QList<QObject*>, dev::mix::QQMLMap*>; |
|||
|
|||
Q_DECLARE_METATYPE(AssemblyDebuggerData) |
|||
Q_DECLARE_METATYPE(dev::mix::ExecutionResult) |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace mix |
|||
{ |
|||
|
|||
class AppContext; |
|||
|
|||
/// Backend transaction config class
|
|||
struct TransactionSettings |
|||
{ |
|||
TransactionSettings(QString const& _functionId, u256 _value, u256 _gas, u256 _gasPrice): |
|||
functionId(_functionId), value(_value), gas(_gas), gasPrice(_gasPrice) {} |
|||
|
|||
/// Contract function name
|
|||
QString functionId; |
|||
/// Transaction value
|
|||
u256 value; |
|||
/// Gas
|
|||
u256 gas; |
|||
/// Gas price
|
|||
u256 gasPrice; |
|||
/// Mapping from contract function parameter name to value
|
|||
std::map<QString, u256> parameterValues; |
|||
}; |
|||
|
|||
|
|||
/**
|
|||
* @brief Ethereum state control |
|||
*/ |
|||
class ClientModel: public QObject |
|||
{ |
|||
Q_OBJECT |
|||
|
|||
public: |
|||
ClientModel(AppContext* _context); |
|||
|
|||
Q_PROPERTY(bool running MEMBER m_running NOTIFY stateChanged) |
|||
|
|||
public slots: |
|||
/// Run the contract constructor and show debugger window.
|
|||
void debugDeployment(); |
|||
/// Setup state, run transaction sequence, show debugger for the last transaction
|
|||
/// @param _state JS object with state configuration
|
|||
void debugState(QVariantMap _state); |
|||
|
|||
private slots: |
|||
/// Update UI with machine states result. Display a modal dialog.
|
|||
void showDebugger(QList<QVariableDefinition*> const& _returnParams = QList<QVariableDefinition*>(), QList<QObject*> const& _wStates = QList<QObject*>(), AssemblyDebuggerData const& _code = AssemblyDebuggerData()); |
|||
/// Update UI with transaction run error.
|
|||
void showDebugError(QString const& _error); |
|||
|
|||
signals: |
|||
/// Transaction execution started
|
|||
void runStarted(); |
|||
/// Transaction execution completed successfully
|
|||
void runComplete(); |
|||
/// Transaction execution completed with error
|
|||
/// @param _message Error message
|
|||
void runFailed(QString const& _message); |
|||
/// Execution state changed
|
|||
void stateChanged(); |
|||
/// Show debugger window request
|
|||
void showDebuggerWindow(); |
|||
|
|||
/// Emited when machine states are available.
|
|||
void dataAvailable(QList<QVariableDefinition*> const& _returnParams = QList<QVariableDefinition*>(), QList<QObject*> const& _wStates = QList<QObject*>(), AssemblyDebuggerData const& _code = AssemblyDebuggerData()); |
|||
|
|||
private: |
|||
void executeSequence(std::vector<TransactionSettings> const& _sequence, u256 _balance); |
|||
ExecutionResult deployContract(bytes const& _code); |
|||
ExecutionResult callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr); |
|||
|
|||
AppContext* m_context; |
|||
std::atomic<bool> m_running; |
|||
std::unique_ptr<MixClient> m_client; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,95 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file FileIo.cpp
|
|||
* @author Arkadiy Paronyan arkadiy@ethdev.com |
|||
* @date 2015 |
|||
* Ethereum IDE client. |
|||
*/ |
|||
|
|||
#include <stdexcept> |
|||
#include <QDir> |
|||
#include <QFile> |
|||
#include <QFileInfo> |
|||
#include <QTextStream> |
|||
#include <QUrl> |
|||
#include "FileIo.h" |
|||
|
|||
using namespace dev::mix; |
|||
|
|||
void FileIo::makeDir(QString const& _url) |
|||
{ |
|||
QUrl url(_url); |
|||
QDir dirPath(url.path()); |
|||
if (!dirPath.exists()) |
|||
dirPath.mkpath(dirPath.path()); |
|||
} |
|||
|
|||
QString FileIo::readFile(QString const& _url) |
|||
{ |
|||
QUrl url(_url); |
|||
QFile file(url.path()); |
|||
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) |
|||
{ |
|||
QTextStream stream(&file); |
|||
QString data = stream.readAll(); |
|||
return data; |
|||
} |
|||
else |
|||
error(tr("Error reading file %1").arg(_url)); |
|||
return QString(); |
|||
} |
|||
|
|||
void FileIo::writeFile(QString const& _url, QString const& _data) |
|||
{ |
|||
QUrl url(_url); |
|||
QFile file(url.path()); |
|||
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) |
|||
{ |
|||
QTextStream stream(&file); |
|||
stream << _data; |
|||
} |
|||
else |
|||
error(tr("Error writing file %1").arg(_url)); |
|||
} |
|||
|
|||
void FileIo::copyFile(QString const& _sourceUrl, QString const& _destUrl) |
|||
{ |
|||
QUrl sourceUrl(_sourceUrl); |
|||
QUrl destUrl(_destUrl); |
|||
if (!QFile::copy(sourceUrl.path(), destUrl.path())) |
|||
error(tr("Error copying file %1 to %2").arg(_sourceUrl).arg(_destUrl)); |
|||
} |
|||
|
|||
QString FileIo::getHomePath() const |
|||
{ |
|||
return QDir::homePath(); |
|||
} |
|||
|
|||
void FileIo::moveFile(QString const& _sourceUrl, QString const& _destUrl) |
|||
{ |
|||
QUrl sourceUrl(_sourceUrl); |
|||
QUrl destUrl(_destUrl); |
|||
if (!QFile::rename(sourceUrl.path(), destUrl.path())) |
|||
error(tr("Error moving file %1 to %2").arg(_sourceUrl).arg(_destUrl)); |
|||
} |
|||
|
|||
bool FileIo::fileExists(QString const& _url) |
|||
{ |
|||
QUrl url(_url); |
|||
QFile file(url.path()); |
|||
return file.exists(); |
|||
} |
@ -0,0 +1,61 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file FileIo.h
|
|||
* @author Arkadiy Paronyan arkadiy@ethdev.com |
|||
* @date 2015 |
|||
* Ethereum IDE client. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <QObject> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace mix |
|||
{ |
|||
|
|||
///File services for QML
|
|||
class FileIo: public QObject |
|||
{ |
|||
Q_OBJECT |
|||
Q_PROPERTY(QString homePath READ getHomePath CONSTANT) |
|||
|
|||
signals: |
|||
/// Signalled in case of IO error
|
|||
void error(QString const& _errorText); |
|||
|
|||
public: |
|||
/// Create a directory if it does not exist. Signals on failure.
|
|||
Q_INVOKABLE void makeDir(QString const& _url); |
|||
/// Read file contents to a string. Signals on failure.
|
|||
Q_INVOKABLE QString readFile(QString const& _url); |
|||
/// Write contents to a file. Signals on failure.
|
|||
Q_INVOKABLE void writeFile(QString const& _url, QString const& _data); |
|||
/// Copy a file from _sourcePath to _destPath. Signals on failure.
|
|||
Q_INVOKABLE void copyFile(QString const& _sourceUrl, QString const& _destUrl); |
|||
/// Move (rename) a file from _sourcePath to _destPath. Signals on failure.
|
|||
Q_INVOKABLE void moveFile(QString const& _sourceUrl, QString const& _destUrl); |
|||
/// Check if file exists
|
|||
Q_INVOKABLE bool fileExists(QString const& _url); |
|||
|
|||
private: |
|||
QString getHomePath() const; |
|||
}; |
|||
|
|||
} |
|||
} |
@ -0,0 +1,336 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file MixClient.cpp
|
|||
* @author Yann yann@ethdev.com |
|||
* @author Arkadiy Paronyan arkadiy@ethdev.com |
|||
* @date 2015 |
|||
* Ethereum IDE client. |
|||
*/ |
|||
|
|||
#include <vector> |
|||
#include <libdevcore/Exceptions.h> |
|||
#include <libethereum/BlockChain.h> |
|||
#include <libethereum/Transaction.h> |
|||
#include <libethereum/Executive.h> |
|||
#include <libethereum/ExtVM.h> |
|||
#include <libevm/VM.h> |
|||
|
|||
#include "MixClient.h" |
|||
|
|||
using namespace dev; |
|||
using namespace dev::eth; |
|||
using namespace dev::mix; |
|||
|
|||
MixClient::MixClient(): |
|||
m_userAccount(KeyPair::create()) |
|||
{ |
|||
resetState(10000000 * ether); |
|||
} |
|||
|
|||
void MixClient::resetState(u256 _balance) |
|||
{ |
|||
WriteGuard l(x_state); |
|||
m_state = eth::State(Address(), m_stateDB, BaseState::Empty); |
|||
m_state.addBalance(m_userAccount.address(), _balance); |
|||
} |
|||
|
|||
void MixClient::executeTransaction(bytesConstRef _rlp, State& _state) |
|||
{ |
|||
Executive execution(_state, LastHashes(), 0); |
|||
execution.setup(_rlp); |
|||
bytes code; |
|||
bytesConstRef data; |
|||
bool firstIteration = true; |
|||
std::vector<MachineState> machineStates; |
|||
std::vector<MachineState const*> levels; |
|||
auto onOp = [&](uint64_t steps, Instruction inst, dev::bigint newMemSize, dev::bigint gasCost, void* voidVM, void const* voidExt) |
|||
{ |
|||
VM& vm = *(VM*)voidVM; |
|||
ExtVM const& ext = *(ExtVM const*)voidExt; |
|||
|
|||
if (firstIteration) |
|||
{ |
|||
code = ext.code; |
|||
data = ext.data; |
|||
firstIteration = false; |
|||
} |
|||
|
|||
if (levels.size() < ext.depth) |
|||
levels.push_back(&machineStates.back()); |
|||
else |
|||
levels.resize(ext.depth); |
|||
|
|||
machineStates.push_back(MachineState({steps, ext.myAddress, vm.curPC(), inst, newMemSize, vm.gas(), |
|||
vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels})); |
|||
}; |
|||
|
|||
execution.go(onOp); |
|||
execution.finalize(); |
|||
|
|||
ExecutionResult d; |
|||
d.returnValue = execution.out().toVector(); |
|||
d.machineStates = machineStates; |
|||
d.executionCode = code; |
|||
d.executionData = data; |
|||
d.contentAvailable = true; |
|||
d.message = "ok"; |
|||
m_lastExecutionResult = d; |
|||
} |
|||
|
|||
void MixClient::validateBlock(int _block) const |
|||
{ |
|||
//TODO: throw exception here if _block != 0
|
|||
(void)_block; |
|||
} |
|||
|
|||
void MixClient::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) |
|||
{ |
|||
WriteGuard l(x_state); |
|||
u256 n = m_state.transactionsFrom(toAddress(_secret)); |
|||
Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); |
|||
bytes rlp = t.rlp(); |
|||
executeTransaction(&rlp, m_state); |
|||
} |
|||
|
|||
Address MixClient::transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) |
|||
{ |
|||
WriteGuard l(x_state); |
|||
u256 n = m_state.transactionsFrom(toAddress(_secret)); |
|||
eth::Transaction t(_endowment, _gasPrice, _gas, _init, n, _secret); |
|||
bytes rlp = t.rlp(); |
|||
executeTransaction(&rlp, m_state); |
|||
return right160(sha3(rlpList(t.sender(), t.nonce()))); |
|||
} |
|||
|
|||
void MixClient::inject(bytesConstRef _rlp) |
|||
{ |
|||
WriteGuard l(x_state); |
|||
executeTransaction(_rlp, m_state); |
|||
} |
|||
|
|||
void MixClient::flushTransactions() |
|||
{ |
|||
} |
|||
|
|||
bytes MixClient::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) |
|||
{ |
|||
bytes out; |
|||
u256 n; |
|||
State temp; |
|||
{ |
|||
ReadGuard lr(x_state); |
|||
temp = m_state; |
|||
n = temp.transactionsFrom(toAddress(_secret)); |
|||
} |
|||
Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); |
|||
bytes rlp = t.rlp(); |
|||
WriteGuard lw(x_state); //TODO: lock is required only for last executoin state
|
|||
executeTransaction(&rlp, temp); |
|||
return m_lastExecutionResult.returnValue; |
|||
} |
|||
|
|||
u256 MixClient::balanceAt(Address _a, int _block) const |
|||
{ |
|||
validateBlock(_block); |
|||
ReadGuard l(x_state); |
|||
return m_state.balance(_a); |
|||
} |
|||
|
|||
u256 MixClient::countAt(Address _a, int _block) const |
|||
{ |
|||
validateBlock(_block); |
|||
ReadGuard l(x_state); |
|||
return m_state.transactionsFrom(_a); |
|||
} |
|||
|
|||
u256 MixClient::stateAt(Address _a, u256 _l, int _block) const |
|||
{ |
|||
validateBlock(_block); |
|||
ReadGuard l(x_state); |
|||
return m_state.storage(_a, _l); |
|||
} |
|||
|
|||
bytes MixClient::codeAt(Address _a, int _block) const |
|||
{ |
|||
validateBlock(_block); |
|||
ReadGuard l(x_state); |
|||
return m_state.code(_a); |
|||
} |
|||
|
|||
std::map<u256, u256> MixClient::storageAt(Address _a, int _block) const |
|||
{ |
|||
validateBlock(_block); |
|||
ReadGuard l(x_state); |
|||
return m_state.storage(_a); |
|||
} |
|||
|
|||
eth::LocalisedLogEntries MixClient::logs(unsigned _watchId) const |
|||
{ |
|||
(void)_watchId; |
|||
return LocalisedLogEntries(); |
|||
} |
|||
|
|||
eth::LocalisedLogEntries MixClient::logs(eth::LogFilter const& _filter) const |
|||
{ |
|||
(void)_filter; |
|||
return LocalisedLogEntries(); |
|||
} |
|||
|
|||
unsigned MixClient::installWatch(eth::LogFilter const& _filter) |
|||
{ |
|||
(void)_filter; |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::installWatch")); |
|||
} |
|||
|
|||
unsigned MixClient::installWatch(h256 _filterId) |
|||
{ |
|||
(void)_filterId; |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::installWatch")); |
|||
} |
|||
|
|||
void MixClient::uninstallWatch(unsigned _watchId) |
|||
{ |
|||
(void)_watchId; |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::uninstallWatch")); |
|||
} |
|||
|
|||
eth::LocalisedLogEntries MixClient::peekWatch(unsigned _watchId) const |
|||
{ |
|||
(void)_watchId; |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::peekWatch")); |
|||
} |
|||
|
|||
eth::LocalisedLogEntries MixClient::checkWatch(unsigned _watchId) |
|||
{ |
|||
(void)_watchId; |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::checkWatch")); |
|||
} |
|||
|
|||
h256 MixClient::hashFromNumber(unsigned _number) const |
|||
{ |
|||
(void)_number; |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::hashFromNumber")); |
|||
} |
|||
|
|||
eth::BlockInfo MixClient::blockInfo(h256 _hash) const |
|||
{ |
|||
(void)_hash; |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::blockInfo")); |
|||
} |
|||
|
|||
eth::BlockDetails MixClient::blockDetails(h256 _hash) const |
|||
{ |
|||
(void)_hash; |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::blockDetails")); |
|||
} |
|||
|
|||
eth::Transaction MixClient::transaction(h256 _blockHash, unsigned _i) const |
|||
{ |
|||
(void)_blockHash; |
|||
(void)_i; |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::transaction")); |
|||
} |
|||
|
|||
eth::BlockInfo MixClient::uncle(h256 _blockHash, unsigned _i) const |
|||
{ |
|||
(void)_blockHash; |
|||
(void)_i; |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::uncle")); |
|||
} |
|||
|
|||
unsigned MixClient::number() const |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
eth::Transactions MixClient::pending() const |
|||
{ |
|||
return eth::Transactions(); |
|||
} |
|||
|
|||
eth::StateDiff MixClient::diff(unsigned _txi, h256 _block) const |
|||
{ |
|||
(void)_txi; |
|||
(void)_block; |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::diff")); |
|||
} |
|||
|
|||
eth::StateDiff MixClient::diff(unsigned _txi, int _block) const |
|||
{ |
|||
(void)_txi; |
|||
(void)_block; |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::diff")); |
|||
} |
|||
|
|||
Addresses MixClient::addresses(int _block) const |
|||
{ |
|||
validateBlock(_block); |
|||
ReadGuard l(x_state); |
|||
Addresses ret; |
|||
for (auto const& i: m_state.addresses()) |
|||
ret.push_back(i.first); |
|||
return ret; |
|||
} |
|||
|
|||
u256 MixClient::gasLimitRemaining() const |
|||
{ |
|||
ReadGuard l(x_state); |
|||
return m_state.gasLimitRemaining(); |
|||
} |
|||
|
|||
void MixClient::setAddress(Address _us) |
|||
{ |
|||
WriteGuard l(x_state); |
|||
m_state.setAddress(_us); |
|||
} |
|||
|
|||
Address MixClient::address() const |
|||
{ |
|||
ReadGuard l(x_state); |
|||
return m_state.address(); |
|||
} |
|||
|
|||
void MixClient::setMiningThreads(unsigned _threads) |
|||
{ |
|||
(void)_threads; |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::setMiningThreads")); |
|||
} |
|||
|
|||
unsigned MixClient::miningThreads() const |
|||
{ |
|||
return 0; |
|||
} |
|||
|
|||
void MixClient::startMining() |
|||
{ |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::startMining")); |
|||
} |
|||
|
|||
void MixClient::stopMining() |
|||
{ |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::stopMining")); |
|||
} |
|||
|
|||
bool MixClient::isMining() |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
eth::MineProgress MixClient::miningProgress() const |
|||
{ |
|||
return eth::MineProgress(); |
|||
} |
@ -0,0 +1,125 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file MixClient.h
|
|||
* @author Yann yann@ethdev.com |
|||
* @author Arkadiy Paronyan arkadiy@ethdev.com |
|||
* @date 2015 |
|||
* Ethereum IDE client. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <libethereum/Interface.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
namespace mix |
|||
{ |
|||
|
|||
/**
|
|||
* @brief Store information about a machine state. |
|||
*/ |
|||
struct MachineState |
|||
{ |
|||
uint64_t steps; |
|||
dev::Address cur; |
|||
dev::u256 curPC; |
|||
dev::eth::Instruction inst; |
|||
dev::bigint newMemSize; |
|||
dev::u256 gas; |
|||
dev::u256s stack; |
|||
dev::bytes memory; |
|||
dev::bigint gasCost; |
|||
std::map<dev::u256, dev::u256> storage; |
|||
std::vector<MachineState const*> levels; |
|||
}; |
|||
|
|||
/**
|
|||
* @brief Store information about a machine states. |
|||
*/ |
|||
struct ExecutionResult |
|||
{ |
|||
std::vector<MachineState> machineStates; |
|||
bytes executionCode; |
|||
bytesConstRef executionData; |
|||
Address contractAddress; |
|||
bool contentAvailable; |
|||
std::string message; |
|||
bytes returnValue; |
|||
}; |
|||
|
|||
class MixClient: public dev::eth::Interface |
|||
{ |
|||
public: |
|||
MixClient(); |
|||
/// Reset state to the empty state with given balance.
|
|||
void resetState(u256 _balance); |
|||
const KeyPair& userAccount() const { return m_userAccount; } |
|||
const ExecutionResult lastExecutionResult() const { ReadGuard l(x_state); return m_lastExecutionResult; } |
|||
|
|||
//dev::eth::Interface
|
|||
void transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) override; |
|||
Address transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) override; |
|||
void inject(bytesConstRef _rlp) override; |
|||
void flushTransactions() override; |
|||
bytes call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) override; |
|||
u256 balanceAt(Address _a, int _block) const override; |
|||
u256 countAt(Address _a, int _block) const override; |
|||
u256 stateAt(Address _a, u256 _l, int _block) const override; |
|||
bytes codeAt(Address _a, int _block) const override; |
|||
std::map<u256, u256> storageAt(Address _a, int _block) const override; |
|||
eth::LocalisedLogEntries logs(unsigned _watchId) const override; |
|||
eth::LocalisedLogEntries logs(eth::LogFilter const& _filter) const override; |
|||
unsigned installWatch(eth::LogFilter const& _filter) override; |
|||
unsigned installWatch(h256 _filterId) override; |
|||
void uninstallWatch(unsigned _watchId) override; |
|||
eth::LocalisedLogEntries peekWatch(unsigned _watchId) const override; |
|||
eth::LocalisedLogEntries checkWatch(unsigned _watchId) override; |
|||
h256 hashFromNumber(unsigned _number) const override; |
|||
eth::BlockInfo blockInfo(h256 _hash) const override; |
|||
eth::BlockDetails blockDetails(h256 _hash) const override; |
|||
eth::Transaction transaction(h256 _blockHash, unsigned _i) const override; |
|||
eth::BlockInfo uncle(h256 _blockHash, unsigned _i) const override; |
|||
unsigned number() const override; |
|||
eth::Transactions pending() const override; |
|||
eth::StateDiff diff(unsigned _txi, h256 _block) const override; |
|||
eth::StateDiff diff(unsigned _txi, int _block) const override; |
|||
Addresses addresses(int _block) const override; |
|||
u256 gasLimitRemaining() const override; |
|||
void setAddress(Address _us) override; |
|||
Address address() const override; |
|||
void setMiningThreads(unsigned _threads) override; |
|||
unsigned miningThreads() const override; |
|||
void startMining() override; |
|||
void stopMining() override; |
|||
bool isMining() override; |
|||
eth::MineProgress miningProgress() const override; |
|||
|
|||
private: |
|||
void executeTransaction(bytesConstRef _rlp, eth::State& _state); |
|||
void validateBlock(int _block) const; |
|||
|
|||
KeyPair m_userAccount; |
|||
eth::State m_state; |
|||
OverlayDB m_stateDB; |
|||
mutable boost::shared_mutex x_state; |
|||
ExecutionResult m_lastExecutionResult; |
|||
}; |
|||
|
|||
} |
|||
|
|||
} |
@ -0,0 +1,55 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Web3Server.h.cpp
|
|||
* @author Arkadiy Paronyan arkadiy@ethdev.com |
|||
* @date 2014 |
|||
* Ethereum IDE client. |
|||
*/ |
|||
|
|||
|
|||
#include <libdevcore/Exceptions.h> |
|||
#include "Web3Server.h" |
|||
|
|||
using namespace dev::mix; |
|||
|
|||
Web3Server::Web3Server(jsonrpc::AbstractServerConnector& _conn, std::vector<dev::KeyPair> const& _accounts, dev::eth::Interface* _client): |
|||
WebThreeStubServerBase(_conn, _accounts), |
|||
m_client(_client) |
|||
{ |
|||
} |
|||
|
|||
std::shared_ptr<dev::shh::Interface> Web3Server::face() |
|||
{ |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::shh::Interface")); |
|||
} |
|||
|
|||
dev::WebThreeNetworkFace* Web3Server::network() |
|||
{ |
|||
BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::WebThreeNetworkFace")); |
|||
} |
|||
|
|||
std::string Web3Server::get(std::string const& _name, std::string const& _key) |
|||
{ |
|||
std::string k(_name + "/" + _key); |
|||
return m_db[k]; |
|||
} |
|||
|
|||
void Web3Server::put(std::string const& _name, std::string const& _key, std::string const& _value) |
|||
{ |
|||
std::string k(_name + "/" + _key); |
|||
m_db[k] = _value; |
|||
} |
@ -0,0 +1,56 @@ |
|||
/*
|
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file Web3Server.h
|
|||
* @author Arkadiy Paronyan arkadiy@ethdev.com |
|||
* @date 2015 |
|||
* Ethereum IDE client. |
|||
*/ |
|||
|
|||
#pragma once |
|||
|
|||
#include <map> |
|||
#include <string> |
|||
#include <libweb3jsonrpc/WebThreeStubServerBase.h> |
|||
|
|||
namespace dev |
|||
{ |
|||
|
|||
namespace mix |
|||
{ |
|||
|
|||
class Web3Server: public dev::WebThreeStubServerBase, public dev::WebThreeStubDatabaseFace |
|||
{ |
|||
public: |
|||
Web3Server(jsonrpc::AbstractServerConnector& _conn, std::vector<dev::KeyPair> const& _accounts, dev::eth::Interface* _client); |
|||
|
|||
private: |
|||
dev::eth::Interface* client() override { return m_client; } |
|||
std::shared_ptr<dev::shh::Interface> face() override; |
|||
dev::WebThreeNetworkFace* network() override; |
|||
dev::WebThreeStubDatabaseFace* db() override { return this; } |
|||
|
|||
std::string get(std::string const& _name, std::string const& _key) override; |
|||
void put(std::string const& _name, std::string const& _key, std::string const& _value) override; |
|||
|
|||
private: |
|||
dev::eth::Interface* m_client; |
|||
std::map<std::string, std::string> m_db; |
|||
}; |
|||
|
|||
} |
|||
|
|||
} |
@ -1,16 +1,23 @@ |
|||
<RCC> |
|||
<qresource prefix="/"> |
|||
<file>qml/BasicContent.qml</file> |
|||
<file>qml/main.qml</file> |
|||
<file>qml/MainContent.qml</file> |
|||
<file>qml/TabStyle.qml</file> |
|||
<file>qml/Debugger.qml</file> |
|||
<file>qml/js/Debugger.js</file> |
|||
<file>qml/AlertMessageDialog.qml</file> |
|||
<file>qml/BasicContent.qml</file> |
|||
<file>qml/BasicMessage.qml</file> |
|||
<file>qml/TransactionDialog.qml</file> |
|||
<file>qml/Debugger.qml</file> |
|||
<file>qml/MainContent.qml</file> |
|||
<file>qml/ModalDialog.qml</file> |
|||
<file>qml/AlertMessageDialog.qml</file> |
|||
<file>qml/ProjectList.qml</file> |
|||
<file>qml/StateDialog.qml</file> |
|||
<file>qml/StateList.qml</file> |
|||
<file>qml/TabStyle.qml</file> |
|||
<file>qml/TransactionDialog.qml</file> |
|||
<file>qml/js/Debugger.js</file> |
|||
<file>qml/NewProjectDialog.qml</file> |
|||
<file>qml/ProjectModel.qml</file> |
|||
<file>qml/CodeEditor.qml</file> |
|||
<file>qml/CodeEditorView.qml</file> |
|||
<file>qml/js/ProjectModel.js</file> |
|||
<file>qml/WebPreview.qml</file> |
|||
</qresource> |
|||
</RCC> |
|||
|
@ -0,0 +1,81 @@ |
|||
import QtQuick 2.0 |
|||
import QtQuick.Window 2.0 |
|||
import QtQuick.Layouts 1.0 |
|||
import QtQuick.Controls 1.0 |
|||
import QtQuick.Controls.Styles 1.1 |
|||
|
|||
Component { |
|||
Item { |
|||
signal editorTextChanged |
|||
|
|||
function setText(text) { |
|||
codeEditor.text = text; |
|||
} |
|||
|
|||
function getText() { |
|||
return codeEditor.text; |
|||
} |
|||
|
|||
anchors.fill: parent |
|||
id: contentView |
|||
width: parent.width |
|||
height: parent.height * 0.7 |
|||
Rectangle { |
|||
id: lineColumn |
|||
property int rowHeight: codeEditor.font.pixelSize + 3 |
|||
color: "#202020" |
|||
width: 50 |
|||
height: parent.height |
|||
Column { |
|||
y: -codeEditor.flickableItem.contentY + 4 |
|||
width: parent.width |
|||
Repeater { |
|||
model: Math.max(codeEditor.lineCount + 2, (lineColumn.height/lineColumn.rowHeight)) |
|||
delegate: Text { |
|||
id: text |
|||
color: codeEditor.textColor |
|||
font: codeEditor.font |
|||
width: lineColumn.width - 4 |
|||
horizontalAlignment: Text.AlignRight |
|||
verticalAlignment: Text.AlignVCenter |
|||
height: lineColumn.rowHeight |
|||
renderType: Text.NativeRendering |
|||
text: index + 1 |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
TextArea { |
|||
id: codeEditor |
|||
textColor: "#EEE8D5" |
|||
style: TextAreaStyle { |
|||
backgroundColor: "#002B36" |
|||
} |
|||
|
|||
anchors.left: lineColumn.right |
|||
anchors.right: parent.right |
|||
anchors.top: parent.top |
|||
anchors.bottom: parent.bottom |
|||
wrapMode: TextEdit.NoWrap |
|||
frameVisible: false |
|||
|
|||
height: parent.height |
|||
font.family: "Monospace" |
|||
font.pointSize: 12 |
|||
width: parent.width |
|||
|
|||
tabChangesFocus: false |
|||
Keys.onPressed: { |
|||
if (event.key === Qt.Key_Tab) { |
|||
codeEditor.insert(codeEditor.cursorPosition, "\t"); |
|||
event.accepted = true; |
|||
} |
|||
} |
|||
onTextChanged: { |
|||
editorTextChanged(); |
|||
} |
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,91 @@ |
|||
import QtQuick 2.0 |
|||
import QtQuick.Window 2.0 |
|||
import QtQuick.Layouts 1.0 |
|||
import QtQuick.Controls 1.0 |
|||
import org.ethereum.qml.ProjectModel 1.0 |
|||
|
|||
Item { |
|||
|
|||
property string currentDocumentId: "" |
|||
|
|||
function getDocumentText(documentId) { |
|||
for (i = 0; i < editorListModel.count; i++) { |
|||
if (editorListModel.get(i).documentId === documentId) { |
|||
return editors.itemAt(i).getText(); |
|||
} |
|||
} |
|||
return ""; |
|||
} |
|||
|
|||
function openDocument(document) { |
|||
loadDocument(document); |
|||
currentDocumentId = document.documentId; |
|||
} |
|||
|
|||
function loadDocument(document) { |
|||
for (var i = 0; i < editorListModel.count; i++) |
|||
if (editorListModel.get(i).documentId === document.documentId) |
|||
return; //already open |
|||
|
|||
editorListModel.append(document); |
|||
} |
|||
|
|||
function doLoadDocument(editor, document) { |
|||
var data = fileIo.readFile(document.path); |
|||
if (document.isContract) |
|||
editor.onEditorTextChanged.connect(function() { |
|||
codeModel.registerCodeChange(editor.getText()); |
|||
}); |
|||
editor.setText(data); |
|||
} |
|||
|
|||
Connections { |
|||
target: ProjectModel |
|||
onDocumentOpened: { |
|||
openDocument(document); |
|||
} |
|||
onProjectSaving: { |
|||
for (var i = 0; i < editorListModel.count; i++) |
|||
fileIo.writeFile(editorListModel.get(i).path, editors.itemAt(i).item.getText()); |
|||
} |
|||
onProjectClosed: { |
|||
for (var i = 0; i < editorListModel.count; i++) { |
|||
editors.itemAt(i).visible = false; |
|||
} |
|||
editorListModel.clear(); |
|||
currentDocumentId = ""; |
|||
} |
|||
} |
|||
|
|||
CodeEditor { |
|||
id: codeEditor |
|||
} |
|||
|
|||
Repeater { |
|||
id: editors |
|||
model: editorListModel |
|||
delegate: Loader { |
|||
active: false; |
|||
asynchronous: true |
|||
anchors.fill: parent |
|||
sourceComponent: codeEditor |
|||
visible: (index >= 0 && index < editorListModel.count && currentDocumentId === editorListModel.get(index).documentId) |
|||
onVisibleChanged: { |
|||
loadIfNotLoaded() |
|||
} |
|||
Component.onCompleted: { |
|||
loadIfNotLoaded() |
|||
} |
|||
onLoaded: { doLoadDocument(item, editorListModel.get(index)) } |
|||
|
|||
function loadIfNotLoaded () { |
|||
if(visible && !active) { |
|||
active = true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
ListModel { |
|||
id: editorListModel |
|||
} |
|||
} |
@ -0,0 +1,93 @@ |
|||
import QtQuick 2.2 |
|||
import QtQuick.Controls 1.2 |
|||
import QtQuick.Layouts 1.1 |
|||
import QtQuick.Window 2.0 |
|||
import QtQuick.Dialogs 1.2 |
|||
|
|||
Window { |
|||
|
|||
modality: Qt.WindowModal |
|||
|
|||
width: 640 |
|||
height: 120 |
|||
|
|||
visible: false |
|||
|
|||
property alias projectTitle: titleField.text |
|||
readonly property string projectPath: "file://" + pathField.text |
|||
signal accepted |
|||
|
|||
function open() { |
|||
visible = true; |
|||
titleField.focus = true; |
|||
} |
|||
|
|||
function close() { |
|||
visible = false; |
|||
} |
|||
|
|||
GridLayout { |
|||
id: dialogContent |
|||
columns: 2 |
|||
anchors.fill: parent |
|||
anchors.margins: 10 |
|||
rowSpacing: 10 |
|||
columnSpacing: 10 |
|||
|
|||
Label { |
|||
text: qsTr("Title") |
|||
} |
|||
TextField { |
|||
id: titleField |
|||
focus: true |
|||
Layout.fillWidth: true |
|||
} |
|||
|
|||
Label { |
|||
text: qsTr("Path") |
|||
} |
|||
RowLayout { |
|||
TextField { |
|||
id: pathField |
|||
Layout.fillWidth: true |
|||
} |
|||
Button { |
|||
text: qsTr("Browse") |
|||
onClicked: createProjectFileDialog.open() |
|||
} |
|||
} |
|||
|
|||
RowLayout |
|||
{ |
|||
anchors.bottom: parent.bottom |
|||
anchors.right: parent.right; |
|||
|
|||
Button { |
|||
enabled: titleField.text != "" && pathField.text != "" |
|||
text: qsTr("Ok"); |
|||
onClicked: { |
|||
close(); |
|||
accepted(); |
|||
} |
|||
} |
|||
Button { |
|||
text: qsTr("Cancel"); |
|||
onClicked: close(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
FileDialog { |
|||
id: createProjectFileDialog |
|||
visible: false |
|||
title: qsTr("Please choose a path for the project") |
|||
selectFolder: true |
|||
onAccepted: { |
|||
var u = createProjectFileDialog.fileUrl.toString(); |
|||
if (u.indexOf("file://") == 0) |
|||
u = u.substring(7, u.length) |
|||
pathField.text = u; |
|||
} |
|||
} |
|||
Component.onCompleted: pathField.text = fileIo.homePath |
|||
} |
@ -0,0 +1,131 @@ |
|||
import QtQuick 2.0 |
|||
import QtQuick.Window 2.0 |
|||
import QtQuick.Layouts 1.0 |
|||
import QtQuick.Controls 1.0 |
|||
import org.ethereum.qml.ProjectModel 1.0 |
|||
|
|||
Item { |
|||
property bool renameMode: false; |
|||
ColumnLayout { |
|||
anchors.fill: parent |
|||
Text { |
|||
Layout.fillWidth: true |
|||
color: "blue" |
|||
text: ProjectModel.projectData ? ProjectModel.projectData.title : "" |
|||
horizontalAlignment: Text.AlignHCenter |
|||
visible: !ProjectModel.isEmpty; |
|||
} |
|||
ListView { |
|||
id: projectList |
|||
Layout.fillWidth: true |
|||
Layout.fillHeight: true |
|||
|
|||
model: ProjectModel.listModel |
|||
|
|||
delegate: renderDelegate |
|||
highlight: Rectangle { |
|||
color: "lightsteelblue"; |
|||
} |
|||
highlightFollowsCurrentItem: true |
|||
focus: true |
|||
clip: true |
|||
|
|||
onCurrentIndexChanged: { |
|||
if (currentIndex >= 0 && currentIndex < ProjectModel.listModel.count) |
|||
ProjectModel.openDocument(ProjectModel.listModel.get(currentIndex).documentId); |
|||
} |
|||
} |
|||
Menu { |
|||
id: contextMenu |
|||
MenuItem { |
|||
text: qsTr("Rename") |
|||
onTriggered: { |
|||
renameMode = true; |
|||
} |
|||
} |
|||
MenuItem { |
|||
text: qsTr("Delete") |
|||
onTriggered: { |
|||
ProjectModel.removeDocument(projectList.model.get(projectList.currentIndex).documentId); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Component { |
|||
id: renderDelegate |
|||
Item { |
|||
id: wrapperItem |
|||
height: 20 |
|||
width: parent.width |
|||
RowLayout { |
|||
anchors.fill: parent |
|||
visible: !(index === projectList.currentIndex) || !renameMode |
|||
Text { |
|||
id: nameText |
|||
Layout.fillWidth: true |
|||
Layout.fillHeight: true |
|||
text: name |
|||
font.pointSize: 12 |
|||
verticalAlignment: Text.AlignBottom |
|||
} |
|||
} |
|||
|
|||
TextInput { |
|||
id: textInput |
|||
text: nameText.text |
|||
visible: (index === projectList.currentIndex) && renameMode |
|||
MouseArea { |
|||
id: textMouseArea |
|||
anchors.fill: parent |
|||
hoverEnabled: true |
|||
z:2 |
|||
onClicked: { |
|||
console.log("clicked"); |
|||
textInput.forceActiveFocus(); |
|||
} |
|||
} |
|||
|
|||
onVisibleChanged: { |
|||
if (visible) { |
|||
selectAll(); |
|||
forceActiveFocus(); |
|||
} |
|||
} |
|||
|
|||
onAccepted: close(true); |
|||
onCursorVisibleChanged: { |
|||
if (!cursorVisible) |
|||
close(false); |
|||
} |
|||
onFocusChanged: { |
|||
if (!focus) |
|||
close(false); |
|||
} |
|||
function close(accept) { |
|||
renameMode = false; |
|||
if (accept) |
|||
ProjectModel.renameDocument(projectList.model.get(projectList.currentIndex).documentId, textInput.text); |
|||
} |
|||
} |
|||
MouseArea { |
|||
id: mouseArea |
|||
z: 1 |
|||
hoverEnabled: false |
|||
anchors.fill: parent |
|||
acceptedButtons: Qt.LeftButton | Qt.RightButton |
|||
onClicked:{ |
|||
projectList.currentIndex = index; |
|||
if (mouse.button === Qt.RightButton && !projectList.model.get(index).isContract) |
|||
contextMenu.popup(); |
|||
} |
|||
} |
|||
Connections { |
|||
target: ProjectModel |
|||
onProjectLoaded: { |
|||
projectList.currentIndex = 0; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
@ -0,0 +1,109 @@ |
|||
pragma Singleton |
|||
|
|||
import QtQuick 2.0 |
|||
import QtQuick.Window 2.0 |
|||
import QtQuick.Layouts 1.0 |
|||
import QtQuick.Controls 1.0 |
|||
import QtQuick.Dialogs 1.1 |
|||
import Qt.labs.settings 1.0 |
|||
|
|||
import "js/ProjectModel.js" as ProjectModelCode |
|||
|
|||
Item { |
|||
id: projectModel |
|||
|
|||
signal projectClosed |
|||
signal projectLoaded |
|||
signal documentOpened(var document) |
|||
signal documentRemoved(var documentId) |
|||
signal documentUpdated(var documentId) //renamed |
|||
signal projectSaving(var projectData) |
|||
|
|||
property bool isEmpty: (projectData === null) |
|||
readonly property string projectFileName: ".mix" |
|||
|
|||
property bool haveUnsavedChanges: false |
|||
property string projectPath: "" |
|||
property var projectData: null |
|||
property var listModel: projectListModel |
|||
|
|||
//interface |
|||
function saveAll() { ProjectModelCode.saveAll(); } |
|||
function createProject() { ProjectModelCode.createProject(); } |
|||
function browseProject() { ProjectModelCode.browseProject(); } |
|||
function closeProject() { ProjectModelCode.closeProject(); } |
|||
function saveProject() { ProjectModelCode.saveProject(); } |
|||
function loadProject(path) { ProjectModelCode.loadProject(path); } |
|||
function addExistingFile() { ProjectModelCode.addExistingFile(); } |
|||
function newHtmlFile() { ProjectModelCode.newHtmlFile(); } |
|||
function newJsFile() { ProjectModelCode.newJsFile(); } |
|||
//function newContract() { ProjectModelCode.newContract(); } |
|||
function openDocument(documentId) { ProjectModelCode.openDocument(documentId); } |
|||
function renameDocument(documentId, newName) { ProjectModelCode.renameDocument(documentId, newName); } |
|||
function removeDocument(documentId) { ProjectModelCode.removeDocument(documentId); } |
|||
|
|||
Connections { |
|||
target: appContext |
|||
onAppLoaded: { |
|||
if (projectSettings.lastProjectPath) |
|||
loadProject(projectSettings.lastProjectPath) |
|||
} |
|||
} |
|||
|
|||
NewProjectDialog { |
|||
id: newProjectDialog |
|||
visible: false |
|||
onAccepted: { |
|||
var title = newProjectDialog.projectTitle; |
|||
var path = newProjectDialog.projectPath; |
|||
ProjectModelCode.doCreateProject(title, path); |
|||
} |
|||
} |
|||
|
|||
MessageDialog { |
|||
id: saveMessageDialog |
|||
title: qsTr("Project") |
|||
text: qsTr("Do you want to save changes?") |
|||
standardButtons: StandardButton.Ok | StandardButton.Cancel |
|||
icon: StandardIcon.Question |
|||
onAccepted: { |
|||
projectModel.saveAll(); |
|||
ProjectModelCode.doCloseProject(); |
|||
} |
|||
onRejected: { |
|||
ProjectModelCode.doCloseProject(); |
|||
} |
|||
} |
|||
|
|||
ListModel { |
|||
id: projectListModel |
|||
} |
|||
|
|||
Settings { |
|||
id: projectSettings |
|||
property string lastProjectPath; |
|||
} |
|||
|
|||
FileDialog { |
|||
id: openProjectFileDialog |
|||
visible: false |
|||
title: qsTr("Open a project") |
|||
selectFolder: true |
|||
onAccepted: { |
|||
var path = openProjectFileDialog.fileUrl.toString(); |
|||
path += "/"; |
|||
loadProject(path); |
|||
} |
|||
} |
|||
|
|||
FileDialog { |
|||
id: addExistingFileDialog |
|||
visible: false |
|||
title: qsTr("Add a file") |
|||
selectFolder: false |
|||
onAccepted: { |
|||
var paths = addExistingFileDialog.fileUrls; |
|||
ProjectModelCode.doAddExistingFiles(paths); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,81 @@ |
|||
import QtQuick 2.0 |
|||
import QtQuick.Window 2.0 |
|||
import QtQuick.Layouts 1.0 |
|||
import QtQuick.Controls 1.0 |
|||
import QtQuick.Controls.Styles 1.1 |
|||
|
|||
Component { |
|||
Item { |
|||
signal editorTextChanged |
|||
|
|||
function setText(text) { |
|||
codeEditor.text = text; |
|||
} |
|||
|
|||
function getText() { |
|||
return codeEditor.text; |
|||
} |
|||
|
|||
anchors.fill: parent |
|||
id: contentView |
|||
width: parent.width |
|||
height: parent.height * 0.7 |
|||
Rectangle { |
|||
id: lineColumn |
|||
property int rowHeight: codeEditor.font.pixelSize + 3 |
|||
color: "#202020" |
|||
width: 50 |
|||
height: parent.height |
|||
Column { |
|||
y: -codeEditor.flickableItem.contentY + 4 |
|||
width: parent.width |
|||
Repeater { |
|||
model: Math.max(codeEditor.lineCount + 2, (lineColumn.height/lineColumn.rowHeight)) |
|||
delegate: Text { |
|||
id: text |
|||
color: codeEditor.textColor |
|||
font: codeEditor.font |
|||
width: lineColumn.width - 4 |
|||
horizontalAlignment: Text.AlignRight |
|||
verticalAlignment: Text.AlignVCenter |
|||
height: lineColumn.rowHeight |
|||
renderType: Text.NativeRendering |
|||
text: index + 1 |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
TextArea { |
|||
id: codeEditor |
|||
textColor: "#EEE8D5" |
|||
style: TextAreaStyle { |
|||
backgroundColor: "#002B36" |
|||
} |
|||
|
|||
anchors.left: lineColumn.right |
|||
anchors.right: parent.right |
|||
anchors.top: parent.top |
|||
anchors.bottom: parent.bottom |
|||
wrapMode: TextEdit.NoWrap |
|||
frameVisible: false |
|||
|
|||
height: parent.height |
|||
font.family: "Monospace" |
|||
font.pointSize: 12 |
|||
width: parent.width |
|||
|
|||
tabChangesFocus: false |
|||
Keys.onPressed: { |
|||
if (event.key === Qt.Key_Tab) { |
|||
codeEditor.insert(codeEditor.cursorPosition, "\t"); |
|||
event.accepted = true; |
|||
} |
|||
} |
|||
onTextChanged: { |
|||
editorTextChanged(); |
|||
} |
|||
|
|||
} |
|||
} |
|||
} |
@ -0,0 +1,204 @@ |
|||
/* |
|||
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 <http://www.gnu.org/licenses/>.
|
|||
*/ |
|||
/** @file ProjectModel.js |
|||
* @author Arkadiy Paronyan arkadiy@ethdev.com |
|||
* @date 2015 |
|||
* Ethereum IDE client. |
|||
*/ |
|||
|
|||
function saveAll() { |
|||
saveProject(); |
|||
} |
|||
|
|||
function createProject() { |
|||
newProjectDialog.open(); |
|||
} |
|||
|
|||
function browseProject() { |
|||
openProjectFileDialog.open(); |
|||
} |
|||
|
|||
function closeProject() { |
|||
if (!isEmpty) { |
|||
if (haveUnsavedChanges) |
|||
saveMessageDialog.open(); |
|||
else |
|||
doCloseProject(); |
|||
} |
|||
} |
|||
|
|||
function saveProject() { |
|||
if (!isEmpty) { |
|||
projectSaving(projectData); |
|||
var json = JSON.stringify(projectData); |
|||
var projectFile = projectPath + projectFileName; |
|||
fileIo.writeFile(projectFile, json); |
|||
} |
|||
} |
|||
|
|||
function loadProject(path) { |
|||
closeProject(); |
|||
console.log("loading project at " + path); |
|||
var projectFile = path + projectFileName; |
|||
var json = fileIo.readFile(projectFile); |
|||
projectData = JSON.parse(json); |
|||
if (!projectData.title) { |
|||
var parts = path.split("/"); |
|||
projectData.title = parts[parts.length - 2]; |
|||
} |
|||
projectPath = path; |
|||
if (!projectData.files) |
|||
projectData.files = []; |
|||
|
|||
for(var i = 0; i < projectData.files.length; i++) { |
|||
addFile(projectData.files[i]); |
|||
} |
|||
projectSettings.lastProjectPath = path; |
|||
projectLoaded(); |
|||
} |
|||
|
|||
function addExistingFile() { |
|||
addExistingFileDialog.open(); |
|||
} |
|||
|
|||
function addProjectFiles(files) { |
|||
for(var i = 0; i < files.length; i++) |
|||
addFile(files[i]); |
|||
} |
|||
|
|||
function addFile(fileName) { |
|||
var p = projectPath + fileName; |
|||
var extension = fileName.substring(fileName.length - 4, fileName.length); |
|||
var isContract = extension === ".sol"; |
|||
var fileData = { |
|||
contract: false, |
|||
path: p, |
|||
name: isContract ? "Contract" : fileName, |
|||
documentId: fileName, |
|||
isText: isContract || extension === ".html" || extension === ".js", |
|||
isContract: fileData, |
|||
}; |
|||
|
|||
projectListModel.append(fileData); |
|||
} |
|||
|
|||
function findDocument(documentId) |
|||
{ |
|||
for (var i = 0; i < projectListModel.count; i++) |
|||
if (projectListModel.get(i).documentId === documentId) |
|||
return i; |
|||
console.error("Cant find document " + documentId); |
|||
return -1; |
|||
} |
|||
|
|||
function openDocument(documentId) { |
|||
documentOpened(projectListModel.get(findDocument(documentId))); |
|||
} |
|||
|
|||
function doCloseProject() { |
|||
console.log("closing project"); |
|||
projectListModel.clear(); |
|||
projectPath = ""; |
|||
projectData = null; |
|||
projectClosed(); |
|||
} |
|||
|
|||
function doCreateProject(title, path) { |
|||
closeProject(); |
|||
console.log("creating project " + title + " at " + path); |
|||
if (path[path.length - 1] !== "/") |
|||
path += "/"; |
|||
var dirPath = path + title + "/"; |
|||
fileIo.makeDir(dirPath); |
|||
var projectFile = dirPath + projectFileName; |
|||
|
|||
var indexFile = "index.html"; |
|||
var contractsFile = "contracts.sol"; |
|||
var projectData = { |
|||
title: title, |
|||
files: [ indexFile, contractsFile ] |
|||
}; |
|||
//TODO: copy from template
|
|||
fileIo.writeFile(dirPath + indexFile, "<html></html>"); |
|||
fileIo.writeFile(dirPath + contractsFile, "contract MyContract {\n }\n"); |
|||
var json = JSON.stringify(projectData); |
|||
fileIo.writeFile(projectFile, json); |
|||
loadProject(dirPath); |
|||
} |
|||
|
|||
function doAddExistingFiles(files) { |
|||
for(var i = 0; i < files.length; i++) { |
|||
var sourcePath = files[i]; |
|||
console.log(sourcePath); |
|||
var sourceFileName = sourcePath.substring(sourcePath.lastIndexOf("/") + 1, sourcePath.length); |
|||
console.log(sourceFileName); |
|||
var destPath = projectPath + sourceFileName; |
|||
console.log(destPath); |
|||
if (sourcePath !== destPath) |
|||
fileIo.copyFile(sourcePath, destPath); |
|||
addFile(sourceFileName); |
|||
} |
|||
} |
|||
|
|||
function renameDocument(documentId, newName) { |
|||
var i = findDocument(documentId); |
|||
var document = projectListModel.get(i); |
|||
if (!document.isContract) { |
|||
var sourcePath = document.path; |
|||
var destPath = projectPath + newName; |
|||
fileIo.moveFile(sourcePath, destPath); |
|||
document.path = destPath; |
|||
document.name = newName; |
|||
projectListModel.set(i, document); |
|||
documentUpdated(documentId); |
|||
} |
|||
} |
|||
|
|||
function removeDocument(documentId) { |
|||
var i = findDocument(documentId); |
|||
var document = projectListModel.get(i); |
|||
if (!document.isContract) { |
|||
projectListModel.remove(i); |
|||
documentUpdated(documentId); |
|||
} |
|||
} |
|||
|
|||
function newHtmlFile() { |
|||
createAndAddFile("page", "html", "<html>\n</html>"); |
|||
} |
|||
|
|||
function newJsFile() { |
|||
createAndAddFile("script", "js", "function foo() {\n}\n"); |
|||
} |
|||
|
|||
function createAndAddFile(name, extension, content) { |
|||
var fileName = generateFileName(name, extension); |
|||
var filePath = projectPath + fileName; |
|||
fileIo.writeFile(filePath, content); |
|||
addFile(fileName); |
|||
} |
|||
|
|||
function generateFileName(name, extension) { |
|||
var i = 1; |
|||
do { |
|||
var fileName = name + i + "." + extension; |
|||
var filePath = projectPath + fileName; |
|||
i++; |
|||
} while (fileIo.fileExists(filePath)); |
|||
return fileName |
|||
} |
|||
|
Loading…
Reference in new issue