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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
//! Module that takes care of loading, checking and preprocessing of a
//! wasm module before execution.
use crate::logic::errors::PrepareError;
use unc_parameters::vm::{Config, VMKind};
mod prepare_v0;
mod prepare_v1;
mod prepare_v2;
/// Loads the given module given in `original_code`, performs some checks on it and
/// does some preprocessing.
///
/// The checks are:
///
/// - module doesn't define an internal memory instance,
/// - imported memory (if any) doesn't reserve more memory than permitted by the `config`,
/// - all imported functions from the external environment matches defined by `env` module,
/// - functions number does not exceed limit specified in Config,
///
/// The preprocessing includes injecting code for gas metering and metering the height of stack.
pub fn prepare_contract(
original_code: &[u8],
config: &Config,
kind: VMKind,
) -> Result<Vec<u8>, PrepareError> {
let prepare = config.limit_config.contract_prepare_version;
// UncVM => ContractPrepareVersion::V2
assert!(
(kind != VMKind::UncVm) || (prepare == crate::logic::ContractPrepareVersion::V2),
"UncVM only works with contract prepare version V2",
);
let features = crate::features::WasmFeatures::from(prepare);
match prepare {
crate::logic::ContractPrepareVersion::V0 => {
// NB: v1 here is not a bug, we are reusing the code.
prepare_v1::validate_contract(original_code, features, config)?;
prepare_v0::prepare_contract(original_code, config)
}
crate::logic::ContractPrepareVersion::V1 => {
prepare_v1::validate_contract(original_code, features, config)?;
prepare_v1::prepare_contract(original_code, config)
}
crate::logic::ContractPrepareVersion::V2 => {
prepare_v2::prepare_contract(original_code, features, config, kind)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::{test_vm_config, with_vm_variants};
use assert_matches::assert_matches;
fn parse_and_prepare_wat(
config: &Config,
vm_kind: VMKind,
wat: &str,
) -> Result<Vec<u8>, PrepareError> {
let wasm = wat::parse_str(wat).unwrap();
prepare_contract(wasm.as_ref(), &config, vm_kind)
}
#[test]
fn internal_memory_declaration() {
let config = test_vm_config();
with_vm_variants(&config, |kind| {
let r = parse_and_prepare_wat(&config, kind, r#"(module (memory 1 1))"#);
assert_matches!(r, Ok(_));
})
}
#[test]
fn memory_imports() {
let config = test_vm_config();
// This test assumes that maximum page number is configured to a certain number.
assert_eq!(config.limit_config.max_memory_pages, 2048);
with_vm_variants(&config, |kind| {
let r = parse_and_prepare_wat(
&config,
kind,
r#"(module (import "env" "memory" (memory 1 1)))"#,
);
assert_matches!(r, Err(PrepareError::Memory));
// No memory import
let r = parse_and_prepare_wat(&config, kind, r#"(module)"#);
assert_matches!(r, Ok(_));
// initial exceed maximum
let r = parse_and_prepare_wat(
&config,
kind,
r#"(module (import "env" "memory" (memory 17 1)))"#,
);
assert_matches!(r, Err(PrepareError::Deserialization));
// no maximum
let r = parse_and_prepare_wat(
&config,
kind,
r#"(module (import "env" "memory" (memory 1)))"#,
);
assert_matches!(r, Err(PrepareError::Memory));
// requested maximum exceed configured maximum
let r = parse_and_prepare_wat(
&config,
kind,
r#"(module (import "env" "memory" (memory 1 33)))"#,
);
assert_matches!(r, Err(PrepareError::Memory));
})
}
#[test]
fn multiple_valid_memory_are_disabled() {
let config = test_vm_config();
with_vm_variants(&config, |kind| {
// Our preparation and sanitization pass assumes a single memory, so we should fail when
// there are multiple specified.
let r = parse_and_prepare_wat(
&config,
kind,
r#"(module
(import "env" "memory" (memory 1 2048))
(import "env" "memory" (memory 1 2048))
)"#,
);
assert_matches!(r, Err(_));
let r = parse_and_prepare_wat(
&config,
kind,
r#"(module
(import "env" "memory" (memory 1 2048))
(memory 1)
)"#,
);
assert_matches!(r, Err(_));
})
}
#[test]
fn imports() {
let config = test_vm_config();
with_vm_variants(&config, |kind| {
// nothing can be imported from non-"env" module for now.
let r = parse_and_prepare_wat(
&config,
kind,
r#"(module (import "another_module" "memory" (memory 1 1)))"#,
);
assert_matches!(r, Err(PrepareError::Instantiate));
let r = parse_and_prepare_wat(
&config,
kind,
r#"(module (import "env" "gas" (func (param i32))))"#,
);
assert_matches!(r, Ok(_));
// TODO: Address tests once we check proper function signatures.
/*
// wrong signature
let r = parse_and_prepare_wat(r#"(module (import "env" "gas" (func (param i64))))"#);
assert_matches!(r, Err(Error::Instantiate));
// unknown function name
let r = parse_and_prepare_wat(r#"(module (import "env" "unknown_func" (func)))"#);
assert_matches!(r, Err(Error::Instantiate));
*/
})
}
}