1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
use crate::errors::ContractPrecompilatonResult;
use crate::logic::errors::{CacheError, CompilationError, VMRunnerError};
use crate::logic::types::PromiseResult;
use crate::logic::{CompiledContractCache, External, VMContext, VMOutcome};
use crate::ContractCode;
use unc_parameters::vm::{Config, VMKind};
use unc_parameters::RuntimeFeesConfig;
/// Returned by VM::run method.
///
/// `VMRunnerError` means framework is buggy or the data base has been corrupted.
/// We are unable to produce a deterministic result. The correct action usually
/// is to crash or maybe ban a peer and/or send a challenge.
///
/// A `VMOutcome` is a graceful completion of a VM execution. It can also contain
/// an guest error message in the `aborted` field. But these are not errors in
/// the real sense, those are just reasons why execution failed at some point.
/// Such as when a smart contract code panics.
/// Note that the fact that `VMOutcome` contains is tracked on the blockchain.
/// All validators must produce an error deterministically or all should succeed.
/// (See also `PartialExecutionStatus`.)
/// Similarly, the gas values on `VMOutcome` must be the exact same on all
/// validators, even when a guest error occurs, or else their state will diverge.
pub(crate) type VMResult<T = VMOutcome> = Result<T, VMRunnerError>;
/// Validate and run the specified contract.
///
/// This is the entry point for executing a UNC protocol contract. Before the
/// entry point (as specified by the `method_name` argument) of the contract
/// code is executed, the contract will be validated (see
/// [`crate::prepare::prepare_contract`]), instrumented (e.g. for gas
/// accounting), and linked with the externs specified via the `ext` argument.
///
/// [`VMContext::input`] will be passed to the contract entrypoint as an
/// argument.
///
/// The contract will be executed with the default VM implementation for the
/// current protocol version.
///
/// The gas cost for contract preparation will be subtracted by the VM
/// implementation.
pub fn run(
code: &ContractCode,
method_name: &str,
ext: &mut dyn External,
context: VMContext,
wasm_config: &Config,
fees_config: &RuntimeFeesConfig,
promise_results: &[PromiseResult],
cache: Option<&dyn CompiledContractCache>,
) -> VMResult {
let vm_kind = wasm_config.vm_kind;
let span = tracing::debug_span!(
target: "vm",
"run",
"code.len" = code.code().len(),
%method_name,
?vm_kind,
burnt_gas = tracing::field::Empty,
)
.entered();
let runtime = vm_kind
.runtime(wasm_config.clone())
.unwrap_or_else(|| panic!("the {vm_kind:?} runtime has not been enabled at compile time"));
let outcome =
runtime.run(code, method_name, ext, context, fees_config, promise_results, cache)?;
span.record("burnt_gas", &outcome.burnt_gas);
Ok(outcome)
}
pub trait VM {
/// Validate and run the specified contract.
///
/// This is the entry point for executing a UNC protocol contract. Before
/// the entry point (as specified by the `method_name` argument) of the
/// contract code is executed, the contract will be validated (see
/// [`crate::prepare::prepare_contract`]), instrumented (e.g. for gas
/// accounting), and linked with the externs specified via the `ext`
/// argument.
///
/// [`VMContext::input`] will be passed to the contract entrypoint as an
/// argument.
///
/// The gas cost for contract preparation will be subtracted by the VM
/// implementation.
fn run(
&self,
code: &ContractCode,
method_name: &str,
ext: &mut dyn External,
context: VMContext,
fees_config: &RuntimeFeesConfig,
promise_results: &[PromiseResult],
cache: Option<&dyn CompiledContractCache>,
) -> VMResult;
/// Precompile a WASM contract to a VM specific format and store the result
/// into the `cache`.
///
/// Further calls to [`Self::run`] or [`Self::precompile`] with the same
/// `code`, `cache` and [`Config`] may reuse the results of this
/// precompilation step.
fn precompile(
&self,
code: &ContractCode,
cache: &dyn CompiledContractCache,
) -> Result<Result<ContractPrecompilatonResult, CompilationError>, CacheError>;
}
pub trait VMKindExt {
/// Make a [`VM`] for this [`VMKind`].
///
/// This is not intended to be used by code other than internal tools like
/// the estimator.
fn runtime(&self, config: Config) -> Option<Box<dyn VM>>;
}
impl VMKindExt for VMKind {
fn runtime(&self, config: Config) -> Option<Box<dyn VM>> {
match self {
#[cfg(all(feature = "wasmer0_vm", target_arch = "x86_64"))]
Self::Wasmer0 => Some(Box::new(crate::wasmer_runner::Wasmer0VM::new(config))),
#[cfg(feature = "wasmtime_vm")]
Self::Wasmtime => Some(Box::new(crate::wasmtime_runner::WasmtimeVM::new(config))),
#[cfg(all(feature = "wasmer2_vm", target_arch = "x86_64"))]
Self::Wasmer2 => Some(Box::new(crate::wasmer2_runner::Wasmer2VM::new(config))),
#[cfg(all(feature = "unc_vm", target_arch = "x86_64"))]
Self::NearVm => Some(Box::new(crate::unc_vm_runner::NearVM::new(config))),
#[allow(unreachable_patterns)] // reachable when some of the VMs are disabled.
_ => None,
}
}
}