diff --git a/alethzero/Main.ui b/alethzero/Main.ui
index 6e79403aa..975ac84ee 100644
--- a/alethzero/Main.ui
+++ b/alethzero/Main.ui
@@ -6,8 +6,8 @@
0
0
- 1504
- 798
+ 1711
+ 1138
@@ -83,11 +83,6 @@
-
-
- Tab 2
-
-
@@ -99,7 +94,7 @@
0
0
- 1504
+ 1711
20
@@ -131,9 +126,18 @@
+
+
@@ -403,7 +407,7 @@
- 442
+ 510
360
@@ -437,29 +441,26 @@
- -
-
-
- &Amount
-
-
- value
+
-
+
+
+ Qt::Vertical
+
+
+
+ Qt::NoFocus
+
+
- -
-
-
- -
-
-
-
-
-
- 430000000
+
-
+
+
+ &Gas
-
- 0
+
+ gas
@@ -479,32 +480,6 @@
- -
-
-
- Qt::Vertical
-
-
-
-
- Qt::NoFocus
-
-
-
-
- -
-
-
- @
-
-
- 1
-
-
- 430000000
-
-
-
-
@@ -521,21 +496,11 @@
- -
-
-
- -
-
-
- &Gas
-
-
- gas
-
-
+
-
+
- -
-
+
-
+
0
@@ -545,28 +510,34 @@
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
- -
-
-
- &Send
+
-
+
+
+ -
+
+
+ @
+
+
+ 1
+
+
+ 430000000
- -
-
+
-
+
-
- 0
+
+ 1
0
-
-
+
+ (Create Contract)
@@ -583,16 +554,49 @@
- -
-
+
-
+
+
+ &Execute
+
+
+
+ -
+
+
+
+
+
+ 430000000
+
+
+ 0
+
+
+
+ -
+
+
+ &Amount
+
+
+ value
+
+
+
+ -
+
-
- 1
+
+ 0
0
-
- (Create Contract)
+
+
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
@@ -820,6 +824,87 @@
+
+
+ QDockWidget::DockWidgetFeatureMask
+
+
+ Debugger
+
+
+ 8
+
+
+
+ false
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ 1
+ 0
+
+
+
+ Qt::Vertical
+
+
+
+ QAbstractItemView::NoSelection
+
+
+
+
+ true
+
+
+ Qt::NoTextInteraction
+
+
+
+
+ true
+
+
+ Qt::NoTextInteraction
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+
&Quit
@@ -875,6 +960,36 @@
&Preview
+
+
+ true
+
+
+ &Debug EVM Execution
+
+
+
+
+ false
+
+
+ &Single Step
+
+
+ F10
+
+
+
+
+ false
+
+
+ &Continue
+
+
+ F5
+
+
diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp
index 14b227e27..f55115a04 100644
--- a/alethzero/MainWin.cpp
+++ b/alethzero/MainWin.cpp
@@ -9,6 +9,7 @@
#include
#include
#include
+#include
#include "BuildInfo.h"
#include "MainWin.h"
#include "ui_Main.h"
@@ -31,6 +32,7 @@ using eth::PeerInfo;
using eth::RLP;
using eth::Secret;
using eth::Transaction;
+using eth::Executive;
// functions
using eth::toHex;
@@ -702,8 +704,8 @@ void Main::on_contracts_currentItemChanged()
auto h = h160((byte const*)hba.data(), h160::ConstructFromPointer);
stringstream s;
- auto mem = state().contractStorage(h);
- for (auto const& i: mem)
+ auto storage = state().contractStorage(h);
+ for (auto const& i: storage)
s << "@" << showbase << hex << i.first << " " << showbase << hex << i.second << "
";
s << "Body Code
" << disassemble(state().contractCode(h));
ui->contractInfo->appendHtml(QString::fromStdString(s.str()));
@@ -929,11 +931,42 @@ void Main::on_send_clicked()
{
m_client->unlock();
Secret s = i.secret();
- if (isCreation())
- m_client->transact(s, value(), m_data, m_init, ui->gas->value(), gasPrice());
+ if (ui->enableDebug->isChecked())
+ {
+ m_client->lock();
+ m_executiveState = state();
+ m_client->unlock();
+ m_currentExecution = unique_ptr(new Executive(m_executiveState));
+ Transaction t;
+ t.nonce = m_executiveState.transactionsFrom(toAddress(s));
+ t.value = value();
+ t.gasPrice = gasPrice();
+ t.gas = ui->gas->value();
+ t.data = m_data;
+ if (isCreation())
+ {
+ t.receiveAddress = Address();
+ t.init = m_init;
+ }
+ else
+ {
+ t.receiveAddress = fromString(ui->destination->text());
+ t.data = m_data;
+ }
+ t.sign(s);
+ auto r = t.rlp();
+ m_currentExecution->setup(&r);
+ initDebugger();
+ updateDebugger();
+ }
else
- m_client->transact(s, value(), fromString(ui->destination->text()), m_data, ui->gas->value(), gasPrice());
- refresh();
+ {
+ if (isCreation())
+ m_client->transact(s, value(), m_data, m_init, ui->gas->value(), gasPrice());
+ else
+ m_client->transact(s, value(), fromString(ui->destination->text()), m_data, ui->gas->value(), gasPrice());
+ refresh();
+ }
return;
}
m_client->unlock();
@@ -946,6 +979,102 @@ void Main::on_create_triggered()
m_keysChanged = true;
}
+void Main::on_enableDebug_triggered()
+{
+ ui->debugPanel->setEnabled(ui->enableDebug->isChecked());
+ ui->send->setText(ui->enableDebug->isChecked() ? "D&ebug" : "&Execute");
+}
+
+void Main::on_debugStep_triggered()
+{
+ if (!m_currentExecution)
+ return;
+ if (m_currentExecution->go(1))
+ debugFinished();
+ else
+ updateDebugger();
+}
+
+void Main::on_debugContinue_triggered()
+{
+ if (!m_currentExecution)
+ return;
+ if (m_currentExecution->go())
+ debugFinished();
+ else
+ updateDebugger();
+}
+
+void Main::debugFinished()
+{
+ m_currentExecution.reset();
+ ui->debugCode->clear();
+ ui->debugStack->clear();
+ ui->debugMemory->setHtml("");
+ ui->debugStorage->setHtml("");
+ ui->debugStateInfo->setText("");
+ ui->send->setEnabled(true);
+ ui->enableDebug->setEnabled(true);
+ ui->debugStep->setEnabled(false);
+ ui->debugContinue->setEnabled(false);
+ ui->debugPanel->setEnabled(false);
+}
+
+void Main::initDebugger()
+{
+ ui->send->setEnabled(false);
+ ui->enableDebug->setEnabled(false);
+ ui->debugStep->setEnabled(true);
+ ui->debugContinue->setEnabled(true);
+ ui->debugPanel->setEnabled(true);
+ ui->debugCode->setEnabled(false);
+
+ QListWidget* dc = ui->debugCode;
+ dc->clear();
+ if (m_currentExecution)
+ {
+ for (unsigned i = 0; i <= m_currentExecution->ext().code.size(); ++i)
+ {
+ byte b = i < m_currentExecution->ext().code.size() ? m_currentExecution->ext().code[i] : 0;
+ QString s = c_instructionInfo.at((Instruction)b).name;
+ m_pcWarp[i] = dc->count();
+ ostringstream out;
+ out << hex << setw(4) << setfill('0') << i;
+ if (b >= (byte)Instruction::PUSH1 && b <= (byte)Instruction::PUSH32)
+ {
+ unsigned bc = b - (byte)Instruction::PUSH1 + 1;
+ s = "PUSH 0x" + QString::fromStdString(toHex(bytesConstRef(&m_currentExecution->ext().code[i + 1], bc)));
+ i += bc;
+ }
+ dc->addItem(QString::fromStdString(out.str()) + " " + s);
+ }
+
+ }
+}
+
+void Main::updateDebugger()
+{
+ QListWidget* ds = ui->debugStack;
+ ds->clear();
+ if (m_currentExecution)
+ {
+ eth::VM const& vm = m_currentExecution->vm();
+ for (auto i: vm.stack())
+ ds->insertItem(0, QString::fromStdString(toHex(((h256)i).asArray())));
+ ui->debugMemory->setHtml(QString::fromStdString(htmlDump(vm.memory(), 16)));
+ ui->debugCode->setCurrentRow(m_pcWarp[(unsigned)vm.curPC()]);
+ ostringstream ss;
+ ss << hex << "PC: 0x" << vm.curPC() << " | GAS: 0x" << vm.gas();
+ ui->debugStateInfo->setText(QString::fromStdString(ss.str()));
+
+ stringstream s;
+ auto storage = m_currentExecution->state().contractStorage(m_currentExecution->ext().myAddress);
+ for (auto const& i: storage)
+ s << "@" << showbase << hex << i.first << " " << showbase << hex << i.second << "
";
+ ui->debugStorage->setHtml(QString::fromStdString(s.str()));
+ }
+}
+
// extra bits needed to link on VS
#ifdef _MSC_VER
diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h
index b40427b66..758b249bb 100644
--- a/alethzero/MainWin.h
+++ b/alethzero/MainWin.h
@@ -7,6 +7,7 @@
#include
#include
#include
+#include
#include
namespace Ui {
@@ -287,6 +288,9 @@ private slots:
void on_preview_triggered() { refresh(true); }
void on_quit_triggered() { close(); }
void on_urlEdit_editingFinished();
+ void on_debugStep_triggered();
+ void on_debugContinue_triggered();
+ void on_enableDebug_triggered();
void refresh(bool _override = false);
void refreshNetwork();
@@ -297,6 +301,9 @@ signals:
private:
QString pretty(eth::Address _a) const;
+ void initDebugger();
+ void updateDebugger();
+ void debugFinished();
QString render(eth::Address _a) const;
eth::Address fromString(QString const& _a) const;
@@ -329,6 +336,10 @@ private:
unsigned m_backupGas;
+ eth::State m_executiveState;
+ std::unique_ptr m_currentExecution;
+ QMap m_pcWarp;
+
QNetworkAccessManager m_webCtrl;
QEthereum* m_ethereum;
diff --git a/libethereum/State.cpp b/libethereum/State.cpp
index 23ad0cbd1..513971565 100644
--- a/libethereum/State.cpp
+++ b/libethereum/State.cpp
@@ -601,6 +601,166 @@ bytes const& State::contractCode(Address _contract) const
return m_cache[_contract].code();
}
+Executive::~Executive()
+{
+ // TODO: Make safe.
+ delete m_ext;
+ delete m_vm;
+}
+
+void Executive::setup(bytesConstRef _rlp)
+{
+ // Entry point for a user-executed transaction.
+ m_t = Transaction(_rlp);
+
+ auto sender = m_t.sender();
+
+ // Avoid invalid transactions.
+ auto nonceReq = m_s.transactionsFrom(sender);
+ if (m_t.nonce != nonceReq)
+ {
+ clog(StateChat) << "Invalid Nonce.";
+ throw InvalidNonce(nonceReq, m_t.nonce);
+ }
+
+ // Don't like transactions whose gas price is too low. NOTE: this won't stay here forever - it's just until we get a proper gas price discovery protocol going.
+ if (m_t.gasPrice < 10 * szabo)
+ {
+ clog(StateChat) << "Offered gas-price is too low.";
+ throw GasPriceTooLow();
+ }
+
+ // Check gas cost is enough.
+ u256 gasCost;
+ if (m_t.isCreation())
+ gasCost = (m_t.init.size() + m_t.data.size()) * c_txDataGas + c_createGas;
+ else
+ gasCost = m_t.data.size() * c_txDataGas + c_callGas;
+
+ if (m_t.gas < gasCost)
+ {
+ clog(StateChat) << "Not enough gas to pay for the transaction.";
+ throw OutOfGas();
+ }
+
+ m_startGas = m_t.gas;
+
+ u256 cost = m_t.value + m_t.gas * m_t.gasPrice;
+
+ // Avoid unaffordable transactions.
+ if (m_s.balance(sender) < cost)
+ {
+ clog(StateChat) << "Not enough cash.";
+ throw NotEnoughCash();
+ }
+
+ // Increment associated nonce for sender.
+ m_s.noteSending(sender);
+
+ // Pay...
+ cnote << "Paying" << formatBalance(cost) << "from sender (includes" << m_t.gas << "gas at" << formatBalance(m_t.gasPrice) << ")";
+ m_s.subBalance(sender, cost);
+
+ if (m_t.isCreation())
+ create(sender, m_t.value, m_t.gasPrice, m_t.gas - gasCost, &m_t.data, &m_t.init, sender);
+ else
+ call(m_t.receiveAddress, sender, m_t.value, m_t.gasPrice, bytesConstRef(&m_t.data), m_t.gas - gasCost, sender);
+}
+
+void Executive::call(Address _receiveAddress, Address _senderAddress, u256 _value, u256 _gasPrice, bytesConstRef _data, u256 _gas, Address _originAddress)
+{
+ cnote << "Transferring" << formatBalance(_value) << "to receiver.";
+ m_s.addBalance(_receiveAddress, _value);
+
+ if (m_s.isContractAddress(_receiveAddress))
+ {
+ m_vm = new VM(_gas);
+ m_ext = new ExtVM(m_s, _receiveAddress, _senderAddress, _originAddress, _value, _gasPrice, _data, &m_s.contractCode(_receiveAddress));
+ }
+ else
+ m_endGas = _gas;
+}
+
+void Executive::create(Address _sender, u256 _endowment, u256 _gasPrice, u256 _gas, bytesConstRef _code, bytesConstRef _init, Address _origin)
+{
+ m_newAddress = right160(sha3(rlpList(_sender, m_s.transactionsFrom(_sender) - 1)));
+ while (m_s.isContractAddress(m_newAddress) || m_s.isNormalAddress(m_newAddress))
+ m_newAddress = (u160)m_newAddress + 1;
+
+ // Set up new account...
+ m_s.m_cache[m_newAddress] = AddressState(0, 0, _code);
+
+ // Execute _init.
+ m_vm = new VM(_gas);
+ m_ext = new ExtVM(m_s, m_newAddress, _sender, _origin, _endowment, _gasPrice, bytesConstRef(), _init);
+}
+
+bool Executive::go(uint64_t _steps)
+{
+ if (m_vm)
+ {
+ bool revert = false;
+ try
+ {
+ m_vm->go(*m_ext, _steps);
+ m_endGas = m_vm->gas();
+ }
+ catch (StepsDone const&)
+ {
+ return false;
+ }
+ catch (OutOfGas const& /*_e*/)
+ {
+ clog(StateChat) << "Out of Gas! Reverting.";
+ revert = true;
+ }
+ catch (VMException const& _e)
+ {
+ clog(StateChat) << "VM Exception: " << _e.description();
+ }
+ catch (Exception const& _e)
+ {
+ clog(StateChat) << "Exception in VM: " << _e.description();
+ }
+ catch (std::exception const& _e)
+ {
+ clog(StateChat) << "std::exception in VM: " << _e.what();
+ }
+
+ // Write state out only in the case of a non-excepted transaction.
+ if (revert)
+ {
+ m_ext->revert();
+ if (m_newAddress)
+ {
+ m_s.m_cache.erase(m_newAddress);
+ m_newAddress = Address();
+ }
+ }
+ }
+ return true;
+}
+
+u256 Executive::gas() const
+{
+ return m_vm->gas();
+}
+
+void Executive::finalize()
+{
+ cnote << "Refunding" << formatBalance(m_endGas * m_ext->gasPrice) << "to origin (=" << m_endGas << "*" << formatBalance(m_ext->gasPrice) << ")";
+ m_s.addBalance(m_ext->origin, m_endGas * m_ext->gasPrice);
+
+ u256 gasSpent = (m_startGas - m_endGas) * m_ext->gasPrice;
+/* unsigned c_feesKept = 8;
+ u256 feesEarned = gasSpent - (gasSpent / c_feesKept);
+ cnote << "Transferring" << (100.0 - 100.0 / c_feesKept) << "% of" << formatBalance(gasSpent) << "=" << formatBalance(feesEarned) << "to miner (" << formatBalance(gasSpent - feesEarned) << "is burnt).";
+*/
+ u256 feesEarned = gasSpent;
+ cnote << "Transferring" << formatBalance(gasSpent) << "to miner.";
+ m_s.addBalance(m_s.m_currentBlock.coinbaseAddress, feesEarned);
+}
+
void State::execute(bytesConstRef _rlp)
{
// Entry point for a user-executed transaction.
@@ -616,7 +776,7 @@ void State::execute(bytesConstRef _rlp)
throw InvalidNonce(nonceReq, t.nonce);
}
- // Don't like transactions whose gas price is too low. NOTE: this won't stay here forever - it's just until we get a proper gas proce discovery protocol going.
+ // Don't like transactions whose gas price is too low. NOTE: this won't stay here forever - it's just until we get a proper gas price discovery protocol going.
if (t.gasPrice < 10 * szabo)
{
clog(StateChat) << "Offered gas-price is too low.";
@@ -671,6 +831,7 @@ void State::execute(bytesConstRef _rlp)
cnote << "Transferring" << formatBalance(gasSpent) << "to miner.";
addBalance(m_currentBlock.coinbaseAddress, feesEarned);
+ // !!!!!!!!!!!!!!!!!!!!! If moving to use Executive, this still needs to be done - Executive won't do it.
// Add to the user-originated transactions that we've executed.
m_transactions.push_back(t);
m_transactionSet.insert(t.sha3());
diff --git a/libethereum/State.h b/libethereum/State.h
index 6a5485df2..82a34cdf8 100644
--- a/libethereum/State.h
+++ b/libethereum/State.h
@@ -50,7 +50,43 @@ static const bytes EmptyBytes;
struct StateChat: public LogChannel { static const char* name() { return "=S="; } static const int verbosity = 4; };
+class VM;
class ExtVM;
+class State;
+
+class Executive
+{
+public:
+ Executive(State& _s): m_s(_s) {}
+ ~Executive();
+
+ void setup(bytesConstRef _transaction);
+ void create(Address _txSender, u256 _endowment, u256 _gasPrice, u256 _gas, bytesConstRef _code, bytesConstRef _init, Address _originAddress);
+ void call(Address _myAddress, Address _txSender, u256 _txValue, u256 _gasPrice, bytesConstRef _txData, u256 _gas, Address _originAddress);
+ bool go(uint64_t _steps = (unsigned)-1);
+ void finalize();
+
+ u256 gas() const;
+
+ bytesConstRef out() const { return m_out; }
+ h160 newAddress() const { return m_newAddress; }
+
+ VM const& vm() const { return *m_vm; }
+ State const& state() const { return m_s; }
+ ExtVM const& ext() const { return *m_ext; }
+
+private:
+ State& m_s;
+ ExtVM* m_ext = nullptr; // TODO: make safe.
+ VM* m_vm = nullptr;
+ bytesConstRef m_out;
+ Address m_newAddress;
+
+ Transaction m_t;
+
+ u256 m_startGas;
+ u256 m_endGas;
+};
/**
* @brief Model of the current state of the ledger.
@@ -61,10 +97,11 @@ class State
{
template friend class UnitTest;
friend class ExtVM;
+ friend class Executive;
public:
/// Construct state object.
- State(Address _coinbaseAddress, Overlay const& _db);
+ State(Address _coinbaseAddress = Address(), Overlay const& _db = Overlay());
/// Copy state object.
State(State const& _s);
diff --git a/libethereum/VM.cpp b/libethereum/VM.cpp
index 6f69c64b3..5383f4906 100644
--- a/libethereum/VM.cpp
+++ b/libethereum/VM.cpp
@@ -28,7 +28,4 @@ void VM::reset(u256 _gas)
{
m_gas = _gas;
m_curPC = 0;
- m_nextPC = 1;
- m_stepCount = 0;
- m_runFee = 0;
}
diff --git a/libethereum/VM.h b/libethereum/VM.h
index 99b0a4740..60584ab4e 100644
--- a/libethereum/VM.h
+++ b/libethereum/VM.h
@@ -64,17 +64,17 @@ public:
void require(u256 _n) { if (m_stack.size() < _n) throw StackTooSmall(_n, m_stack.size()); }
void requireMem(unsigned _n) { if (m_temp.size() < _n) { m_temp.resize(_n); } }
- u256 runFee() const { return m_runFee; }
u256 gas() const { return m_gas; }
+ u256 curPC() const { return m_curPC; }
+
+ bytes const& memory() const { return m_temp; }
+ u256s const& stack() const { return m_stack; }
private:
u256 m_gas = 0;
u256 m_curPC = 0;
- u256 m_nextPC = 1;
- uint64_t m_stepCount = 0;
bytes m_temp;
- std::vector m_stack;
- u256 m_runFee = 0;
+ u256s m_stack;
};
}
@@ -82,10 +82,9 @@ private:
// INLINE:
template eth::bytesConstRef eth::VM::go(Ext& _ext, uint64_t _steps)
{
- for (bool stopped = false; !stopped && _steps--; m_curPC = m_nextPC, m_nextPC = m_curPC + 1)
+ u256 nextPC = m_curPC + 1;
+ for (bool stopped = false; !stopped && _steps--; m_curPC = nextPC, nextPC = m_curPC + 1)
{
- m_stepCount++;
-
// INSTRUCTION...
Instruction inst = (Instruction)_ext.getCode(m_curPC);
@@ -310,7 +309,7 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, uint64_t _steps)
case Instruction::CALLDATALOAD:
{
require(1);
- if ((unsigned)m_stack.back() + 32 < _ext.data.size())
+ if ((unsigned)m_stack.back() + 31 < _ext.data.size())
m_stack.back() = (u256)*(h256 const*)(_ext.data.data() + (unsigned)m_stack.back());
else
{
@@ -379,10 +378,10 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, uint64_t _steps)
case Instruction::PUSH32:
{
int i = (int)inst - (int)Instruction::PUSH1 + 1;
- m_nextPC = m_curPC + 1;
+ nextPC = m_curPC + 1;
m_stack.push_back(0);
- for (; i--; m_nextPC++)
- m_stack.back() = (m_stack.back() << 8) | _ext.getCode(m_nextPC);
+ for (; i--; nextPC++)
+ m_stack.back() = (m_stack.back() << 8) | _ext.getCode(nextPC);
break;
}
case Instruction::POP:
@@ -456,13 +455,13 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, uint64_t _steps)
break;
case Instruction::JUMP:
require(1);
- m_nextPC = m_stack.back();
+ nextPC = m_stack.back();
m_stack.pop_back();
break;
case Instruction::JUMPI:
require(2);
if (m_stack[m_stack.size() - 2])
- m_nextPC = m_stack.back();
+ nextPC = m_stack.back();
m_stack.pop_back();
m_stack.pop_back();
break;
@@ -557,7 +556,7 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, uint64_t _steps)
throw BadInstruction();
}
}
- if (_steps == (unsigned)-1)
+ if (_steps == (uint64_t)-1)
throw StepsDone();
return bytesConstRef();
}