diff --git a/.github/workflows/arbitrator-ci.yml b/.github/workflows/arbitrator-ci.yml index 7b05bbf238..e19bb73038 100644 --- a/.github/workflows/arbitrator-ci.yml +++ b/.github/workflows/arbitrator-ci.yml @@ -138,7 +138,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: clippy - args: --all --manifest-path arbitrator/Cargo.toml + args: --all --manifest-path arbitrator/Cargo.toml -- -D warnings - name: Run rust tests uses: actions-rs/cargo@v1 diff --git a/.gitignore b/.gitignore index 5f79cf7daa..56042c284f 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ contracts/cache/ solgen/go/ contracts/deployments/ contracts/test/prover/proofs/*.json +contracts/test/prover/spec-proofs/*.json .make/ /cmd/statetransfer/statetransfer /reproducible-wasm/*.wasm diff --git a/.gitmodules b/.gitmodules index de04ba435c..ae5be5e866 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "blockscout"] path = blockscout url = https://github.com/OffchainLabs/blockscout.git +[submodule "arbitrator/wasm-testsuite/testsuite"] + path = arbitrator/wasm-testsuite/testsuite + url = https://github.com/WebAssembly/testsuite.git diff --git a/Dockerfile b/Dockerfile index 897fd0dc67..0ffaa8e7fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -76,6 +76,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ apt-get install -y make && \ cargo install --force cbindgen COPY arbitrator/Cargo.* arbitrator/cbindgen.toml arbitrator/ +COPY arbitrator/prover/Cargo.toml arbitrator/prover/ COPY ./Makefile ./ COPY arbitrator/prover arbitrator/prover RUN NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-prover-header diff --git a/Makefile b/Makefile index 6e8cd22aab..3d1b15301b 100644 --- a/Makefile +++ b/Makefile @@ -118,8 +118,9 @@ clean: rm -rf arbitrator/prover/test-cases/rust/target rm -f arbitrator/prover/test-cases/*.wasm rm -f arbitrator/prover/test-cases/go/main + rm -rf arbitrator/wasm-testsuite/tests rm -rf $(output_root) - rm -f contracts/test/prover/proofs/*.json + rm -f contracts/test/prover/proofs/*.json contracts/test/prover/spec-proofs/*.json rm -rf arbitrator/target rm -rf arbitrator/wasm-libraries/target rm -f arbitrator/wasm-libraries/soft-float/soft-float.wasm @@ -214,62 +215,16 @@ $(output_root)/machines/latest/soft-float.wasm: $(DEP_PREDICATE) \ arbitrator/wasm-libraries/soft-float/bindings64.o \ arbitrator/wasm-libraries/soft-float/SoftFloat/build/Wasm-Clang/*.o \ --no-entry -o $@ \ - --export wavm__f32_abs \ - --export wavm__f32_neg \ - --export wavm__f32_ceil \ - --export wavm__f32_floor \ - --export wavm__f32_trunc \ - --export wavm__f32_nearest \ - --export wavm__f32_sqrt \ - --export wavm__f32_add \ - --export wavm__f32_sub \ - --export wavm__f32_mul \ - --export wavm__f32_div \ - --export wavm__f32_min \ - --export wavm__f32_max \ - --export wavm__f32_copysign \ - --export wavm__f32_eq \ - --export wavm__f32_ne \ - --export wavm__f32_lt \ - --export wavm__f32_le \ - --export wavm__f32_gt \ - --export wavm__f32_ge \ - --export wavm__i32_trunc_f32_s \ - --export wavm__i32_trunc_f32_u \ - --export wavm__i64_trunc_f32_s \ - --export wavm__i64_trunc_f32_u \ - --export wavm__f32_convert_i32_s \ - --export wavm__f32_convert_i32_u \ - --export wavm__f32_convert_i64_s \ - --export wavm__f32_convert_i64_u \ - --export wavm__f64_abs \ - --export wavm__f64_neg \ - --export wavm__f64_ceil \ - --export wavm__f64_floor \ - --export wavm__f64_trunc \ - --export wavm__f64_nearest \ - --export wavm__f64_sqrt \ - --export wavm__f64_add \ - --export wavm__f64_sub \ - --export wavm__f64_mul \ - --export wavm__f64_div \ - --export wavm__f64_min \ - --export wavm__f64_max \ - --export wavm__f64_copysign \ - --export wavm__f64_eq \ - --export wavm__f64_ne \ - --export wavm__f64_lt \ - --export wavm__f64_le \ - --export wavm__f64_gt \ - --export wavm__f64_ge \ - --export wavm__i32_trunc_f64_s \ - --export wavm__i32_trunc_f64_u \ - --export wavm__i64_trunc_f64_s \ - --export wavm__i64_trunc_f64_u \ - --export wavm__f64_convert_i32_s \ - --export wavm__f64_convert_i32_u \ - --export wavm__f64_convert_i64_s \ - --export wavm__f64_convert_i64_u \ + $(patsubst %,--export wavm__f32_%, abs neg ceil floor trunc nearest sqrt add sub mul div min max) \ + $(patsubst %,--export wavm__f32_%, copysign eq ne lt le gt ge) \ + $(patsubst %,--export wavm__f64_%, abs neg ceil floor trunc nearest sqrt add sub mul div min max) \ + $(patsubst %,--export wavm__f64_%, copysign eq ne lt le gt ge) \ + $(patsubst %,--export wavm__i32_trunc_%, f32_s f32_u f64_s f64_u) \ + $(patsubst %,--export wavm__i32_trunc_sat_%, f32_s f32_u f64_s f64_u) \ + $(patsubst %,--export wavm__i64_trunc_%, f32_s f32_u f64_s f64_u) \ + $(patsubst %,--export wavm__i64_trunc_sat_%, f32_s f32_u f64_s f64_u) \ + $(patsubst %,--export wavm__f32_convert_%, i32_s i32_u i64_s i64_u) \ + $(patsubst %,--export wavm__f64_convert_%, i32_s i32_u i64_s i64_u) \ --export wavm__f32_demote_f64 \ --export wavm__f64_promote_f32 @@ -323,6 +278,7 @@ contracts/test/prover/proofs/%.json: arbitrator/prover/test-cases/%.wasm $(arbit .make/fmt: $(DEP_PREDICATE) build-node-deps .make/yarndeps $(ORDER_ONLY_PREDICATE) .make golangci-lint run --disable-all -E gofmt --fix cargo fmt --all --manifest-path arbitrator/Cargo.toml -- --check + cargo fmt --all --manifest-path arbitrator/wasm-testsuite/Cargo.toml -- --check yarn --cwd contracts prettier:solidity @touch $@ diff --git a/arbitrator/Cargo.toml b/arbitrator/Cargo.toml index 7cb14d8b11..415602a5a3 100644 --- a/arbitrator/Cargo.toml +++ b/arbitrator/Cargo.toml @@ -1,6 +1,6 @@ [workspace] members = [ - "prover", + "prover", ] [profile.release] diff --git a/arbitrator/prover/fuzz/fuzz_targets/osp.rs b/arbitrator/prover/fuzz/fuzz_targets/osp.rs index 10f7174b99..1ceef8355c 100644 --- a/arbitrator/prover/fuzz/fuzz_targets/osp.rs +++ b/arbitrator/prover/fuzz/fuzz_targets/osp.rs @@ -187,6 +187,7 @@ fn fuzz_impl(data: &[u8]) -> Result<()> { &[], wavm_binary, true, + true, false, GlobalState::default(), Default::default(), diff --git a/arbitrator/prover/src/binary.rs b/arbitrator/prover/src/binary.rs index 12c45f8235..c4e64f9991 100644 --- a/arbitrator/prover/src/binary.rs +++ b/arbitrator/prover/src/binary.rs @@ -66,7 +66,8 @@ pub enum FloatInstruction { UnOp(FloatType, FloatUnOp), BinOp(FloatType, FloatBinOp), RelOp(FloatType, FloatRelOp), - TruncIntOp(IntegerValType, FloatType, bool), + /// The bools represent (saturating, signed) + TruncIntOp(IntegerValType, FloatType, bool, bool), ConvertIntOp(FloatType, IntegerValType, bool), F32DemoteF64, F64PromoteF32, @@ -80,7 +81,7 @@ impl FloatInstruction { FloatInstruction::RelOp(t, _) => { FunctionType::new(vec![t.into(); 2], vec![ArbValueType::I32]) } - FloatInstruction::TruncIntOp(i, f, _) => { + FloatInstruction::TruncIntOp(i, f, ..) => { FunctionType::new(vec![f.into()], vec![i.into()]) } FloatInstruction::ConvertIntOp(f, i, _) => { @@ -171,12 +172,15 @@ impl FromStr for FloatInstruction { map( all_consuming(tuple(( parse_int_type, - tag("_trunc_"), + alt(( + value(true, tag("_trunc_sat_")), + value(false, tag("_trunc_")), + )), parse_fp_type, tag("_"), parse_signedness, ))), - |(i, _, f, _, s)| FloatInstruction::TruncIntOp(i, f, s), + |(i, sat, f, _, s)| FloatInstruction::TruncIntOp(i, f, sat, s), ), map( all_consuming(tuple(( @@ -230,9 +234,6 @@ pub struct Local { pub struct NameCustomSection { pub module: String, pub functions: HashMap, - // TODO: remove this when re-initializing the rollup - // this is kept around to deserialize old binaries - pub _locals_removed: HashMap>, } #[derive(Clone, Default)] diff --git a/arbitrator/prover/src/console.rs b/arbitrator/prover/src/console.rs new file mode 100644 index 0000000000..1abb509065 --- /dev/null +++ b/arbitrator/prover/src/console.rs @@ -0,0 +1,85 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +#![allow(dead_code)] + +use std::fmt; + +pub struct Color; + +impl Color { + pub const RED: &'static str = "\x1b[31;1m"; + pub const BLUE: &'static str = "\x1b[34;1m"; + pub const YELLOW: &'static str = "\x1b[33;1m"; + pub const PINK: &'static str = "\x1b[38;5;161;1m"; + pub const MINT: &'static str = "\x1b[38;5;48;1m"; + pub const GREY: &'static str = "\x1b[90m"; + pub const RESET: &'static str = "\x1b[0;0m"; + + pub const LIME: &'static str = "\x1b[38;5;119;1m"; + pub const LAVENDER: &'static str = "\x1b[38;5;183;1m"; + pub const MAROON: &'static str = "\x1b[38;5;124;1m"; + pub const ORANGE: &'static str = "\x1b[38;5;202;1m"; + + pub fn color(color: &str, text: S) -> String { + format!("{}{}{}", color, text, Color::RESET) + } + + /// Colors text red. + pub fn red(text: S) -> String { + Color::color(Color::RED, text) + } + + /// Colors text blue. + pub fn blue(text: S) -> String { + Color::color(Color::BLUE, text) + } + + /// Colors text yellow. + pub fn yellow(text: S) -> String { + Color::color(Color::YELLOW, text) + } + + /// Colors text pink. + pub fn pink(text: S) -> String { + Color::color(Color::PINK, text) + } + + /// Colors text grey. + pub fn grey(text: S) -> String { + Color::color(Color::GREY, text) + } + + /// Colors text lavender. + pub fn lavender(text: S) -> String { + Color::color(Color::LAVENDER, text) + } + + /// Colors text mint. + pub fn mint(text: S) -> String { + Color::color(Color::MINT, text) + } + + /// Colors text lime. + pub fn lime(text: S) -> String { + Color::color(Color::LIME, text) + } + + /// Colors text orange. + pub fn orange(text: S) -> String { + Color::color(Color::ORANGE, text) + } + + /// Colors text maroon. + pub fn maroon(text: S) -> String { + Color::color(Color::MAROON, text) + } + + /// Color a bool one of two colors depending on its value. + pub fn color_if(cond: bool, true_color: &str, false_color: &str) -> String { + match cond { + true => Color::color(true_color, &format!("{}", cond)), + false => Color::color(false_color, &format!("{}", cond)), + } + } +} diff --git a/arbitrator/prover/src/lib.rs b/arbitrator/prover/src/lib.rs index dcbdfac9b3..be6f117b5c 100644 --- a/arbitrator/prover/src/lib.rs +++ b/arbitrator/prover/src/lib.rs @@ -1,9 +1,11 @@ // Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -#![allow(clippy::missing_safety_doc)] // We have a lot of unsafe ABI +#![allow(clippy::missing_safety_doc, clippy::too_many_arguments)] pub mod binary; +/// cbindgen:ignore +pub mod console; mod host; pub mod machine; /// cbindgen:ignore @@ -11,7 +13,7 @@ mod memory; mod merkle; mod reinterpret; pub mod utils; -mod value; +pub mod value; pub mod wavm; use crate::machine::{argument_data_to_inbox, Machine}; @@ -77,6 +79,7 @@ unsafe fn arbitrator_load_machine_impl( let mach = Machine::from_paths( &libraries, binary_path, + true, false, false, Default::default(), diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index 9a8e6e992d..63261e88e1 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -3,11 +3,12 @@ use crate::{ binary::{parse, FloatInstruction, Local, NameCustomSection, WasmBinary}, + console::Color, host::get_host_impl, memory::Memory, merkle::{Merkle, MerkleType}, reinterpret::{ReinterpretAsSigned, ReinterpretAsUnsigned}, - utils::{file_bytes, Bytes32, CBytes, DeprecatedTableType}, + utils::{file_bytes, Bytes32, CBytes, RemoteTableType}, value::{ArbValueType, FunctionType, IntegerValType, ProgramCounter, Value}, wavm::{ pack_cross_module_call, unpack_cross_module_call, wasm_to_wavm, FloatingPointImpls, @@ -20,11 +21,12 @@ use fnv::FnvHashMap as HashMap; use num::{traits::PrimInt, Zero}; use rayon::prelude::*; use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, FromInto}; +use serde_with::serde_as; use sha3::Keccak256; use std::{ borrow::Cow, convert::TryFrom, + fmt, fs::File, io::{BufReader, BufWriter, Write}, num::Wrapping, @@ -209,7 +211,7 @@ impl TableElement { #[serde_as] #[derive(Clone, Debug, Serialize, Deserialize)] struct Table { - #[serde_as(as = "FromInto")] + #[serde(with = "RemoteTableType")] ty: TableType, elems: Vec, #[serde(skip)] @@ -345,7 +347,7 @@ impl Module { ) }, func_ty.clone(), - &types, + types, )?); host_call_hooks.push(None); } @@ -354,12 +356,24 @@ impl Module { "Multiple memories are not supported" ); if let Some(limits) = bin.memories.get(0) { - // We ignore the maximum size - let size = usize::try_from(limits.initial) - .ok() - .and_then(|x| x.checked_mul(Memory::PAGE_SIZE)) - .ok_or_else(|| eyre!("Memory size is too large"))?; - memory = Memory::new(size); + let page_size = Memory::PAGE_SIZE; + let initial = limits.initial; // validate() checks this is less than max::u32 + let allowed = u32::MAX as u64 / Memory::PAGE_SIZE - 1; // we require the size remain *below* 2^32 + + let max_size = match limits.maximum { + Some(pages) => u64::min(allowed, pages), + _ => allowed, + }; + if initial > max_size { + bail!( + "Memory inits to a size larger than its max: {} vs {}", + limits.initial, + max_size + ); + } + let size = initial * page_size; + + memory = Memory::new(size as usize, max_size); } let mut globals = vec![]; @@ -398,7 +412,7 @@ impl Module { }; if !matches!( offset.checked_add(data.data.len()), - Some(x) if (x as u64) < memory.size() as u64, + Some(x) if (x as u64) <= memory.size() as u64, ) { bail!( "Out-of-bounds data memory init with offset {} and size {}", @@ -568,6 +582,7 @@ impl Module { ); data.extend(self.memory.size().to_be_bytes()); + data.extend(self.memory.max_size.to_be_bytes()); data.extend(mem_merkle.root()); data.extend(self.tables_merkle.root()); @@ -619,6 +634,23 @@ impl GlobalState { } } +#[derive(Serialize)] +pub struct ProofInfo { + pub before: String, + pub proof: String, + pub after: String, +} + +impl ProofInfo { + pub fn new(before: String, proof: String, after: String) -> Self { + Self { + before, + proof, + after, + } + } +} + /// cbindgen:ignore #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] #[repr(u8)] @@ -641,7 +673,6 @@ pub struct MachineState<'a> { status: MachineStatus, value_stack: Cow<'a, Vec>, internal_stack: Cow<'a, Vec>, - block_stack: Cow<'a, Vec>, frame_stack: Cow<'a, Vec>, modules: Vec>, global_state: GlobalState, @@ -660,6 +691,12 @@ struct PreimageResolverWrapper { last_resolved: Option<(Bytes32, CBytes)>, } +impl fmt::Debug for PreimageResolverWrapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "resolver...") + } +} + impl PreimageResolverWrapper { pub fn new(resolver: PreimageResolver) -> PreimageResolverWrapper { PreimageResolverWrapper { @@ -693,13 +730,12 @@ impl PreimageResolverWrapper { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Machine { steps: u64, // Not part of machine hash status: MachineStatus, value_stack: Vec, internal_stack: Vec, - block_stack: Vec, frame_stack: Vec, modules: Vec, modules_merkle: Option, @@ -732,13 +768,6 @@ fn hash_value_stack(stack: &[Value]) -> Bytes32 { hash_stack(stack.iter().map(|v| v.hash()), "Value stack:") } -fn hash_pc_stack(pcs: &[usize]) -> Bytes32 { - hash_stack( - pcs.iter().map(|pc| (*pc as u32).to_be_bytes()), - "Program counter stack:", - ) -} - fn hash_stack_frame_stack(frames: &[StackFrame]) -> Bytes32 { hash_stack(frames.iter().map(|f| f.hash()), "Stack frame stack:") } @@ -786,7 +815,7 @@ where } #[must_use] -fn exec_ibin_op(a: T, b: T, op: IBinOpType) -> T +fn exec_ibin_op(a: T, b: T, op: IBinOpType) -> Option where Wrapping: ReinterpretAsSigned, T: Zero, @@ -798,7 +827,7 @@ where IBinOpType::DivS | IBinOpType::DivU | IBinOpType::RemS | IBinOpType::RemU, ) && b.is_zero() { - return T::zero(); + return None; } let res = match op { IBinOpType::Add => a + b, @@ -817,7 +846,7 @@ where IBinOpType::Rotl => a.rotl(b.cast_usize()), IBinOpType::Rotr => a.rotr(b.cast_usize()), }; - res.0 + Some(res.0) } #[must_use] @@ -857,6 +886,7 @@ impl Machine { pub fn from_paths( library_paths: &[PathBuf], binary_path: &Path, + language_support: bool, always_merkleize: bool, allow_hostapi_from_main: bool, global_state: GlobalState, @@ -866,7 +896,6 @@ impl Machine { let bin_source = file_bytes(binary_path)?; let bin = parse(&bin_source) .wrap_err_with(|| format!("failed to validate WASM binary at {:?}", binary_path))?; - let mut libraries = vec![]; let mut lib_sources = vec![]; for path in library_paths { @@ -880,6 +909,7 @@ impl Machine { Self::from_binaries( &libraries, bin, + language_support, always_merkleize, allow_hostapi_from_main, global_state, @@ -891,6 +921,7 @@ impl Machine { pub fn from_binaries( libraries: &[WasmBinary<'_>], bin: WasmBinary<'_>, + runtime_support: bool, always_merkleize: bool, allow_hostapi_from_main: bool, global_state: GlobalState, @@ -997,8 +1028,9 @@ impl Machine { } let main_module_idx = modules.len() - 1; let main_module = &modules[main_module_idx]; + // Rust support - if let Some(&f) = main_module.exports.get("main") { + if let Some(&f) = main_module.exports.get("main").filter(|_| runtime_support) { let mut expected_type = FunctionType::default(); expected_type.inputs.push(ArbValueType::I32); // argc expected_type.inputs.push(ArbValueType::I32); // argv @@ -1013,8 +1045,9 @@ impl Machine { entry!(Drop); entry!(HaltAndSetFinished); } + // Go support - if let Some(&f) = main_module.exports.get("run") { + if let Some(&f) = main_module.exports.get("run").filter(|_| runtime_support) { let mut expected_type = FunctionType::default(); expected_type.inputs.push(ArbValueType::I32); // argc expected_type.inputs.push(ArbValueType::I32); // argv @@ -1067,11 +1100,11 @@ impl Machine { entry!(@cross, i.module, i.func); } } + let entrypoint_types = vec![FunctionType::default()]; let mut entrypoint_names = NameCustomSection { module: "entry".into(), functions: HashMap::default(), - _locals_removed: HashMap::default(), }; entrypoint_names .functions @@ -1139,7 +1172,6 @@ impl Machine { steps: 0, value_stack: vec![Value::RefNull, Value::I32(0), Value::I32(0)], internal_stack: Vec::new(), - block_stack: Vec::new(), frame_stack: Vec::new(), modules, modules_merkle, @@ -1187,7 +1219,6 @@ impl Machine { steps: 0, value_stack: vec![Value::RefNull, Value::I32(0), Value::I32(0)], internal_stack: Vec::new(), - block_stack: Vec::new(), frame_stack: Vec::new(), modules, modules_merkle: None, @@ -1233,7 +1264,6 @@ impl Machine { status: self.status, value_stack: Cow::Borrowed(&self.value_stack), internal_stack: Cow::Borrowed(&self.internal_stack), - block_stack: Cow::Borrowed(&self.block_stack), frame_stack: Cow::Borrowed(&self.frame_stack), modules, global_state: self.global_state.clone(), @@ -1270,7 +1300,6 @@ impl Machine { self.status = new_state.status; self.value_stack = new_state.value_stack.into_owned(); self.internal_stack = new_state.internal_stack.into_owned(); - self.block_stack = new_state.block_stack.into_owned(); self.frame_stack = new_state.frame_stack.into_owned(); self.global_state = new_state.global_state; self.pc = new_state.pc; @@ -1278,6 +1307,56 @@ impl Machine { Ok(()) } + pub fn start_merkle_caching(&mut self) { + for module in &mut self.modules { + module.memory.cache_merkle_tree(); + } + self.modules_merkle = Some(Merkle::new( + MerkleType::Module, + self.modules.iter().map(Module::hash).collect(), + )); + } + + pub fn stop_merkle_caching(&mut self) { + self.modules_merkle = None; + for module in &mut self.modules { + module.memory.merkle = None; + } + } + + pub fn jump_into_function(&mut self, func: &str, mut args: Vec) { + let frame_args = [Value::RefNull, Value::I32(0), Value::I32(0)]; + args.extend(frame_args); + self.value_stack = args; + + let module = self.modules.last().expect("no module"); + let export = module.exports.iter().find(|x| x.0 == func); + let export = export + .unwrap_or_else(|| panic!("func {} not found", func)) + .1; + + self.frame_stack.clear(); + self.internal_stack.clear(); + + self.pc = ProgramCounter { + module: self.modules.len() - 1, + func: *export as usize, + inst: 0, + }; + self.status = MachineStatus::Running; + self.steps = 0; + } + + pub fn get_final_result(&self) -> Result> { + if !self.frame_stack.is_empty() { + bail!( + "machine has not successfully computed a final result {:?}", + self.status + ) + } + Ok(self.value_stack.clone()) + } + pub fn get_next_instruction(&self) -> Option { if self.is_halted() { return None; @@ -1317,12 +1396,17 @@ impl Machine { } }; } + macro_rules! error { + () => {{ + self.status = MachineStatus::Errored; + break; + }}; + } for _ in 0..n { self.steps += 1; if self.steps == Self::MAX_STEPS { - self.status = MachineStatus::Errored; - break; + error!(); } let inst = func.code[self.pc.inst]; if self.pc.inst == 1 { @@ -1348,25 +1432,8 @@ impl Machine { } self.pc.inst += 1; match inst.opcode { - Opcode::Unreachable => { - self.status = MachineStatus::Errored; - break; - } + Opcode::Unreachable => error!(), Opcode::Nop => {} - Opcode::Block => { - let idx = inst.argument_data as usize; - self.block_stack.push(idx); - debug_assert!(func.code.len() > idx); - } - Opcode::EndBlock => { - self.block_stack.pop(); - } - Opcode::EndBlockIf => { - let x = self.value_stack.last().unwrap(); - if !x.is_i32_zero() { - self.block_stack.pop().unwrap(); - } - } Opcode::InitFrame => { let caller_module_internals = self.value_stack.pop().unwrap().assume_u32(); let caller_module = self.value_stack.pop().unwrap().assume_u32(); @@ -1394,24 +1461,10 @@ impl Machine { Machine::test_next_instruction(func, &self.pc); } } - Opcode::Branch => { - self.pc.inst = self.block_stack.pop().unwrap(); - Machine::test_next_instruction(func, &self.pc); - } - Opcode::BranchIf => { - let x = self.value_stack.pop().unwrap(); - if !x.is_i32_zero() { - self.pc.inst = self.block_stack.pop().unwrap(); - Machine::test_next_instruction(func, &self.pc); - } - } Opcode::Return => { let frame = self.frame_stack.pop().unwrap(); match frame.return_ref { - Value::RefNull => { - self.status = MachineStatus::Errored; - break; - } + Value::RefNull => error!(), Value::InternalRef(pc) => { let changing_module = pc.module != self.pc.module; if changing_module { @@ -1469,8 +1522,7 @@ impl Machine { func = &module.funcs[self.pc.func]; } else { // The caller module has no internals - self.status = MachineStatus::Errored; - break; + error!(); } } Opcode::CallIndirect => { @@ -1497,15 +1549,11 @@ impl Machine { self.pc.inst = 0; func = &module.funcs[self.pc.func]; } - Value::RefNull => { - self.status = MachineStatus::Errored; - break; - } + Value::RefNull => error!(), v => bail!("invalid table element value {:?}", v), } } else { - self.status = MachineStatus::Errored; - break; + error!(); } } Opcode::LocalGet => { @@ -1537,12 +1585,10 @@ impl Machine { if let Some(val) = val { self.value_stack.push(val); } else { - self.status = MachineStatus::Errored; - break; + error!(); } } else { - self.status = MachineStatus::Errored; - break; + error!(); } } Opcode::MemoryStore { ty: _, bytes } => { @@ -1565,12 +1611,10 @@ impl Machine { }; if let Some(idx) = inst.argument_data.checked_add(base.into()) { if !module.memory.store_value(idx, val, bytes) { - self.status = MachineStatus::Errored; - break; + error!(); } } else { - self.status = MachineStatus::Errored; - break; + error!(); } } Opcode::I32Const => { @@ -1647,12 +1691,13 @@ impl Machine { Some(Value::I32(x)) => x, v => bail!("WASM validation failed: bad value for memory.grow {:?}", v), }; + let page_size = Memory::PAGE_SIZE; + let max_size = module.memory.max_size * page_size; + let new_size = (|| { - let adding_size = - u64::from(adding_pages).checked_mul(Memory::PAGE_SIZE as u64)?; + let adding_size = u64::from(adding_pages).checked_mul(page_size)?; let new_size = old_size.checked_add(adding_size)?; - // Note: we require the size remain *below* 2^32, meaning the actual limit is 2^32-PAGE_SIZE - if new_size < (1 << 32) { + if new_size <= max_size { Some(new_size) } else { None @@ -1661,7 +1706,7 @@ impl Machine { if let Some(new_size) = new_size { module.memory.resize(usize::try_from(new_size).unwrap()); // Push the old number of pages - let old_pages = u32::try_from(old_size / Memory::PAGE_SIZE as u64).unwrap(); + let old_pages = u32::try_from(old_size / page_size).unwrap(); self.value_stack.push(Value::I32(old_pages)); } else { // Push -1 @@ -1693,14 +1738,34 @@ impl Machine { match w { IntegerValType::I32 => { if let (Some(Value::I32(a)), Some(Value::I32(b))) = (va, vb) { - self.value_stack.push(Value::I32(exec_ibin_op(a, b, op))); + if op == IBinOpType::DivS + && (a as i32) == i32::MIN + && (b as i32) == -1 + { + error!(); + } + let value = match exec_ibin_op(a, b, op) { + Some(value) => value, + None => error!(), + }; + self.value_stack.push(Value::I32(value)) } else { bail!("WASM validation failed: wrong types for i32binop"); } } IntegerValType::I64 => { if let (Some(Value::I64(a)), Some(Value::I64(b))) = (va, vb) { - self.value_stack.push(Value::I64(exec_ibin_op(a, b, op))); + if op == IBinOpType::DivS + && (a as i64) == i64::MIN + && (b as i64) == -1 + { + error!(); + } + let value = match exec_ibin_op(a, b, op) { + Some(value) => value, + None => error!(), + }; + self.value_stack.push(Value::I64(value)) } else { bail!("WASM validation failed: wrong types for i64binop"); } @@ -1765,20 +1830,12 @@ impl Machine { } self.value_stack.push(Value::I64(x)); } - Opcode::PushStackBoundary => { - self.value_stack.push(Value::StackBoundary); - } Opcode::MoveFromStackToInternal => { self.internal_stack.push(self.value_stack.pop().unwrap()); } Opcode::MoveFromInternalToStack => { self.value_stack.push(self.internal_stack.pop().unwrap()); } - Opcode::IsStackBoundary => { - let val = self.value_stack.pop().unwrap(); - self.value_stack - .push(Value::I32((val == Value::StackBoundary) as u32)); - } Opcode::Dup => { let val = self.value_stack.last().cloned().unwrap(); self.value_stack.push(val); @@ -1791,28 +1848,24 @@ impl Machine { .memory .store_slice_aligned(ptr.into(), &*self.global_state.bytes32_vals[idx]) { - self.status = MachineStatus::Errored; - break; + error!(); } } Opcode::SetGlobalStateBytes32 => { let ptr = self.value_stack.pop().unwrap().assume_u32(); let idx = self.value_stack.pop().unwrap().assume_u32() as usize; if idx >= self.global_state.bytes32_vals.len() { - self.status = MachineStatus::Errored; - break; + error!(); } else if let Some(hash) = module.memory.load_32_byte_aligned(ptr.into()) { self.global_state.bytes32_vals[idx] = hash; } else { - self.status = MachineStatus::Errored; - break; + error!(); } } Opcode::GetGlobalStateU64 => { let idx = self.value_stack.pop().unwrap().assume_u32() as usize; if idx >= self.global_state.u64_vals.len() { - self.status = MachineStatus::Errored; - break; + error!(); } else { self.value_stack .push(Value::I64(self.global_state.u64_vals[idx])); @@ -1822,8 +1875,7 @@ impl Machine { let val = self.value_stack.pop().unwrap().assume_u64(); let idx = self.value_stack.pop().unwrap().assume_u32() as usize; if idx >= self.global_state.u64_vals.len() { - self.status = MachineStatus::Errored; - break; + error!(); } else { self.global_state.u64_vals[idx] = val } @@ -1855,8 +1907,7 @@ impl Machine { bail!("missing requested preimage for hash {}", hash); } } else { - self.status = MachineStatus::Errored; - break; + error!(); } } Opcode::ReadInboxMessage => { @@ -1867,8 +1918,7 @@ impl Machine { argument_data_to_inbox(inst.argument_data).expect("Bad inbox indentifier"); if let Some(message) = self.inbox_contents.get(&(inbox_identifier, msg_num)) { if ptr as u64 + 32 > module.memory.size() { - self.status = MachineStatus::Errored; - break; + error!(); } else { let offset = usize::try_from(offset).unwrap(); let len = std::cmp::min(32, message.len().saturating_sub(offset)); @@ -1876,8 +1926,7 @@ impl Machine { if module.memory.store_slice_aligned(ptr.into(), read) { self.value_stack.push(Value::I32(len as u32)); } else { - self.status = MachineStatus::Errored; - break; + error!(); } } } else { @@ -1895,7 +1944,8 @@ impl Machine { if self.is_halted() && !self.stdio_output.is_empty() { // If we halted, print out any trailing output that didn't have a newline. println!( - "\x1b[33mWASM says:\x1b[0m {}", + "{} {}", + Color::yellow("WASM says:"), String::from_utf8_lossy(&self.stdio_output), ); self.stdio_output.clear(); @@ -2011,7 +2061,6 @@ impl Machine { h.update(b"Machine running:"); h.update(&hash_value_stack(&self.value_stack)); h.update(&hash_value_stack(&self.internal_stack)); - h.update(&hash_pc_stack(&self.block_stack)); h.update(hash_stack_frame_stack(&self.frame_stack)); h.update(self.global_state.hash()); h.update(&u32::try_from(self.pc.module).unwrap().to_be_bytes()); @@ -2053,10 +2102,6 @@ impl Machine { |v| v.serialize_for_proof(), )); - data.extend(prove_stack(&self.block_stack, 1, hash_pc_stack, |pc| { - (*pc as u32).to_be_bytes() - })); - data.extend(prove_window( &self.frame_stack, hash_stack_frame_stack, diff --git a/arbitrator/prover/src/main.rs b/arbitrator/prover/src/main.rs index b65fcc1529..9a2b8eb62b 100644 --- a/arbitrator/prover/src/main.rs +++ b/arbitrator/prover/src/main.rs @@ -4,11 +4,10 @@ use eyre::{Context, Result}; use fnv::{FnvHashMap as HashMap, FnvHashSet as HashSet}; use prover::{ - machine::{GlobalState, InboxIdentifier, Machine, MachineStatus, PreimageResolver}, + machine::{GlobalState, InboxIdentifier, Machine, MachineStatus, PreimageResolver, ProofInfo}, utils::{Bytes32, CBytes}, wavm::Opcode, }; -use serde::Serialize; use sha3::{Digest, Keccak256}; use std::io::BufWriter; use std::sync::Arc; @@ -75,13 +74,6 @@ struct Opts { generate_binaries: Option, } -#[derive(Serialize)] -struct ProofInfo { - before: String, - proof: String, - after: String, -} - fn parse_size_delim(path: &Path) -> Result>> { let mut file = BufReader::new(File::open(path)?); let mut contents = Vec::new(); @@ -188,6 +180,7 @@ fn main() -> Result<()> { let mut mach = Machine::from_paths( &opts.libraries, &opts.binary, + true, opts.always_merkleize, opts.allow_hostapi, global_state, diff --git a/arbitrator/prover/src/memory.rs b/arbitrator/prover/src/memory.rs index ec691463c2..3276c280dc 100644 --- a/arbitrator/prover/src/memory.rs +++ b/arbitrator/prover/src/memory.rs @@ -16,7 +16,8 @@ use std::{borrow::Cow, convert::TryFrom}; pub struct Memory { buffer: Vec, #[serde(skip)] - merkle: Option, + pub merkle: Option, + pub max_size: u64, } fn hash_leaf(bytes: [u8; Memory::LEAF_SIZE]) -> Bytes32 { @@ -48,15 +49,16 @@ fn div_round_up(num: usize, denom: usize) -> usize { impl Memory { pub const LEAF_SIZE: usize = 32; /// Only used when initializing a memory to determine its size - pub const PAGE_SIZE: usize = 65536; + 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 fn new(size: usize) -> Memory { + pub fn new(size: usize, max_size: u64) -> Memory { Memory { buffer: vec![0u8; size], merkle: None, + max_size, } } @@ -106,6 +108,7 @@ impl Memory { let mut h = Keccak256::new(); h.update("Memory:"); h.update((self.buffer.len() as u64).to_be_bytes()); + h.update(self.max_size.to_be_bytes()); h.update(self.merkelize().root()); h.finalize().into() } diff --git a/arbitrator/prover/src/utils.rs b/arbitrator/prover/src/utils.rs index f0b3b4bf73..4579536eda 100644 --- a/arbitrator/prover/src/utils.rs +++ b/arbitrator/prover/src/utils.rs @@ -102,19 +102,6 @@ impl fmt::Debug for Bytes32 { } } -impl From for TableType { - fn from(table: DeprecatedTableType) -> Self { - Self { - element_type: match table.ty { - DeprecatedRefType::FuncRef => Type::FuncRef, - DeprecatedRefType::ExternRef => Type::ExternRef, - }, - initial: table.limits.minimum_size, - maximum: table.limits.maximum_size, - } - } -} - /// A Vec allocated with libc::malloc pub struct CBytes { ptr: *mut u8, @@ -144,43 +131,9 @@ impl Default for CBytes { } } -// TODO: remove this when re-initializing the rollup -// this is kept around to deserialize old binaries -#[derive(Serialize, Deserialize)] -pub enum DeprecatedRefType { - FuncRef, - ExternRef, -} - -// TODO: remove this when re-initializing the rollup -// this is kept around to deserialize old binaries -#[derive(Serialize, Deserialize)] -pub struct DeprecatedLimits { - pub minimum_size: u32, - pub maximum_size: Option, -} - -// TODO: remove this when re-initializing the rollup -// this is kept around to deserialize old binaries -#[derive(Serialize, Deserialize)] -pub struct DeprecatedTableType { - pub ty: DeprecatedRefType, - pub limits: DeprecatedLimits, -} - -impl From for DeprecatedTableType { - fn from(table: TableType) -> Self { - Self { - ty: match table.element_type { - Type::FuncRef => DeprecatedRefType::FuncRef, - Type::ExternRef => DeprecatedRefType::ExternRef, - x => panic!("impossible table type {:?}", x), - }, - limits: DeprecatedLimits { - minimum_size: table.initial, - maximum_size: table.maximum, - }, - } +impl fmt::Debug for CBytes { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self.as_slice()) } } @@ -203,6 +156,27 @@ impl From<&[u8]> for CBytes { } } +#[derive(Serialize, Deserialize)] +#[serde(remote = "Type")] +enum RemoteType { + I32, + I64, + F32, + F64, + V128, + FuncRef, + ExternRef, +} + +#[derive(Serialize, Deserialize)] +#[serde(remote = "TableType")] +pub struct RemoteTableType { + #[serde(with = "RemoteType")] + pub element_type: Type, + pub initial: u32, + pub maximum: Option, +} + impl Drop for CBytes { fn drop(&mut self) { unsafe { libc::free(self.ptr as _) } diff --git a/arbitrator/prover/src/value.rs b/arbitrator/prover/src/value.rs index 9ad0c83440..9267a21782 100644 --- a/arbitrator/prover/src/value.rs +++ b/arbitrator/prover/src/value.rs @@ -3,7 +3,7 @@ use std::convert::TryFrom; -use crate::{binary::FloatType, utils::Bytes32}; +use crate::{binary::FloatType, console::Color, utils::Bytes32}; use digest::Digest; use eyre::{bail, Result}; use serde::{Deserialize, Serialize}; @@ -20,7 +20,6 @@ pub enum ArbValueType { RefNull, FuncRef, InternalRef, - StackBoundary, } impl ArbValueType { @@ -96,7 +95,6 @@ pub enum Value { RefNull, FuncRef(u32), InternalRef(ProgramCounter), - StackBoundary, } impl Value { @@ -109,7 +107,6 @@ impl Value { Value::RefNull => ArbValueType::RefNull, Value::FuncRef(_) => ArbValueType::FuncRef, Value::InternalRef(_) => ArbValueType::InternalRef, - Value::StackBoundary => ArbValueType::StackBoundary, } } @@ -122,7 +119,6 @@ impl Value { Value::RefNull => Bytes32::default(), Value::FuncRef(x) => x.into(), Value::InternalRef(pc) => pc.serialize(), - Value::StackBoundary => Bytes32::default(), } } @@ -186,9 +182,52 @@ impl Value { ArbValueType::RefNull | ArbValueType::FuncRef | ArbValueType::InternalRef => { Value::RefNull } - ArbValueType::StackBoundary => { - panic!("Attempted to make default of StackBoundary type") + } + } + + pub fn pretty_print(&self) -> String { + let lparem = Color::grey("("); + let rparem = Color::grey(")"); + + macro_rules! single { + ($ty:expr, $value:expr) => {{ + format!("{}{}{}{}", Color::grey($ty), lparem, $value, rparem) + }}; + } + macro_rules! pair { + ($ty:expr, $left:expr, $right:expr) => {{ + let eq = Color::grey("="); + format!( + "{}{}{} {} {}{}", + Color::grey($ty), + lparem, + $left, + eq, + $right, + rparem + ) + }}; + } + match self { + Value::I32(value) => { + if (*value as i32) < 0 { + pair!("i32", *value as i32, value) + } else { + single!("i32", *value) + } + } + Value::I64(value) => { + if (*value as i64) < 0 { + pair!("i64", *value as i64, value) + } else { + single!("i64", *value) + } } + Value::F32(value) => single!("f32", *value), + Value::F64(value) => single!("f64", *value), + Value::RefNull => "null".into(), + Value::FuncRef(func) => format!("func {}", func), + Value::InternalRef(pc) => format!("inst {} in {}-{}", pc.inst, pc.module, pc.func), } } } diff --git a/arbitrator/prover/src/wavm.rs b/arbitrator/prover/src/wavm.rs index 58a1499ad6..2a44fc0cd6 100644 --- a/arbitrator/prover/src/wavm.rs +++ b/arbitrator/prover/src/wavm.rs @@ -71,10 +71,6 @@ pub enum IBinOpType { pub enum Opcode { Unreachable, Nop, - Block, - // Loop and If are wrapped into Block - Branch, - BranchIf, Return, Call, @@ -130,24 +126,16 @@ pub enum Opcode { IBinOp(IntegerValType, IBinOpType), // Custom opcodes not in WASM. Documented more in "Custom opcodes.md". - /// Branch is partially split up into these. - EndBlock, - /// Custom opcode not in wasm. - /// Like "EndBlock" but conditional. - /// Keeps its condition on the stack. - EndBlockIf, /// Custom opcode not in wasm. InitFrame, + /// Unconditional jump to an arbitrary point in code. + ArbitraryJump, /// Conditional jump to an arbitrary point in code. ArbitraryJumpIf, - /// Push a Value::StackBoundary to the stack - PushStackBoundary, /// Pop a value from the value stack and push it to the internal stack MoveFromStackToInternal, /// Pop a value from the internal stack and push it to the value stack MoveFromInternalToStack, - /// Pop a value from the value stack, then push an I32 1 if it's a stack boundary, I32 0 otherwise. - IsStackBoundary, /// Duplicate the top value on the stack Dup, /// Call a function in a different module @@ -168,8 +156,6 @@ pub enum Opcode { ReadInboxMessage, /// Stop exexcuting the machine and move to the finished status HaltAndSetFinished, - /// Unconditional jump to an arbitrary point in code. - ArbitraryJump, } impl Opcode { @@ -177,9 +163,6 @@ impl Opcode { match self { Opcode::Unreachable => 0x00, Opcode::Nop => 0x01, - Opcode::Block => 0x02, - Opcode::Branch => 0x0C, - Opcode::BranchIf => 0x0D, Opcode::Return => 0x0F, Opcode::Call => 0x10, Opcode::CallIndirect => 0x11, @@ -268,14 +251,11 @@ impl Opcode { _ => panic!("Unsupported {:?}", self), }, // Internal instructions: - Opcode::EndBlock => 0x8000, - Opcode::EndBlockIf => 0x8001, Opcode::InitFrame => 0x8002, - Opcode::ArbitraryJumpIf => 0x8003, - Opcode::PushStackBoundary => 0x8004, + Opcode::ArbitraryJump => 0x8003, + Opcode::ArbitraryJumpIf => 0x8004, Opcode::MoveFromStackToInternal => 0x8005, Opcode::MoveFromInternalToStack => 0x8006, - Opcode::IsStackBoundary => 0x8007, Opcode::Dup => 0x8008, Opcode::CrossModuleCall => 0x8009, Opcode::CallerModuleInternalCall => 0x800A, @@ -286,7 +266,6 @@ impl Opcode { Opcode::ReadPreImage => 0x8020, Opcode::ReadInboxMessage => 0x8021, Opcode::HaltAndSetFinished => 0x8022, - Opcode::ArbitraryJump => 0x8023, } } @@ -960,16 +939,16 @@ pub fn wasm_to_wavm<'a>( F64Max => float!(BinOp, F64, Max), F64Copysign => float!(BinOp, F64, CopySign), I32WrapI64 => opcode!(I32WrapI64), - I32TruncF32S => float!(TruncIntOp, I32, F32, true), - I32TruncF32U => float!(TruncIntOp, I32, F32, false), - I32TruncF64S => float!(TruncIntOp, I32, F64, true), - I32TruncF64U => float!(TruncIntOp, I32, F64, false), + I32TruncF32S => float!(TruncIntOp, I32, F32, false, true), + I32TruncF32U => float!(TruncIntOp, I32, F32, false, false), + I32TruncF64S => float!(TruncIntOp, I32, F64, false, true), + I32TruncF64U => float!(TruncIntOp, I32, F64, false, false), I64ExtendI32S => opcode!(I64ExtendI32(true)), I64ExtendI32U => opcode!(I64ExtendI32(false)), - I64TruncF32S => float!(TruncIntOp, I64, F32, true), - I64TruncF32U => float!(TruncIntOp, I64, F32, false), - I64TruncF64S => float!(TruncIntOp, I64, F64, true), - I64TruncF64U => float!(TruncIntOp, I64, F64, false), + I64TruncF32S => float!(TruncIntOp, I64, F32, false, true), + I64TruncF32U => float!(TruncIntOp, I64, F32, false, false), + I64TruncF64S => float!(TruncIntOp, I64, F64, false, true), + I64TruncF64U => float!(TruncIntOp, I64, F64, false, false), F32ConvertI32S => float!(ConvertIntOp, F32, I32, true), F32ConvertI32U => float!(ConvertIntOp, F32, I32, false), F32ConvertI64S => float!(ConvertIntOp, F32, I64, true), @@ -989,14 +968,14 @@ pub fn wasm_to_wavm<'a>( I64Extend8S => opcode!(I64ExtendS(8)), I64Extend16S => opcode!(I64ExtendS(16)), I64Extend32S => opcode!(I64ExtendS(32)), - I32TruncSatF32S => float!(TruncIntOp, I32, F32, true), - I32TruncSatF32U => float!(TruncIntOp, I32, F32, false), - I32TruncSatF64S => float!(TruncIntOp, I32, F64, true), - I32TruncSatF64U => float!(TruncIntOp, I32, F64, false), - I64TruncSatF32S => float!(TruncIntOp, I64, F32, true), - I64TruncSatF32U => float!(TruncIntOp, I64, F32, false), - I64TruncSatF64S => float!(TruncIntOp, I64, F64, true), - I64TruncSatF64U => float!(TruncIntOp, I64, F64, false), + I32TruncSatF32S => float!(TruncIntOp, I32, F32, true, true), + I32TruncSatF32U => float!(TruncIntOp, I32, F32, true, false), + I32TruncSatF64S => float!(TruncIntOp, I32, F64, true, true), + I32TruncSatF64U => float!(TruncIntOp, I32, F64, true, false), + I64TruncSatF32S => float!(TruncIntOp, I64, F32, true, true), + I64TruncSatF32U => float!(TruncIntOp, I64, F32, true, false), + I64TruncSatF64S => float!(TruncIntOp, I64, F64, true, true), + I64TruncSatF64U => float!(TruncIntOp, I64, F64, true, false), unsupported @ ( dot!( diff --git a/arbitrator/prover/test-cases/float32.wat b/arbitrator/prover/test-cases/float32.wat index a136bf6760..b49b7e769b 100644 --- a/arbitrator/prover/test-cases/float32.wat +++ b/arbitrator/prover/test-cases/float32.wat @@ -170,63 +170,63 @@ ;; f32 -> i32 truncation (f32.const -2.5) - (i32.trunc_f32_s) + (i32.trunc_sat_f32_s) (call $assert_i32 (i32.const -2)) (f32.const -2.5) - (i32.trunc_f32_u) + (i32.trunc_sat_f32_u) (call $assert_i32 (i32.const 0)) (f32.const 1000000000000) - (i32.trunc_f32_u) + (i32.trunc_sat_f32_u) (i32.const -1) (call $assert_i32) (f32.const 1000000000000) - (i32.trunc_f32_s) + (i32.trunc_sat_f32_s) (i32.const 1) (i32.shl (i32.const 31)) (i32.sub (i32.const 1)) (call $assert_i32) (f32.const 1000000000000) - (i32.trunc_f32_s) + (i32.trunc_sat_f32_s) (i32.gt_s (i32.const 0)) (call $assert_true) (f32.const -1000000000000) - (i32.trunc_f32_s) + (i32.trunc_sat_f32_s) (i32.const 1) (i32.shl (i32.const 31)) (call $assert_i32) (f32.const -1000000000000) - (i32.trunc_f32_s) + (i32.trunc_sat_f32_s) (i32.lt_s (i32.const 0)) (call $assert_true) ;; f32 -> i64 truncation (f32.const -2.5) - (i64.trunc_f32_s) + (i64.trunc_sat_f32_s) (call $assert_i64 (i64.const -2)) (f32.const -2.5) - (i64.trunc_f32_u) + (i64.trunc_sat_f32_u) (call $assert_i64 (i64.const 0)) (f32.const 1000000000000000000000) - (i64.trunc_f32_u) + (i64.trunc_sat_f32_u) (i64.const -1) (call $assert_i64) (f32.const 1000000000000000000000) - (i64.trunc_f32_s) + (i64.trunc_sat_f32_s) (i64.const 1) (i64.shl (i64.const 63)) (i64.sub (i64.const 1)) (call $assert_i64) (f32.const 1000000000000000000000) - (i64.trunc_f32_s) + (i64.trunc_sat_f32_s) (i64.gt_s (i64.const 0)) (call $assert_true) (f32.const -1000000000000000000000) - (i64.trunc_f32_s) + (i64.trunc_sat_f32_s) (i64.const 1) (i64.shl (i64.const 63)) (call $assert_i64) (f32.const -1000000000000000000000) - (i64.trunc_f32_s) + (i64.trunc_sat_f32_s) (i64.lt_s (i64.const 0)) (call $assert_true) diff --git a/arbitrator/prover/test-cases/float64.wat b/arbitrator/prover/test-cases/float64.wat index 7c22e10d8a..791ba1de8f 100644 --- a/arbitrator/prover/test-cases/float64.wat +++ b/arbitrator/prover/test-cases/float64.wat @@ -170,63 +170,63 @@ ;; f64 -> i32 truncation (f64.const -2.5) - (i32.trunc_f64_s) + (i32.trunc_sat_f64_s) (call $assert_i32 (i32.const -2)) (f64.const -2.5) - (i32.trunc_f64_u) + (i32.trunc_sat_f64_u) (call $assert_i32 (i32.const 0)) (f64.const 1000000000000) - (i32.trunc_f64_u) + (i32.trunc_sat_f64_u) (i32.const -1) (call $assert_i32) (f64.const 1000000000000) - (i32.trunc_f64_s) + (i32.trunc_sat_f64_s) (i32.const 1) (i32.shl (i32.const 63)) (i32.sub (i32.const 1)) (call $assert_i32) (f64.const 1000000000000) - (i32.trunc_f64_s) + (i32.trunc_sat_f64_s) (i32.gt_s (i32.const 0)) (call $assert_true) (f64.const -1000000000000) - (i32.trunc_f64_s) + (i32.trunc_sat_f64_s) (i32.const 1) (i32.shl (i32.const 63)) (call $assert_i32) (f64.const -1000000000000) - (i32.trunc_f64_s) + (i32.trunc_sat_f64_s) (i32.lt_s (i32.const 0)) (call $assert_true) ;; f64 -> i64 truncation (f64.const -2.5) - (i64.trunc_f64_s) + (i64.trunc_sat_f64_s) (call $assert_i64 (i64.const -2)) (f64.const -2.5) - (i64.trunc_f64_u) + (i64.trunc_sat_f64_u) (call $assert_i64 (i64.const 0)) (f64.const 1000000000000000000000) - (i64.trunc_f64_u) + (i64.trunc_sat_f64_u) (i64.const -1) (call $assert_i64) (f64.const 1000000000000000000000) - (i64.trunc_f64_s) + (i64.trunc_sat_f64_s) (i64.const 1) (i64.shl (i64.const 63)) (i64.sub (i64.const 1)) (call $assert_i64) (f64.const 1000000000000000000000) - (i64.trunc_f64_s) + (i64.trunc_sat_f64_s) (i64.gt_s (i64.const 0)) (call $assert_true) (f64.const -1000000000000000000000) - (i64.trunc_f64_s) + (i64.trunc_sat_f64_s) (i64.const 1) (i64.shl (i64.const 63)) (call $assert_i64) (f64.const -1000000000000000000000) - (i64.trunc_f64_s) + (i64.trunc_sat_f64_s) (i64.lt_s (i64.const 0)) (call $assert_true) diff --git a/arbitrator/wasm-libraries/soft-float/bindings32.c b/arbitrator/wasm-libraries/soft-float/bindings32.c index be550b03d1..f0e54ba8f2 100644 --- a/arbitrator/wasm-libraries/soft-float/bindings32.c +++ b/arbitrator/wasm-libraries/soft-float/bindings32.c @@ -108,8 +108,10 @@ uint32_t wavm__f32_div(uint32_t va, uint32_t vb) { uint32_t wavm__f32_min(uint32_t va, uint32_t vb) { float32_t a = {va}; float32_t b = {vb}; - if (f32_isNaN(a) || f32_isNaN(b)) { + if (f32_isNaN(a)) { return a.v; + } else if (f32_isNaN(b)) { + return b.v; } else if (f32_isInfinity(a) && f32_isNegative(a)) { return a.v; } else if (f32_isInfinity(b) && f32_isNegative(b)) { @@ -132,8 +134,10 @@ uint32_t wavm__f32_min(uint32_t va, uint32_t vb) { uint32_t wavm__f32_max(uint32_t va, uint32_t vb) { float32_t a = {va}; float32_t b = {vb}; - if (f32_isNaN(a) || f32_isNaN(b)) { + if (f32_isNaN(a)) { return a.v; + } else if (f32_isNaN(b)) { + return b.v; } else if (f32_isInfinity(a) && !f32_isNegative(a)) { return a.v; } else if (f32_isInfinity(b) && !f32_isNegative(b)) { @@ -198,39 +202,113 @@ uint8_t wavm__f32_ge(uint32_t va, uint32_t vb) { } int32_t wavm__i32_trunc_f32_s(uint32_t v) { - float32_t f = {v}; - // A rounded up floating point version of 1 << 32 - float32_t max = {0x4f800000}; - if (f32_le(max, f)) { - return (1u << 31) - 1; + // signed truncation is defined over (i32::min - 1, i32::max + 1) + float32_t max = {0x4f000000}; // i32::max + 1 = 0x4F000000 + float32_t min = {0xcf000001}; // i32::min - 1 = 0xCF000000 (adjusted due to rounding) + float32_t val = {v}; + if (f32_isNaN(val) || f32_le(max, val) || f32_le(val, min)) { + __builtin_trap(); } - return f32_to_i32(f, softfloat_round_minMag, true); + return f32_to_i32(val, softfloat_round_minMag, true); +} + +int32_t wavm__i32_trunc_sat_f32_s(uint32_t v) { + // signed truncation is defined over (i32::min - 1, i32::max + 1) + float32_t max = {0x4f000000}; // i32::max + 1 = 0x4F000000 + float32_t min = {0xcf000001}; // i32::min - 1 = 0xCF000000 (adjusted due to rounding) + float32_t val = {v}; + if (f32_isNaN(val)) { + return 0; + } + if (f32_le(max, val)) { + return 2147483647; + } + if (f32_le(val, min)) { + return -2147483648; + } + return f32_to_i32(val, softfloat_round_minMag, true); } uint32_t wavm__i32_trunc_f32_u(uint32_t v) { - float32_t f = {v}; - if (f32_isNegative(f)) { + // unsigned truncation is defined over (-1, u32::max + 1) + float32_t max = {0x4f800000}; // u32::max + 1 = 0x4f800000 + float32_t min = {0xbf800000}; // -1 = 0xbf800000 + float32_t val = {v}; + if (f32_isNaN(val) || f32_le(max, val) || f32_le(val, min)) { + __builtin_trap(); + } + if (f32_isNegative(val)) { + return 0; + } + return f32_to_ui32(val, softfloat_round_minMag, true); +} + +uint32_t wavm__i32_trunc_sat_f32_u(uint32_t v) { + // unsigned truncation is defined over (-1, u32::max + 1) + float32_t max = {0x4f800000}; // u32::max + 1 = 0x4f800000 + float32_t val = {v}; + if (f32_isNaN(val) || f32_isNegative(val)) { return 0; } - return f32_to_ui32(f, softfloat_round_minMag, true); + if (f32_le(max, val)) { + return ~0u; + } + return f32_to_ui32(val, softfloat_round_minMag, true); } int64_t wavm__i64_trunc_f32_s(uint32_t v) { - float32_t f = {v}; - // A rounded down floating point version of 1 << 64 - float32_t max = {0x5f800000}; - if (f32_lt(max, f)) { - return (1ull << 63) - 1; + // unsigned truncation is defined over (i64::min - 1, i64::max + 1) + float32_t max = {0x5f000000}; // i64::max + 1 = 0x5f000000 + float32_t min = {0xdf000001}; // i64::min - 1 = 0xdf000000 (adjusted due to rounding) + float32_t val = {v}; + if (f32_isNaN(val) || f32_le(max, val) || f32_le(val, min)) { + __builtin_trap(); + } + return f32_to_i64(val, softfloat_round_minMag, true); +} + +int64_t wavm__i64_trunc_sat_f32_s(uint32_t v) { + // unsigned truncation is defined over (i64::min - 1, i64::max + 1) + float32_t max = {0x5f000000}; // i64::max + 1 = 0x5f000000 + float32_t min = {0xdf000001}; // i64::min - 1 = 0xdf000000 (adjusted due to rounding) + float32_t val = {v}; + if (f32_isNaN(val)) { + return 0; + } + if (f32_le(max, val)) { + return 9223372036854775807ll; + } + if (f32_le(val, min)) { + return -(((int64_t) 1) << 63); } - return f32_to_i64(f, softfloat_round_minMag, true); + return f32_to_i64(val, softfloat_round_minMag, true); } uint64_t wavm__i64_trunc_f32_u(uint32_t v) { - float32_t f = {v}; - if (f32_isNegative(f)) { + // unsigned truncation is defined over (-1, i64::max + 1) + float32_t max = {0x5f800000}; // i64::max + 1 = 0x5f800000 + float32_t min = {0xbf800000}; // -1 = 0xbf800000 + float32_t val = {v}; + if (f32_isNaN(val) || f32_le(max, val) || f32_le(val, min)) { + __builtin_trap(); + } + if (f32_isNegative(val)) { return 0; } - return f32_to_ui64(f, softfloat_round_minMag, true); + return f32_to_ui64(val, softfloat_round_minMag, true); +} + +uint64_t wavm__i64_trunc_sat_f32_u(uint32_t v) { + // unsigned truncation is defined over (-1, i64::max + 1) + float32_t max = {0x5f800000}; // i64::max + 1 = 0x5f800000 + float32_t val = {v}; + if (f32_isNaN(val) || f32_isNegative(val)) { + return 0; + } + if (f32_le(max, val)) { + return ~0ull; + } + return f32_to_ui64(val, softfloat_round_minMag, true); } uint32_t wavm__f32_convert_i32_s(int32_t x) { diff --git a/arbitrator/wasm-libraries/soft-float/bindings64.c b/arbitrator/wasm-libraries/soft-float/bindings64.c index 9d56b8c3e5..968b76fdbc 100644 --- a/arbitrator/wasm-libraries/soft-float/bindings64.c +++ b/arbitrator/wasm-libraries/soft-float/bindings64.c @@ -108,8 +108,10 @@ uint64_t wavm__f64_div(uint64_t va, uint64_t vb) { uint64_t wavm__f64_min(uint64_t va, uint64_t vb) { float64_t a = {va}; float64_t b = {vb}; - if (f64_isNaN(a) || f64_isNaN(b)) { + if (f64_isNaN(a)) { return a.v; + } else if (f64_isNaN(b)) { + return b.v; } else if (f64_isInfinity(a) && f64_isNegative(a)) { return a.v; } else if (f64_isInfinity(b) && f64_isNegative(b)) { @@ -132,10 +134,10 @@ uint64_t wavm__f64_min(uint64_t va, uint64_t vb) { uint64_t wavm__f64_max(uint64_t va, uint64_t vb) { float64_t a = {va}; float64_t b = {vb}; - if (f64_isNaN(a) || f64_isNaN(b)) { - return a.v; - } else if (f64_isInfinity(a) && !f64_isNegative(a)) { + if (f64_isNaN(a)) { return a.v; + } else if (f64_isNaN(b)) { + return b.v; } else if (f64_isInfinity(b) && !f64_isNegative(b)) { return b.v; } else if (f64_isInfinity(a) && f64_isNegative(a)) { @@ -198,41 +200,115 @@ uint8_t wavm__f64_ge(uint64_t va, uint64_t vb) { } int32_t wavm__i32_trunc_f64_s(uint64_t v) { - float64_t f = {v}; - // An exact floating point version of 1 << 32 - float64_t max = {0x41f0000000000000}; - if (f64_le(max, f)) { - return (1u << 31) - 1; + // signed truncation is defined over (i32::min - 1, i32::max + 1) + float64_t max = {0x41e0000000000000}; // i32::max + 1 = 0x41e0000000000000 + float64_t min = {0xc1e0000000200000}; // i32::min - 1 = 0xc1e0000000200000 + float64_t val = {v}; + if (f64_isNaN(val) || f64_le(max, val) || f64_le(val, min)) { + __builtin_trap(); + } + return f64_to_i32(val, softfloat_round_minMag, true); +} + +int32_t wavm__i32_trunc_sat_f64_s(uint64_t v) { + // signed truncation is defined over (i32::min - 1, i32::max + 1) + float64_t max = {0x41e0000000000000}; // i32::max + 1 = 0x41e0000000000000 + float64_t min = {0xc1e0000000200000}; // i32::min - 1 = 0xc1e0000000200000 + float64_t val = {v}; + if (f64_isNaN(val)) { + return 0; + } + if (f64_le(max, val)) { + return 2147483647; } - return f64_to_i32(f, softfloat_round_minMag, true); + if (f64_le(val, min)) { + return -2147483648; + } + return f64_to_i32(val, softfloat_round_minMag, true); } uint32_t wavm__i32_trunc_f64_u(uint64_t v) { - float64_t f = {v}; - if (f64_isNegative(f)) { + // unsigned truncation is defined over (-1, u32::max + 1) + float64_t max = {0x41f0000000000000}; // u32::max + 1 = 0x41f0000000000000 + float64_t min = {0xbff0000000000000}; // -1 = 0xbff0000000000000 + float64_t val = {v}; + if (f64_isNaN(val) || f64_le(max, val) || f64_le(val, min)) { + __builtin_trap(); + } + if (f64_isNegative(val)) { + return 0; + } + return f64_to_ui32(val, softfloat_round_minMag, true); +} + +uint32_t wavm__i32_trunc_sat_f64_u(uint64_t v) { + // unsigned truncation is defined over (-1, u32::max + 1) + float64_t max = {0x41f0000000000000}; // u32::max + 1 = 0x41f0000000000000 + float64_t val = {v}; + if (f64_isNaN(val) || f64_isNegative(val)) { return 0; } - return f64_to_ui32(f, softfloat_round_minMag, true); + if (f64_le(max, val)) { + return ~0u; + } + return f64_to_ui32(val, softfloat_round_minMag, true); } int64_t wavm__i64_trunc_f64_s(uint64_t v) { - float64_t f = {v}; - // A rounded up floating point version of 1 << 32 - float64_t max = {0x43f0000000000000}; - if (f64_le(max, f)) { - return (1ull << 63) - 1; + // signed truncation is defined over (i64::min - 1, u64::max + 1) + float64_t max = {0x43e0000000000000}; // i64::max + 1 = 0x43e0000000000000 + float64_t min = {0xc3e0000000000001}; // i64::min - 1 = 0xc3e0000000000000 (adjusted due to rounding) + float64_t val = {v}; + if (f64_isNaN(val) || f64_le(max, val) || f64_le(val, min)) { + __builtin_trap(); + } + return f64_to_i64(val, softfloat_round_minMag, true); +} + +int64_t wavm__i64_trunc_sat_f64_s(uint64_t v) { + // signed truncation is defined over (i64::min - 1, u64::max + 1) + float64_t max = {0x43e0000000000000}; // i64::max + 1 = 0x43e0000000000000 + float64_t min = {0xc3e0000000000001}; // i64::min - 1 = 0xc3e0000000000000 (adjusted due to rounding) + float64_t val = {v}; + if (f64_isNaN(val)) { + return 0; } - return f64_to_i64(f, softfloat_round_minMag, true); + if (f64_le(max, val)) { + return 9223372036854775807ll; + } + if (f64_le(val, min)) { + return -(((int64_t) 1) << 63); + } + return f64_to_i64(val, softfloat_round_minMag, true); } uint64_t wavm__i64_trunc_f64_u(uint64_t v) { + // unsigned truncation is defined over (-1, u64::max + 1) + float64_t max = {0x43f0000000000000}; // u64::max + 1 = 0x43f0000000000000 + float64_t min = {0xbff0000000000000}; // -1 = 0xbff0000000000000 float64_t f = {v}; + if (f64_isNaN(f) || f64_le(max, f) || f64_le(f, min)) { + __builtin_trap(); + } if (f64_isNegative(f)) { return 0; } return f64_to_ui64(f, softfloat_round_minMag, true); } +uint64_t wavm__i64_trunc_sat_f64_u(uint64_t v) { + // unsigned truncation is defined over (-1, u64::max + 1) + float64_t max = {0x43f0000000000000}; // u64::max + 1 = 0x43f0000000000000 + float64_t val = {v}; + if (f64_isNaN(val) || f64_isNegative(val)) { + return 0; + } + if (f64_le(max, val)) { + return 18446744073709551615ull; + } + return f64_to_ui64(val, softfloat_round_minMag, true); +} + uint64_t wavm__f64_convert_i32_s(int32_t x) { return i32_to_f64(x).v; } diff --git a/arbitrator/wasm-testsuite/.gitignore b/arbitrator/wasm-testsuite/.gitignore new file mode 100644 index 0000000000..e7e1fb04f4 --- /dev/null +++ b/arbitrator/wasm-testsuite/.gitignore @@ -0,0 +1 @@ +tests/* diff --git a/arbitrator/wasm-testsuite/Cargo.lock b/arbitrator/wasm-testsuite/Cargo.lock new file mode 100644 index 0000000000..60e48adf1f --- /dev/null +++ b/arbitrator/wasm-testsuite/Cargo.lock @@ -0,0 +1,772 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" + +[[package]] +name = "brotli-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "brotli2" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e" +dependencies = [ + "brotli-sys", + "libc", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim 0.8.0", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +dependencies = [ + "cfg-if", + "lazy_static", +] + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.10.0", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" + +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom-leb128" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a73b6c3a9ecfff12ad50dedba051ef838d2f478d938bb3e6b3842431028e62" +dependencies = [ + "arrayvec", + "nom", + "num-traits", +] + +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fbc387afefefd5e9e39493299f3069e14a140dd34dc19b4c1c1a8fddb6a790" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "prover" +version = "0.1.0" +dependencies = [ + "bincode", + "brotli2", + "digest", + "eyre", + "fnv", + "hex", + "libc", + "nom", + "nom-leb128", + "num", + "rayon", + "rustc-demangle", + "serde", + "serde_json", + "serde_with", + "sha3", + "static_assertions", + "structopt", + "wasmparser", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd249e82c21598a9a426a4e00dd7adc1d640b22445ec8545feef801d1a74c221" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "ryu" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b827f2113224f3f19a665136f006709194bdfdcb1fdc1e4b2b5cbac8e0cced54" +dependencies = [ + "rustversion", + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha3" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" +dependencies = [ + "block-buffer", + "digest", + "keccak", + "opaque-debug", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a07e33e919ebcd69113d5be0e4d70c5707004ff45188910106854f38b960df4a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasm-testsuite" +version = "0.1.0" +dependencies = [ + "eyre", + "hex", + "prover", + "serde", + "serde_json", + "structopt", +] + +[[package]] +name = "wasmparser" +version = "0.84.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77dc97c22bb5ce49a47b745bed8812d30206eff5ef3af31424f2c1820c0974b2" +dependencies = [ + "indexmap", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/arbitrator/wasm-testsuite/Cargo.toml b/arbitrator/wasm-testsuite/Cargo.toml new file mode 100644 index 0000000000..5ace2ca584 --- /dev/null +++ b/arbitrator/wasm-testsuite/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "wasm-testsuite" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +prover = { path = "../prover" } +structopt = "0.3.23" +serde = { version = "1.0.130", features = ["derive", "rc"] } +serde_json = "1.0.67" +eyre = "0.6.5" +hex = "0.4.3" + +[workspace] +members = [] diff --git a/arbitrator/wasm-testsuite/check.sh b/arbitrator/wasm-testsuite/check.sh new file mode 100755 index 0000000000..9c67557dc8 --- /dev/null +++ b/arbitrator/wasm-testsuite/check.sh @@ -0,0 +1,24 @@ +#!/usr/bin/bash + +# Copyright 2022, Offchain Labs, Inc. +# For license information, see https://github.com/nitro/blob/master/LICENSE + +rm -rf tests ../../contracts/test/prover/spec-proofs +mkdir -p tests/ +mkdir -p ../../contracts/test/prover/spec-proofs/ + +for file in testsuite/*wast; do + wast="${file##testsuite/}" + json="tests/${wast%.wast}.json" + wast2json $file -o $json 2>/dev/null +done + +cargo build --release + +for file in tests/*.json; do + base="${file#tests/}" + name="${base%.wasm}" + target/release/wasm-testsuite $name & +done + +wait diff --git a/arbitrator/wasm-testsuite/src/main.rs b/arbitrator/wasm-testsuite/src/main.rs new file mode 100644 index 0000000000..4ff511d9de --- /dev/null +++ b/arbitrator/wasm-testsuite/src/main.rs @@ -0,0 +1,409 @@ +// Copyright 2022, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use eyre::bail; +use prover::{ + console::Color, + machine, + machine::{GlobalState, Machine, MachineStatus, ProofInfo}, + value::Value, +}; +use serde::{Deserialize, Serialize}; +use std::{ + collections::{HashMap, HashSet}, + fs::File, + io::BufReader, + path::PathBuf, + time::Instant, +}; +use structopt::StructOpt; + +#[derive(StructOpt)] +#[structopt(name = "wasm-testsuite")] +struct Opts { + json: PathBuf, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct Case { + source_filename: String, + commands: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +enum Command { + Module { + filename: String, + }, + AssertReturn { + action: Action, + expected: Vec, + }, + AssertExhaustion { + action: Action, + }, + AssertTrap { + action: Action, + }, + Action { + action: Action, + }, + AssertMalformed { + filename: String, + }, + AssertInvalid {}, + AssertUninstantiable {}, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "snake_case")] +enum Action { + Invoke { field: String, args: Vec }, + Get { field: String }, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +struct TextValue { + #[serde(rename = "type")] + ty: TextValueType, + value: String, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +enum TextValueType { + I32, + I64, + F32, + F64, +} + +impl Into for TextValue { + fn into(self) -> Value { + match self.ty { + TextValueType::I32 => { + let value = self.value.parse().expect("not an i32"); + Value::I32(value) + } + TextValueType::I64 => { + let value = self.value.parse().expect("not an i64"); + Value::I64(value) + } + TextValueType::F32 => { + if self.value.contains("nan") { + return Value::F32(f32::NAN); + } + let message = format!("{} not the bit representation of an f32", self.value); + let bits: u32 = self.value.parse().expect(&message); + Value::F32(f32::from_bits(bits)) + } + TextValueType::F64 => { + if self.value.contains("nan") { + return Value::F64(f64::NAN); + } + let message = format!("{} not the bit representation of an f64", self.value); + let bits: u64 = self.value.parse().expect(&message); + Value::F64(f64::from_bits(bits)) + } + } + } +} + +impl PartialEq for TextValue { + fn eq(&self, other: &Value) -> bool { + if &Into::::into(self.clone()) == other { + return true; + } + + match self.ty { + TextValueType::F32 => match other { + Value::F32(value) => value.is_nan() && self.value.contains("nan"), + _ => false, + }, + TextValueType::F64 => match other { + Value::F64(value) => value.is_nan() && self.value.contains("nan"), + _ => false, + }, + _ => false, + } + } +} + +fn pretty_print_values(prefix: &str, values: Vec) { + let mut result = format!(" {} ", prefix); + for value in values { + result += &format!("{}, ", value.pretty_print()); + } + if result.len() > 2 { + result.pop(); + result.pop(); + } + println!("{}", result) +} + +fn main() -> eyre::Result<()> { + let opts = Opts::from_args(); + println!("test {:?}", opts.json); + + let mut path = PathBuf::from("tests/"); + path.push(&opts.json); + + let reader = BufReader::new(File::open(path)?); + let case: Case = serde_json::from_reader(reader)?; + let start_time = Instant::now(); + + let soft_float = PathBuf::from("../../target/machines/latest/soft-float.wasm"); + + // The modules listed below will be tested for compliance with the spec, but won't produce proofs for the OSP test. + // We list the soft-float modules because, while compliance is necessary, the funcs are comprised of opcodes + // better tested elsewhere and aren't worth 10x the test time. + let mut do_not_prove = HashSet::new(); + do_not_prove.insert(PathBuf::from("f32.json")); + do_not_prove.insert(PathBuf::from("f64.json")); + do_not_prove.insert(PathBuf::from("f32_cmp.json")); + do_not_prove.insert(PathBuf::from("f64_cmp.json")); + do_not_prove.insert(PathBuf::from("float_exprs.json")); + let export_proofs = !do_not_prove.contains(&opts.json); + if !export_proofs { + println!("{}", Color::grey("skipping OSP proof generation")); + } + + let mut wasmfile = String::new(); + let mut machine = None; + let mut subtest = 0; + let mut skip = false; + + macro_rules! run { + ($machine:expr, $bound:expr, $path:expr, $prove:expr) => {{ + let mut proofs = vec![]; + let mut count = 0; + let mut leap = 1; + let prove = $prove && export_proofs; + + if !prove { + $machine.step_n($bound)?; + } + + while count + leap < $bound && prove { + count += 1; + + let prior = $machine.hash().to_string(); + let proof = hex::encode($machine.serialize_proof()); + $machine.step_n(1)?; + let after = $machine.hash().to_string(); + proofs.push(ProofInfo::new(prior, proof, after)); + $machine.step_n(leap - 1)?; + + if count % 100 == 0 { + leap *= leap + 1; + if leap > 6 { + let message = format!("backing off {} {} {}", leap, count, $bound); + println!("{}", Color::grey(message)); + $machine.stop_merkle_caching(); + } + } + if $machine.is_halted() { + break; + } + } + if prove { + let out = File::create($path)?; + serde_json::to_writer_pretty(out, &proofs)?; + } + }}; + } + macro_rules! action { + ($action:expr) => { + match $action { + Action::Invoke { field, args } => (field, args), + Action::Get { .. } => { + // get() is only used in the export test, which we don't support + println!("skipping unsupported action {}", Color::red("get")); + continue; + } + } + }; + } + macro_rules! outname { + () => { + format!( + "../../contracts/test/prover/spec-proofs/{}-{:04}.json", + wasmfile, subtest + ) + }; + } + + for (index, command) in case.commands.into_iter().enumerate() { + macro_rules! test_success { + ($func:expr, $args:expr, $expected:expr) => { + let args: Vec<_> = $args.into_iter().map(Into::into).collect(); + if skip { + println!("skipping {}", Color::red($func)); + subtest += 1; + continue; + } + + let machine = machine.as_mut().expect("no machine"); + machine.jump_into_function(&$func, args.clone()); + machine.start_merkle_caching(); + run!(machine, 10_000_000, outname!(), true); + + let output = match machine.get_final_result() { + Ok(output) => output, + Err(error) => { + let expected: Vec = $expected.into_iter().map(Into::into).collect(); + println!( + "Divergence in func {} of test {}", + Color::red($func), + Color::red(index), + ); + pretty_print_values("Args ", args); + pretty_print_values("Expected", expected); + println!(); + bail!("{}", error) + } + }; + + if $expected != output { + let expected: Vec = $expected.into_iter().map(Into::into).collect(); + println!( + "Divergence in func {} of test {}", + Color::red($func), + Color::red(index), + ); + pretty_print_values("Args ", args); + pretty_print_values("Expected", expected); + pretty_print_values("Observed", output); + println!(); + bail!( + "Failure in test {}", + Color::red(format!("{} #{}", wasmfile, subtest)) + ) + } + subtest += 1; + }; + } + + match command { + Command::Module { filename } => { + wasmfile = filename; + machine = None; + subtest = 1; + + let mech = Machine::from_paths( + &[soft_float.clone()], + &PathBuf::from("tests").join(&wasmfile), + false, + false, + false, + GlobalState::default(), + HashMap::default(), + machine::get_empty_preimage_resolver(), + ); + + if let Err(error) = &mech { + let error = error.root_cause().to_string(); + skip = true; + + if error.contains("Module has no code") { + // We don't support metadata-only modules that have no code + continue; + } + if error.contains("Unsupported import") { + // We don't support the import test's functions + continue; + } + if error.contains("multiple tables") { + // We don't support the reference-type extension + continue; + } + if error.contains("bulk memory") { + // We don't support the bulk-memory extension + continue; + } + bail!("Unexpected error parsing module {}: {}", wasmfile, error) + } + + machine = mech.ok(); + skip = false; + + if let Some(machine) = &mut machine { + machine.step_n(1000)?; // run init + machine.start_merkle_caching(); + } + } + Command::AssertReturn { action, expected } => { + let (func, args) = action!(action); + test_success!(func, args, expected); + } + Command::Action { action } => { + let (func, args) = action!(action); + let expected: Vec = vec![]; + test_success!(func, args, expected); + } + Command::AssertTrap { action } => { + let (func, args) = action!(action); + let args: Vec<_> = args.into_iter().map(Into::into).collect(); + let test = Color::red(format!("{} #{}", wasmfile, subtest)); + + let machine = machine.as_mut().unwrap(); + machine.jump_into_function(&func, args.clone()); + run!(machine, 1000, outname!(), true); + + if machine.get_status() == MachineStatus::Running { + bail!("machine failed to trap in test {}", test) + } + if let Ok(output) = machine.get_final_result() { + println!( + "Divergence in func {} of test {}", + Color::red(func), + Color::red(index), + ); + pretty_print_values("Args ", args); + pretty_print_values("Output", output); + println!(); + bail!("Unexpected success in test {}", test) + } + subtest += 1; + } + Command::AssertExhaustion { action } => { + let (func, args) = action!(action); + let args: Vec<_> = args.into_iter().map(Into::into).collect(); + let test = Color::red(format!("{} #{}", wasmfile, subtest)); + + let machine = machine.as_mut().unwrap(); + machine.jump_into_function(&func, args.clone()); + run!(machine, 100_000, outname!(), false); // this is proportional to the amount of RAM + + if machine.get_status() != MachineStatus::Running { + bail!("machine should spin {}", test) + } + subtest += 1; + } + Command::AssertMalformed { filename } => { + let wasmpath = PathBuf::from("tests").join(&filename); + + let _ = Machine::from_paths( + &[soft_float.clone()], + &wasmpath, + false, + false, + false, + GlobalState::default(), + HashMap::default(), + machine::get_empty_preimage_resolver(), + ) + .expect_err(&format!("failed to reject invalid module {}", filename)); + } + _ => {} + } + } + + println!( + "{} {}", + Color::grey("done in"), + Color::pink(format!("{}ms", start_time.elapsed().as_millis())) + ); + Ok(()) +} diff --git a/arbitrator/wasm-testsuite/testsuite b/arbitrator/wasm-testsuite/testsuite new file mode 160000 index 0000000000..e25ae15935 --- /dev/null +++ b/arbitrator/wasm-testsuite/testsuite @@ -0,0 +1 @@ +Subproject commit e25ae159357c055b3a6fac99043644e208d26d2a diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 8d31f11426..e4026fdca4 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -41,6 +41,9 @@ module.exports = { mocha: { timeout: 0, }, + gasReporter: { + enabled: (process.env.DISABLE_GAS_REPORTER) ? false : true + }, typechain: { outDir: 'build/types', target: 'ethers-v5', diff --git a/contracts/src/challenge/ChallengeLib.sol b/contracts/src/challenge/ChallengeLib.sol index f1b660c5b3..e225ea1fe4 100644 --- a/contracts/src/challenge/ChallengeLib.sol +++ b/contracts/src/challenge/ChallengeLib.sol @@ -61,14 +61,12 @@ library ChallengeLib { ValueArray memory valuesArray = ValueArray({inner: startingValues}); ValueStack memory values = ValueStack({proved: valuesArray, remainingHash: 0}); ValueStack memory internalStack; - PcStack memory blocks; StackFrameWindow memory frameStack; Machine memory mach = Machine({ status: MachineStatus.RUNNING, valueStack: values, internalStack: internalStack, - blockStack: blocks, frameStack: frameStack, globalStateHash: globalStateHash, moduleIdx: 0, diff --git a/contracts/src/osp/OneStepProver0.sol b/contracts/src/osp/OneStepProver0.sol index 4b50313cdb..2767a8e980 100644 --- a/contracts/src/osp/OneStepProver0.sol +++ b/contracts/src/osp/OneStepProver0.sol @@ -12,7 +12,6 @@ import "./IOneStepProver.sol"; contract OneStepProver0 is IOneStepProver { using MerkleProofLib for MerkleProof; - using PcStackLib for PcStack; using StackFrameLib for StackFrameWindow; using ValueLib for Value; using ValueStackLib for ValueStack; @@ -51,8 +50,6 @@ contract OneStepProver0 is IOneStepProver { ty = ValueType.F32; } else if (opcode == Instructions.F64_CONST) { ty = ValueType.F64; - } else if (opcode == Instructions.PUSH_STACK_BOUNDARY) { - ty = ValueType.STACK_BOUNDARY; } else { revert("CONST_PUSH_INVALID_OPCODE"); } @@ -86,39 +83,6 @@ contract OneStepProver0 is IOneStepProver { } } - function executeBlock( - Machine memory mach, - Module memory, - Instruction calldata inst, - bytes calldata - ) internal pure { - uint32 targetPc = uint32(inst.argumentData); - require(targetPc == inst.argumentData, "BAD_BLOCK_PC"); - mach.blockStack.push(targetPc); - } - - function executeBranch( - Machine memory mach, - Module memory, - Instruction calldata, - bytes calldata - ) internal pure { - mach.functionPc = mach.blockStack.pop(); - } - - function executeBranchIf( - Machine memory mach, - Module memory, - Instruction calldata, - bytes calldata - ) internal pure { - uint32 cond = mach.valueStack.pop().assumeI32(); - if (cond != 0) { - // Jump to target - mach.functionPc = mach.blockStack.pop(); - } - } - function executeReturn( Machine memory mach, Module memory, @@ -419,27 +383,6 @@ contract OneStepProver0 is IOneStepProver { ); } - function executeEndBlock( - Machine memory mach, - Module memory, - Instruction calldata, - bytes calldata - ) internal pure { - mach.blockStack.pop(); - } - - function executeEndBlockIf( - Machine memory mach, - Module memory, - Instruction calldata, - bytes calldata - ) internal pure { - uint32 cond = mach.valueStack.peek().assumeI32(); - if (cond != 0) { - mach.blockStack.pop(); - } - } - function executeInitFrame( Machine memory mach, Module memory, @@ -476,20 +419,6 @@ contract OneStepProver0 is IOneStepProver { } } - function executeIsStackBoundary( - Machine memory mach, - Module memory, - Instruction calldata, - bytes calldata - ) internal pure { - Value memory val = mach.valueStack.pop(); - uint32 newContents = 0; - if (val.valueType == ValueType.STACK_BOUNDARY) { - newContents = 1; - } - mach.valueStack.push(ValueLib.newI32(newContents)); - } - function executeDup( Machine memory mach, Module memory, @@ -519,12 +448,6 @@ contract OneStepProver0 is IOneStepProver { impl = executeUnreachable; } else if (opcode == Instructions.NOP) { impl = executeNop; - } else if (opcode == Instructions.BLOCK) { - impl = executeBlock; - } else if (opcode == Instructions.BRANCH) { - impl = executeBranch; - } else if (opcode == Instructions.BRANCH_IF) { - impl = executeBranchIf; } else if (opcode == Instructions.RETURN) { impl = executeReturn; } else if (opcode == Instructions.CALL) { @@ -535,10 +458,6 @@ contract OneStepProver0 is IOneStepProver { impl = executeCallerModuleInternalCall; } else if (opcode == Instructions.CALL_INDIRECT) { impl = executeCallIndirect; - } else if (opcode == Instructions.END_BLOCK) { - impl = executeEndBlock; - } else if (opcode == Instructions.END_BLOCK_IF) { - impl = executeEndBlockIf; } else if (opcode == Instructions.ARBITRARY_JUMP) { impl = executeArbitraryJump; } else if (opcode == Instructions.ARBITRARY_JUMP_IF) { @@ -557,18 +476,13 @@ contract OneStepProver0 is IOneStepProver { impl = executeDrop; } else if (opcode == Instructions.SELECT) { impl = executeSelect; - } else if ( - (opcode >= Instructions.I32_CONST && opcode <= Instructions.F64_CONST) || - opcode == Instructions.PUSH_STACK_BOUNDARY - ) { + } else if (opcode >= Instructions.I32_CONST && opcode <= Instructions.F64_CONST) { impl = executeConstPush; } else if ( opcode == Instructions.MOVE_FROM_STACK_TO_INTERNAL || opcode == Instructions.MOVE_FROM_INTERNAL_TO_STACK ) { impl = executeMoveInternal; - } else if (opcode == Instructions.IS_STACK_BOUNDARY) { - impl = executeIsStackBoundary; } else if (opcode == Instructions.DUP) { impl = executeDup; } else { diff --git a/contracts/src/osp/OneStepProverMath.sol b/contracts/src/osp/OneStepProverMath.sol index 498d0ca66d..1a23347463 100644 --- a/contracts/src/osp/OneStepProverMath.sol +++ b/contracts/src/osp/OneStepProverMath.sol @@ -210,38 +210,38 @@ contract OneStepProverMath is IOneStepProver { uint64 a, uint64 b, uint16 opcodeOffset - ) internal pure returns (uint64) { + ) internal pure returns (uint64, bool) { unchecked { if (opcodeOffset == 0) { // add - return a + b; + return (a + b, false); } else if (opcodeOffset == 1) { // sub - return a - b; + return (a - b, false); } else if (opcodeOffset == 2) { // mul - return a * b; + return (a * b, false); } else if (opcodeOffset == 4) { // div_u if (b == 0) { - return 0; + return (0, true); } - return a / b; + return (a / b, false); } else if (opcodeOffset == 6) { // rem_u if (b == 0) { - return 0; + return (0, true); } - return a % b; + return (a % b, false); } else if (opcodeOffset == 7) { // and - return a & b; + return (a & b, false); } else if (opcodeOffset == 8) { // or - return a | b; + return (a | b, false); } else if (opcodeOffset == 9) { // xor - return a ^ b; + return (a ^ b, false); } else { revert("INVALID_GENERIC_BIN_OP"); } @@ -263,18 +263,18 @@ contract OneStepProverMath is IOneStepProver { unchecked { if (opcodeOffset == 3) { // div_s - if (b == 0) { - res = 0; - } else { - res = uint32(int32(a) / int32(b)); + if (b == 0 || (int32(a) == -2147483648 && int32(b) == -1)) { + mach.status = MachineStatus.ERRORED; + return; } + res = uint32(int32(a) / int32(b)); } else if (opcodeOffset == 5) { // rem_s if (b == 0) { - res = 0; - } else { - res = uint32(int32(a) % int32(b)); + mach.status = MachineStatus.ERRORED; + return; } + res = uint32(int32(a) % int32(b)); } else if (opcodeOffset == 10) { // shl res = a << (b % 32); @@ -291,7 +291,12 @@ contract OneStepProverMath is IOneStepProver { // rotr res = rotr32(a, b); } else { - res = uint32(genericBinOp(a, b, opcodeOffset)); + (uint64 computed, bool err) = genericBinOp(a, b, opcodeOffset); + if (err) { + mach.status = MachineStatus.ERRORED; + return; + } + res = uint32(computed); } } @@ -313,18 +318,18 @@ contract OneStepProverMath is IOneStepProver { unchecked { if (opcodeOffset == 3) { // div_s - if (b == 0) { - res = 0; - } else { - res = uint64(int64(a) / int64(b)); + if (b == 0 || (int64(a) == -9223372036854775808 && int64(b) == -1)) { + mach.status = MachineStatus.ERRORED; + return; } + res = uint64(int64(a) / int64(b)); } else if (opcodeOffset == 5) { // rem_s if (b == 0) { - res = 0; - } else { - res = uint64(int64(a) % int64(b)); + mach.status = MachineStatus.ERRORED; + return; } + res = uint64(int64(a) % int64(b)); } else if (opcodeOffset == 10) { // shl res = a << (b % 64); @@ -341,7 +346,12 @@ contract OneStepProverMath is IOneStepProver { // rotr res = rotr64(a, b); } else { - res = genericBinOp(a, b, opcodeOffset); + bool err; + (res, err) = genericBinOp(a, b, opcodeOffset); + if (err) { + mach.status = MachineStatus.ERRORED; + return; + } } } diff --git a/contracts/src/osp/OneStepProverMemory.sol b/contracts/src/osp/OneStepProverMemory.sol index 7d2254d1b0..0135ef67d5 100644 --- a/contracts/src/osp/OneStepProverMemory.sol +++ b/contracts/src/osp/OneStepProverMemory.sol @@ -271,10 +271,9 @@ contract OneStepProverMemory is IOneStepProver { uint32 oldPages = uint32(mod.moduleMemory.size / PAGE_SIZE); uint32 growingPages = mach.valueStack.pop().assumeI32(); // Safe as the input integers are too small to overflow a uint256 - uint256 newSize = (uint256(oldPages) + uint256(growingPages)) * PAGE_SIZE; - // Note: we require the size remain *below* 2^32, meaning the actual limit is 2^32-PAGE_SIZE - if (newSize < (1 << 32)) { - mod.moduleMemory.size = uint64(newSize); + uint256 newSize = uint256(oldPages) + uint256(growingPages); + if (newSize <= mod.moduleMemory.maxSize) { + mod.moduleMemory.size = uint64(newSize * PAGE_SIZE); mach.valueStack.push(ValueLib.newI32(oldPages)); } else { mach.valueStack.push(ValueLib.newI32(~uint32(0))); diff --git a/contracts/src/state/Deserialize.sol b/contracts/src/state/Deserialize.sol index 96f2da7159..8c98baa168 100644 --- a/contracts/src/state/Deserialize.sol +++ b/contracts/src/state/Deserialize.sol @@ -6,7 +6,6 @@ pragma solidity ^0.8.0; import "./Value.sol"; import "./ValueStack.sol"; -import "./PcStack.sol"; import "./Machine.sol"; import "./Instructions.sol"; import "./StackFrame.sol"; @@ -120,23 +119,6 @@ library Deserialize { stack = ValueStack({proved: ValueArray(proved), remainingHash: remainingHash}); } - function pcStack(bytes calldata proof, uint256 startOffset) - internal - pure - returns (PcStack memory stack, uint256 offset) - { - offset = startOffset; - bytes32 remainingHash; - (remainingHash, offset) = b32(proof, offset); - uint256 provedLength; - (provedLength, offset) = u256(proof, offset); - uint32[] memory proved = new uint32[](provedLength); - for (uint256 i = 0; i < proved.length; i++) { - (proved[i], offset) = u32(proof, offset); - } - stack = PcStack({proved: PcArray(proved), remainingHash: remainingHash}); - } - function instruction(bytes calldata proof, uint256 startOffset) internal pure @@ -199,10 +181,12 @@ library Deserialize { { offset = startOffset; uint64 size; + uint64 maxSize; bytes32 root; (size, offset) = u64(proof, offset); + (maxSize, offset) = u64(proof, offset); (root, offset) = b32(proof, offset); - mem = ModuleMemory({size: size, merkleRoot: root}); + mem = ModuleMemory({size: size, maxSize: maxSize, merkleRoot: root}); } function module(bytes calldata proof, uint256 startOffset) @@ -274,7 +258,6 @@ library Deserialize { } ValueStack memory values; ValueStack memory internalStack; - PcStack memory blocks; bytes32 globalStateHash; uint32 moduleIdx; uint32 functionIdx; @@ -283,7 +266,6 @@ library Deserialize { bytes32 modulesRoot; (values, offset) = valueStack(proof, offset); (internalStack, offset) = valueStack(proof, offset); - (blocks, offset) = pcStack(proof, offset); (frameStack, offset) = stackFrameWindow(proof, offset); (globalStateHash, offset) = b32(proof, offset); (moduleIdx, offset) = u32(proof, offset); @@ -294,7 +276,6 @@ library Deserialize { status: status, valueStack: values, internalStack: internalStack, - blockStack: blocks, frameStack: frameStack, globalStateHash: globalStateHash, moduleIdx: moduleIdx, diff --git a/contracts/src/state/Instructions.sol b/contracts/src/state/Instructions.sol index 625da095c6..196899c93f 100644 --- a/contracts/src/state/Instructions.sol +++ b/contracts/src/state/Instructions.sol @@ -12,9 +12,6 @@ struct Instruction { library Instructions { uint16 internal constant UNREACHABLE = 0x00; uint16 internal constant NOP = 0x01; - uint16 internal constant BLOCK = 0x02; - uint16 internal constant BRANCH = 0x0C; - uint16 internal constant BRANCH_IF = 0x0D; uint16 internal constant RETURN = 0x0F; uint16 internal constant CALL = 0x10; uint16 internal constant CALL_INDIRECT = 0x11; @@ -129,14 +126,11 @@ library Instructions { uint16 internal constant I64_EXTEND_16S = 0xC3; uint16 internal constant I64_EXTEND_32S = 0xC4; - uint16 internal constant END_BLOCK = 0x8000; - uint16 internal constant END_BLOCK_IF = 0x8001; uint16 internal constant INIT_FRAME = 0x8002; - uint16 internal constant ARBITRARY_JUMP_IF = 0x8003; - uint16 internal constant PUSH_STACK_BOUNDARY = 0x8004; + uint16 internal constant ARBITRARY_JUMP = 0x8003; + uint16 internal constant ARBITRARY_JUMP_IF = 0x8004; uint16 internal constant MOVE_FROM_STACK_TO_INTERNAL = 0x8005; uint16 internal constant MOVE_FROM_INTERNAL_TO_STACK = 0x8006; - uint16 internal constant IS_STACK_BOUNDARY = 0x8007; uint16 internal constant DUP = 0x8008; uint16 internal constant CROSS_MODULE_CALL = 0x8009; uint16 internal constant CALLER_MODULE_INTERNAL_CALL = 0x800A; @@ -150,8 +144,6 @@ library Instructions { uint16 internal constant READ_INBOX_MESSAGE = 0x8021; uint16 internal constant HALT_AND_SET_FINISHED = 0x8022; - uint16 internal constant ARBITRARY_JUMP = 0x8023; - uint256 internal constant INBOX_INDEX_SEQUENCER = 0; uint256 internal constant INBOX_INDEX_DELAYED = 1; diff --git a/contracts/src/state/Machine.sol b/contracts/src/state/Machine.sol index 1e5a1b4749..a7a5e9273d 100644 --- a/contracts/src/state/Machine.sol +++ b/contracts/src/state/Machine.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.0; import "./ValueStack.sol"; -import "./PcStack.sol"; import "./Instructions.sol"; import "./StackFrame.sol"; @@ -20,7 +19,6 @@ struct Machine { MachineStatus status; ValueStack valueStack; ValueStack internalStack; - PcStack blockStack; StackFrameWindow frameStack; bytes32 globalStateHash; uint32 moduleIdx; @@ -30,7 +28,6 @@ struct Machine { } library MachineLib { - using PcStackLib for PcStack; using StackFrameLib for StackFrameWindow; using ValueStackLib for ValueStack; @@ -43,7 +40,6 @@ library MachineLib { "Machine running:", mach.valueStack.hash(), mach.internalStack.hash(), - mach.blockStack.hash(), mach.frameStack.hash(), mach.globalStateHash, mach.moduleIdx, diff --git a/contracts/src/state/ModuleMemory.sol b/contracts/src/state/ModuleMemory.sol index 362da7fdcf..c1f0adb103 100644 --- a/contracts/src/state/ModuleMemory.sol +++ b/contracts/src/state/ModuleMemory.sol @@ -9,6 +9,7 @@ import "./Deserialize.sol"; struct ModuleMemory { uint64 size; + uint64 maxSize; bytes32 merkleRoot; } @@ -16,7 +17,7 @@ library ModuleMemoryLib { using MerkleProofLib for MerkleProof; function hash(ModuleMemory memory mem) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("Memory:", mem.size, mem.merkleRoot)); + return keccak256(abi.encodePacked("Memory:", mem.size, mem.maxSize, mem.merkleRoot)); } function proveLeaf( diff --git a/contracts/src/state/PcStack.sol b/contracts/src/state/PcStack.sol deleted file mode 100644 index f8d75a385b..0000000000 --- a/contracts/src/state/PcStack.sol +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2021-2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE -// SPDX-License-Identifier: BUSL-1.1 - -pragma solidity ^0.8.0; - -import "./PcArray.sol"; - -struct PcStack { - PcArray proved; - bytes32 remainingHash; -} - -library PcStackLib { - using PcArrayLib for PcArray; - - function hash(PcStack memory stack) internal pure returns (bytes32 h) { - h = stack.remainingHash; - uint256 len = stack.proved.length(); - for (uint256 i = 0; i < len; i++) { - h = keccak256(abi.encodePacked("Program counter stack:", stack.proved.get(i), h)); - } - } - - function pop(PcStack memory stack) internal pure returns (uint32) { - return stack.proved.pop(); - } - - function push(PcStack memory stack, uint32 val) internal pure { - return stack.proved.push(val); - } -} diff --git a/contracts/src/state/Value.sol b/contracts/src/state/Value.sol index 3784eab669..6e0a837b2b 100644 --- a/contracts/src/state/Value.sol +++ b/contracts/src/state/Value.sol @@ -11,8 +11,7 @@ enum ValueType { F64, REF_NULL, FUNC_REF, - INTERNAL_REF, - STACK_BOUNDARY + INTERNAL_REF } struct Value { @@ -26,7 +25,7 @@ library ValueLib { } function maxValueType() internal pure returns (ValueType) { - return ValueType.STACK_BOUNDARY; + return ValueType.INTERNAL_REF; } function assumeI32(Value memory val) internal pure returns (uint32) { diff --git a/contracts/test/prover/one-step-proof.ts b/contracts/test/prover/one-step-proof.ts index eb4d3d1703..91cf3065d7 100644 --- a/contracts/test/prover/one-step-proof.ts +++ b/contracts/test/prover/one-step-proof.ts @@ -20,8 +20,8 @@ async function sendTestMessages() { } describe("OneStepProof", function () { - const root = "./test/prover/proofs/"; - const dir = fs.readdirSync(root); + const arbProofsRoot = "./test/prover/proofs/"; + const specProofsRoot = "./test/prover/spec-proofs/"; before(async function () { await run("deploy", { "tags": "OneStepProofEntry" }); @@ -30,12 +30,22 @@ describe("OneStepProof", function () { await sendTestMessages(); }) - it("should deploy test harness", function() {}) - - for (let file of dir) { + const proofs = []; + for (let file of fs.readdirSync(arbProofsRoot)) { if (!file.endsWith(".json")) continue; + proofs.push([arbProofsRoot + file, file]); + } + if (fs.existsSync(specProofsRoot)) { + for (let file of fs.readdirSync(specProofsRoot)) { + if (!file.endsWith(".json")) continue; + proofs.push([specProofsRoot + file, file]); + } + } + + it("should deploy test harness with " + proofs.length + " proofs", function() {}) + + for (const [path, file] of proofs) { it("Should pass " + file + " proofs", async function () { - let path = root + file; let proofs = JSON.parse(fs.readFileSync(path).toString('utf8')); const osp = await ethers.getContract("OneStepProofEntry"); const seqInbox = await ethers.getContract("SequencerInboxStub"); @@ -50,7 +60,7 @@ describe("OneStepProof", function () { const inboxLimit = 1000000; const promise = osp.proveOneStep([inboxLimit, seqInbox.address, bridge.address], i, [...Buffer.from(proof.before, "hex")], [...Buffer.from(proof.proof, "hex")]) .catch((err: any) => { - console.error("Error executing proof " + i); + console.error("Error executing proof " + i, err.reason); throw err; }) .then((after: any) => assert.equal(after, "0x" + proof.after, "After state doesn't match after proof " + i)) @@ -65,6 +75,9 @@ describe("OneStepProof", function () { let stillWaiting = [] do { + if (promises.length == 0) { + break; + } const finished: any = await Promise.race(promises.map((p, k) => p.then((_: any) => k))); if (finished == promises.length - 1) { promises.pop()