use crate::logic::errors::PrepareError;
use crate::logic::Config;
use parity_wasm::builder;
use parity_wasm::elements::{self, External, MemorySection};
pub(crate) fn prepare_contract(
original_code: &[u8],
config: &Config,
) -> Result<Vec<u8>, PrepareError> {
ContractModule::init(original_code, config)?
.scan_imports()?
.standardize_mem()
.ensure_no_internal_memory()?
.inject_gas_metering()?
.inject_stack_height_metering()?
.into_wasm_code()
}
pub(crate) struct ContractModule<'a> {
module: elements::Module,
config: &'a Config,
}
impl<'a> ContractModule<'a> {
pub(crate) fn init(original_code: &[u8], config: &'a Config) -> Result<Self, PrepareError> {
let module = parity_wasm::deserialize_buffer(original_code).map_err(|e| {
tracing::debug!(err=?e, "parity_wasm failed decoding a contract");
PrepareError::Deserialization
})?;
Ok(ContractModule { module, config })
}
pub(crate) fn standardize_mem(self) -> Self {
let Self { mut module, config } = self;
let mut tmp = MemorySection::default();
module.memory_section_mut().unwrap_or(&mut tmp).entries_mut().pop();
let entry = elements::MemoryType::new(
config.limit_config.initial_memory_pages,
Some(config.limit_config.max_memory_pages),
);
let mut builder = builder::from_module(module);
builder.push_import(elements::ImportEntry::new(
"env".to_string(),
"memory".to_string(),
elements::External::Memory(entry),
));
Self { module: builder.build(), config }
}
pub(crate) fn ensure_no_internal_memory(self) -> Result<Self, PrepareError> {
if self.module.memory_section().map_or(false, |ms| !ms.entries().is_empty()) {
Err(PrepareError::InternalMemoryDeclared)
} else {
Ok(self)
}
}
fn inject_gas_metering(self) -> Result<Self, PrepareError> {
let Self { module, config } = self;
if config.regular_op_cost == 0 {
return Ok(Self { module, config });
}
let gas_rules = crate::instrument::rules::Set::new(1, Default::default())
.with_grow_cost(config.grow_mem_cost);
let module = crate::instrument::gas::inject_gas_counter(module, &gas_rules, "env")
.map_err(|_| PrepareError::GasInstrumentation)?;
Ok(Self { module, config })
}
fn inject_stack_height_metering(self) -> Result<Self, PrepareError> {
let Self { module, config } = self;
let module = crate::instrument::stack_height::inject_limiter(
module,
config.limit_config.max_stack_height,
)
.map_err(|_| PrepareError::StackHeightInstrumentation)?;
Ok(Self { module, config })
}
pub(crate) fn scan_imports(self) -> Result<Self, PrepareError> {
let Self { module, config } = self;
let types = module.type_section().map(elements::TypeSection::types).unwrap_or(&[]);
let import_entries =
module.import_section().map(elements::ImportSection::entries).unwrap_or(&[]);
for import in import_entries {
if import.module() != "env" {
return Err(PrepareError::Instantiate);
}
let type_idx = match *import.external() {
External::Function(ref type_idx) => type_idx,
External::Memory(_) => return Err(PrepareError::Memory),
_ => continue,
};
let elements::Type::Function(ref _func_ty) =
types.get(*type_idx as usize).ok_or(PrepareError::Instantiate)?;
}
Ok(Self { module, config })
}
pub(crate) fn into_wasm_code(self) -> Result<Vec<u8>, PrepareError> {
elements::serialize(self.module).map_err(|_| PrepareError::Serialization)
}
}
fn wasmparser_decode(
code: &[u8],
features: crate::features::WasmFeatures,
) -> Result<(Option<u64>, Option<u64>), wasmparser::BinaryReaderError> {
use wasmparser::{FuncValidatorAllocations, TypeRef, ValidPayload};
let mut validator = wasmparser::Validator::new_with_features(features.into());
let mut function_count = Some(0u64);
let mut local_count = Some(0u64);
for payload in wasmparser::Parser::new(0).parse_all(code) {
let payload = payload?;
if let wasmparser::Payload::ImportSection(ref import_section_reader) = payload {
let import_section_reader = import_section_reader.clone();
for import in import_section_reader {
match import?.ty {
TypeRef::Func(_) => {
function_count = function_count.and_then(|f| f.checked_add(1))
}
TypeRef::Table(_)
| TypeRef::Memory(_)
| TypeRef::Tag(_)
| TypeRef::Global(_) => {}
}
}
}
match validator.payload(&payload)? {
ValidPayload::Ok => (),
ValidPayload::Func(func, body) => {
let allocs = FuncValidatorAllocations::default();
let mut validator = func.into_validator(allocs);
validator.validate(&body)?;
function_count = function_count.and_then(|f| f.checked_add(1));
let mut local_reader = body.get_locals_reader()?;
for _ in 0..local_reader.get_count() {
let (count, _type) = local_reader.read()?;
local_count = local_count.and_then(|l| l.checked_add(count.into()));
}
}
_ => {}
}
}
Ok((function_count, local_count))
}
pub(crate) fn validate_contract(
code: &[u8],
features: crate::features::WasmFeatures,
config: &Config,
) -> Result<(), PrepareError> {
let (function_count, local_count) = wasmparser_decode(code, features).map_err(|e| {
tracing::debug!(err=?e, "wasmparser failed decoding a contract");
PrepareError::Deserialization
})?;
if let Some(max_functions) = config.limit_config.max_functions_number_per_contract {
if function_count.ok_or(PrepareError::TooManyFunctions)? > max_functions {
return Err(PrepareError::TooManyFunctions);
}
}
if let Some(max_locals) = config.limit_config.max_locals_per_contract {
if local_count.ok_or(PrepareError::TooManyLocals)? > max_locals {
return Err(PrepareError::TooManyLocals);
}
}
Ok(())
}
#[cfg(test)]
mod test {
use crate::logic::ContractPrepareVersion;
use crate::tests::test_vm_config;
#[test]
fn v1_preparation_generates_valid_contract_fuzzer() {
let mut config = test_vm_config();
let prepare_version = ContractPrepareVersion::V1;
config.limit_config.contract_prepare_version = prepare_version;
let features = crate::features::WasmFeatures::from(prepare_version);
bolero::check!().for_each(|input: &[u8]| {
if let Ok(_) = super::validate_contract(input, features, &config) {
match super::prepare_contract(input, &config) {
Err(_e) => (), Ok(code) => {
let mut validator =
wasmparser::Validator::new_with_features(features.into());
match validator.validate_all(&code) {
Ok(_) => (),
Err(e) => panic!(
"prepared code failed validation: {e:?}\ncontract: {}",
hex::encode(input),
),
}
}
}
}
});
}
}