#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
#[cfg(not(feature = "std"))]
use alloc::rc::Rc;
#[cfg(feature = "std")]
use std::rc::Rc;
#[cfg(not(feature = "std"))]
use core::ops::AddAssign;
#[cfg(feature = "std")]
use std::ops::AddAssign;
use super::commit::{AccountState, BlockhashState};
use super::errors::{CommitError, EvalOnChainError, NotSupportedError, OnChainError, RequireError, RuntimeError};
use super::pc::Instruction;
use super::{AccountCommitment, Context, HeaderParams, Log, Memory, Opcode, PCMut, Patch, Stack, Valids, PC};
use bigint::{Address, Gas, M256, U256};
use self::check::{check_opcode, check_static, check_support, extra_check_opcode};
use self::cost::{gas_cost, gas_refund, gas_stipend, memory_cost, memory_gas, AddRefund};
use self::run::run_opcode;
macro_rules! reset_error_hard {
($self: expr, $err: expr) => {
$self.status = MachineStatus::ExitedErr($err);
$self.state.used_gas = GasUsage::All;
$self.state.refunded_gas = Gas::zero();
$self.state.logs = Vec::new();
$self.state.out = Rc::new(Vec::new());
};
}
macro_rules! reset_error_revert {
($self: expr) => {
$self.status = MachineStatus::ExitedErr(OnChainError::Revert);
};
}
macro_rules! reset_error_not_supported {
($self: expr, $err: expr) => {
$self.status = MachineStatus::ExitedNotSupported($err);
$self.state.used_gas = GasUsage::Some(Gas::zero());
$self.state.refunded_gas = Gas::zero();
$self.state.logs = Vec::new();
$self.state.out = Rc::new(Vec::new());
};
}
mod check;
mod cost;
mod lifecycle;
mod run;
mod util;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GasUsage {
All,
Some(Gas),
}
impl AddAssign<Gas> for GasUsage {
fn add_assign(&mut self, rhs: Gas) {
match self {
GasUsage::All => (),
GasUsage::Some(ref mut gas) => {
*gas = *gas + rhs;
}
}
}
}
pub struct State<'a, M, P: Patch> {
pub patch: &'a P,
pub memory: M,
pub stack: Stack,
pub context: Context,
pub out: Rc<Vec<u8>>,
pub ret: Rc<Vec<u8>>,
pub memory_cost: Gas,
pub used_gas: GasUsage,
pub refunded_gas: Gas,
pub account_state: AccountState<'a, P::Account>,
pub logs: Vec<Log>,
pub removed: Vec<Address>,
pub depth: usize,
pub valids: Valids,
pub position: usize,
}
impl<'a, M, P: Patch> State<'a, M, P> {
pub fn memory_gas(&self) -> Gas {
memory_gas(self.memory_cost)
}
pub fn available_gas(&self) -> Gas {
self.context.gas_limit - self.total_used_gas()
}
pub fn total_used_gas(&self) -> Gas {
match self.used_gas {
GasUsage::All => self.context.gas_limit,
GasUsage::Some(gas) => self.memory_gas() + gas,
}
}
}
pub struct Runtime {
pub blockhash_state: BlockhashState,
pub block: HeaderParams,
pub context_history_hooks: Vec<Box<Fn(&Context)>>,
}
impl Runtime {
pub fn new(block: HeaderParams) -> Self {
Self::with_states(block, BlockhashState::default())
}
pub fn with_states(block: HeaderParams, blockhash_state: BlockhashState) -> Self {
Runtime {
block,
blockhash_state,
context_history_hooks: Vec::new(),
}
}
}
pub struct Machine<'a, M, P: Patch> {
state: State<'a, M, P>,
status: MachineStatus,
}
#[derive(Debug, Clone)]
pub enum MachineStatus {
Running,
ExitedOk,
ExitedErr(OnChainError),
ExitedNotSupported(NotSupportedError),
InvokeCreate(Context),
InvokeCall(Context, (U256, U256)),
}
#[derive(Debug, Clone)]
pub enum ControlCheck {
Jump(M256),
}
#[derive(Debug, Clone)]
pub enum Control {
Stop,
Revert,
Jump(M256),
InvokeCreate(Context),
InvokeCall(Context, (U256, U256)),
}
impl<'a, M: Memory, P: Patch> Machine<'a, M, P> {
pub fn derive(&self, context: Context) -> Self {
Machine {
status: MachineStatus::Running,
state: State {
patch: self.state.patch,
memory: M::new(self.state.patch.memory_limit()),
stack: Stack::default(),
out: Rc::new(Vec::new()),
ret: Rc::new(Vec::new()),
memory_cost: Gas::zero(),
used_gas: GasUsage::Some(Gas::zero()),
refunded_gas: Gas::zero(),
account_state: self.state.account_state.clone(),
logs: Vec::new(),
removed: self.state.removed.clone(),
depth: self.state.depth + 1,
position: 0,
valids: Valids::new(context.code.as_slice()),
context,
},
}
}
pub fn new(patch: &'a P, context: Context, depth: usize) -> Self {
let account_patch = patch.account_patch();
Self::with_states(patch, context, depth, AccountState::new(account_patch))
}
pub fn with_states(
patch: &'a P,
context: Context,
depth: usize,
account_state: AccountState<'a, P::Account>,
) -> Self {
let memory_limit = patch.memory_limit();
Machine {
status: MachineStatus::Running,
state: State {
patch,
memory: M::new(memory_limit),
stack: Stack::default(),
out: Rc::new(Vec::new()),
ret: Rc::new(Vec::new()),
memory_cost: Gas::zero(),
used_gas: GasUsage::Some(Gas::zero()),
refunded_gas: Gas::zero(),
account_state,
logs: Vec::new(),
removed: Vec::new(),
depth,
position: 0,
valids: Valids::new(context.code.as_slice()),
context,
},
}
}
pub fn commit_account(&mut self, commitment: AccountCommitment) -> Result<(), CommitError> {
self.state.account_state.commit(commitment)
}
pub fn step_precompiled(&mut self) -> bool {
for precompiled in self.state.patch.precompileds() {
if self.state.context.callee == precompiled.0
&& self
.state
.patch
.is_precompiled_contract_enabled(&self.state.context.callee)
&& (precompiled.1.is_none() || precompiled.1.unwrap() == self.state.context.code.as_slice())
{
let data = &self.state.context.data;
match precompiled.2.gas_and_step(data, self.state.context.gas_limit) {
Err(RuntimeError::OnChain(err)) => {
reset_error_hard!(self, err);
}
Err(RuntimeError::NotSupported(err)) => {
reset_error_not_supported!(self, err);
}
Ok((gas, ret)) => {
assert!(gas <= self.state.context.gas_limit);
self.state.used_gas = GasUsage::Some(gas);
self.state.out = ret;
self.status = MachineStatus::ExitedOk;
}
}
return true;
}
}
false
}
pub fn peek(&self) -> Option<Instruction> {
let pc = PC::<P>::new(
&self.state.patch,
&self.state.context.code,
&self.state.valids,
&self.state.position,
);
match pc.peek() {
Ok(val) => Some(val),
Err(_) => None,
}
}
pub fn peek_opcode(&self) -> Option<Opcode> {
let pc = PC::<P>::new(
&self.state.patch,
&self.state.context.code,
&self.state.valids,
&self.state.position,
);
match pc.peek_opcode() {
Ok(val) => Some(val),
Err(_) => None,
}
}
pub fn step(&mut self, runtime: &Runtime) -> Result<(), RequireError> {
debug!("VM step started");
debug!("Code: {:x?}", &self.state.context.code[self.state.position..]);
debug!("Stack: {:#x?}", self.state.stack);
struct Precheck {
position: usize,
memory_cost: Gas,
gas_cost: Gas,
gas_stipend: Gas,
gas_refund: isize,
after_gas: Gas,
}
match &self.status {
MachineStatus::Running => (),
_ => panic!(),
}
if self.step_precompiled() {
trace!("precompiled step succeeded");
return Ok(());
}
let Precheck {
position,
memory_cost,
gas_cost,
gas_stipend,
gas_refund,
after_gas,
} = {
let pc = PC::<P>::new(
&self.state.patch,
&self.state.context.code,
&self.state.valids,
&self.state.position,
);
if pc.is_end() {
debug!("reached code EOF");
self.status = MachineStatus::ExitedOk;
return Ok(());
}
let instruction = match pc.peek() {
Ok(val) => val,
Err(err) => {
reset_error_hard!(self, err);
return Ok(());
}
};
match check_opcode(instruction, &self.state, runtime).and_then(|v| match v {
None => Ok(()),
Some(ControlCheck::Jump(dest)) => {
if dest <= M256::from(usize::max_value()) && pc.is_valid(dest.as_usize()) {
Ok(())
} else {
Err(OnChainError::BadJumpDest.into())
}
}
}) {
Ok(()) => (),
Err(EvalOnChainError::OnChain(error)) => {
reset_error_hard!(self, error);
return Ok(());
}
Err(EvalOnChainError::Require(error)) => {
return Err(error);
}
}
if self.state.context.is_static {
match check_static(instruction, &self.state, runtime) {
Ok(()) => (),
Err(EvalOnChainError::OnChain(error)) => {
reset_error_hard!(self, error);
return Ok(());
}
Err(EvalOnChainError::Require(error)) => {
return Err(error);
}
}
}
let used_gas = match self.state.used_gas {
GasUsage::Some(gas) => gas,
GasUsage::All => {
reset_error_hard!(self, OnChainError::EmptyGas);
return Ok(());
}
};
let position = pc.position();
let memory_cost = memory_cost(instruction, &self.state);
let memory_gas = memory_gas(memory_cost);
let gas_cost = gas_cost::<M, P>(instruction, &self.state);
let gas_stipend = gas_stipend(instruction, &self.state);
let gas_refund = gas_refund(instruction, &self.state);
let all_gas_cost = memory_gas + used_gas + gas_cost;
if self.state.context.gas_limit < all_gas_cost {
reset_error_hard!(self, OnChainError::EmptyGas);
return Ok(());
}
match check_support(instruction, &self.state) {
Ok(()) => (),
Err(err) => {
reset_error_not_supported!(self, err);
return Ok(());
}
};
let after_gas = self.state.context.gas_limit - all_gas_cost;
match extra_check_opcode::<M, P>(instruction, &self.state, gas_stipend, after_gas) {
Ok(()) => (),
Err(err) => {
reset_error_hard!(self, err);
return Ok(());
}
}
Precheck {
position,
memory_cost,
gas_cost,
gas_stipend,
gas_refund,
after_gas,
}
};
trace!("position: {}", position);
trace!("memory_cost: {:x?}", memory_cost);
trace!("gas_cost: {:x?}", gas_cost);
trace!("gas_stipend: {:x?}", gas_stipend);
trace!("gas_refund: {:x}", gas_refund);
trace!("after_gas: {:x?}", after_gas);
let instruction = PCMut::<P>::new(
&self.state.patch,
&self.state.context.code,
&self.state.valids,
&mut self.state.position,
)
.read()
.unwrap();
let result = run_opcode::<M, P>(
(instruction, position),
&mut self.state,
runtime,
gas_stipend,
after_gas,
);
self.state.used_gas += gas_cost - gas_stipend;
self.state.memory_cost = memory_cost;
self.state.refunded_gas = self.state.refunded_gas.add_refund(gas_refund);;
debug!("{:?} => {:?}", instruction, result);
debug!("gas used: {:x?}", self.state.total_used_gas());
debug!("gas left: {:x?}", self.state.available_gas());
match result {
None => Ok(()),
Some(Control::Jump(dest)) => {
PCMut::<P>::new(
&self.state.patch,
&self.state.context.code,
&self.state.valids,
&mut self.state.position,
)
.jump(dest.as_usize())
.unwrap();
Ok(())
}
Some(Control::InvokeCall(context, (from, len))) => {
self.status = MachineStatus::InvokeCall(context, (from, len));
Ok(())
}
Some(Control::InvokeCreate(context)) => {
self.status = MachineStatus::InvokeCreate(context);
Ok(())
}
Some(Control::Stop) => {
self.status = MachineStatus::ExitedOk;
Ok(())
}
Some(Control::Revert) => {
reset_error_revert!(self);
Ok(())
}
}
}
pub fn state(&self) -> &State<M, P> {
&self.state
}
pub fn take_state(self) -> State<'a, M, P> {
self.state
}
pub fn pc(&self) -> PC<P> {
PC::new(
&self.state.patch,
&self.state.context.code,
&self.state.valids,
&self.state.position,
)
}
pub fn status(&self) -> MachineStatus {
self.status.clone()
}
}