#[cfg(not(feature = "std"))]
use alloc::Vec;
#[cfg(not(feature = "std"))] use alloc::rc::Rc;
#[cfg(feature = "std")] use std::rc::Rc;
use bigint::{M256, U256, Gas, Address};
use super::pc::Instruction;
use super::commit::{AccountState, BlockhashState};
use super::errors::{RequireError, RuntimeError, CommitError, EvalOnChainError,
OnChainError, NotSupportedError};
use super::{Stack, Context, HeaderParams, Patch, PC, PCMut, Valids, Memory,
AccountCommitment, Log, Opcode};
use self::check::{check_opcode, check_support, extra_check_opcode};
use self::run::run_opcode;
use self::cost::{gas_refund, gas_stipend, gas_cost, memory_cost, memory_gas};
mod cost;
mod run;
mod check;
mod util;
mod lifecycle;
pub struct State<M, P: Patch> {
pub memory: M,
pub stack: Stack,
pub context: Context,
pub out: Rc<Vec<u8>>,
pub memory_cost: Gas,
pub used_gas: Gas,
pub refunded_gas: Gas,
pub account_state: AccountState<P::Account>,
pub logs: Vec<Log>,
pub removed: Vec<Address>,
pub depth: usize,
pub valids: Valids,
pub position: usize,
}
impl<M, P: Patch> State<M, P> {
pub fn memory_gas(&self) -> Gas {
memory_gas(self.memory_cost)
}
pub fn available_gas(&self) -> Gas {
self.context.gas_limit - self.memory_gas() - self.used_gas
}
pub fn total_used_gas(&self) -> Gas {
self.memory_gas() + self.used_gas
}
}
pub struct Runtime {
pub blockhash_state: BlockhashState,
pub block: HeaderParams,
}
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
}
}
}
pub struct Machine<M, P: Patch> {
state: State<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,
Jump(M256),
InvokeCreate(Context),
InvokeCall(Context, (U256, U256)),
}
impl<M: Memory + Default, P: Patch> Machine<M, P> {
pub fn new(context: Context, depth: usize) -> Self {
Self::with_states(context, depth,
AccountState::default())
}
pub fn with_states(context: Context,
depth: usize, account_state: AccountState<P::Account>) -> Self {
Machine {
status: MachineStatus::Running,
state: State {
memory: M::default(),
stack: Stack::default(),
out: Rc::new(Vec::new()),
memory_cost: Gas::zero(),
used_gas: 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 derive(&self, context: Context) -> Self {
Machine {
status: MachineStatus::Running,
state: State {
memory: M::default(),
stack: Stack::default(),
out: Rc::new(Vec::new()),
memory_cost: Gas::zero(),
used_gas: Gas::zero(),
refunded_gas: Gas::zero(),
account_state: self.state.account_state.clone(),
logs: self.state.logs.clone(),
removed: self.state.removed.clone(),
depth: self.state.depth + 1,
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 P::precompileds() {
if self.state.context.address == precompiled.0 &&
(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)) => {
self.state.used_gas = self.state.context.gas_limit;
self.status = MachineStatus::ExitedErr(err);
},
Err(RuntimeError::NotSupported(err)) => {
self.status = MachineStatus::ExitedNotSupported(err);
},
Ok((gas, ret)) => {
assert!(gas <= self.state.context.gas_limit);
self.state.used_gas = gas;
self.state.out = ret;
self.status = MachineStatus::ExitedOk;
}
}
return true;
}
}
return false;
}
pub fn peek(&self) -> Option<Instruction> {
let pc = PC::<P>::new(&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.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> {
struct Precheck {
position: usize,
memory_cost: Gas,
gas_cost: Gas,
gas_stipend: Gas,
gas_refund: Gas,
after_gas: Gas,
}
match &self.status {
&MachineStatus::Running => (),
_ => panic!(),
}
if self.step_precompiled() {
return Ok(());
}
let Precheck {
position, memory_cost,
gas_cost, gas_stipend, gas_refund, after_gas
} = {
let pc = PC::<P>::new(&self.state.context.code,
&self.state.valids, &self.state.position);
if pc.is_end() {
self.status = MachineStatus::ExitedOk;
return Ok(());
}
let instruction = match pc.peek() {
Ok(val) => val,
Err(err) => {
self.status = MachineStatus::ExitedErr(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)) => {
self.status = MachineStatus::ExitedErr(error);
return Ok(());
},
Err(EvalOnChainError::Require(error)) => {
return Err(error);
},
}
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 + self.state.used_gas + gas_cost;
if self.state.context.gas_limit < all_gas_cost {
self.status = MachineStatus::ExitedErr(OnChainError::EmptyGas);
return Ok(());
}
match check_support(instruction, &self.state) {
Ok(()) => (),
Err(err) => {
self.status = MachineStatus::ExitedNotSupported(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) => {
self.status = MachineStatus::ExitedErr(err);
return Ok(());
},
}
Precheck {
position, memory_cost,
gas_cost, gas_stipend, gas_refund, after_gas
}
};
let instruction = PCMut::<P>::new(&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 = self.state.used_gas + gas_cost - gas_stipend;
self.state.memory_cost = memory_cost;
self.state.refunded_gas = self.state.refunded_gas + gas_refund;
match result {
None => Ok(()),
Some(Control::Jump(dest)) => {
PCMut::<P>::new(&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(())
},
}
}
pub fn state(&self) -> &State<M, P> {
&self.state
}
pub fn pc(&self) -> PC<P> {
PC::new(&self.state.context.code, &self.state.valids, &self.state.position)
}
pub fn status(&self) -> MachineStatus {
self.status.clone()
}
}