diff --git a/arbitrator/prover/src/host.rs b/arbitrator/prover/src/host.rs index 1802ea609..8296eba96 100644 --- a/arbitrator/prover/src/host.rs +++ b/arbitrator/prover/src/host.rs @@ -358,7 +358,7 @@ pub fn get_impl(module: &str, name: &str) -> Result<(Function, bool)> { }; let debug = module == "console"; - Function::new(&[], append, hostio.ty(), &[]).map(|x| (x, debug)) + Function::new(&[], append, hostio.ty()).map(|x| (x, debug)) } /// Adds internal functions to a module. @@ -458,7 +458,6 @@ lazy_static! { 0, // impls don't use other internals ), ty.clone(), - &[] // impls don't make calls ); func.expect("failed to create bulk memory func") }) diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index d1f515f20..5f9f87a45 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -31,6 +31,7 @@ use sha3::Keccak256; use smallvec::SmallVec; use std::{ borrow::Cow, + cmp::min, convert::{TryFrom, TryInto}, fmt::{self, Display}, fs::File, @@ -46,14 +47,6 @@ use wasmparser::{DataKind, ElementItem, ElementKind, Operator, TableType}; #[cfg(feature = "native")] use rayon::prelude::*; -fn hash_call_indirect_data(table: u32, ty: &FunctionType) -> Bytes32 { - let mut h = Keccak256::new(); - h.update("Call indirect:"); - h.update((table as u64).to_be_bytes()); - h.update(ty.hash()); - h.finalize().into() -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum InboxIdentifier { Sequencer = 0, @@ -73,8 +66,56 @@ pub struct Function { code: Vec, ty: FunctionType, #[serde(skip)] - code_merkle: Merkle, + opcode_merkle: Merkle, + #[serde(skip)] + argument_data_merkle: Merkle, local_types: Vec, + #[serde(skip)] + empty_locals_hash: Bytes32, +} + +fn code_to_opcode_hash(code: &Vec, opcode_idx: usize) -> Bytes32 { + let seg = opcode_idx / 16; + let seg_start = seg * 16; + let seg_end = min(seg_start + 16, code.len()); + let mut b = [0u8; 32]; + for i in 0..(seg_end - seg_start) { + b[i * 2..i * 2 + 2] + .copy_from_slice(code[seg_start + i].opcode.repr().to_be_bytes().as_slice()) + } + Bytes32(b) +} + +fn code_to_opcode_hashes(code: &Vec) -> Vec { + #[cfg(feature = "native")] + let iter = (0..(code.len() + 15) / 16).into_par_iter(); + + #[cfg(not(feature = "native"))] + let iter = (0..(code.len() + 15) / 16).into_iter(); + + iter.map(|i| code_to_opcode_hash(code, i * 16)).collect() +} + +fn code_to_argdata_hash(code: &Vec, opcode_idx: usize) -> Bytes32 { + let seg = opcode_idx / 4; + let seg_start = seg * 4; + let seg_end = min(seg_start + 4, code.len()); + let mut b = [0u8; 32]; + for i in 0..(seg_end - seg_start) { + b[i * 8..i * 8 + 8] + .copy_from_slice(code[seg_start + i].argument_data.to_be_bytes().as_slice()) + } + Bytes32(b) +} + +fn code_to_argdata_hashes(code: &Vec) -> Vec { + #[cfg(feature = "native")] + let iter = (0..(code.len() + 3) / 4).into_par_iter(); + + #[cfg(not(feature = "native"))] + let iter = (0..(code.len() + 3) / 4).into_iter(); + + iter.map(|i| code_to_argdata_hash(code, i * 4)).collect() } impl Function { @@ -82,44 +123,26 @@ impl Function { locals: &[Local], add_body: F, func_ty: FunctionType, - module_types: &[FunctionType], ) -> Result { let mut locals_with_params = func_ty.inputs.clone(); locals_with_params.extend(locals.iter().map(|x| x.value)); let mut insts = Vec::new(); - let empty_local_hashes = locals_with_params - .iter() - .cloned() - .map(Value::default_of_type) - .map(Value::hash) - .collect::>(); insts.push(Instruction { opcode: Opcode::InitFrame, argument_data: 0, - proving_argument_data: Some(Merkle::new(MerkleType::Value, empty_local_hashes).root()), }); // Fill in parameters for i in (0..func_ty.inputs.len()).rev() { insts.push(Instruction { opcode: Opcode::LocalSet, argument_data: i as u64, - proving_argument_data: None, }); } add_body(&mut insts)?; insts.push(Instruction::simple(Opcode::Return)); - // Insert missing proving argument data - for inst in insts.iter_mut() { - if inst.opcode == Opcode::CallIndirect { - let (table, ty) = crate::wavm::unpack_call_indirect(inst.argument_data); - let ty = &module_types[usize::try_from(ty).unwrap()]; - inst.proving_argument_data = Some(hash_call_indirect_data(table, ty)); - } - } - Ok(Function::new_from_wavm(insts, func_ty, locals_with_params)) } @@ -133,24 +156,35 @@ impl Function { "Function instruction count doesn't fit in a u32", ); - #[cfg(feature = "native")] - let code_hashes = code.par_iter().map(|i| i.hash()).collect(); - - #[cfg(not(feature = "native"))] - let code_hashes = code.iter().map(|i| i.hash()).collect(); + let argument_data_hashes = code_to_argdata_hashes(&code); + let opcode_hashes = code_to_opcode_hashes(&code); Function { code, + empty_locals_hash: Self::calc_empty_locals_hash(&local_types), ty, - code_merkle: Merkle::new(MerkleType::Instruction, code_hashes), + opcode_merkle: Merkle::new(MerkleType::Opcode, opcode_hashes), + argument_data_merkle: Merkle::new(MerkleType::ArgumentData, argument_data_hashes), local_types, } } + fn calc_empty_locals_hash(locals_with_params: &Vec) -> Bytes32 { + let empty_local_hashes = locals_with_params + .iter() + .cloned() + .map(Value::default_of_type) + .map(Value::hash) + .collect::>(); + Merkle::new(MerkleType::Value, empty_local_hashes).root() + } + fn hash(&self) -> Bytes32 { let mut h = Keccak256::new(); h.update("Function:"); - h.update(self.code_merkle.root()); + h.update(self.opcode_merkle.root()); + h.update(self.argument_data_merkle.root()); + h.update(self.empty_locals_hash); h.finalize().into() } } @@ -273,6 +307,8 @@ struct Module { #[serde(skip)] funcs_merkle: Arc, types: Arc>, + #[serde(skip)] + types_merkle: Arc, internals_offset: u32, names: Arc, host_call_hooks: Arc>>, @@ -387,7 +423,6 @@ impl Module { ) }, func_ty.clone(), - &types, )?); } code.extend(internals); @@ -531,6 +566,10 @@ impl Module { code.iter().map(|f| f.hash()).collect(), )), funcs: Arc::new(code), + types_merkle: Arc::new(Merkle::new( + MerkleType::FunctionType, + types.iter().map(FunctionType::hash).collect(), + )), types: Arc::new(types.to_owned()), internals_offset, names: Arc::new(bin.names.to_owned()), @@ -566,6 +605,7 @@ impl Module { h.update(self.memory.hash()); h.update(self.tables_merkle.root()); h.update(self.funcs_merkle.root()); + h.update(self.types_merkle.root()); h.update(self.internals_offset.to_be_bytes()); h.finalize().into() } @@ -587,6 +627,7 @@ impl Module { data.extend(self.tables_merkle.root()); data.extend(self.funcs_merkle.root()); + data.extend(self.types_merkle.root()); data.extend(self.internals_offset.to_be_bytes()); @@ -1325,7 +1366,6 @@ impl Machine { Ok(()) }, FunctionType::default(), - &entrypoint_types, )?]; let entrypoint = Module { globals: Vec::new(), @@ -1337,6 +1377,10 @@ impl Machine { entrypoint_funcs.iter().map(Function::hash).collect(), )), funcs: Arc::new(entrypoint_funcs), + types_merkle: Arc::new(Merkle::new( + MerkleType::FunctionType, + entrypoint_types.iter().map(FunctionType::hash).collect(), + )), types: Arc::new(entrypoint_types), names: Arc::new(entrypoint_names), internals_offset: 0, @@ -1427,18 +1471,21 @@ impl Machine { let funcs = Arc::get_mut(&mut module.funcs).expect("Multiple copies of module functions"); for func in funcs.iter_mut() { - #[cfg(feature = "native")] - let code_hashes = func.code.par_iter().map(|i| i.hash()).collect(); - - #[cfg(not(feature = "native"))] - let code_hashes = func.code.iter().map(|i| i.hash()).collect(); + let opcode_hashes = code_to_opcode_hashes(&func.code); + let argdata_hashes = code_to_argdata_hashes(&func.code); - func.code_merkle = Merkle::new(MerkleType::Instruction, code_hashes); + func.opcode_merkle = Merkle::new(MerkleType::Opcode, opcode_hashes); + func.argument_data_merkle = Merkle::new(MerkleType::ArgumentData, argdata_hashes); + func.empty_locals_hash = Function::calc_empty_locals_hash(&func.local_types) } module.funcs_merkle = Arc::new(Merkle::new( MerkleType::Function, module.funcs.iter().map(Function::hash).collect(), )); + module.types_merkle = Arc::new(Merkle::new( + MerkleType::FunctionType, + module.types.iter().map(FunctionType::hash).collect(), + )) } let mut mach = Machine { status: MachineStatus::Running, @@ -2640,11 +2687,17 @@ impl Machine { // Begin next instruction proof let func = &module.funcs[self.pc.func()]; - out!(func.code[self.pc.inst()].serialize_for_proof()); + out!(code_to_opcode_hash(&func.code, self.pc.inst())); out!(func - .code_merkle - .prove(self.pc.inst()) + .opcode_merkle + .prove(self.pc.inst() / 16) .expect("Failed to prove against code merkle")); + out!(code_to_argdata_hash(&func.code, self.pc.inst())); + out!(func + .argument_data_merkle + .prove(self.pc.inst() / 4) + .expect("Failed to prove against argument data merkle")); + out!(func.empty_locals_hash); out!(module .funcs_merkle .prove(self.pc.func()) @@ -2729,11 +2782,14 @@ impl Machine { Some(Value::I32(i)) => *i, x => fail!("top of stack before call_indirect is {x:?}"), }; - let ty = &module.types[usize::try_from(ty).unwrap()]; - out!((table as u64).to_be_bytes()); - out!(ty.hash()); let table_usize = usize::try_from(table).unwrap(); + let type_usize = usize::try_from(ty).unwrap(); let table = &module.tables[table_usize]; + out!(module.types[type_usize].hash()); + out!(module + .types_merkle + .prove(type_usize) + .expect("failed to prove types merkle")); out!(table .serialize_for_proof() .expect("failed to serialize table")); diff --git a/arbitrator/prover/src/memory.rs b/arbitrator/prover/src/memory.rs index 60fe15bad..7d97c9693 100644 --- a/arbitrator/prover/src/memory.rs +++ b/arbitrator/prover/src/memory.rs @@ -8,6 +8,7 @@ use crate::{ use arbutil::Bytes32; use digest::Digest; use eyre::{bail, ErrReport, Result}; +use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use sha3::Keccak256; use std::{borrow::Cow, convert::TryFrom}; @@ -59,6 +60,10 @@ fn hash_leaf(bytes: [u8; Memory::LEAF_SIZE]) -> Bytes32 { h.finalize().into() } +lazy_static! { + pub static ref EMPTY_MEM_HASH: Bytes32 = hash_leaf([0u8; 32]); +} + fn round_up_to_power_of_two(mut input: usize) -> usize { if input == 0 { return 1; @@ -84,7 +89,7 @@ impl Memory { pub const PAGE_SIZE: u64 = 65536; /// The number of layers in the memory merkle tree /// 1 + log2(2^32 / LEAF_SIZE) = 1 + log2(2^(32 - log2(LEAF_SIZE))) = 1 + 32 - 5 - const MEMORY_LAYERS: usize = 1 + 32 - 5; + pub const MEMORY_LAYERS: usize = 1 + 32 - 5; pub fn new(size: usize, max_size: u64) -> Memory { Memory { @@ -119,15 +124,10 @@ impl Memory { }) .collect(); if leaf_hashes.len() < leaves { - let empty_hash = hash_leaf([0u8; 32]); + let empty_hash = *EMPTY_MEM_HASH; leaf_hashes.resize(leaves, empty_hash); } - Cow::Owned(Merkle::new_advanced( - MerkleType::Memory, - leaf_hashes, - hash_leaf([0u8; 32]), - Self::MEMORY_LAYERS, - )) + Cow::Owned(Merkle::new(MerkleType::Memory, leaf_hashes)) } pub fn get_leaf_data(&self, leaf_idx: usize) -> [u8; Self::LEAF_SIZE] { diff --git a/arbitrator/prover/src/merkle.rs b/arbitrator/prover/src/merkle.rs index c00712821..b3597a9ae 100644 --- a/arbitrator/prover/src/merkle.rs +++ b/arbitrator/prover/src/merkle.rs @@ -1,20 +1,25 @@ // Copyright 2021-2023, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE +use crate::memory::{Memory, EMPTY_MEM_HASH}; use arbutil::Bytes32; use digest::Digest; +use lazy_static::lazy_static; use sha3::Keccak256; -use std::convert::TryFrom; +use std::collections::HashMap; +use std::sync::RwLock; #[cfg(feature = "native")] use rayon::prelude::*; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] pub enum MerkleType { Empty, Value, Function, - Instruction, + FunctionType, + Opcode, + ArgumentData, Memory, Table, TableElement, @@ -33,7 +38,9 @@ impl MerkleType { MerkleType::Empty => panic!("Attempted to get prefix of empty merkle type"), MerkleType::Value => "Value merkle tree:", MerkleType::Function => "Function merkle tree:", - MerkleType::Instruction => "Instruction merkle tree:", + MerkleType::FunctionType => "Function type merkle tree:", + MerkleType::Opcode => "Opcode merkle tree:", + MerkleType::ArgumentData => "Argument data merkle tree:", MerkleType::Memory => "Memory merkle tree:", MerkleType::Table => "Table merkle tree:", MerkleType::TableElement => "Table element merkle tree:", @@ -46,7 +53,6 @@ impl MerkleType { pub struct Merkle { ty: MerkleType, layers: Vec>, - empty_layers: Vec, min_depth: usize, } @@ -58,24 +64,63 @@ fn hash_node(ty: MerkleType, a: Bytes32, b: Bytes32) -> Bytes32 { h.finalize().into() } +lazy_static! { + static ref EMPTY_LAYERS: RwLock>>> = Default::default(); +} + impl Merkle { - pub fn new(ty: MerkleType, hashes: Vec) -> Merkle { - Self::new_advanced(ty, hashes, Bytes32::default(), 0) + fn get_empty_readonly(ty: MerkleType, layer: usize) -> Option { + match EMPTY_LAYERS.read().unwrap().get(&ty) { + None => None, + Some(rwvec) => rwvec.read().unwrap().get(layer).copied(), + } } - pub fn new_advanced( - ty: MerkleType, - hashes: Vec, - empty_hash: Bytes32, - min_depth: usize, - ) -> Merkle { + pub fn get_empty(ty: MerkleType, layer: usize) -> Bytes32 { + let exists = Self::get_empty_readonly(ty, layer); + if let Some(val_exists) = exists { + return val_exists; + } + let new_val: Bytes32; + if layer == 0 { + new_val = match ty { + MerkleType::Empty => { + panic!("attempted to fetch empty-layer value from empty merkle") + } + MerkleType::Memory => *EMPTY_MEM_HASH, + _ => Bytes32::default(), + } + } else { + let prev_val = Self::get_empty(ty, layer - 1); + new_val = hash_node(ty, prev_val, prev_val); + } + let mut layers = EMPTY_LAYERS.write().unwrap(); + let mut typed = layers.entry(ty).or_default().write().unwrap(); + if typed.len() > layer { + assert_eq!(typed[layer], new_val); + } else if typed.len() == layer { + typed.push(new_val); + } else { + panic!("trying to compute empty merkle entries out of order") + } + return typed[layer]; + } + + pub fn new(ty: MerkleType, hashes: Vec) -> Merkle { if hashes.is_empty() { return Merkle::default(); } + let min_depth = match ty { + MerkleType::Empty => panic!("attempted to fetch empty-layer value from empty merkle"), + MerkleType::Memory => Memory::MEMORY_LAYERS, + MerkleType::Opcode => 2, + MerkleType::ArgumentData => 2, + _ => 0, + }; let mut layers = vec![hashes]; - let mut empty_layers = vec![empty_hash]; while layers.last().unwrap().len() > 1 || layers.len() < min_depth { - let empty_layer = *empty_layers.last().unwrap(); + let empty_layer = Self::get_empty(ty, layers.len() - 1); + let next_empty_layer = Self::get_empty(ty, layers.len()); #[cfg(feature = "native")] let new_layer = layers.last().unwrap().par_chunks(2); @@ -84,15 +129,21 @@ impl Merkle { let new_layer = layers.last().unwrap().chunks(2); let new_layer = new_layer - .map(|chunk| hash_node(ty, chunk[0], chunk.get(1).cloned().unwrap_or(empty_layer))) + .map(|chunk| { + let left = chunk[0]; + let right = chunk.get(1).cloned().unwrap_or(empty_layer); + if left == empty_layer && right == empty_layer { + next_empty_layer + } else { + hash_node(ty, left, right) + } + }) .collect(); - empty_layers.push(hash_node(ty, empty_layer, empty_layer)); layers.push(new_layer); } Merkle { ty, layers, - empty_layers, min_depth, } } @@ -135,7 +186,7 @@ impl Merkle { layer .get(counterpart) .cloned() - .unwrap_or_else(|| self.empty_layers[layer_i]), + .unwrap_or_else(|| Self::get_empty(self.ty, layer_i)), ); idx >>= 1; } @@ -147,8 +198,7 @@ impl Merkle { pub fn push_leaf(&mut self, leaf: Bytes32) { let mut leaves = self.layers.swap_remove(0); leaves.push(leaf); - let empty = self.empty_layers[0]; - *self = Self::new_advanced(self.ty, leaves, empty, self.min_depth); + *self = Self::new(self.ty, leaves); } /// Removes the rightmost leaf from the merkle @@ -156,8 +206,7 @@ impl Merkle { pub fn pop_leaf(&mut self) { let mut leaves = self.layers.swap_remove(0); leaves.pop(); - let empty = self.empty_layers[0]; - *self = Self::new_advanced(self.ty, leaves, empty, self.min_depth); + *self = Self::new(self.ty, leaves); } pub fn set(&mut self, mut idx: usize, hash: Bytes32) { @@ -165,7 +214,6 @@ impl Merkle { return; } let mut next_hash = hash; - let empty_layers = &self.empty_layers; let layers_len = self.layers.len(); for (layer_i, layer) in self.layers.iter_mut().enumerate() { layer[idx] = next_hash; @@ -176,7 +224,7 @@ impl Merkle { let counterpart = layer .get(idx ^ 1) .cloned() - .unwrap_or_else(|| empty_layers[layer_i]); + .unwrap_or_else(|| Self::get_empty(self.ty, layer_i)); if idx % 2 == 0 { next_hash = hash_node(self.ty, next_hash, counterpart); } else { diff --git a/arbitrator/prover/src/wavm.rs b/arbitrator/prover/src/wavm.rs index 54aa3d0a8..c12f9d171 100644 --- a/arbitrator/prover/src/wavm.rs +++ b/arbitrator/prover/src/wavm.rs @@ -6,14 +6,9 @@ use crate::{ host::InternalFunc, value::{ArbValueType, FunctionType, IntegerValType}, }; -use arbutil::Bytes32; -use digest::Digest; use eyre::{bail, ensure, Result}; use fnv::FnvHashMap as HashMap; -use lazy_static::lazy_static; -use parking_lot::Mutex; use serde::{Deserialize, Serialize}; -use sha3::Keccak256; use std::ops::{Add, AddAssign, Sub, SubAssign}; use wasmparser::{Operator, Type, TypeOrFuncType as BlockType}; @@ -309,7 +304,6 @@ pub type FloatingPointImpls = HashMap; pub struct Instruction { pub opcode: Opcode, pub argument_data: u64, - pub proving_argument_data: Option, } fn pack_call_indirect(table: u32, ty: u32) -> u64 { @@ -328,17 +322,12 @@ pub fn unpack_cross_module_call(data: u64) -> (u32, u32) { ((data >> 32) as u32, data as u32) } -lazy_static! { - static ref OP_HASHES: Mutex> = Mutex::new(HashMap::default()); -} - impl Instruction { #[must_use] pub fn simple(opcode: Opcode) -> Instruction { Instruction { opcode, argument_data: 0, - proving_argument_data: None, } } @@ -347,45 +336,7 @@ impl Instruction { Instruction { opcode, argument_data, - proving_argument_data: None, - } - } - - pub fn get_proving_argument_data(self) -> Bytes32 { - if let Some(data) = self.proving_argument_data { - data - } else { - let mut b = [0u8; 32]; - b[24..].copy_from_slice(&self.argument_data.to_be_bytes()); - Bytes32(b) - } - } - - pub fn serialize_for_proof(self) -> [u8; 34] { - let mut ret = [0u8; 34]; - ret[..2].copy_from_slice(&self.opcode.repr().to_be_bytes()); - ret[2..].copy_from_slice(&*self.get_proving_argument_data()); - ret - } - - pub fn hash(&self) -> Bytes32 { - let dataless = self.proving_argument_data.is_none() && self.argument_data == 0; - if dataless { - if let Some(hash) = OP_HASHES.lock().get(&self.opcode) { - return *hash; - } - } - - let mut h = Keccak256::new(); - h.update(b"Instruction:"); - h.update(self.opcode.repr().to_be_bytes()); - h.update(self.get_proving_argument_data()); - let hash: Bytes32 = h.finalize().into(); - - if dataless { - OP_HASHES.lock().insert(self.opcode, hash); } - hash } } diff --git a/arbitrator/prover/test-cases/dynamic.wat b/arbitrator/prover/test-cases/dynamic.wat index 0505e074b..0cdcc2b27 100644 --- a/arbitrator/prover/test-cases/dynamic.wat +++ b/arbitrator/prover/test-cases/dynamic.wat @@ -7,7 +7,8 @@ (import "hostio" "program_ink_status" (func $ink_status (param i32 i32) (result i32))) (import "hostio" "program_call_main" (func $user_func (param i32 i32 i32) (result i32))) (data (i32.const 0x0) - "\dd\13\30\ba\64\c6\e1\e9\e9\c3\b0\f9\63\c8\d9\69\c9\f0\13\70\42\17\f3\9e\60\c3\0d\13\5b\48\a5\88") ;; user + "\ef\75\83\c3\de\bf\e1\41\0a\3d\24\81\1c\a3\b7\4d\a5\18\3b\46\63\f2\5d\c9\14\d3\70\aa\b8\b3\21\56") + (func $start (local $user i32) (local $internals i32) ;; link in user.wat i32.const 0 diff --git a/arbitrator/prover/test-cases/link.wat b/arbitrator/prover/test-cases/link.wat index 69597f737..d9a60f97f 100644 --- a/arbitrator/prover/test-cases/link.wat +++ b/arbitrator/prover/test-cases/link.wat @@ -5,31 +5,32 @@ (import "hostio" "wavm_link_module" (func $link (param i32) (result i32))) (import "hostio" "wavm_unlink_module" (func $unlink (param) (result))) (data (i32.const 0x000) - "\b8\ef\6c\9d\e3\a2\aa\9a\94\56\09\7b\13\ef\19\ab\82\ab\c6\d2\eb\76\ca\72\9a\dd\b8\20\84\7b\7a\f7") ;; block + "\7a\cd\ed\ed\a3\18\fa\b7\4b\21\35\f5\08\8f\37\02\f3\96\fa\70\79\5d\c0\c9\55\92\8b\42\77\58\09\95") (data (i32.const 0x020) - "\e0\7a\1c\2e\5f\26\ab\ff\5b\ef\d4\ae\b4\c1\7a\18\52\06\cd\be\9c\ea\fb\c3\b6\4a\03\4b\c0\c4\51\14") ;; call + "\91\3a\58\fe\e6\8f\2f\3f\a4\d7\a6\60\7e\f8\06\8a\db\de\37\31\e8\66\99\9a\f5\2b\00\39\b0\df\f0\29") (data (i32.const 0x040) - "\4c\85\59\6e\e9\89\38\42\7e\52\63\f5\98\c9\fe\23\4a\17\11\d8\37\e3\ed\d4\2a\93\e7\32\15\c2\d7\1c") ;; indirect + "\94\63\42\17\b2\0a\c3\94\ce\e1\0c\50\74\7b\fb\2c\22\3f\ca\97\3e\18\30\90\df\9b\36\37\40\fa\57\df") (data (i32.const 0x060) - "\f9\0f\a0\15\f7\e0\52\c2\c8\ec\ac\1c\b7\a3\58\8c\00\52\52\95\a1\ec\a0\55\90\74\aa\11\3f\4e\75\55") ;; const + "\f1\37\72\b3\30\37\09\8e\b2\d5\e5\49\20\57\ea\17\1d\a6\1c\85\58\82\e1\96\c7\80\a2\e9\26\e7\fe\23") (data (i32.const 0x080) - "\2d\e2\b0\dd\15\63\31\27\e5\31\2d\2d\23\e4\13\67\a0\d5\4d\0e\66\d9\2d\79\37\5d\44\7c\d0\80\f8\b0") ;; div + "\03\a1\b5\e0\fa\39\f0\89\12\a4\f7\af\fe\af\5b\21\e1\61\1f\4b\f7\fb\51\c5\e7\07\6b\12\cd\b6\fd\ad") (data (i32.const 0x0a0) - "\91\7a\9d\9d\03\77\07\f0\f7\84\b4\f1\01\e5\50\ff\5f\40\13\34\c1\af\c1\f2\f6\8f\f7\03\a3\ba\32\47") ;; globals + "\b0\53\f4\29\a3\9d\e0\2d\d4\7f\c9\d4\fa\0d\e8\ed\ff\90\c2\c2\e8\f4\9d\41\3d\04\67\2a\75\2d\ff\d2") (data (i32.const 0x0c0) - "\31\81\c9\76\80\55\57\40\6d\93\0d\46\3b\60\31\de\4b\0f\93\14\8e\78\58\63\8c\66\88\55\c3\d3\47\b2") ;; if-else + "\c3\4c\a0\04\4e\cb\62\af\fb\15\f9\da\08\22\91\28\be\97\fa\04\f4\da\a9\1b\07\de\6d\79\f1\af\27\30") (data (i32.const 0x0e0) - "\8d\e8\a2\29\d8\22\25\97\3e\57\7f\17\2f\b0\24\94\3f\fe\73\6b\ef\34\18\10\4b\18\73\87\4c\3d\97\88") ;; locals + "\e0\44\10\9b\5d\95\19\8b\e0\5c\20\fe\31\ce\f0\cf\b7\c7\6a\9d\a9\88\dd\4d\a9\fd\78\ff\5f\69\dd\82") (data (i32.const 0x100) - "\6a\0b\f4\9a\9f\c4\68\18\a5\23\79\11\bf\3a\8d\02\72\ea\e1\21\db\b8\f3\a5\72\d3\66\9a\27\f1\01\74") ;; loop + "\ef\75\83\c3\de\bf\e1\41\0a\3d\24\81\1c\a3\b7\4d\a5\18\3b\46\63\f2\5d\c9\14\d3\70\aa\b8\b3\21\56") (data (i32.const 0x120) - "\35\fb\41\8d\94\da\56\dc\3b\2e\8f\6c\7f\43\bf\dd\9a\30\7c\27\b9\b2\e0\dd\7d\15\29\36\53\ca\b7\77") ;; math + "\c9\a6\64\85\89\33\76\a1\8f\17\51\9a\2d\74\b1\9b\89\16\0d\ea\87\3b\ed\f4\62\b7\e5\1b\aa\13\02\8f") (data (i32.const 0x140) - "\26\d0\56\ce\4a\fa\f1\ef\cd\d2\7a\4d\64\48\a3\86\5a\00\5f\6f\89\7e\a4\95\5c\b9\2d\b8\f1\04\eb\3e") ;; iops + "\e2\b4\0f\72\1d\71\60\c7\5b\37\ce\89\7e\de\e1\ab\08\ed\27\d0\06\0e\55\a3\9c\44\e3\4b\3c\b9\54\86") (data (i32.const 0x160) - "\dd\13\30\ba\64\c6\e1\e9\e9\c3\b0\f9\63\c8\d9\69\c9\f0\13\70\42\17\f3\9e\60\c3\0d\13\5b\48\a5\88") ;; user + "\fc\39\b7\9f\6a\2b\66\33\8f\2c\a5\9b\33\67\a7\f4\45\5e\0b\34\49\6d\1a\6c\a6\c1\7e\9a\7e\91\27\d7") (data (i32.const 0x180) - "\07\33\43\e0\1d\b9\16\3e\99\1a\99\bd\cc\93\bf\26\15\f4\4c\11\c3\32\c0\2c\65\ba\76\11\76\eb\c1\7b") ;; return + "\16\c7\a1\3d\ad\27\8f\a9\11\7c\6e\0c\a1\4c\68\ce\46\c6\c0\e2\cf\97\45\88\53\08\22\17\33\0b\8c\fb") + (func $start (local $counter i32) ;; add modules diff --git a/contracts b/contracts index 623a8f017..79cafd280 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 623a8f017d0e1cb9e55fe8ee8c46e9ac326d07f6 +Subproject commit 79cafd28024389653692ab29f7b1f231a07623c4