123456789101112131415161718192021222324252627use crate::{Config, CurrentSchedule};28use parity_wasm::elements::{29 Instruction, Instructions, FuncBody, ValueType, BlockType, Section, CustomSection,30};31use pwasm_utils::stack_height::inject_limiter;32use sp_core::crypto::UncheckedFrom;33use sp_runtime::traits::Hash;34use sp_sandbox::{EnvironmentDefinitionBuilder, Memory};35use sp_std::{prelude::*, convert::TryFrom, borrow::ToOwned};36373839404142#[derive(Default)]43pub struct ModuleDefinition {44 45 pub memory: Option<ImportedMemory>,46 47 pub data_segments: Vec<DataSegment>,48 49 pub num_globals: u32,50 51 pub imported_functions: Vec<ImportedFunction>,52 53 54 pub deploy_body: Option<FuncBody>,55 56 57 pub call_body: Option<FuncBody>,58 59 pub aux_body: Option<FuncBody>,60 61 pub aux_arg_num: u32,62 63 64 65 66 pub inject_stack_metering: bool,67 68 pub table: Option<TableSegment>,69 70 71 72 pub dummy_section: u32,73}7475pub struct TableSegment {76 77 pub num_elements: u32,78 79 pub function_index: u32,80}8182pub struct DataSegment {83 pub offset: u32,84 pub value: Vec<u8>,85}8687#[derive(Clone)]88pub struct ImportedMemory {89 pub min_pages: u32,90 pub max_pages: u32,91}9293impl ImportedMemory {94 pub fn max<T: Config>() -> Self95 where96 T: Config,97 T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,98 {99 let pages = max_pages::<T>();100 Self {101 min_pages: pages,102 max_pages: pages,103 }104 }105}106107pub struct ImportedFunction {108 pub name: &'static str,109 pub params: Vec<ValueType>,110 pub return_type: Option<ValueType>,111}112113114#[derive(Clone)]115pub struct WasmModule<T: Config> {116 pub code: Vec<u8>,117 pub hash: <T::Hashing as Hash>::Output,118 memory: Option<ImportedMemory>,119}120121impl<T: Config> From<ModuleDefinition> for WasmModule<T>122where123 T: Config,124 T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,125{126 fn from(def: ModuleDefinition) -> Self {127 128 let func_offset = u32::try_from(def.imported_functions.len()).unwrap();129130 131 let mut contract = parity_wasm::builder::module()132 133 .function()134 .signature()135 .build()136 .with_body(137 def.deploy_body138 .unwrap_or_else(|| FuncBody::new(Vec::new(), Instructions::empty())),139 )140 .build()141 142 .function()143 .signature()144 .build()145 .with_body(146 def.call_body147 .unwrap_or_else(|| FuncBody::new(Vec::new(), Instructions::empty())),148 )149 .build()150 .export()151 .field("deploy")152 .internal()153 .func(func_offset)154 .build()155 .export()156 .field("call")157 .internal()158 .func(func_offset + 1)159 .build();160161 162 if let Some(body) = def.aux_body {163 let mut signature = contract.function().signature();164 for _ in 0..def.aux_arg_num {165 signature = signature.with_param(ValueType::I64);166 }167 contract = signature.build().with_body(body).build();168 }169170 171 if let Some(memory) = &def.memory {172 contract = contract173 .import()174 .module("env")175 .field("memory")176 .external()177 .memory(memory.min_pages, Some(memory.max_pages))178 .build();179 }180181 182 for func in def.imported_functions {183 let sig = parity_wasm::builder::signature()184 .with_params(func.params)185 .with_results(func.return_type.into_iter().collect())186 .build_sig();187 let sig = contract.push_signature(sig);188 contract = contract189 .import()190 .module("seal0")191 .field(func.name)192 .with_external(parity_wasm::elements::External::Function(sig))193 .build();194 }195196 197 for data in def.data_segments {198 contract = contract199 .data()200 .offset(Instruction::I32Const(data.offset as i32))201 .value(data.value)202 .build()203 }204205 206 if def.num_globals > 0 {207 use rand::{prelude::*, distributions::Standard};208 let rng = rand_pcg::Pcg32::seed_from_u64(3112244599778833558);209 for val in rng.sample_iter(Standard).take(def.num_globals as usize) {210 contract = contract211 .global()212 .value_type()213 .i64()214 .mutable()215 .init_expr(Instruction::I64Const(val))216 .build()217 }218 }219220 221 if let Some(table) = def.table {222 contract = contract223 .table()224 .with_min(table.num_elements)225 .with_max(Some(table.num_elements))226 .with_element(0, vec![table.function_index; table.num_elements as usize])227 .build();228 }229230 231 if def.dummy_section > 0 {232 contract = contract.with_section(Section::Custom(CustomSection::new(233 "dummy".to_owned(),234 vec![42; def.dummy_section as usize],235 )));236 }237238 let mut code = contract.build();239240 241 if def.inject_stack_metering {242 code = inject_limiter(code, <CurrentSchedule<T>>::get().limits.stack_height).unwrap();243 }244245 let code = code.to_bytes().unwrap();246 let hash = T::Hashing::hash(&code);247 Self {248 code,249 hash,250 memory: def.memory,251 }252 }253}254255impl<T: Config> WasmModule<T>256where257 T: Config,258 T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,259{260 261 pub fn dummy() -> Self {262 ModuleDefinition::default().into()263 }264265 266 pub fn dummy_with_bytes(dummy_bytes: u32) -> Self {267 ModuleDefinition {268 memory: Some(ImportedMemory::max::<T>()),269 dummy_section: dummy_bytes,270 ..Default::default()271 }272 .into()273 }274275 276 277 278 pub fn sized(target_bytes: u32) -> Self {279 use parity_wasm::elements::Instruction::{If, I32Const, Return, End};280 281 282 283 284 285 let expansions = (target_bytes.saturating_sub(63) / 6).saturating_sub(1);286 const EXPANSION: [Instruction; 4] = [I32Const(0), If(BlockType::NoResult), Return, End];287 ModuleDefinition {288 call_body: Some(body::repeated(expansions, &EXPANSION)),289 memory: Some(ImportedMemory::max::<T>()),290 ..Default::default()291 }292 .into()293 }294295 296 297 298 pub fn getter(getter_name: &'static str, repeat: u32) -> Self {299 let pages = max_pages::<T>();300 ModuleDefinition {301 memory: Some(ImportedMemory::max::<T>()),302 imported_functions: vec![ImportedFunction {303 name: getter_name,304 params: vec![ValueType::I32, ValueType::I32],305 return_type: None,306 }],307 308 309 310 311 data_segments: vec![DataSegment {312 offset: 0,313 value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(),314 }],315 call_body: Some(body::repeated(316 repeat,317 &[318 Instruction::I32Const(4), 319 Instruction::I32Const(0), 320 Instruction::Call(0), 321 ],322 )),323 ..Default::default()324 }325 .into()326 }327328 329 330 331 pub fn hasher(name: &'static str, repeat: u32, data_size: u32) -> Self {332 ModuleDefinition {333 memory: Some(ImportedMemory::max::<T>()),334 imported_functions: vec![ImportedFunction {335 name,336 params: vec![ValueType::I32, ValueType::I32, ValueType::I32],337 return_type: None,338 }],339 call_body: Some(body::repeated(340 repeat,341 &[342 Instruction::I32Const(0), 343 Instruction::I32Const(data_size as i32), 344 Instruction::I32Const(0), 345 Instruction::Call(0),346 ],347 )),348 ..Default::default()349 }350 .into()351 }352353 354 355 356 pub fn add_memory<S>(&self, env: &mut EnvironmentDefinitionBuilder<S>) -> Option<Memory> {357 let memory = if let Some(memory) = &self.memory {358 memory359 } else {360 return None;361 };362 let memory = Memory::new(memory.min_pages, Some(memory.max_pages)).unwrap();363 env.add_memory("env", "memory", memory.clone());364 Some(memory)365 }366367 pub fn unary_instr(instr: Instruction, repeat: u32) -> Self {368 use body::DynInstr::{RandomI64Repeated, Regular};369 ModuleDefinition {370 call_body: Some(body::repeated_dyn(371 repeat,372 vec![373 RandomI64Repeated(1),374 Regular(instr),375 Regular(Instruction::Drop),376 ],377 )),378 ..Default::default()379 }380 .into()381 }382383 pub fn binary_instr(instr: Instruction, repeat: u32) -> Self {384 use body::DynInstr::{RandomI64Repeated, Regular};385 ModuleDefinition {386 call_body: Some(body::repeated_dyn(387 repeat,388 vec![389 RandomI64Repeated(2),390 Regular(instr),391 Regular(Instruction::Drop),392 ],393 )),394 ..Default::default()395 }396 .into()397 }398}399400401pub mod body {402 use super::*;403404 405 406 407 pub enum DynInstr {408 409 Regular(Instruction),410 411 412 Counter(u32, u32),413 414 415 RandomUnaligned(u32, u32),416 417 418 RandomI32(i32, i32),419 420 RandomI32Repeated(usize),421 422 RandomI64Repeated(usize),423 424 425 RandomGetLocal(u32, u32),426 427 428 RandomSetLocal(u32, u32),429 430 431 RandomTeeLocal(u32, u32),432 433 434 RandomGetGlobal(u32, u32),435 436 437 RandomSetGlobal(u32, u32),438 }439440 pub fn plain(instructions: Vec<Instruction>) -> FuncBody {441 FuncBody::new(Vec::new(), Instructions::new(instructions))442 }443444 pub fn repeated(repetitions: u32, instructions: &[Instruction]) -> FuncBody {445 let instructions = Instructions::new(446 instructions447 .iter()448 .cycle()449 .take(instructions.len() * usize::try_from(repetitions).unwrap())450 .cloned()451 .chain(sp_std::iter::once(Instruction::End))452 .collect(),453 );454 FuncBody::new(Vec::new(), instructions)455 }456457 pub fn repeated_dyn(repetitions: u32, mut instructions: Vec<DynInstr>) -> FuncBody {458 use rand::{prelude::*, distributions::Standard};459460 461 let mut rng = rand_pcg::Pcg32::seed_from_u64(8446744073709551615);462463 464 let body = (0..instructions.len())465 .cycle()466 .take(instructions.len() * usize::try_from(repetitions).unwrap())467 .flat_map(|idx| match &mut instructions[idx] {468 DynInstr::Regular(instruction) => vec![instruction.clone()],469 DynInstr::Counter(offset, increment_by) => {470 let current = *offset;471 *offset += *increment_by;472 vec![Instruction::I32Const(current as i32)]473 }474 DynInstr::RandomUnaligned(low, high) => {475 let unaligned = rng.gen_range(*low..*high) | 1;476 vec![Instruction::I32Const(unaligned as i32)]477 }478 DynInstr::RandomI32(low, high) => {479 vec![Instruction::I32Const(rng.gen_range(*low..*high))]480 }481 DynInstr::RandomI32Repeated(num) => (&mut rng)482 .sample_iter(Standard)483 .take(*num)484 .map(|val| Instruction::I32Const(val))485 .collect(),486 DynInstr::RandomI64Repeated(num) => (&mut rng)487 .sample_iter(Standard)488 .take(*num)489 .map(|val| Instruction::I64Const(val))490 .collect(),491 DynInstr::RandomGetLocal(low, high) => {492 vec![Instruction::GetLocal(rng.gen_range(*low..*high))]493 }494 DynInstr::RandomSetLocal(low, high) => {495 vec![Instruction::SetLocal(rng.gen_range(*low..*high))]496 }497 DynInstr::RandomTeeLocal(low, high) => {498 vec![Instruction::TeeLocal(rng.gen_range(*low..*high))]499 }500 DynInstr::RandomGetGlobal(low, high) => {501 vec![Instruction::GetGlobal(rng.gen_range(*low..*high))]502 }503 DynInstr::RandomSetGlobal(low, high) => {504 vec![Instruction::SetGlobal(rng.gen_range(*low..*high))]505 }506 })507 .chain(sp_std::iter::once(Instruction::End))508 .collect();509 FuncBody::new(Vec::new(), Instructions::new(body))510 }511512 513 pub fn inject_locals(body: &mut FuncBody, num: u32) {514 use parity_wasm::elements::Local;515 *body.locals_mut() = (0..num).map(|i| Local::new(i, ValueType::I64)).collect()516 }517}518519520pub fn max_pages<T: Config>() -> u32521where522 T: Config,523 T::AccountId: UncheckedFrom<T::Hash> + AsRef<[u8]>,524{525 <CurrentSchedule<T>>::get().limits.memory_pages526}