#![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;
#[cfg(feature = "c-secp256k1")]
extern crate secp256k1;
#[cfg(feature = "rust-secp256k1")]
extern crate secp256k1;
#[cfg(feature = "std")]
extern crate block;
#[macro_use]
extern crate log;
mod commit;
pub mod errors;
mod eval;
mod memory;
mod params;
mod patch;
mod pc;
mod stack;
mod transaction;
mod util;
pub use crate::commit::{AccountChange, AccountCommitment, AccountState, BlockhashState, Storage};
pub use crate::errors::{CommitError, NotSupportedError, OnChainError, PreExecutionError, RequireError};
pub use crate::eval::{Machine, MachineStatus, Runtime, State};
pub use crate::memory::{Memory, SeqMemory};
pub use crate::params::*;
pub use crate::patch::*;
pub use crate::pc::{Instruction, PCMut, Valids, PC};
pub use crate::stack::Stack;
pub use crate::transaction::{TransactionVM, UntrustedTransaction, ValidTransaction};
pub use crate::util::opcode::Opcode;
pub use block_core::TransactionAction;
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
#[cfg(not(feature = "std"))]
use alloc::boxed::Box;
#[cfg(not(feature = "std"))]
use alloc::{collections::btree_map as map, collections::BTreeSet as Set};
use bigint::{Address, Gas, H256, U256};
#[cfg(not(feature = "std"))]
use core::cmp::min;
#[cfg(feature = "std")]
use std::cmp::min;
#[cfg(feature = "std")]
use std::collections::{hash_map as map, HashSet as Set};
#[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<'a, P> = ContextVM<'a, SeqMemory, P>;
pub type SeqTransactionVM<'a, P> = TransactionVM<'a, SeqMemory, P>;
pub struct ContextVM<'a, M, P: Patch> {
runtime: Runtime,
machines: Vec<Machine<'a, M, P>>,
fresh_account_state: AccountState<'a, P::Account>,
}
impl<'a, M: Memory, P: Patch> ContextVM<'a, M, P> {
pub fn new(patch: &'a P, context: Context, block: HeaderParams) -> Self {
let mut machines = Vec::new();
let account_patch = patch.account_patch();
machines.push(Machine::new(patch, context, 1));
ContextVM {
machines,
runtime: Runtime::new(block),
fresh_account_state: AccountState::new(account_patch),
}
}
pub fn with_states(
patch: &'a P,
context: Context,
block: HeaderParams,
account_state: AccountState<'a, P::Account>,
blockhash_state: BlockhashState,
) -> Self {
let mut machines = Vec::new();
machines.push(Machine::with_states(patch, 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>)>(
patch: &'a P,
context: Context,
block: HeaderParams,
account_state: AccountState<'a, P::Account>,
blockhash_state: BlockhashState,
f: F,
) -> Self {
let mut vm = Self::with_states(patch, context, block, account_state, blockhash_state);
f(&mut vm);
vm.fresh_account_state =
AccountState::derive_from(patch.account_patch(), &vm.machines[0].state().account_state);
vm
}
pub fn with_previous(patch: &'a P, context: Context, block: HeaderParams, vm: &'a ContextVM<'a, M, P>) -> Self {
Self::with_states(
patch,
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<'a, M: Memory, P: Patch> VM for ContextVM<'a, 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(())
}
#[allow(clippy::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
}
}