Browse Source

Vastly cleaner Transact dialog code & mechanisms. Works better for lower

balances.
cl-refactor
Gav Wood 10 years ago
parent
commit
7738cb7d4f
  1. 386
      alethzero/Transact.cpp
  2. 2
      alethzero/Transact.h

386
alethzero/Transact.cpp

@ -25,6 +25,7 @@
#include "Transact.h" #include "Transact.h"
#include <fstream> #include <fstream>
#include <boost/algorithm/string.hpp>
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
#include <liblll/Compiler.h> #include <liblll/Compiler.h>
@ -135,21 +136,6 @@ void Transact::updateFee()
ui->total->setPalette(p); ui->total->setPalette(p);
} }
string Transact::getFunctionHashes(dev::solidity::CompilerStack const& _compiler, string const& _contractName)
{
string ret = "";
auto const& contract = _compiler.getContractDefinition(_contractName);
auto interfaceFunctions = contract.getInterfaceFunctions();
for (auto const& it: interfaceFunctions)
{
ret += it.first.abridged();
ret += " :";
ret += it.second->getDeclaration().getName() + "\n";
}
return ret;
}
void Transact::on_destination_currentTextChanged(QString) void Transact::on_destination_currentTextChanged(QString)
{ {
if (ui->destination->currentText().size() && ui->destination->currentText() != "(Create Contract)") if (ui->destination->currentText().size() && ui->destination->currentText() != "(Create Contract)")
@ -182,202 +168,222 @@ static std::string toString(TransactionException _te)
} }
} }
void Transact::rejigData() static string getFunctionHashes(dev::solidity::CompilerStack const& _compiler, string const& _contractName)
{ {
if (!ethereum()) string ret = "";
return; auto const& contract = _compiler.getContractDefinition(_contractName);
m_allGood = true; auto interfaceFunctions = contract.getInterfaceFunctions();
if (isCreation())
for (auto const& it: interfaceFunctions)
{ {
string src = ui->data->toPlainText().toStdString(); ret += it.first.abridged();
vector<string> errors; ret += " :";
QString lll; ret += it.second->getDeclaration().getName() + "\n";
QString solidity; }
if (src.find_first_not_of("1234567890abcdefABCDEF") == string::npos && src.size() % 2 == 0) return ret;
m_data = fromHex(src); }
else if (sourceIsSolidity(src))
static tuple<vector<string>, bytes, string> userInputToCode(string const& _user, bool _opt)
{
string lll;
string solidity;
bytes data;
vector<string> errors;
if (_user.find_first_not_of("1234567890abcdefABCDEF\n\t ") == string::npos && _user.size() % 2 == 0)
{
std::string u = _user;
boost::replace_all_copy(u, "\n", "");
boost::replace_all_copy(u, "\t", "");
boost::replace_all_copy(u, " ", "");
data = fromHex(u);
}
else if (sourceIsSolidity(_user))
{
dev::solidity::CompilerStack compiler(true);
try
{ {
dev::solidity::CompilerStack compiler(true);
try
{
// compiler.addSources(dev::solidity::StandardSources); // compiler.addSources(dev::solidity::StandardSources);
m_data = compiler.compile(src, ui->optimize->isChecked()); data = compiler.compile(_user, _opt);
solidity = "<h4>Solidity</h4>"; solidity = "<h4>Solidity</h4>";
solidity += "<pre>var " + QString::fromStdString(compiler.defaultContractName()) + " = web3.eth.contractFromAbi(" + QString::fromStdString(compiler.getInterface()).replace(QRegExp("\\s"), "").toHtmlEscaped() + ");</pre>"; solidity += "<pre>var " + compiler.defaultContractName() + " = web3.eth.contract(" + QString::fromStdString(compiler.getInterface()).replace(QRegExp("\\s"), "").toHtmlEscaped().toStdString() + ");</pre>";
solidity += "<pre>" + QString::fromStdString(compiler.getSolidityInterface()).toHtmlEscaped() + "</pre>"; solidity += "<pre>" + QString::fromStdString(compiler.getSolidityInterface()).toHtmlEscaped().toStdString() + "</pre>";
solidity += "<pre>" + QString::fromStdString(getFunctionHashes(compiler)).toHtmlEscaped() + "</pre>"; solidity += "<pre>" + QString::fromStdString(getFunctionHashes(compiler, "")).toHtmlEscaped().toStdString() + "</pre>";
}
catch (dev::Exception const& exception)
{
ostringstream error;
solidity::SourceReferenceFormatter::printExceptionInformation(error, exception, "Error", compiler);
errors.push_back("Solidity: " + error.str());
}
catch (...)
{
errors.push_back("Solidity: Uncaught exception");
}
} }
catch (dev::Exception const& exception)
{
ostringstream error;
solidity::SourceReferenceFormatter::printExceptionInformation(error, exception, "Error", compiler);
errors.push_back("Solidity: " + error.str());
}
catch (...)
{
errors.push_back("Solidity: Uncaught exception");
}
}
#ifndef _MSC_VER #ifndef _MSC_VER
else if (sourceIsSerpent(src)) else if (sourceIsSerpent(_user))
{
try
{ {
try data = dev::asBytes(::compile(_user));
{
m_data = dev::asBytes(::compile(src));
}
catch (string const& err)
{
errors.push_back("Serpent " + err);
}
} }
#endif catch (string const& err)
else
{ {
m_data = compileLLL(src, ui->optimize->isChecked(), &errors); errors.push_back("Serpent " + err);
if (errors.empty())
{
auto asmcode = compileLLLToAsm(src, ui->optimize->isChecked());
lll = "<h4>LLL</h4><pre>" + QString::fromStdString(asmcode).toHtmlEscaped() + "</pre>";
}
} }
QString htmlErrors; }
qint64 gasNeeded = 0; #endif
if (errors.size()) else
{
data = compileLLL(_user, _opt, &errors);
if (errors.empty())
{ {
htmlErrors = "<h4>Errors</h4>"; auto asmcode = compileLLLToAsm(_user, _opt);
for (auto const& i: errors) lll = "<h4>LLL</h4><pre>" + QString::fromStdString(asmcode).toHtmlEscaped().toStdString() + "</pre>";
htmlErrors.append("<div style=\"border-left: 6px solid #c00; margin-top: 2px\">" + QString::fromStdString(i).toHtmlEscaped() + "</div>");
m_allGood = false;
} }
}
return make_tuple(errors, data, lll + solidity);
}
string Transact::natspecNotice(Address _to, bytes const& _data)
{
if (ethereum()->codeAt(_to, PendingBlock).size())
{
string userNotice = m_natSpecDB->getUserNotice(ethereum()->postState().codeHash(_to), _data);
if (userNotice.empty())
return "Destination contract unknown.";
else else
{ {
if (true) NatspecExpressionEvaluator evaluator;
{ return evaluator.evalExpression(QString::fromStdString(userNotice)).toStdString();
auto s = findSecret(value() + ethereum()->gasLimitRemaining() * gasPrice());
if (!s)
htmlErrors += "<div class=\"error\"><span class=\"icon\">WARNING</span> No single account contains a comfortable amount of gas.</div>";
// TODO: use account with most balance anyway.
else
{
ExecutionResult er = ethereum()->create(s, value(), m_data, ethereum()->gasLimitRemaining(), gasPrice());
gasNeeded = (qint64)er.gasUsed;
auto base = (qint64)Interface::txGas(m_data, 0);
htmlErrors += QString("<div class=\"info\"><span class=\"icon\">INFO</span> Gas required: %1 base, %2 init</div>").arg(base).arg((qint64)er.gasUsed - base);
if (er.excepted != TransactionException::None)
{
htmlErrors += "<div class=\"error\"><span class=\"icon\">ERROR</span> " + QString::fromStdString(toString(er.excepted)) + "</div>";
m_allGood = false;
}
if (er.codeDeposit == CodeDeposit::Failed)
{
htmlErrors += "<div class=\"error\"><span class=\"icon\">ERROR</span> Code deposit failed due to insufficient gas</div>";
m_allGood = false;
}
}
}
else
gasNeeded = (qint64)Interface::txGas(m_data, 0);
} }
}
else
return "Destination not a contract.";
}
void Transact::rejigData()
{
if (!ethereum())
return;
ui->code->setHtml(htmlErrors + lll + solidity + "<h4>Code</h4>" + QString::fromStdString(disassemble(m_data)).toHtmlEscaped() + "<h4>Hex</h4>" Div(Mono) + QString::fromStdString(toHex(m_data)) + "</div>"); // Determine how much balance we have to play with...
auto s = findSecret(value() + ethereum()->gasLimitRemaining() * gasPrice());
auto b = ethereum()->balanceAt(KeyPair(s).address(), PendingBlock);
m_allGood = true;
QString htmlInfo;
if (ui->gas->value() == ui->gas->minimum()) auto bail = [&](QString he) {
m_allGood = false;
ui->send->setEnabled(false);
ui->code->setHtml(he + htmlInfo);
};
// Determine m_info.
if (isCreation())
{
string info;
vector<string> errors;
tie(errors, m_data, info) = userInputToCode(ui->data->toPlainText().toStdString(), ui->optimize->isChecked());
if (errors.size())
{ {
ui->gas->setMinimum(gasNeeded); // Errors determining transaction data (i.e. init code). Bail.
ui->gas->setValue(gasNeeded); QString htmlErrors;
for (auto const& i: errors)
htmlErrors.append("<div class=\"error\"><span class=\"icon\">ERROR</span> " + QString::fromStdString(i).toHtmlEscaped() + "</div>");
bail(htmlErrors);
return;
} }
else htmlInfo = QString::fromStdString(info) + "<h4>Code</h4>" + QString::fromStdString(disassemble(m_data)).toHtmlEscaped();
ui->gas->setMinimum(gasNeeded);
// if (!ui->gas->isEnabled())
// ui->gas->setValue(m_backupGas);
ui->gas->setEnabled(true);
// if (ui->gas->value() == ui->gas->minimum() && !src.empty())
// ui->gas->setValue((int)(m_ethereum->postState().gasLimitRemaining() / 10));
} }
else else
{ {
m_allGood = true;
auto base = (qint64)Interface::txGas(m_data, 0);
m_data = parseData(ui->data->toPlainText().toStdString()); m_data = parseData(ui->data->toPlainText().toStdString());
auto to = m_context->fromString(ui->destination->currentText()); htmlInfo = "<h4>Dump</h4>" + QString::fromStdString(dev::memDump(m_data, 8, true));
QString natspec; }
QString errs;
if (ethereum()->codeAt(to, PendingBlock).size())
{
string userNotice = m_natSpecDB->getUserNotice(ethereum()->postState().codeHash(to), m_data);
if (userNotice.empty())
natspec = "Destination contract unknown.";
else
{
NatspecExpressionEvaluator evaluator;
natspec = evaluator.evalExpression(QString::fromStdString(userNotice));
}
qint64 gasNeeded = 0; htmlInfo += "<h4>Hex</h4>" + QString(Div(Mono)) + QString::fromStdString(toHex(m_data)) + "</div>";
if (true)
{
auto s = findSecret(value() + ethereum()->gasLimitRemaining() * gasPrice());
if (!s)
errs += "<div class=\"error\"><span class=\"icon\">ERROR</span> No single account contains enough gas.</div>";
// TODO: use account with most balance anyway.
else
{
ExecutionResult er = ethereum()->call(s, value(), to, m_data, ethereum()->gasLimitRemaining(), gasPrice());
gasNeeded = (qint64)er.gasUsed;
errs += QString("<div class=\"info\"><span class=\"icon\">INFO</span> Gas required: %1 base, %2 exec</div>").arg(base).arg((qint64)er.gasUsed - base);
if (er.excepted != TransactionException::None)
{
errs += "<div class=\"error\"><span class=\"icon\">ERROR</span> " + QString::fromStdString(toString(er.excepted)) + "</div>";
m_allGood = false;
}
}
}
else
gasNeeded = (qint64)Interface::txGas(m_data, 0);
if (ui->gas->value() == ui->gas->minimum()) // Determine the minimum amount of gas we need to play...
{ qint64 baseGas = (qint64)Interface::txGas(m_data, 0);
ui->gas->setMinimum(gasNeeded); qint64 gasNeeded = 0;
ui->gas->setValue(gasNeeded);
}
else
ui->gas->setMinimum(gasNeeded);
if (!ui->gas->isEnabled()) if (b < value() + baseGas * gasPrice())
ui->gas->setValue(m_backupGas); {
ui->gas->setEnabled(true); // Not enough - bail.
} bail("<div class=\"error\"><span class=\"icon\">ERROR</span> No single account contains enough for paying even the basic amount of gas required.</div>");
else return;
{ }
natspec += "Destination not a contract."; else
if (ui->gas->isEnabled()) gasNeeded = min<qint64>((qint64)ethereum()->gasLimitRemaining(), (qint64)((b - value()) / gasPrice()));
m_backupGas = ui->gas->value();
ui->gas->setMinimum(base); // Dry-run execution to determine gas requirement and any execution errors
ui->gas->setValue(base); Address to;
ui->gas->setEnabled(false); ExecutionResult er;
} if (isCreation())
ui->code->setHtml(errs + "<h3>NatSpec</h3>" + natspec + "<h3>Dump</h3>" + QString::fromStdString(dev::memDump(m_data, 8, true)) + "<h3>Hex</h3>" + Div(Mono) + QString::fromStdString(toHex(m_data)) + "</div>"); er = ethereum()->create(s, value(), m_data, gasNeeded, gasPrice());
else
{
to = m_context->fromString(ui->destination->currentText());
er = ethereum()->call(s, value(), to, m_data, ethereum()->gasLimitRemaining(), gasPrice());
}
gasNeeded = (qint64)er.gasUsed;
htmlInfo = QString("<div class=\"info\"><span class=\"icon\">INFO</span> Gas required: %1 total = %2 base, %3 exec</div>").arg(gasNeeded).arg(baseGas).arg(gasNeeded - baseGas) + htmlInfo;
if (er.excepted != TransactionException::None)
{
bail("<div class=\"error\"><span class=\"icon\">ERROR</span> " + QString::fromStdString(toString(er.excepted)) + "</div>");
return;
} }
if (er.codeDeposit == CodeDeposit::Failed)
{
bail("<div class=\"error\"><span class=\"icon\">ERROR</span> Code deposit failed due to insufficient gas</div>");
return;
}
// Add Natspec information
if (!isCreation())
htmlInfo = "<div class=\"info\"><span class=\"icon\">INFO</span> " + QString::fromStdString(natspecNotice(to, m_data)).toHtmlEscaped() + "</div>" + htmlInfo;
// Update gas
if (ui->gas->value() == ui->gas->minimum())
{
ui->gas->setMinimum(gasNeeded);
ui->gas->setValue(gasNeeded);
}
else
ui->gas->setMinimum(gasNeeded);
updateFee(); updateFee();
ui->code->setHtml(htmlInfo);
ui->send->setEnabled(m_allGood); ui->send->setEnabled(m_allGood);
} }
Secret Transact::findSecret(u256 _totalReq) const Secret Transact::findSecret(u256 _totalReq) const
{ {
if (ethereum()) if (!ethereum())
for (auto const& i: m_myKeys) return Secret();
if (ethereum()->balanceAt(i.address(), PendingBlock) >= _totalReq)
return i.secret(); Secret best;
return Secret(); u256 bestBalance = 0;
for (auto const& i: m_myKeys)
{
auto b = ethereum()->balanceAt(i.address(), PendingBlock);
if (b >= _totalReq)
return i.secret();
if (b > bestBalance)
bestBalance = b, best = i.secret();
}
return best;
} }
void Transact::on_send_clicked() void Transact::on_send_clicked()
{ {
Secret s = findSecret(value() + fee()); Secret s = findSecret(value() + fee());
if (!s) auto b = ethereum()->balanceAt(KeyPair(s).address(), PendingBlock);
if (!s || b < value() + fee())
{ {
QMessageBox::critical(this, "Transaction Failed", "Couldn't make transaction: no single account contains at least the required amount."); QMessageBox::critical(this, "Transaction Failed", "Couldn't make transaction: no single account contains at least the required amount.");
return; return;
@ -400,9 +406,7 @@ void Transact::on_send_clicked()
m_natSpecDB->add(contractHash, compiler.getMetadata(s, dev::solidity::DocumentationType::NatspecUser)); m_natSpecDB->add(contractHash, compiler.getMetadata(s, dev::solidity::DocumentationType::NatspecUser));
} }
} }
catch (...) catch (...) {}
{
}
} }
else else
ethereum()->submitTransaction(s, value(), m_context->fromString(ui->destination->currentText()), m_data, ui->gas->value(), gasPrice()); ethereum()->submitTransaction(s, value(), m_context->fromString(ui->destination->currentText()), m_data, ui->gas->value(), gasPrice());
@ -411,24 +415,24 @@ void Transact::on_send_clicked()
void Transact::on_debug_clicked() void Transact::on_debug_clicked()
{ {
Secret s = findSecret(value() + fee());
auto b = ethereum()->balanceAt(KeyPair(s).address(), PendingBlock);
if (!s || b < value() + fee())
{
QMessageBox::critical(this, "Transaction Failed", "Couldn't make transaction: no single account contains at least the required amount.");
return;
}
try try
{ {
u256 totalReq = value() + fee(); State st(ethereum()->postState());
for (auto i: m_myKeys) Transaction t = isCreation() ?
if (ethereum()->balanceAt(i.address()) >= totalReq) Transaction(value(), gasPrice(), ui->gas->value(), m_data, st.transactionsFrom(dev::toAddress(s)), s) :
{ Transaction(value(), gasPrice(), ui->gas->value(), m_context->fromString(ui->destination->currentText()), m_data, st.transactionsFrom(dev::toAddress(s)), s);
State st(ethereum()->postState()); Debugger dw(m_context, this);
Secret s = i.secret(); Executive e(st, ethereum()->blockChain(), 0);
Transaction t = isCreation() ? dw.populate(e, t);
Transaction(value(), gasPrice(), ui->gas->value(), m_data, st.transactionsFrom(dev::toAddress(s)), s) : dw.exec();
Transaction(value(), gasPrice(), ui->gas->value(), m_context->fromString(ui->destination->currentText()), m_data, st.transactionsFrom(dev::toAddress(s)), s);
Debugger dw(m_context, this);
Executive e(st, ethereum()->blockChain(), 0);
dw.populate(e, t);
dw.exec();
return;
}
QMessageBox::critical(this, "Transaction Failed", "Couldn't make transaction: no single account contains at least the required amount.");
} }
catch (dev::Exception const& _e) catch (dev::Exception const& _e)
{ {

2
alethzero/Transact.h

@ -68,7 +68,7 @@ private:
dev::u256 value() const; dev::u256 value() const;
dev::u256 gasPrice() const; dev::u256 gasPrice() const;
std::string getFunctionHashes(dev::solidity::CompilerStack const& _compiler, std::string const& _contractName = std::string()); std::string natspecNotice(dev::Address _to, dev::bytes const& _data);
dev::Secret findSecret(dev::u256 _totalReq) const; dev::Secret findSecret(dev::u256 _totalReq) const;
Ui::Transact* ui = nullptr; Ui::Transact* ui = nullptr;

Loading…
Cancel
Save