use crate::Config;
use alloc::{borrow::ToOwned, vec, vec::Vec};
use frame_support::traits::Get;
use sp_runtime::{traits::Hash, Saturating};
use wasm_instrument::parity_wasm::{
builder,
elements::{
self, BlockType, CustomSection, FuncBody, Instruction, Instructions, Local, Section,
ValueType,
},
};
pub enum Location {
Call,
Deploy,
}
#[derive(Default)]
pub struct ModuleDefinition {
pub memory: Option<ImportedMemory>,
pub data_segments: Vec<DataSegment>,
pub num_globals: u32,
pub imported_functions: Vec<ImportedFunction>,
pub deploy_body: Option<FuncBody>,
pub call_body: Option<FuncBody>,
pub aux_body: Option<FuncBody>,
pub aux_arg_num: u32,
pub table: Option<TableSegment>,
pub dummy_section: u32,
}
pub struct TableSegment {
pub num_elements: u32,
pub function_index: u32,
}
pub struct DataSegment {
pub offset: u32,
pub value: Vec<u8>,
}
#[derive(Clone)]
pub struct ImportedMemory {
pub min_pages: u32,
pub max_pages: u32,
}
impl ImportedMemory {
pub fn max<T: Config>() -> Self {
let pages = max_pages::<T>();
Self { min_pages: pages, max_pages: pages }
}
}
pub struct ImportedFunction {
pub module: &'static str,
pub name: &'static str,
pub params: Vec<ValueType>,
pub return_type: Option<ValueType>,
}
#[derive(Clone)]
pub struct WasmModule<T: Config> {
pub code: Vec<u8>,
pub hash: <T::Hashing as Hash>::Output,
}
impl<T: Config> From<ModuleDefinition> for WasmModule<T> {
fn from(def: ModuleDefinition) -> Self {
let func_offset = u32::try_from(def.imported_functions.len()).unwrap();
let mut contract = builder::module()
.function()
.signature()
.build()
.with_body(
def.deploy_body
.unwrap_or_else(|| FuncBody::new(Vec::new(), Instructions::empty())),
)
.build()
.function()
.signature()
.build()
.with_body(
def.call_body
.unwrap_or_else(|| FuncBody::new(Vec::new(), Instructions::empty())),
)
.build()
.export()
.field("deploy")
.internal()
.func(func_offset)
.build()
.export()
.field("call")
.internal()
.func(func_offset + 1)
.build();
if let Some(body) = def.aux_body {
let mut signature = contract.function().signature();
for _ in 0..def.aux_arg_num {
signature = signature.with_param(ValueType::I64);
}
contract = signature.build().with_body(body).build();
}
let (init, max) = if let Some(memory) = &def.memory {
(memory.min_pages, Some(memory.max_pages))
} else {
(1, Some(1))
};
contract = contract.import().path("env", "memory").external().memory(init, max).build();
for func in def.imported_functions {
let sig = builder::signature()
.with_params(func.params)
.with_results(func.return_type)
.build_sig();
let sig = contract.push_signature(sig);
contract = contract
.import()
.module(func.module)
.field(func.name)
.with_external(elements::External::Function(sig))
.build();
}
for data in def.data_segments {
contract = contract
.data()
.offset(Instruction::I32Const(data.offset as i32))
.value(data.value)
.build()
}
if def.num_globals > 0 {
use rand::{distributions::Standard, prelude::*};
let rng = rand_pcg::Pcg32::seed_from_u64(3112244599778833558);
for val in rng.sample_iter(Standard).take(def.num_globals as usize) {
contract = contract
.global()
.value_type()
.i64()
.mutable()
.init_expr(Instruction::I64Const(val))
.build()
}
}
if let Some(table) = def.table {
contract = contract
.table()
.with_min(table.num_elements)
.with_max(Some(table.num_elements))
.with_element(0, vec![table.function_index; table.num_elements as usize])
.build();
}
if def.dummy_section > 0 {
contract = contract.with_section(Section::Custom(CustomSection::new(
"dummy".to_owned(),
vec![42; def.dummy_section as usize],
)));
}
let code = contract.build().into_bytes().unwrap();
let hash = T::Hashing::hash(&code);
Self { code: code.into(), hash }
}
}
impl<T: Config> WasmModule<T> {
pub fn dummy() -> Self {
ModuleDefinition::default().into()
}
pub fn dummy_with_bytes(dummy_bytes: u32) -> Self {
let module_overhead = 65;
ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
dummy_section: dummy_bytes.saturating_sub(module_overhead),
..Default::default()
}
.into()
}
pub fn sized(target_bytes: u32, code_location: Location, use_float: bool) -> Self {
use self::elements::Instruction::{End, GetLocal, If, Return};
let mut expansions = (target_bytes.saturating_sub(63) / 6).saturating_sub(1);
const EXPANSION: [Instruction; 4] = [GetLocal(0), If(BlockType::NoResult), Return, End];
let mut locals = vec![Local::new(1, ValueType::I32)];
if use_float {
locals.push(Local::new(1, ValueType::F32));
locals.push(Local::new(2, ValueType::F32));
locals.push(Local::new(3, ValueType::F32));
expansions.saturating_dec();
}
let mut module =
ModuleDefinition { memory: Some(ImportedMemory::max::<T>()), ..Default::default() };
let body = Some(body::repeated_with_locals(&locals, expansions, &EXPANSION));
match code_location {
Location::Call => module.call_body = body,
Location::Deploy => module.deploy_body = body,
}
module.into()
}
pub fn noop(repeat: u32) -> Self {
let pages = max_pages::<T>();
ModuleDefinition {
memory: Some(ImportedMemory::max::<T>()),
imported_functions: vec![ImportedFunction {
module: "seal0",
name: "noop",
params: vec![],
return_type: None,
}],
data_segments: vec![DataSegment {
offset: 0,
value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(),
}],
call_body: Some(body::repeated(
repeat,
&[
Instruction::Call(0), ],
)),
..Default::default()
}
.into()
}
}
pub mod body {
use super::*;
pub fn repeated(repetitions: u32, instructions: &[Instruction]) -> FuncBody {
repeated_with_locals(&[], repetitions, instructions)
}
pub fn repeated_with_locals(
locals: &[Local],
repetitions: u32,
instructions: &[Instruction],
) -> FuncBody {
let instructions = Instructions::new(
instructions
.iter()
.cycle()
.take(instructions.len() * usize::try_from(repetitions).unwrap())
.cloned()
.chain(core::iter::once(Instruction::End))
.collect(),
);
FuncBody::new(locals.to_vec(), instructions)
}
pub fn repeated_with_locals_using<const N: usize>(
locals: &[Local],
repetitions: u32,
mut f: impl FnMut() -> [Instruction; N],
) -> FuncBody {
let mut instructions = Vec::new();
for _ in 0..repetitions {
instructions.extend(f());
}
instructions.push(Instruction::End);
FuncBody::new(locals.to_vec(), Instructions::new(instructions))
}
}
pub fn max_pages<T: Config>() -> u32 {
T::Schedule::get().limits.memory_pages
}