#![deny(unused_import_braces, unused_imports,
unused_comparisons, unused_must_use,
unused_variables, non_shorthand_field_patterns,
unreachable_code, missing_docs)]
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(not(feature = "std"), feature(alloc))]
#[cfg(not(feature = "std"))]
extern crate alloc;
extern crate rlp;
extern crate bigint;
extern crate block_core;
extern crate sha3;
extern crate ripemd160;
extern crate sha2;
extern crate digest;
#[macro_use]
extern crate log;
#[cfg(feature = "c-secp256k1")]
extern crate secp256k1;
#[cfg(feature = "rust-secp256k1")]
extern crate secp256k1;
#[cfg(feature = "std")]
extern crate block;
#[cfg(test)]
extern crate hexutil;
mod util;
mod memory;
mod stack;
mod pc;
mod params;
mod eval;
mod commit;
mod patch;
mod transaction;
pub mod errors;
pub use self::memory::{Memory, SeqMemory};
pub use self::stack::Stack;
pub use self::pc::{PC, PCMut, Instruction, Valids};
pub use self::params::*;
pub use self::patch::*;
pub use self::eval::{State, Machine, Runtime, MachineStatus};
pub use self::commit::{AccountCommitment, AccountChange, AccountState, BlockhashState, Storage};
pub use self::transaction::{ValidTransaction, TransactionVM, UntrustedTransaction};
pub use self::errors::{OnChainError, NotSupportedError, RequireError, CommitError, PreExecutionError};
pub use self::util::opcode::Opcode;
pub use block_core::TransactionAction;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(feature = "std")] use std::collections::{HashSet as Set, hash_map as map};
#[cfg(not(feature = "std"))] use alloc::{collections::BTreeSet as Set, collections::btree_map as map};
#[cfg(not(feature = "std"))] use alloc::boxed::Box;
#[cfg(feature = "std")] use std::cmp::min;
#[cfg(not(feature = "std"))] use core::cmp::min;
use bigint::{U256, H256, Gas, Address};
#[derive(Debug, Clone, PartialEq)]
pub enum VMStatus {
Running,
ExitedOk,
ExitedErr(OnChainError),
ExitedNotSupported(NotSupportedError),
}
pub trait VM {
fn commit_account(&mut self, commitment: AccountCommitment) -> Result<(), CommitError>;
fn commit_blockhash(&mut self, number: U256, hash: H256) -> Result<(), CommitError>;
fn status(&self) -> VMStatus;
fn peek(&self) -> Option<Instruction>;
fn peek_opcode(&self) -> Option<Opcode>;
fn step(&mut self) -> Result<(), RequireError>;
fn fire(&mut self) -> Result<(), RequireError> {
loop {
match self.status() {
VMStatus::Running => self.step()?,
VMStatus::ExitedOk | VMStatus::ExitedErr(_) |
VMStatus::ExitedNotSupported(_) => return Ok(()),
}
}
}
fn accounts(&self) -> map::Values<Address, AccountChange>;
fn used_addresses(&self) -> Set<Address>;
fn out(&self) -> &[u8];
fn available_gas(&self) -> Gas;
fn refunded_gas(&self) -> Gas;
fn logs(&self) -> &[Log];
fn removed(&self) -> &[Address];
fn used_gas(&self) -> Gas;
}
pub type SeqContextVM<P> = ContextVM<SeqMemory<P>, P>;
pub type SeqTransactionVM<P> = TransactionVM<SeqMemory<P>, P>;
pub struct ContextVM<M, P: Patch> {
runtime: Runtime,
machines: Vec<Machine<M, P>>,
fresh_account_state: AccountState<P::Account>,
}
impl<M: Memory + Default, P: Patch> ContextVM<M, P> {
pub fn new(context: Context, block: HeaderParams) -> Self {
let mut machines = Vec::new();
machines.push(Machine::new(context, 1));
ContextVM {
machines,
runtime: Runtime::new(block),
fresh_account_state: AccountState::default(),
}
}
pub fn with_states(context: Context, block: HeaderParams,
account_state: AccountState<P::Account>, blockhash_state: BlockhashState) -> Self {
let mut machines = Vec::new();
machines.push(Machine::with_states(context, 1, account_state.clone()));
ContextVM {
machines,
runtime: Runtime::with_states(block, blockhash_state),
fresh_account_state: account_state,
}
}
pub fn with_init<F: FnOnce(&mut ContextVM<M, P>)>(
context: Context, block: HeaderParams,
account_state: AccountState<P::Account>, blockhash_state: BlockhashState,
f: F) -> Self {
let mut vm = Self::with_states(context, block, account_state, blockhash_state);
f(&mut vm);
vm.fresh_account_state = vm.machines[0].state().account_state.clone();
vm
}
pub fn with_previous(context: Context, block: HeaderParams, vm: &ContextVM<M, P>) -> Self {
Self::with_states(context, block,
vm.machines[0].state().account_state.clone(),
vm.runtime.blockhash_state.clone())
}
pub fn current_state(&self) -> &State<M, P> {
self.current_machine().state()
}
pub fn current_machine(&self) -> &Machine<M, P> {
self.machines.last().unwrap()
}
pub fn add_context_history_hook<F: 'static + Fn(&Context)>(&mut self, f: F) {
self.runtime.context_history_hooks.push(Box::new(f));
debug!("registered a new history hook");
}
}
impl<M: Memory + Default, P: Patch> VM for ContextVM<M, P> {
fn commit_account(&mut self, commitment: AccountCommitment) -> Result<(), CommitError> {
for machine in &mut self.machines {
machine.commit_account(commitment.clone())?;
}
debug!("committed account info: {:?}", commitment);
Ok(())
}
fn commit_blockhash(&mut self, number: U256, hash: H256) -> Result<(), CommitError> {
self.runtime.blockhash_state.commit(number, hash)?;
debug!("committed blockhash number {}: {}", number, hash);
Ok(())
}
#[cfg_attr(feature = "cargo-clippy", allow(single_match))]
fn status(&self) -> VMStatus {
match self.machines.last().unwrap().status().clone() {
MachineStatus::ExitedNotSupported(err) => return VMStatus::ExitedNotSupported(err),
_ => (),
}
match self.machines[0].status() {
MachineStatus::Running | MachineStatus::InvokeCreate(_) | MachineStatus::InvokeCall(_, _) => VMStatus::Running,
MachineStatus::ExitedOk => VMStatus::ExitedOk,
MachineStatus::ExitedErr(err) => VMStatus::ExitedErr(err),
MachineStatus::ExitedNotSupported(err) => VMStatus::ExitedNotSupported(err),
}
}
fn peek(&self) -> Option<Instruction> {
match self.machines.last().unwrap().status().clone() {
MachineStatus::Running => {
self.machines.last().unwrap().peek()
},
_ => None,
}
}
fn peek_opcode(&self) -> Option<Opcode> {
match self.machines.last().unwrap().status().clone() {
MachineStatus::Running => {
self.machines.last().unwrap().peek_opcode()
},
_ => None,
}
}
fn step(&mut self) -> Result<(), RequireError> {
match self.machines.last().unwrap().status().clone() {
MachineStatus::Running => {
self.machines.last_mut().unwrap().step(&self.runtime)?;
if self.machines.len() == 1 {
match self.machines.last().unwrap().status().clone() {
MachineStatus::ExitedOk | MachineStatus::ExitedErr(_) =>
self.machines.last_mut().unwrap().finalize_context(&self.fresh_account_state),
_ => (),
}
}
Ok(())
},
MachineStatus::ExitedOk | MachineStatus::ExitedErr(_) => {
if self.machines.is_empty() {
panic!()
} else if self.machines.len() == 1 {
Ok(())
} else {
let finished = self.machines.pop().unwrap();
self.machines.last_mut().unwrap().apply_sub(finished);
Ok(())
}
},
MachineStatus::ExitedNotSupported(_) => {
Ok(())
},
MachineStatus::InvokeCall(context, _) => {
for hook in &self.runtime.context_history_hooks {
hook(&context)
}
let mut sub = self.machines.last().unwrap().derive(context);
sub.invoke_call()?;
self.machines.push(sub);
Ok(())
},
MachineStatus::InvokeCreate(context) => {
for hook in &self.runtime.context_history_hooks {
hook(&context)
}
let mut sub = self.machines.last().unwrap().derive(context);
sub.invoke_create()?;
self.machines.push(sub);
Ok(())
},
}
}
fn fire(&mut self) -> Result<(), RequireError> {
loop {
debug!("machines status:");
for (n, machine) in self.machines.iter().enumerate() {
debug!("Machine {}: {:x?}", n, machine.status());
}
match self.status() {
VMStatus::Running => self.step()?,
VMStatus::ExitedOk | VMStatus::ExitedErr(_) |
VMStatus::ExitedNotSupported(_) => return Ok(()),
}
}
}
fn accounts(&self) -> map::Values<Address, AccountChange> {
self.machines[0].state().account_state.accounts()
}
fn used_addresses(&self) -> Set<Address> {
self.machines[0].state().account_state.used_addresses()
}
fn out(&self) -> &[u8] {
self.machines[0].state().out.as_slice()
}
fn available_gas(&self) -> Gas {
self.machines[0].state().available_gas()
}
fn refunded_gas(&self) -> Gas {
self.machines[0].state().refunded_gas
}
fn logs(&self) -> &[Log] {
self.machines[0].state().logs.as_slice()
}
fn removed(&self) -> &[Address] {
self.machines[0].state().removed.as_slice()
}
fn used_gas(&self) -> Gas {
let total_used = self.machines[0].state().total_used_gas();
let refund_cap = total_used / Gas::from(2u64);
let refunded = min(refund_cap, self.machines[0].state().refunded_gas);
total_used - refunded
}
}