You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
573 lines
15 KiB
573 lines
15 KiB
/*
|
|
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 VM.h
|
|
* @author Gav Wood <i@gavwood.com>
|
|
* @date 2014
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <unordered_map>
|
|
#include "CryptoHeaders.h"
|
|
#include "Common.h"
|
|
#include "Exceptions.h"
|
|
#include "FeeStructure.h"
|
|
#include "Instruction.h"
|
|
#include "BlockInfo.h"
|
|
#include "ExtVMFace.h"
|
|
|
|
namespace eth
|
|
{
|
|
|
|
// Convert from a 256-bit integer stack/memory entry into a 160-bit Address hash.
|
|
// Currently we just pull out the right (low-order in BE) 160-bits.
|
|
inline Address asAddress(u256 _item)
|
|
{
|
|
return right160(h256(_item));
|
|
}
|
|
|
|
inline u256 fromAddress(Address _a)
|
|
{
|
|
return (u160)_a;
|
|
}
|
|
|
|
/**
|
|
*/
|
|
class VM
|
|
{
|
|
template <unsigned T> friend class UnitTest;
|
|
|
|
public:
|
|
/// Construct VM object.
|
|
VM();
|
|
|
|
void reset();
|
|
|
|
template <class Ext>
|
|
void go(Ext& _ext, uint64_t _steps = (uint64_t)-1);
|
|
|
|
void require(u256 _n) { if (m_stack.size() < _n) throw StackTooSmall(_n, m_stack.size()); }
|
|
u256 runFee() const { return m_runFee; }
|
|
|
|
private:
|
|
u256 m_curPC = 0;
|
|
u256 m_nextPC = 1;
|
|
uint64_t m_stepCount = 0;
|
|
std::map<u256, u256> m_temp;
|
|
std::vector<u256> m_stack;
|
|
u256 m_runFee = 0;
|
|
};
|
|
|
|
}
|
|
|
|
// INLINE:
|
|
template <class Ext> void eth::VM::go(Ext& _ext, uint64_t _steps)
|
|
{
|
|
for (bool stopped = false; !stopped && _steps--; m_curPC = m_nextPC, m_nextPC = m_curPC + 1)
|
|
{
|
|
m_stepCount++;
|
|
|
|
// INSTRUCTION...
|
|
auto rawInst = _ext.store(m_curPC);
|
|
if (rawInst > 0xff)
|
|
throw BadInstruction();
|
|
Instruction inst = (Instruction)(uint8_t)rawInst;
|
|
|
|
// FEES...
|
|
bigint runFee = m_stepCount > 16 ? _ext.fees.m_stepFee : 0;
|
|
bigint storeCostDelta = 0;
|
|
switch (inst)
|
|
{
|
|
case Instruction::SSTORE:
|
|
require(2);
|
|
if (!_ext.store(m_stack.back()) && m_stack[m_stack.size() - 2])
|
|
storeCostDelta += _ext.fees.m_memoryFee;
|
|
if (_ext.store(m_stack.back()) && !m_stack[m_stack.size() - 2])
|
|
storeCostDelta -= _ext.fees.m_memoryFee;
|
|
// continue on to...
|
|
case Instruction::SLOAD:
|
|
runFee += _ext.fees.m_dataFee;
|
|
break;
|
|
|
|
case Instruction::EXTRO:
|
|
case Instruction::BALANCE:
|
|
runFee += _ext.fees.m_extroFee;
|
|
break;
|
|
|
|
case Instruction::MKTX:
|
|
runFee += _ext.fees.m_txFee;
|
|
break;
|
|
|
|
case Instruction::SHA256:
|
|
case Instruction::RIPEMD160:
|
|
case Instruction::ECMUL:
|
|
case Instruction::ECADD:
|
|
case Instruction::ECSIGN:
|
|
case Instruction::ECRECOVER:
|
|
case Instruction::ECVALID:
|
|
runFee += _ext.fees.m_cryptoFee;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
_ext.payFee(runFee + storeCostDelta);
|
|
m_runFee += (u256)runFee;
|
|
|
|
// EXECUTE...
|
|
switch (inst)
|
|
{
|
|
case Instruction::ADD:
|
|
//pops two items and pushes S[-1] + S[-2] mod 2^256.
|
|
require(2);
|
|
m_stack[m_stack.size() - 2] += m_stack.back();
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::MUL:
|
|
//pops two items and pushes S[-1] * S[-2] mod 2^256.
|
|
require(2);
|
|
m_stack[m_stack.size() - 2] *= m_stack.back();
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::SUB:
|
|
require(2);
|
|
m_stack[m_stack.size() - 2] = m_stack.back() - m_stack[m_stack.size() - 2];
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::DIV:
|
|
require(2);
|
|
if (!m_stack[m_stack.size() - 2])
|
|
return;
|
|
m_stack[m_stack.size() - 2] = m_stack.back() / m_stack[m_stack.size() - 2];
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::SDIV:
|
|
require(2);
|
|
if (!m_stack[m_stack.size() - 2])
|
|
return;
|
|
(s256&)m_stack[m_stack.size() - 2] = (s256&)m_stack.back() / (s256&)m_stack[m_stack.size() - 2];
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::MOD:
|
|
require(2);
|
|
if (!m_stack[m_stack.size() - 2])
|
|
return;
|
|
m_stack[m_stack.size() - 2] = m_stack.back() % m_stack[m_stack.size() - 2];
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::SMOD:
|
|
require(2);
|
|
if (!m_stack[m_stack.size() - 2])
|
|
return;
|
|
(s256&)m_stack[m_stack.size() - 2] = (s256&)m_stack.back() % (s256&)m_stack[m_stack.size() - 2];
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::EXP:
|
|
{
|
|
// TODO: better implementation?
|
|
require(2);
|
|
auto n = m_stack.back();
|
|
auto x = m_stack[m_stack.size() - 2];
|
|
m_stack.pop_back();
|
|
for (u256 i = 0; i < x; ++i)
|
|
n *= n;
|
|
m_stack.back() = n;
|
|
break;
|
|
}
|
|
case Instruction::NEG:
|
|
require(1);
|
|
m_stack.back() = ~(m_stack.back() - 1);
|
|
break;
|
|
case Instruction::LT:
|
|
require(2);
|
|
m_stack[m_stack.size() - 2] = m_stack.back() < m_stack[m_stack.size() - 2] ? 1 : 0;
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::LE:
|
|
require(2);
|
|
m_stack[m_stack.size() - 2] = m_stack.back() <= m_stack[m_stack.size() - 2] ? 1 : 0;
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::GT:
|
|
require(2);
|
|
m_stack[m_stack.size() - 2] = m_stack.back() > m_stack[m_stack.size() - 2] ? 1 : 0;
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::GE:
|
|
require(2);
|
|
m_stack[m_stack.size() - 2] = m_stack.back() >= m_stack[m_stack.size() - 2] ? 1 : 0;
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::EQ:
|
|
require(2);
|
|
m_stack[m_stack.size() - 2] = m_stack.back() == m_stack[m_stack.size() - 2] ? 1 : 0;
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::NOT:
|
|
require(1);
|
|
m_stack.back() = m_stack.back() ? 0 : 1;
|
|
break;
|
|
case Instruction::MYADDRESS:
|
|
m_stack.push_back(fromAddress(_ext.myAddress));
|
|
break;
|
|
case Instruction::TXSENDER:
|
|
m_stack.push_back(fromAddress(_ext.txSender));
|
|
break;
|
|
case Instruction::TXVALUE:
|
|
m_stack.push_back(_ext.txValue);
|
|
break;
|
|
case Instruction::TXDATAN:
|
|
m_stack.push_back(_ext.txData.size());
|
|
break;
|
|
case Instruction::TXDATA:
|
|
require(1);
|
|
m_stack.back() = m_stack.back() < _ext.txData.size() ? _ext.txData[(uint)m_stack.back()] : 0;
|
|
break;
|
|
case Instruction::BLK_PREVHASH:
|
|
m_stack.push_back(_ext.previousBlock.hash);
|
|
break;
|
|
case Instruction::BLK_COINBASE:
|
|
m_stack.push_back((u160)_ext.currentBlock.coinbaseAddress);
|
|
break;
|
|
case Instruction::BLK_TIMESTAMP:
|
|
m_stack.push_back(_ext.currentBlock.timestamp);
|
|
break;
|
|
case Instruction::BLK_NUMBER:
|
|
m_stack.push_back(_ext.currentNumber);
|
|
break;
|
|
case Instruction::BLK_DIFFICULTY:
|
|
m_stack.push_back(_ext.currentBlock.difficulty);
|
|
break;
|
|
case Instruction::BLK_NONCE:
|
|
m_stack.push_back(_ext.previousBlock.nonce);
|
|
break;
|
|
case Instruction::BASEFEE:
|
|
m_stack.push_back(_ext.fees.multiplier());
|
|
break;
|
|
case Instruction::SHA256:
|
|
{
|
|
require(1);
|
|
uint s = (uint)std::min(m_stack.back(), (u256)(m_stack.size() - 1) * 32);
|
|
m_stack.pop_back();
|
|
|
|
CryptoPP::SHA256 digest;
|
|
uint i = 0;
|
|
for (; s; s = (s >= 32 ? s - 32 : 0), i += 32)
|
|
{
|
|
bytes b = toBigEndian(m_stack.back());
|
|
digest.Update(b.data(), (int)std::min<u256>(32, s)); // b.size() == 32
|
|
m_stack.pop_back();
|
|
}
|
|
std::array<byte, 32> final;
|
|
digest.TruncatedFinal(final.data(), 32);
|
|
m_stack.push_back(fromBigEndian<u256>(final));
|
|
break;
|
|
}
|
|
case Instruction::RIPEMD160:
|
|
{
|
|
require(1);
|
|
uint s = (uint)std::min(m_stack.back(), (u256)(m_stack.size() - 1) * 32);
|
|
m_stack.pop_back();
|
|
|
|
CryptoPP::RIPEMD160 digest;
|
|
uint i = 0;
|
|
for (; s; s = (s >= 32 ? s - 32 : 0), i += 32)
|
|
{
|
|
bytes b = toBigEndian(m_stack.back());
|
|
digest.Update(b.data(), (int)std::min<u256>(32, s)); // b.size() == 32
|
|
m_stack.pop_back();
|
|
}
|
|
std::array<byte, 20> final;
|
|
digest.TruncatedFinal(final.data(), 20);
|
|
// NOTE: this aligns to right of 256-bit container (low-order bytes).
|
|
// This won't work if they're treated as byte-arrays and thus left-aligned in a 256-bit container.
|
|
m_stack.push_back((u256)fromBigEndian<u160>(final));
|
|
break;
|
|
}
|
|
case Instruction::ECMUL:
|
|
{
|
|
// ECMUL - pops three items.
|
|
// If (S[-2],S[-1]) are a valid point in secp256k1, including both coordinates being less than P, pushes (S[-1],S[-2]) * S[-3], using (0,0) as the point at infinity.
|
|
// Otherwise, pushes (0,0).
|
|
require(3);
|
|
|
|
bytes pub(1, 4);
|
|
pub += toBigEndian(m_stack[m_stack.size() - 2]);
|
|
pub += toBigEndian(m_stack.back());
|
|
m_stack.pop_back();
|
|
m_stack.pop_back();
|
|
|
|
bytes x = toBigEndian(m_stack.back());
|
|
m_stack.pop_back();
|
|
|
|
if (secp256k1_ecdsa_pubkey_verify(pub.data(), (int)pub.size())) // TODO: Check both are less than P.
|
|
{
|
|
secp256k1_ecdsa_pubkey_tweak_mul(pub.data(), (int)pub.size(), x.data());
|
|
m_stack.push_back(fromBigEndian<u256>(bytesConstRef(&pub).cropped(1, 32)));
|
|
m_stack.push_back(fromBigEndian<u256>(bytesConstRef(&pub).cropped(33, 32)));
|
|
}
|
|
else
|
|
{
|
|
m_stack.push_back(0);
|
|
m_stack.push_back(0);
|
|
}
|
|
break;
|
|
}
|
|
case Instruction::ECADD:
|
|
{
|
|
// ECADD - pops four items and pushes (S[-4],S[-3]) + (S[-2],S[-1]) if both points are valid, otherwise (0,0).
|
|
require(4);
|
|
|
|
bytes pub(1, 4);
|
|
pub += toBigEndian(m_stack[m_stack.size() - 2]);
|
|
pub += toBigEndian(m_stack.back());
|
|
m_stack.pop_back();
|
|
m_stack.pop_back();
|
|
|
|
bytes tweak(1, 4);
|
|
tweak += toBigEndian(m_stack[m_stack.size() - 2]);
|
|
tweak += toBigEndian(m_stack.back());
|
|
m_stack.pop_back();
|
|
m_stack.pop_back();
|
|
|
|
if (secp256k1_ecdsa_pubkey_verify(pub.data(),(int) pub.size()) && secp256k1_ecdsa_pubkey_verify(tweak.data(),(int) tweak.size()))
|
|
{
|
|
secp256k1_ecdsa_pubkey_tweak_add(pub.data(), (int)pub.size(), tweak.data());
|
|
m_stack.push_back(fromBigEndian<u256>(bytesConstRef(&pub).cropped(1, 32)));
|
|
m_stack.push_back(fromBigEndian<u256>(bytesConstRef(&pub).cropped(33, 32)));
|
|
}
|
|
else
|
|
{
|
|
m_stack.push_back(0);
|
|
m_stack.push_back(0);
|
|
}
|
|
break;
|
|
}
|
|
case Instruction::ECSIGN:
|
|
{
|
|
require(2);
|
|
bytes sig(64);
|
|
int v = 0;
|
|
|
|
u256 msg = m_stack.back();
|
|
m_stack.pop_back();
|
|
u256 priv = m_stack.back();
|
|
m_stack.pop_back();
|
|
bytes nonce = toBigEndian(Transaction::kFromMessage(msg, priv));
|
|
|
|
if (!secp256k1_ecdsa_sign_compact(toBigEndian(msg).data(), 64, sig.data(), toBigEndian(priv).data(), nonce.data(), &v))
|
|
throw InvalidSignature();
|
|
|
|
m_stack.push_back(v + 27);
|
|
m_stack.push_back(fromBigEndian<u256>(bytesConstRef(&sig).cropped(0, 32)));
|
|
m_stack.push_back(fromBigEndian<u256>(bytesConstRef(&sig).cropped(32)));
|
|
break;
|
|
}
|
|
case Instruction::ECRECOVER:
|
|
{
|
|
require(4);
|
|
|
|
bytes sig = toBigEndian(m_stack[m_stack.size() - 2]) + toBigEndian(m_stack.back());
|
|
m_stack.pop_back();
|
|
m_stack.pop_back();
|
|
int v = (int)m_stack.back();
|
|
m_stack.pop_back();
|
|
bytes msg = toBigEndian(m_stack.back());
|
|
m_stack.pop_back();
|
|
|
|
byte pubkey[65];
|
|
int pubkeylen = 65;
|
|
if (secp256k1_ecdsa_recover_compact(msg.data(), (int)msg.size(), sig.data(), pubkey, &pubkeylen, 0, v - 27))
|
|
{
|
|
m_stack.push_back(0);
|
|
m_stack.push_back(0);
|
|
}
|
|
else
|
|
{
|
|
m_stack.push_back(fromBigEndian<u256>(bytesConstRef(&pubkey[1], 32)));
|
|
m_stack.push_back(fromBigEndian<u256>(bytesConstRef(&pubkey[33], 32)));
|
|
}
|
|
break;
|
|
}
|
|
case Instruction::ECVALID:
|
|
{
|
|
require(2);
|
|
bytes pub(1, 4);
|
|
pub += toBigEndian(m_stack[m_stack.size() - 2]);
|
|
pub += toBigEndian(m_stack.back());
|
|
m_stack.pop_back();
|
|
m_stack.pop_back();
|
|
|
|
m_stack.back() = secp256k1_ecdsa_pubkey_verify(pub.data(), (int)pub.size()) ? 1 : 0;
|
|
break;
|
|
}
|
|
case Instruction::SHA3:
|
|
{
|
|
require(1);
|
|
uint s = (uint)std::min(m_stack.back(), (u256)(m_stack.size() - 1) * 32);
|
|
m_stack.pop_back();
|
|
|
|
CryptoPP::SHA3_256 digest;
|
|
uint i = 0;
|
|
for (; s; s = (s >= 32 ? s - 32 : 0), i += 32)
|
|
{
|
|
bytes b = toBigEndian(m_stack.back());
|
|
digest.Update(b.data(), (int)std::min<u256>(32, s)); // b.size() == 32
|
|
m_stack.pop_back();
|
|
}
|
|
std::array<byte, 32> final;
|
|
digest.TruncatedFinal(final.data(), 32);
|
|
m_stack.push_back(fromBigEndian<u256>(final));
|
|
break;
|
|
}
|
|
case Instruction::PUSH:
|
|
{
|
|
m_stack.push_back(_ext.store(m_curPC + 1));
|
|
m_nextPC = m_curPC + 2;
|
|
break;
|
|
}
|
|
case Instruction::POP:
|
|
require(1);
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::DUP:
|
|
require(1);
|
|
m_stack.push_back(m_stack.back());
|
|
break;
|
|
/*case Instruction::DUPN:
|
|
{
|
|
auto s = store(curPC + 1);
|
|
if (s == 0 || s > stack.size())
|
|
throw OperandOutOfRange(1, stack.size(), s);
|
|
stack.push_back(stack[stack.size() - (uint)s]);
|
|
nextPC = curPC + 2;
|
|
break;
|
|
}*/
|
|
case Instruction::SWAP:
|
|
{
|
|
require(2);
|
|
auto d = m_stack.back();
|
|
m_stack.back() = m_stack[m_stack.size() - 2];
|
|
m_stack[m_stack.size() - 2] = d;
|
|
break;
|
|
}
|
|
/*case Instruction::SWAPN:
|
|
{
|
|
require(1);
|
|
auto d = stack.back();
|
|
auto s = store(curPC + 1);
|
|
if (s == 0 || s > stack.size())
|
|
throw OperandOutOfRange(1, stack.size(), s);
|
|
stack.back() = stack[stack.size() - (uint)s];
|
|
stack[stack.size() - (uint)s] = d;
|
|
nextPC = curPC + 2;
|
|
break;
|
|
}*/
|
|
case Instruction::MLOAD:
|
|
{
|
|
require(1);
|
|
m_stack.back() = m_temp[m_stack.back()];
|
|
break;
|
|
}
|
|
case Instruction::MSTORE:
|
|
{
|
|
require(2);
|
|
m_temp[m_stack.back()] = m_stack[m_stack.size() - 2];
|
|
m_stack.pop_back();
|
|
m_stack.pop_back();
|
|
break;
|
|
}
|
|
case Instruction::SLOAD:
|
|
require(1);
|
|
m_stack.back() = _ext.store(m_stack.back());
|
|
break;
|
|
case Instruction::SSTORE:
|
|
require(2);
|
|
_ext.setStore(m_stack.back(), m_stack[m_stack.size() - 2]);
|
|
m_stack.pop_back();
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::JMP:
|
|
require(1);
|
|
m_nextPC = m_stack.back();
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::JMPI:
|
|
require(2);
|
|
if (m_stack.back())
|
|
m_nextPC = m_stack[m_stack.size() - 2];
|
|
m_stack.pop_back();
|
|
m_stack.pop_back();
|
|
break;
|
|
case Instruction::IND:
|
|
m_stack.push_back(m_curPC);
|
|
break;
|
|
case Instruction::EXTRO:
|
|
{
|
|
require(2);
|
|
auto memoryAddress = m_stack.back();
|
|
m_stack.pop_back();
|
|
Address contractAddress = asAddress(m_stack.back());
|
|
m_stack.back() = _ext.extro(contractAddress, memoryAddress);
|
|
break;
|
|
}
|
|
case Instruction::BALANCE:
|
|
{
|
|
require(1);
|
|
m_stack.back() = _ext.balance(asAddress(m_stack.back()));
|
|
break;
|
|
}
|
|
case Instruction::MKTX:
|
|
{
|
|
require(3);
|
|
|
|
Transaction t;
|
|
t.receiveAddress = asAddress(m_stack.back());
|
|
m_stack.pop_back();
|
|
t.value = m_stack.back();
|
|
m_stack.pop_back();
|
|
|
|
auto itemCount = m_stack.back();
|
|
m_stack.pop_back();
|
|
if (m_stack.size() < itemCount)
|
|
throw OperandOutOfRange(0, m_stack.size(), itemCount);
|
|
t.data.reserve((uint)itemCount);
|
|
for (auto i = 0; i < itemCount; ++i)
|
|
{
|
|
t.data.push_back(m_stack.back());
|
|
m_stack.pop_back();
|
|
}
|
|
|
|
_ext.mktx(t);
|
|
break;
|
|
}
|
|
case Instruction::SUICIDE:
|
|
{
|
|
require(1);
|
|
Address dest = asAddress(m_stack.back());
|
|
_ext.suicide(dest);
|
|
// ...follow through to...
|
|
}
|
|
case Instruction::STOP:
|
|
return;
|
|
default:
|
|
throw BadInstruction();
|
|
}
|
|
}
|
|
if (_steps == (unsigned)-1)
|
|
throw StepsDone();
|
|
}
|
|
|
|
|