diff --git a/Dockerfile b/Dockerfile index 7652527c4..bca9a7cc3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,6 +46,7 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --de COPY ./Makefile ./ COPY arbitrator/Cargo.* arbitrator/ COPY arbitrator/arbutil arbitrator/arbutil +COPY arbitrator/caller-env arbitrator/caller-env COPY arbitrator/prover arbitrator/prover COPY arbitrator/wasm-libraries arbitrator/wasm-libraries COPY arbitrator/tools/wasmer arbitrator/tools/wasmer @@ -92,6 +93,7 @@ RUN export DEBIAN_FRONTEND=noninteractive && \ COPY arbitrator/Cargo.* arbitrator/ COPY ./Makefile ./ COPY arbitrator/arbutil arbitrator/arbutil +COPY arbitrator/caller-env arbitrator/caller-env COPY arbitrator/prover arbitrator/prover COPY arbitrator/wasm-libraries arbitrator/wasm-libraries COPY arbitrator/jit arbitrator/jit @@ -113,6 +115,7 @@ RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - && \ apt-get install -y llvm-15-dev libclang-common-15-dev libpolly-15-dev COPY arbitrator/Cargo.* arbitrator/ COPY arbitrator/arbutil arbitrator/arbutil +COPY arbitrator/caller-env arbitrator/caller-env COPY arbitrator/prover/Cargo.toml arbitrator/prover/ COPY arbitrator/jit/Cargo.toml arbitrator/jit/ COPY arbitrator/stylus/Cargo.toml arbitrator/stylus/ @@ -153,6 +156,7 @@ COPY --from=wasm-libs-builder /workspace/arbitrator/prover/ arbitrator/prover/ COPY --from=wasm-libs-builder /workspace/arbitrator/tools/wasmer/ arbitrator/tools/wasmer/ COPY --from=wasm-libs-builder /workspace/arbitrator/wasm-libraries/ arbitrator/wasm-libraries/ COPY --from=wasm-libs-builder /workspace/arbitrator/arbutil arbitrator/arbutil +COPY --from=wasm-libs-builder /workspace/arbitrator/caller-env arbitrator/caller-env COPY --from=wasm-libs-builder /workspace/.make/ .make/ COPY ./Makefile ./ COPY ./arbitrator ./arbitrator diff --git a/Makefile b/Makefile index 096668f37..f99318399 100644 --- a/Makefile +++ b/Makefile @@ -74,7 +74,7 @@ WASI_SYSROOT?=/opt/wasi-sdk/wasi-sysroot arbitrator_wasm_lib_flags=$(patsubst %, -l %, $(arbitrator_wasm_libs)) -rust_arbutil_files = $(wildcard arbitrator/arbutil/src/*.* arbitrator/arbutil/src/*/*.* arbitrator/arbutil/*.toml) +rust_arbutil_files = $(wildcard arbitrator/arbutil/src/*.* arbitrator/arbutil/src/*/*.* arbitrator/arbutil/*.toml arbitrator/caller-env/src/*.* arbitrator/caller-env/src/*/*.* arbitrator/caller-env/*.toml) prover_direct_includes = $(patsubst %,$(output_latest)/%.wasm, forward forward_stub) prover_src = arbitrator/prover/src @@ -341,7 +341,7 @@ $(output_latest)/user_host.wasm: $(DEP_PREDICATE) $(wasm_lib_user_host) $(rust_p cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --package user-host install arbitrator/wasm-libraries/$(wasm32_wasi)/user_host.wasm $@ -$(output_latest)/program_exec.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,user-host) $(rust_prover_files) .make/machines +$(output_latest)/program_exec.wasm: $(DEP_PREDICATE) $(call wasm_lib_deps,program-exec) $(rust_prover_files) .make/machines cargo build --manifest-path arbitrator/wasm-libraries/Cargo.toml --release --target wasm32-wasi --package program-exec install arbitrator/wasm-libraries/$(wasm32_wasi)/program_exec.wasm $@ diff --git a/arbcompress/compress_wasm.go b/arbcompress/compress_wasm.go index 9f3ff9404..250b46705 100644 --- a/arbcompress/compress_wasm.go +++ b/arbcompress/compress_wasm.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE //go:build wasm @@ -13,16 +13,18 @@ import ( "github.com/offchainlabs/nitro/arbutil" ) -//go:wasmimport arbcompress brotliCompress +//go:wasmimport arbcompress brotli_compress func brotliCompress(inBuf unsafe.Pointer, inBufLen uint32, outBuf unsafe.Pointer, outBufLen unsafe.Pointer, level, windowSize uint32) BrotliStatus -//go:wasmimport arbcompress brotliDecompress +//go:wasmimport arbcompress brotli_decompress func brotliDecompress(inBuf unsafe.Pointer, inBufLen uint32, outBuf unsafe.Pointer, outBufLen unsafe.Pointer) BrotliStatus func Decompress(input []byte, maxSize int) ([]byte, error) { outBuf := make([]byte, maxSize) outLen := uint32(len(outBuf)) - status := brotliDecompress(arbutil.SliceToUnsafePointer(input), uint32(len(input)), arbutil.SliceToUnsafePointer(outBuf), unsafe.Pointer(&outLen)) + status := brotliDecompress( + arbutil.SliceToUnsafePointer(input), uint32(len(input)), arbutil.SliceToUnsafePointer(outBuf), unsafe.Pointer(&outLen), + ) if status != BrotliSuccess { return nil, fmt.Errorf("failed decompression") } @@ -33,7 +35,12 @@ func compressLevel(input []byte, level uint32) ([]byte, error) { maxOutSize := compressedBufferSizeFor(len(input)) outBuf := make([]byte, maxOutSize) outLen := uint32(len(outBuf)) - status := brotliCompress(arbutil.SliceToUnsafePointer(input), uint32(len(input)), arbutil.SliceToUnsafePointer(outBuf), unsafe.Pointer(&outLen), level, WINDOW_SIZE) + status := brotliCompress( + arbutil.SliceToUnsafePointer(input), uint32(len(input)), + arbutil.SliceToUnsafePointer(outBuf), unsafe.Pointer(&outLen), + level, + WINDOW_SIZE, + ) if status != BrotliSuccess { return nil, fmt.Errorf("failed compression") } diff --git a/arbitrator/Cargo.lock b/arbitrator/Cargo.lock index ca210f55b..d54403153 100644 --- a/arbitrator/Cargo.lock +++ b/arbitrator/Cargo.lock @@ -202,6 +202,16 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "caller-env" +version = "0.1.0" +dependencies = [ + "num_enum", + "rand", + "rand_pcg", + "wasmer", +] + [[package]] name = "cc" version = "1.0.73" @@ -779,6 +789,7 @@ name = "jit" version = "0.1.0" dependencies = [ "arbutil", + "caller-env", "eyre", "hex", "libc", @@ -1056,18 +1067,18 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -1632,6 +1643,7 @@ version = "0.1.0" dependencies = [ "arbutil", "bincode", + "caller-env", "derivative", "eyre", "fnv", @@ -1842,6 +1854,7 @@ name = "user-host-trait" version = "0.1.0" dependencies = [ "arbutil", + "caller-env", "eyre", "prover", ] diff --git a/arbitrator/arbutil/Cargo.toml b/arbitrator/arbutil/Cargo.toml index 47f14d60a..f9404ddb8 100644 --- a/arbitrator/arbutil/Cargo.toml +++ b/arbitrator/arbutil/Cargo.toml @@ -13,6 +13,3 @@ tiny-keccak = { version = "2.0.2", features = ["keccak"] } wasmparser.workspace = true serde = { version = "1.0.130", features = ["derive", "rc"] } num_enum = "0.7.1" - -[features] -wavm = [] diff --git a/arbitrator/arbutil/src/evm/api.rs b/arbitrator/arbutil/src/evm/api.rs index 26fc149a9..a7968fcc8 100644 --- a/arbitrator/arbutil/src/evm/api.rs +++ b/arbitrator/arbutil/src/evm/api.rs @@ -1,4 +1,4 @@ -// Copyright 2023, Offchain Labs, Inc. +// Copyright 2023-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE use crate::{evm::user::UserOutcomeKind, Bytes20, Bytes32}; @@ -48,17 +48,18 @@ pub enum EvmApiMethod { CaptureHostIO, } -// This offset is added to EvmApiMethod when sending a request -// in WASM - program done is also indicated by a "request", with the -// id below that offset, indicating program status +/// This offset is added to EvmApiMethod when sending a request +/// in WASM - program done is also indicated by a "request", with the +/// id below that offset, indicating program status pub const EVM_API_METHOD_REQ_OFFSET: u32 = 0x10000000; -// note: clone should not clone actual data, just the reader +/// Copies data from Go into Rust. +/// Note: clone should not clone actual data, just the reader. pub trait DataReader: Clone + Send + 'static { fn slice(&self) -> &[u8]; } -// simple implementation for DataReader, in case data comes from a Vec +/// Simple implementation for `DataReader`, in case data comes from a `Vec`. #[derive(Clone, Debug)] pub struct VecReader(Arc>); diff --git a/arbitrator/arbutil/src/evm/req.rs b/arbitrator/arbutil/src/evm/req.rs index 98b5cd09c..bafd0eb73 100644 --- a/arbitrator/arbutil/src/evm/req.rs +++ b/arbitrator/arbutil/src/evm/req.rs @@ -1,4 +1,4 @@ -// Copyright 2023, Offchain Labs, Inc. +// Copyright 2023-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE use crate::{ @@ -11,7 +11,7 @@ use crate::{ use eyre::{bail, eyre, Result}; pub trait RequestHandler: Send + 'static { - fn handle_request(&mut self, _req_type: EvmApiMethod, _req_data: &[u8]) -> (Vec, D, u64); + fn handle_request(&mut self, req_type: EvmApiMethod, req_data: &[u8]) -> (Vec, D, u64); } pub struct EvmApiRequestor> { @@ -33,6 +33,7 @@ impl> EvmApiRequestor { self.handler.handle_request(req_type, req_data) } + /// Call out to a contract. fn call_request( &mut self, call_type: EvmApiMethod, @@ -41,13 +42,14 @@ impl> EvmApiRequestor { gas: u64, value: Bytes32, ) -> (u32, u64, UserOutcomeKind) { - let mut request = vec![]; - request.extend(contract.as_slice()); - request.extend(value.as_slice()); - request.extend(&gas.to_be_bytes()); + let mut request = Vec::with_capacity(20 + 32 + 8 + input.len()); + request.extend(contract); + request.extend(value); + request.extend(gas.to_be_bytes()); request.extend(input); + let (res, data, cost) = self.handle_request(call_type, &request); - let status: UserOutcomeKind = res[0].try_into().unwrap(); + let status: UserOutcomeKind = res[0].try_into().expect("unknown outcome"); let data_len = data.slice().len() as u32; self.last_return_data = Some(data); (data_len, cost, status) @@ -65,24 +67,23 @@ impl> EvmApiRequestor { salt: Option, gas: u64, ) -> (Result, u32, u64) { - let mut request = vec![]; - request.extend(&gas.to_be_bytes()); - request.extend(endowment.as_slice()); + let mut request = Vec::with_capacity(8 + 2 * 32 + code.len()); + request.extend(gas.to_be_bytes()); + request.extend(endowment); if let Some(salt) = salt { - request.extend(salt.as_slice()); + request.extend(salt); } - request.extend(&code); + request.extend(code); let (mut res, data, cost) = self.handle_request(create_type, &request); if res.len() != 21 || res[0] == 0 { - if res.len() > 0 { - res.drain(0..=0); + if !res.is_empty() { + res.remove(0); } - let err_string = - String::from_utf8(res).unwrap_or(String::from("create_response_malformed")); + let err_string = String::from_utf8(res).unwrap_or("create_response_malformed".into()); return (Err(eyre!(err_string)), 0, cost); } - res.drain(0..=0); + res.remove(0); let address = res.try_into().unwrap(); let data_len = data.slice().len() as u32; self.last_return_data = Some(data); @@ -97,9 +98,9 @@ impl> EvmApi for EvmApiRequestor { } fn set_bytes32(&mut self, key: Bytes32, value: Bytes32) -> Result { - let mut request = vec![]; - request.extend(key.as_slice()); - request.extend(value.as_slice()); + let mut request = Vec::with_capacity(64); + request.extend(key); + request.extend(value); let (res, _, cost) = self.handle_request(EvmApiMethod::SetBytes32, &request); if res.len() != 1 { bail!("bad response from set_bytes32") @@ -170,18 +171,17 @@ impl> EvmApi for EvmApiRequestor { } fn get_return_data(&self) -> D { - self.last_return_data - .as_ref() - .expect("get return data when no data") - .clone() + self.last_return_data.clone().expect("missing return data") } fn emit_log(&mut self, data: Vec, topics: u32) -> Result<()> { - let mut request = topics.to_be_bytes().to_vec(); - request.extend(data.iter()); + let mut request = Vec::with_capacity(4 + data.len()); + request.extend(topics.to_be_bytes()); + request.extend(data); + let (res, _, _) = self.handle_request(EvmApiMethod::EmitLog, &request); if !res.is_empty() { - bail!(String::from_utf8(res).unwrap_or(String::from("malformed emit-log response"))) + bail!(String::from_utf8(res).unwrap_or("malformed emit-log response".into())) } Ok(()) } @@ -197,10 +197,11 @@ impl> EvmApi for EvmApiRequestor { return (data.clone(), 0); } } - let mut req: Vec = address.as_slice().into(); + let mut req = Vec::with_capacity(20 + 8); + req.extend(address); req.extend(gas_left.to_be_bytes()); - let (_, data, cost) = self.handle_request(EvmApiMethod::AccountCode, &req); + let (_, data, cost) = self.handle_request(EvmApiMethod::AccountCode, &req); self.last_code = Some((address, data.clone())); (data, cost) } @@ -211,8 +212,8 @@ impl> EvmApi for EvmApiRequestor { } fn add_pages(&mut self, pages: u16) -> u64 { - let (_, _, cost) = self.handle_request(EvmApiMethod::AddPages, &pages.to_be_bytes()); - cost + self.handle_request(EvmApiMethod::AddPages, &pages.to_be_bytes()) + .2 } fn capture_hostio( @@ -223,13 +224,12 @@ impl> EvmApi for EvmApiRequestor { start_ink: u64, end_ink: u64, ) { - let mut request = vec![]; - - request.extend(&start_ink.to_be_bytes()); - request.extend(&end_ink.to_be_bytes()); - request.extend(&(name.len() as u16).to_be_bytes()); - request.extend(&(args.len() as u16).to_be_bytes()); - request.extend(&(outs.len() as u16).to_be_bytes()); + let mut request = Vec::with_capacity(2 * 8 + 3 * 2 + name.len() + args.len() + outs.len()); + request.extend(start_ink.to_be_bytes()); + request.extend(end_ink.to_be_bytes()); + request.extend((name.len() as u16).to_be_bytes()); + request.extend((args.len() as u16).to_be_bytes()); + request.extend((outs.len() as u16).to_be_bytes()); request.extend(name.as_bytes()); request.extend(args); request.extend(outs); diff --git a/arbitrator/arbutil/src/lib.rs b/arbitrator/arbutil/src/lib.rs index 9fd2c0940..5315a7e1a 100644 --- a/arbitrator/arbutil/src/lib.rs +++ b/arbitrator/arbutil/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE /// cbindgen:ignore @@ -12,11 +12,9 @@ pub mod pricing; pub mod types; pub use color::{Color, DebugColor}; +use num_traits::Unsigned; pub use types::{Bytes20, Bytes32}; -#[cfg(feature = "wavm")] -pub mod wavm; - /// Puts an arbitrary type on the heap. /// Note: the type must be later freed or the value will be leaked. pub fn heapify(value: T) -> *mut T { @@ -24,7 +22,13 @@ pub fn heapify(value: T) -> *mut T { } /// Equivalent to &[start..offset], but truncates when out of bounds rather than panicking. -pub fn slice_with_runoff(data: &impl AsRef<[T]>, start: usize, end: usize) -> &[T] { +pub fn slice_with_runoff(data: &impl AsRef<[T]>, start: I, end: I) -> &[T] +where + I: TryInto + Unsigned, +{ + let start = start.try_into().unwrap_or(usize::MAX); + let end = end.try_into().unwrap_or(usize::MAX); + let data = data.as_ref(); if start >= data.len() || end < start { return &[]; @@ -35,12 +39,12 @@ pub fn slice_with_runoff(data: &impl AsRef<[T]>, start: usize, end: usize) -> #[test] fn test_limit_vec() { let testvec = vec![0, 1, 2, 3]; - assert_eq!(slice_with_runoff(&testvec, 4, 4), &testvec[0..0]); - assert_eq!(slice_with_runoff(&testvec, 1, 0), &testvec[0..0]); - assert_eq!(slice_with_runoff(&testvec, 0, 0), &testvec[0..0]); - assert_eq!(slice_with_runoff(&testvec, 0, 1), &testvec[0..1]); - assert_eq!(slice_with_runoff(&testvec, 1, 3), &testvec[1..3]); - assert_eq!(slice_with_runoff(&testvec, 0, 4), &testvec[0..4]); - assert_eq!(slice_with_runoff(&testvec, 0, 5), &testvec[0..4]); + assert_eq!(slice_with_runoff(&testvec, 4_u32, 4), &testvec[0..0]); + assert_eq!(slice_with_runoff(&testvec, 1_u16, 0), &testvec[0..0]); + assert_eq!(slice_with_runoff(&testvec, 0_u64, 0), &testvec[0..0]); + assert_eq!(slice_with_runoff(&testvec, 0_u32, 1), &testvec[0..1]); + assert_eq!(slice_with_runoff(&testvec, 1_u64, 3), &testvec[1..3]); + assert_eq!(slice_with_runoff(&testvec, 0_u16, 4), &testvec[0..4]); + assert_eq!(slice_with_runoff(&testvec, 0_u8, 5), &testvec[0..4]); assert_eq!(slice_with_runoff(&testvec, 2, usize::MAX), &testvec[2..4]); } diff --git a/arbitrator/arbutil/src/wavm.rs b/arbitrator/arbutil/src/wavm.rs deleted file mode 100644 index 5f7e06042..000000000 --- a/arbitrator/arbutil/src/wavm.rs +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2022-2023, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE - -use crate::{Bytes20, Bytes32}; - -/// WASM page size, or 2^16 bytes. -pub const PAGE_SIZE: u32 = 1 << 16; - -extern "C" { - fn wavm_caller_load8(ptr: usize) -> u8; - fn wavm_caller_load32(ptr: usize) -> u32; - fn wavm_caller_store8(ptr: usize, val: u8); - fn wavm_caller_store32(ptr: usize, val: u32); -} - -pub unsafe fn caller_load8(ptr: usize) -> u8 { - wavm_caller_load8(ptr) -} - -pub unsafe fn caller_load16(ptr: usize) -> u16 { - let lower = caller_load8(ptr); - let upper = caller_load8(ptr + 1); - lower as u16 | ((upper as u16) << 8) -} - -pub unsafe fn caller_load32(ptr: usize) -> u32 { - wavm_caller_load32(ptr) -} - -pub unsafe fn caller_store8(ptr: usize, val: u8) { - wavm_caller_store8(ptr, val) -} - -pub unsafe fn caller_store16(ptr: usize, val: u16) { - caller_store8(ptr, val as u8); - caller_store8(ptr + 1, (val >> 8) as u8); -} - -pub unsafe fn caller_store32(ptr: usize, val: u32) { - wavm_caller_store32(ptr, val) -} - -pub unsafe fn caller_load64(ptr: usize) -> u64 { - let lower = caller_load32(ptr); - let upper = caller_load32(ptr + 4); - lower as u64 | ((upper as u64) << 32) -} - -pub unsafe fn caller_store64(ptr: usize, val: u64) { - caller_store32(ptr, val as u32); - caller_store32(ptr + 4, (val >> 32) as u32); -} - -pub unsafe fn write_slice(src: &[u8], ptr: u64) { - let ptr = usize::try_from(ptr).expect("pointer doesn't fit in usize"); - write_slice_usize(src, ptr) -} - -pub unsafe fn write_slice_u32(src: &[u8], ptr: u32) { - write_slice_usize(src, ptr as usize) -} - -pub unsafe fn write_slice_usize(mut src: &[u8], mut ptr: usize) { - while src.len() >= 4 { - let mut arr = [0u8; 4]; - arr.copy_from_slice(&src[..4]); - caller_store32(ptr, u32::from_le_bytes(arr)); - ptr += 4; - src = &src[4..]; - } - for &byte in src { - caller_store8(ptr, byte); - ptr += 1; - } -} - -pub unsafe fn read_slice(ptr: u64, len: u64) -> Vec { - let ptr = usize::try_from(ptr).expect("pointer doesn't fit in usize"); - let len = usize::try_from(len).expect("length doesn't fit in usize"); - read_slice_usize(ptr, len) -} - -pub unsafe fn read_slice_u32(ptr: u32, len: u32) -> Vec { - read_slice_usize(ptr as usize, len as usize) -} - -pub unsafe fn read_slice_usize(mut ptr: usize, mut len: usize) -> Vec { - let mut data = Vec::with_capacity(len); - if len == 0 { - return data; - } - while len >= 4 { - data.extend(caller_load32(ptr).to_le_bytes()); - ptr += 4; - len -= 4; - } - for _ in 0..len { - data.push(caller_load8(ptr)); - ptr += 1; - } - data -} - -pub unsafe fn read_bytes20_usize(ptr: usize) -> Bytes20 { - let data = read_slice_usize(ptr, 20); - data.try_into().unwrap() -} - -pub unsafe fn read_bytes32_usize(ptr: usize) -> Bytes32 { - let data = read_slice_usize(ptr, 32); - data.try_into().unwrap() -} - -pub unsafe fn read_bytes20(ptr: u32) -> Bytes20 { - let data = read_slice_u32(ptr, 20); - data.try_into().unwrap() -} - -pub unsafe fn read_bytes32(ptr: u32) -> Bytes32 { - let data = read_slice_u32(ptr, 32); - data.try_into().unwrap() -} - -pub unsafe fn write_bytes20(ptr: u32, value: Bytes20) { - write_slice_u32(&value.0, ptr) -} - -pub unsafe fn write_bytes32(ptr: u32, value: Bytes32) { - write_slice_u32(&value.0, ptr) -} - -pub unsafe fn write_bytes20_usize(ptr: usize, value: Bytes20) { - write_slice_usize(&value.0, ptr) -} - -pub unsafe fn write_bytes32_usize(ptr: usize, value: Bytes32) { - write_slice_usize(&value.0, ptr) -} diff --git a/arbitrator/caller-env/Cargo.toml b/arbitrator/caller-env/Cargo.toml new file mode 100644 index 000000000..8b278f606 --- /dev/null +++ b/arbitrator/caller-env/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "caller-env" +version = "0.1.0" +edition = "2021" + +[dependencies] +num_enum = { version = "0.7.2", default-features = false } +rand_pcg = { version = "0.3.1", default-features = false } +rand = { version = "0.8.4", default-features = false } +wasmer = { path = "../tools/wasmer/lib/api", optional = true } + +[features] +static_caller = [] +wasmer_traits = ["dep:wasmer"] diff --git a/arbitrator/caller-env/src/brotli.rs b/arbitrator/caller-env/src/brotli.rs new file mode 100644 index 000000000..9f6f47e7e --- /dev/null +++ b/arbitrator/caller-env/src/brotli.rs @@ -0,0 +1,106 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +#![allow(clippy::too_many_arguments)] + +use crate::{ExecEnv, GuestPtr, MemAccess}; +use alloc::vec; +use num_enum::{IntoPrimitive, TryFromPrimitive}; + +#[derive(PartialEq, IntoPrimitive, TryFromPrimitive)] +#[repr(u32)] +pub enum BrotliStatus { + Failure, + Success, +} + +extern "C" { + pub fn BrotliDecoderDecompress( + encoded_size: usize, + encoded_buffer: *const u8, + decoded_size: *mut usize, + decoded_buffer: *mut u8, + ) -> BrotliStatus; + + pub fn BrotliEncoderCompress( + quality: u32, + lgwin: u32, + mode: u32, + input_size: usize, + input_buffer: *const u8, + encoded_size: *mut usize, + encoded_buffer: *mut u8, + ) -> BrotliStatus; +} + +const BROTLI_MODE_GENERIC: u32 = 0; + +/// Brotli decompresses a go slice. +/// +/// # Safety +/// +/// The output buffer must be sufficiently large enough. +pub fn brotli_decompress( + mem: &mut M, + _env: &mut E, + in_buf_ptr: GuestPtr, + in_buf_len: u32, + out_buf_ptr: GuestPtr, + out_len_ptr: GuestPtr, +) -> BrotliStatus { + let in_slice = mem.read_slice(in_buf_ptr, in_buf_len as usize); + let orig_output_len = mem.read_u32(out_len_ptr) as usize; + let mut output = vec![0; orig_output_len]; + let mut output_len = orig_output_len; + unsafe { + let res = BrotliDecoderDecompress( + in_buf_len as usize, + in_slice.as_ptr(), + &mut output_len, + output.as_mut_ptr(), + ); + if (res != BrotliStatus::Success) || (output_len > orig_output_len) { + return BrotliStatus::Failure; + } + } + mem.write_slice(out_buf_ptr, &output[..output_len]); + mem.write_u32(out_len_ptr, output_len as u32); + BrotliStatus::Success +} + +/// Brotli compresses a go slice +/// +/// The output buffer must be large enough. +pub fn brotli_compress( + mem: &mut M, + _env: &mut E, + in_buf_ptr: GuestPtr, + in_buf_len: u32, + out_buf_ptr: GuestPtr, + out_len_ptr: GuestPtr, + level: u32, + window_size: u32, +) -> BrotliStatus { + let in_slice = mem.read_slice(in_buf_ptr, in_buf_len as usize); + let orig_output_len = mem.read_u32(out_len_ptr) as usize; + let mut output = vec![0; orig_output_len]; + let mut output_len = orig_output_len; + + unsafe { + let res = BrotliEncoderCompress( + level, + window_size, + BROTLI_MODE_GENERIC, + in_buf_len as usize, + in_slice.as_ptr(), + &mut output_len, + output.as_mut_ptr(), + ); + if (res != BrotliStatus::Success) || (output_len > orig_output_len) { + return BrotliStatus::Failure; + } + } + mem.write_slice(out_buf_ptr, &output[..output_len]); + mem.write_u32(out_len_ptr, output_len as u32); + BrotliStatus::Success +} diff --git a/arbitrator/caller-env/src/guest_ptr.rs b/arbitrator/caller-env/src/guest_ptr.rs new file mode 100644 index 000000000..566d2d61d --- /dev/null +++ b/arbitrator/caller-env/src/guest_ptr.rs @@ -0,0 +1,43 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use core::ops::{Add, AddAssign, Deref}; + +/// Represents a pointer to a Guest WASM's memory. +#[derive(Clone, Copy, Eq, PartialEq)] +#[repr(transparent)] +pub struct GuestPtr(pub u32); + +impl Add for GuestPtr { + type Output = Self; + + fn add(self, rhs: u32) -> Self::Output { + Self(self.0 + rhs) + } +} + +impl AddAssign for GuestPtr { + fn add_assign(&mut self, rhs: u32) { + *self = *self + rhs; + } +} + +impl From for u32 { + fn from(value: GuestPtr) -> Self { + value.0 + } +} + +impl From for u64 { + fn from(value: GuestPtr) -> Self { + value.0.into() + } +} + +impl Deref for GuestPtr { + type Target = u32; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/arbitrator/caller-env/src/lib.rs b/arbitrator/caller-env/src/lib.rs new file mode 100644 index 000000000..39ee65e59 --- /dev/null +++ b/arbitrator/caller-env/src/lib.rs @@ -0,0 +1,66 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +#![no_std] + +extern crate alloc; + +use alloc::vec::Vec; +use rand_pcg::Pcg32; + +pub use brotli::BrotliStatus; +pub use guest_ptr::GuestPtr; +pub use wasip1_stub::Errno; + +#[cfg(feature = "static_caller")] +pub mod static_caller; + +#[cfg(feature = "wasmer_traits")] +pub mod wasmer_traits; + +pub mod brotli; +mod guest_ptr; +pub mod wasip1_stub; + +/// Initializes a deterministic, psuedo-random number generator with a fixed seed. +pub fn create_pcg() -> Pcg32 { + const PCG_INIT_STATE: u64 = 0xcafef00dd15ea5e5; + const PCG_INIT_STREAM: u64 = 0xa02bdbf7bb3c0a7; + Pcg32::new(PCG_INIT_STATE, PCG_INIT_STREAM) +} + +/// Access Guest memory. +pub trait MemAccess { + fn read_u8(&self, ptr: GuestPtr) -> u8; + + fn read_u16(&self, ptr: GuestPtr) -> u16; + + fn read_u32(&self, ptr: GuestPtr) -> u32; + + fn read_u64(&self, ptr: GuestPtr) -> u64; + + fn write_u8(&mut self, ptr: GuestPtr, x: u8); + + fn write_u16(&mut self, ptr: GuestPtr, x: u16); + + fn write_u32(&mut self, ptr: GuestPtr, x: u32); + + fn write_u64(&mut self, ptr: GuestPtr, x: u64); + + fn read_slice(&self, ptr: GuestPtr, len: usize) -> Vec; + + fn read_fixed(&self, ptr: GuestPtr) -> [u8; N]; + + fn write_slice(&mut self, ptr: GuestPtr, data: &[u8]); +} + +/// Update the Host environment. +pub trait ExecEnv { + fn advance_time(&mut self, ns: u64); + + fn get_time(&self) -> u64; + + fn next_rand_u32(&mut self) -> u32; + + fn print_string(&mut self, message: &[u8]); +} diff --git a/arbitrator/caller-env/src/static_caller.rs b/arbitrator/caller-env/src/static_caller.rs new file mode 100644 index 000000000..46a2a3f48 --- /dev/null +++ b/arbitrator/caller-env/src/static_caller.rs @@ -0,0 +1,119 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{create_pcg, ExecEnv, GuestPtr, MemAccess}; +use alloc::vec::Vec; +use rand::RngCore; +use rand_pcg::Pcg32; + +extern crate alloc; + +static mut TIME: u64 = 0; +static mut RNG: Option = None; + +pub struct StaticMem; +pub struct StaticExecEnv; + +pub static mut STATIC_MEM: StaticMem = StaticMem; +pub static mut STATIC_ENV: StaticExecEnv = StaticExecEnv; + +extern "C" { + fn wavm_caller_load8(ptr: GuestPtr) -> u8; + fn wavm_caller_load32(ptr: GuestPtr) -> u32; + fn wavm_caller_store8(ptr: GuestPtr, val: u8); + fn wavm_caller_store32(ptr: GuestPtr, val: u32); +} + +impl MemAccess for StaticMem { + fn read_u8(&self, ptr: GuestPtr) -> u8 { + unsafe { wavm_caller_load8(ptr) } + } + + fn read_u16(&self, ptr: GuestPtr) -> u16 { + let lsb = self.read_u8(ptr); + let msb = self.read_u8(ptr + 1); + (msb as u16) << 8 | (lsb as u16) + } + + fn read_u32(&self, ptr: GuestPtr) -> u32 { + unsafe { wavm_caller_load32(ptr) } + } + + fn read_u64(&self, ptr: GuestPtr) -> u64 { + let lsb = self.read_u32(ptr); + let msb = self.read_u32(ptr + 4); + (msb as u64) << 32 | (lsb as u64) + } + + fn write_u8(&mut self, ptr: GuestPtr, x: u8) { + unsafe { wavm_caller_store8(ptr, x) } + } + + fn write_u16(&mut self, ptr: GuestPtr, x: u16) { + self.write_u8(ptr, (x & 0xff) as u8); + self.write_u8(ptr + 1, ((x >> 8) & 0xff) as u8); + } + + fn write_u32(&mut self, ptr: GuestPtr, x: u32) { + unsafe { wavm_caller_store32(ptr, x) } + } + + fn write_u64(&mut self, ptr: GuestPtr, x: u64) { + self.write_u32(ptr, (x & 0xffffffff) as u32); + self.write_u32(ptr + 4, ((x >> 32) & 0xffffffff) as u32); + } + + fn read_slice(&self, mut ptr: GuestPtr, mut len: usize) -> Vec { + let mut data = Vec::with_capacity(len); + if len == 0 { + return data; + } + while len >= 4 { + data.extend(self.read_u32(ptr).to_le_bytes()); + ptr += 4; + len -= 4; + } + for _ in 0..len { + data.push(self.read_u8(ptr)); + ptr += 1; + } + data + } + + fn read_fixed(&self, ptr: GuestPtr) -> [u8; N] { + self.read_slice(ptr, N).try_into().unwrap() + } + + fn write_slice(&mut self, mut ptr: GuestPtr, mut src: &[u8]) { + while src.len() >= 4 { + let mut arr = [0; 4]; + arr.copy_from_slice(&src[..4]); + self.write_u32(ptr, u32::from_le_bytes(arr)); + ptr += 4; + src = &src[4..]; + } + for &byte in src { + self.write_u8(ptr, byte); + ptr += 1; + } + } +} + +impl ExecEnv for StaticExecEnv { + fn print_string(&mut self, _data: &[u8]) { + // printing is done by arbitrator machine host_call_hook + // capturing the fd_write call directly + } + + fn get_time(&self) -> u64 { + unsafe { TIME } + } + + fn advance_time(&mut self, delta: u64) { + unsafe { TIME += delta } + } + + fn next_rand_u32(&mut self) -> u32 { + unsafe { RNG.get_or_insert_with(create_pcg) }.next_u32() + } +} diff --git a/arbitrator/caller-env/src/wasip1_stub.rs b/arbitrator/caller-env/src/wasip1_stub.rs new file mode 100644 index 000000000..75fc03e73 --- /dev/null +++ b/arbitrator/caller-env/src/wasip1_stub.rs @@ -0,0 +1,406 @@ +// Copyright 2021-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +//! A stub impl of [WASI Preview 1][Wasi] for proving fraud. +//! +//! [Wasi]: https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md + +#![allow(clippy::too_many_arguments)] + +use crate::{ExecEnv, GuestPtr, MemAccess}; + +#[repr(transparent)] +pub struct Errno(pub(crate) u16); + +pub const ERRNO_SUCCESS: Errno = Errno(0); +pub const ERRNO_BADF: Errno = Errno(8); +pub const ERRNO_INVAL: Errno = Errno(28); + +/// Writes the number and total size of args passed by the OS. +/// Note that this currently consists of just the program name `bin`. +pub fn args_sizes_get( + mem: &mut M, + _: &mut E, + length_ptr: GuestPtr, + data_size_ptr: GuestPtr, +) -> Errno { + mem.write_u32(length_ptr, 1); + mem.write_u32(data_size_ptr, 4); + ERRNO_SUCCESS +} + +/// Writes the args passed by the OS. +/// Note that this currently consists of just the program name `bin`. +pub fn args_get( + mem: &mut M, + _: &mut E, + argv_buf: GuestPtr, + data_buf: GuestPtr, +) -> Errno { + mem.write_u32(argv_buf, data_buf.into()); + mem.write_u32(data_buf, 0x6E6962); // "bin\0" + ERRNO_SUCCESS +} + +/// Writes the number and total size of OS environment variables. +/// Note that none exist in Nitro. +pub fn environ_sizes_get( + mem: &mut M, + _env: &mut E, + length_ptr: GuestPtr, + data_size_ptr: GuestPtr, +) -> Errno { + mem.write_u32(length_ptr, 0); + mem.write_u32(data_size_ptr, 0); + ERRNO_SUCCESS +} + +/// Writes the number and total size of OS environment variables. +/// Note that none exist in Nitro. +pub fn environ_get( + _: &mut M, + _: &mut E, + _: GuestPtr, + _: GuestPtr, +) -> Errno { + ERRNO_SUCCESS +} + +/// Writes to the given file descriptor. +/// Note that we only support stdout and stderr. +pub fn fd_write( + mem: &mut M, + env: &mut E, + fd: u32, + iovecs_ptr: GuestPtr, + iovecs_len: u32, + ret_ptr: GuestPtr, +) -> Errno { + if fd != 1 && fd != 2 { + return ERRNO_BADF; + } + let mut size = 0; + for i in 0..iovecs_len { + let ptr = iovecs_ptr + i * 8; + let len = mem.read_u32(ptr + 4); + let data = mem.read_slice(ptr, len as usize); + env.print_string(&data); + size += len; + } + mem.write_u32(ret_ptr, size); + ERRNO_SUCCESS +} + +/// Closes the given file descriptor. Unsupported. +pub fn fd_close(_: &mut M, _: &mut E, _: u32) -> Errno { + ERRNO_BADF +} + +/// Reads from the given file descriptor. Unsupported. +pub fn fd_read( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Reads the contents of a directory. Unsupported. +pub fn fd_readdir( + _: &mut M, + _: &mut E, + _fd: u32, + _: u32, + _: u32, + _: u64, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Syncs a file to disk. Unsupported. +pub fn fd_sync(_: &mut M, _: &mut E, _: u32) -> Errno { + ERRNO_SUCCESS +} + +/// Move within a file. Unsupported. +pub fn fd_seek( + _: &mut M, + _: &mut E, + _fd: u32, + _offset: u64, + _whence: u8, + _filesize: u32, +) -> Errno { + ERRNO_BADF +} + +/// Syncs file contents to disk. Unsupported. +pub fn fd_datasync(_: &mut M, _: &mut E, _fd: u32) -> Errno { + ERRNO_BADF +} + +/// Retrieves attributes about a file descriptor. Unsupported. +pub fn fd_fdstat_get(_: &mut M, _: &mut E, _: u32, _: u32) -> Errno { + ERRNO_INVAL +} + +/// Sets the attributes of a file descriptor. Unsupported. +pub fn fd_fdstat_set_flags( + _: &mut M, + _: &mut E, + _: u32, + _: u32, +) -> Errno { + ERRNO_INVAL +} + +/// Opens the file or directory at the given path. Unsupported. +pub fn path_open( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, + _: u32, + _: u32, + _: u64, + _: u64, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Creates a directory. Unsupported. +pub fn path_create_directory( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Unlinks a directory. Unsupported. +pub fn path_remove_directory( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Resolves a symbolic link. Unsupported. +pub fn path_readlink( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Moves a file. Unsupported. +pub fn path_rename( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Retrieves info about an open file. Unsupported. +pub fn path_filestat_get( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Unlinks the file at the given path. Unsupported. +pub fn path_unlink_file( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Retrieves info about a file. Unsupported. +pub fn fd_prestat_get(_: &mut M, _: &mut E, _: u32, _: u32) -> Errno { + ERRNO_BADF +} + +/// Retrieves info about a directory. Unsupported. +pub fn fd_prestat_dir_name( + _: &mut M, + _: &mut E, + _: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Retrieves info about a file. Unsupported. +pub fn fd_filestat_get( + _: &mut M, + _: &mut E, + _fd: u32, + _filestat: u32, +) -> Errno { + ERRNO_BADF +} + +/// Sets the size of an open file. Unsupported. +pub fn fd_filestat_set_size( + _: &mut M, + _: &mut E, + _fd: u32, + _: u64, +) -> Errno { + ERRNO_BADF +} + +/// Peaks within a descriptor without modifying its state. Unsupported. +pub fn fd_pread( + _: &mut M, + _: &mut E, + _fd: u32, + _: u32, + _: u32, + _: u64, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Writes to a descriptor without modifying the current offset. Unsupported. +pub fn fd_pwrite( + _: &mut M, + _: &mut E, + _fd: u32, + _: u32, + _: u32, + _: u64, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Accepts a new connection. Unsupported. +pub fn sock_accept( + _: &mut M, + _: &mut E, + _fd: u32, + _: u32, + _: u32, +) -> Errno { + ERRNO_BADF +} + +/// Shuts down a socket. Unsupported. +pub fn sock_shutdown(_: &mut M, _: &mut E, _: u32, _: u32) -> Errno { + ERRNO_BADF +} + +/// Yields execution to the OS scheduler. Effectively does nothing in Nitro due to the lack of threads. +pub fn sched_yield(_: &mut M, _: &mut E) -> Errno { + ERRNO_SUCCESS +} + +/// 10ms in ns +static TIME_INTERVAL: u64 = 10_000_000; + +/// Retrieves the time in ns of the given clock. +/// Note that in Nitro, all clocks point to the same deterministic counter that advances 10ms whenever +/// this function is called. +pub fn clock_time_get( + mem: &mut M, + env: &mut E, + _clock_id: u32, + _precision: u64, + time_ptr: GuestPtr, +) -> Errno { + env.advance_time(TIME_INTERVAL); + mem.write_u64(time_ptr, env.get_time()); + ERRNO_SUCCESS +} + +/// Fills a slice with psuedo-random bytes. +/// Note that in Nitro, the bytes are deterministically generated from a common seed. +pub fn random_get( + mem: &mut M, + env: &mut E, + mut buf: GuestPtr, + mut len: u32, +) -> Errno { + while len >= 4 { + let next_rand = env.next_rand_u32(); + mem.write_u32(buf, next_rand); + buf += 4; + len -= 4; + } + if len > 0 { + let mut rem = env.next_rand_u32(); + for _ in 0..len { + mem.write_u8(buf, rem as u8); + buf += 1; + rem >>= 8; + } + } + ERRNO_SUCCESS +} + +/// Poll for events. +/// Note that we always simulate a timeout and skip all others. +pub fn poll_oneoff( + mem: &mut M, + env: &mut E, + in_subs: GuestPtr, + out_evt: GuestPtr, + num_subscriptions: u32, + num_events_ptr: GuestPtr, +) -> Errno { + // simulate the passage of time each poll request + env.advance_time(TIME_INTERVAL); + + const SUBSCRIPTION_SIZE: u32 = 48; // user data + 40-byte union + for index in 0..num_subscriptions { + let subs_base = in_subs + (SUBSCRIPTION_SIZE * index); + let subs_type = mem.read_u32(subs_base + 8); + if subs_type != 0 { + // not a clock subscription type + continue; + } + let user_data = mem.read_u32(subs_base); + mem.write_u32(out_evt, user_data); + mem.write_u32(out_evt + 8, subs_type); + mem.write_u32(num_events_ptr, 1); + return ERRNO_SUCCESS; + } + ERRNO_INVAL +} diff --git a/arbitrator/caller-env/src/wasmer_traits.rs b/arbitrator/caller-env/src/wasmer_traits.rs new file mode 100644 index 000000000..5cc6f9e67 --- /dev/null +++ b/arbitrator/caller-env/src/wasmer_traits.rs @@ -0,0 +1,47 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{BrotliStatus, Errno, GuestPtr}; +use wasmer::{FromToNativeWasmType, WasmPtr}; + +unsafe impl FromToNativeWasmType for GuestPtr { + type Native = i32; + + fn from_native(native: i32) -> Self { + Self(u32::from_native(native)) + } + + fn to_native(self) -> i32 { + self.0.to_native() + } +} + +unsafe impl FromToNativeWasmType for Errno { + type Native = i32; + + fn from_native(native: i32) -> Self { + Self(u16::from_native(native)) + } + + fn to_native(self) -> i32 { + self.0.to_native() + } +} + +unsafe impl FromToNativeWasmType for BrotliStatus { + type Native = i32; + + fn from_native(native: i32) -> Self { + Self::try_from(u32::from_native(native)).expect("unknown brotli status") + } + + fn to_native(self) -> i32 { + (self as u32).to_native() + } +} + +impl From for WasmPtr { + fn from(value: GuestPtr) -> Self { + WasmPtr::new(value.0) + } +} diff --git a/arbitrator/jit/Cargo.toml b/arbitrator/jit/Cargo.toml index 3c94f3ac9..58861e873 100644 --- a/arbitrator/jit/Cargo.toml +++ b/arbitrator/jit/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] arbutil = { path = "../arbutil/" } +caller-env = { path = "../caller-env/", features = ["wasmer_traits"] } prover = { path = "../prover/", default-features = false, features = ["native"] } stylus = { path = "../stylus/", default-features = false } wasmer = { path = "../tools/wasmer/lib/api/" } diff --git a/arbitrator/jit/src/arbcompress.rs b/arbitrator/jit/src/arbcompress.rs index b85f0611b..6815a480e 100644 --- a/arbitrator/jit/src/arbcompress.rs +++ b/arbitrator/jit/src/arbcompress.rs @@ -1,105 +1,38 @@ -// Copyright 2022, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE +use crate::caller_env::{JitEnv, JitExecEnv}; use crate::machine::Escape; -use crate::{callerenv::CallerEnv, machine::WasmEnvMut}; - -extern "C" { - pub fn BrotliDecoderDecompress( - encoded_size: usize, - encoded_buffer: *const u8, - decoded_size: *mut usize, - decoded_buffer: *mut u8, - ) -> BrotliStatus; - - pub fn BrotliEncoderCompress( - quality: u32, - lgwin: u32, - mode: u32, - input_size: usize, - input_buffer: *const u8, - encoded_size: *mut usize, - encoded_buffer: *mut u8, - ) -> BrotliStatus; +use crate::machine::WasmEnvMut; +use caller_env::brotli::BrotliStatus; +use caller_env::{self, GuestPtr}; + +macro_rules! wrap { + ($(fn $func_name:ident ($($arg_name:ident : $arg_type:ty),* ) -> $return_type:ty);*) => { + $( + pub fn $func_name(mut src: WasmEnvMut, $($arg_name : $arg_type),*) -> Result<$return_type, Escape> { + let (mut mem, wenv) = src.jit_env(); + + Ok(caller_env::brotli::$func_name(&mut mem, &mut JitExecEnv { wenv }, $($arg_name),*)) + } + )* + }; } -const BROTLI_MODE_GENERIC: u32 = 0; - -#[derive(PartialEq)] -#[repr(u32)] -pub enum BrotliStatus { - Failure, - Success, -} - -type Uptr = u32; - -/// Brotli decompresses a go slice -/// -/// # Safety -/// -/// The output buffer must be sufficiently large enough. -pub fn brotli_decompress( - mut env: WasmEnvMut, - in_buf_ptr: Uptr, - in_buf_len: u32, - out_buf_ptr: Uptr, - out_len_ptr: Uptr, -) -> Result { - let mut caller_env = CallerEnv::new(&mut env); - let in_slice = caller_env.caller_read_slice(in_buf_ptr, in_buf_len); - let orig_output_len = caller_env.caller_read_u32(out_len_ptr) as usize; - let mut output = vec![0u8; orig_output_len as usize]; - let mut output_len = orig_output_len; - unsafe { - let res = BrotliDecoderDecompress( - in_buf_len as usize, - in_slice.as_ptr(), - &mut output_len, - output.as_mut_ptr(), - ); - if (res != BrotliStatus::Success) || (output_len > orig_output_len) { - return Ok(0); - } - } - caller_env.caller_write_slice(out_buf_ptr, &output[..output_len]); - caller_env.caller_write_u32(out_len_ptr, output_len as u32); - Ok(1) -} - -/// Brotli compresses a go slice -/// -/// The output buffer must be sufficiently large enough. -pub fn brotli_compress( - mut env: WasmEnvMut, - in_buf_ptr: Uptr, - in_buf_len: u32, - out_buf_ptr: Uptr, - out_len_ptr: Uptr, - level: u32, - window_size: u32, -) -> Result { - let mut caller_env = CallerEnv::new(&mut env); - let in_slice = caller_env.caller_read_slice(in_buf_ptr, in_buf_len); - let orig_output_len = caller_env.caller_read_u32(out_len_ptr) as usize; - let mut output = vec![0u8; orig_output_len]; - let mut output_len = orig_output_len; +wrap! { + fn brotli_decompress( + in_buf_ptr: GuestPtr, + in_buf_len: u32, + out_buf_ptr: GuestPtr, + out_len_ptr: GuestPtr + ) -> BrotliStatus; - unsafe { - let res = BrotliEncoderCompress( - level, - window_size, - BROTLI_MODE_GENERIC, - in_buf_len as usize, - in_slice.as_ptr(), - &mut output_len, - output.as_mut_ptr(), - ); - if (res != BrotliStatus::Success) || (output_len > orig_output_len) { - return Ok(0); - } - } - caller_env.caller_write_slice(out_buf_ptr, &output[..output_len]); - caller_env.caller_write_u32(out_len_ptr, output_len as u32); - Ok(1) + fn brotli_compress( + in_buf_ptr: GuestPtr, + in_buf_len: u32, + out_buf_ptr: GuestPtr, + out_len_ptr: GuestPtr, + level: u32, + window_size: u32 + ) -> BrotliStatus } diff --git a/arbitrator/jit/src/caller_env.rs b/arbitrator/jit/src/caller_env.rs new file mode 100644 index 000000000..ac1b8d70d --- /dev/null +++ b/arbitrator/jit/src/caller_env.rs @@ -0,0 +1,185 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use crate::machine::{WasmEnv, WasmEnvMut}; +use arbutil::{Bytes20, Bytes32}; +use caller_env::{ExecEnv, GuestPtr, MemAccess}; +use rand::RngCore; +use rand_pcg::Pcg32; +use std::{ + cmp::Ordering, + collections::{BTreeSet, BinaryHeap}, + fmt::Debug, + mem::{self, MaybeUninit}, +}; +use wasmer::{Memory, MemoryView, StoreMut, WasmPtr}; + +pub struct JitMemAccess<'s> { + pub memory: Memory, + pub store: StoreMut<'s>, +} + +pub struct JitExecEnv<'s> { + pub wenv: &'s mut WasmEnv, +} + +pub(crate) trait JitEnv<'a> { + fn jit_env(&mut self) -> (JitMemAccess<'_>, &mut WasmEnv); +} + +impl<'a> JitEnv<'a> for WasmEnvMut<'a> { + fn jit_env(&mut self) -> (JitMemAccess<'_>, &mut WasmEnv) { + let memory = self.data().memory.clone().unwrap(); + let (wenv, store) = self.data_and_store_mut(); + (JitMemAccess { memory, store }, wenv) + } +} + +impl<'s> JitMemAccess<'s> { + fn view(&self) -> MemoryView { + self.memory.view(&self.store) + } + + pub fn write_bytes32(&mut self, ptr: GuestPtr, val: Bytes32) { + self.write_slice(ptr, val.as_slice()) + } + + pub fn read_bytes20(&mut self, ptr: GuestPtr) -> Bytes20 { + self.read_fixed(ptr).into() + } + + pub fn read_bytes32(&mut self, ptr: GuestPtr) -> Bytes32 { + self.read_fixed(ptr).into() + } +} + +impl MemAccess for JitMemAccess<'_> { + fn read_u8(&self, ptr: GuestPtr) -> u8 { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).read().unwrap() + } + + fn read_u16(&self, ptr: GuestPtr) -> u16 { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).read().unwrap() + } + + fn read_u32(&self, ptr: GuestPtr) -> u32 { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).read().unwrap() + } + + fn read_u64(&self, ptr: GuestPtr) -> u64 { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).read().unwrap() + } + + fn write_u8(&mut self, ptr: GuestPtr, x: u8) { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).write(x).unwrap(); + } + + fn write_u16(&mut self, ptr: GuestPtr, x: u16) { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).write(x).unwrap(); + } + + fn write_u32(&mut self, ptr: GuestPtr, x: u32) { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).write(x).unwrap(); + } + + fn write_u64(&mut self, ptr: GuestPtr, x: u64) { + let ptr: WasmPtr = ptr.into(); + ptr.deref(&self.view()).write(x).unwrap(); + } + + fn read_slice(&self, ptr: GuestPtr, len: usize) -> Vec { + let mut data: Vec> = Vec::with_capacity(len); + // SAFETY: read_uninit fills all available space + unsafe { + data.set_len(len); + self.view() + .read_uninit(ptr.into(), &mut data) + .expect("bad read"); + mem::transmute(data) + } + } + + fn read_fixed(&self, ptr: GuestPtr) -> [u8; N] { + self.read_slice(ptr, N).try_into().unwrap() + } + + fn write_slice(&mut self, ptr: GuestPtr, src: &[u8]) { + self.view().write(ptr.into(), src).unwrap(); + } +} + +impl ExecEnv for JitExecEnv<'_> { + fn advance_time(&mut self, ns: u64) { + self.wenv.go_state.time += ns; + } + + fn get_time(&self) -> u64 { + self.wenv.go_state.time + } + + fn next_rand_u32(&mut self) -> u32 { + self.wenv.go_state.rng.next_u32() + } + + fn print_string(&mut self, bytes: &[u8]) { + match String::from_utf8(bytes.to_vec()) { + Ok(s) => eprintln!("JIT: WASM says: {s}"), + Err(e) => { + let bytes = e.as_bytes(); + eprintln!("Go string {} is not valid utf8: {e:?}", hex::encode(bytes)); + } + } + } +} + +pub struct GoRuntimeState { + /// An increasing clock used when Go asks for time, measured in nanoseconds. + pub time: u64, + /// Deterministic source of random data. + pub rng: Pcg32, +} + +impl Default for GoRuntimeState { + fn default() -> Self { + Self { + time: 0, + rng: caller_env::create_pcg(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TimeoutInfo { + pub time: u64, + pub id: u32, +} + +impl Ord for TimeoutInfo { + fn cmp(&self, other: &Self) -> Ordering { + other + .time + .cmp(&self.time) + .then_with(|| other.id.cmp(&self.id)) + } +} + +impl PartialOrd for TimeoutInfo { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[derive(Default, Debug)] +pub struct TimeoutState { + /// Contains tuples of (time, id) + pub times: BinaryHeap, + pub pending_ids: BTreeSet, + pub next_id: u32, +} diff --git a/arbitrator/jit/src/callerenv.rs b/arbitrator/jit/src/callerenv.rs deleted file mode 100644 index 9feb0b9dc..000000000 --- a/arbitrator/jit/src/callerenv.rs +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2022, Offchain Labs, Inc. -// For license information, see https://github.com/nitro/blob/master/LICENSE - -#![allow(clippy::useless_transmute)] - -use crate::machine::{WasmEnv, WasmEnvMut}; -use arbutil::{Bytes20, Bytes32}; -use rand_pcg::Pcg32; -use std::{ - collections::{BTreeSet, BinaryHeap}, - fmt::Debug, -}; -use wasmer::{Memory, MemoryView, StoreMut, WasmPtr}; - -pub struct CallerEnv<'s> { - pub memory: Memory, - pub store: StoreMut<'s>, - pub wenv: &'s mut WasmEnv, -} - -#[allow(dead_code)] -impl<'s> CallerEnv<'s> { - pub fn new(env: &'s mut WasmEnvMut) -> Self { - let memory = env.data().memory.clone().unwrap(); - let (data, store) = env.data_and_store_mut(); - Self { - memory, - store, - wenv: data, - } - } - - fn view(&self) -> MemoryView { - self.memory.view(&self.store) - } - - /// Returns the memory size, in bytes. - /// note: wasmer measures memory in 65536-byte pages. - pub fn memory_size(&self) -> u64 { - self.view().size().0 as u64 * 65536 - } - - pub fn caller_read_u8(&self, ptr: u32) -> u8 { - let ptr: WasmPtr = WasmPtr::new(ptr); - ptr.deref(&self.view()).read().unwrap() - } - - pub fn caller_read_u16(&self, ptr: u32) -> u16 { - let ptr: WasmPtr = WasmPtr::new(ptr); - ptr.deref(&self.view()).read().unwrap() - } - - pub fn caller_read_u32(&self, ptr: u32) -> u32 { - let ptr: WasmPtr = WasmPtr::new(ptr); - ptr.deref(&self.view()).read().unwrap() - } - - pub fn caller_read_u64(&self, ptr: u32) -> u64 { - let ptr: WasmPtr = WasmPtr::new(ptr); - ptr.deref(&self.view()).read().unwrap() - } - - pub fn caller_write_u8(&mut self, ptr: u32, x: u8) -> &mut Self { - let ptr: WasmPtr = WasmPtr::new(ptr); - ptr.deref(&self.view()).write(x).unwrap(); - self - } - - pub fn caller_write_u16(&mut self, ptr: u32, x: u16) -> &mut Self { - let ptr: WasmPtr = WasmPtr::new(ptr); - ptr.deref(&self.view()).write(x).unwrap(); - self - } - - pub fn caller_write_u32(&mut self, ptr: u32, x: u32) -> &mut Self { - let ptr: WasmPtr = WasmPtr::new(ptr); - ptr.deref(&self.view()).write(x).unwrap(); - self - } - - pub fn caller_write_u64(&mut self, ptr: u32, x: u64) -> &mut Self { - let ptr: WasmPtr = WasmPtr::new(ptr); - ptr.deref(&self.view()).write(x).unwrap(); - self - } - - pub fn caller_read_slice(&self, ptr: u32, len: u32) -> Vec { - u32::try_from(ptr).expect("Go pointer not a u32"); // kept for consistency - let len = u32::try_from(len).expect("length isn't a u32") as usize; - let mut data = vec![0; len]; - self.view() - .read(ptr.into(), &mut data) - .expect("failed to read"); - data - } - - pub fn caller_write_slice>(&self, ptr: T, src: &[u8]) - where - T::Error: Debug, - { - let ptr: u32 = ptr.try_into().expect("Go pointer not a u32"); - self.view().write(ptr.into(), src).unwrap(); - } - - pub fn caller_write_bytes20(&mut self, ptr: u32, val: Bytes20) { - self.caller_write_slice(ptr, val.as_slice()) - } - - pub fn caller_write_bytes32(&mut self, ptr: u32, val: Bytes32) { - self.caller_write_slice(ptr, val.as_slice()) - } - - pub fn caller_read_bytes20(&mut self, ptr: u32) -> Bytes20 { - self.caller_read_slice(ptr, 20).try_into().unwrap() - } - - pub fn caller_read_bytes32(&mut self, ptr: u32) -> Bytes32 { - self.caller_read_slice(ptr, 32).try_into().unwrap() - } - - pub fn caller_read_string(&mut self, ptr: u32, len: u32) -> String { - let bytes = self.caller_read_slice(ptr, len); - match String::from_utf8(bytes) { - Ok(s) => s, - Err(e) => { - let bytes = e.as_bytes(); - eprintln!("Go string {} is not valid utf8: {e:?}", hex::encode(bytes)); - String::from_utf8_lossy(bytes).into_owned() - } - } - } -} - -pub struct GoRuntimeState { - /// An increasing clock used when Go asks for time, measured in nanoseconds - pub time: u64, - /// The amount of time advanced each check. Currently 10 milliseconds - pub time_interval: u64, - /// Deterministic source of random data - pub rng: Pcg32, -} - -impl Default for GoRuntimeState { - fn default() -> Self { - Self { - time: 0, - time_interval: 10_000_000, - rng: Pcg32::new(0xcafef00dd15ea5e5, 0xa02bdbf7bb3c0a7), - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct TimeoutInfo { - pub time: u64, - pub id: u32, -} - -impl Ord for TimeoutInfo { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - other - .time - .cmp(&self.time) - .then_with(|| other.id.cmp(&self.id)) - } -} - -impl PartialOrd for TimeoutInfo { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -#[derive(Default, Debug)] -pub struct TimeoutState { - /// Contains tuples of (time, id) - pub times: BinaryHeap, - pub pending_ids: BTreeSet, - pub next_id: u32, -} diff --git a/arbitrator/jit/src/machine.rs b/arbitrator/jit/src/machine.rs index 903671913..a15c0256e 100644 --- a/arbitrator/jit/src/machine.rs +++ b/arbitrator/jit/src/machine.rs @@ -1,21 +1,13 @@ -// Copyright 2022-2023, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE use crate::{ - arbcompress, callerenv::GoRuntimeState, program, socket, stylus_backend::CothreadHandler, + arbcompress, caller_env::GoRuntimeState, program, socket, stylus_backend::CothreadHandler, wasip1_stub, wavmio, Opts, }; -// runtime, socket, syscall, user use arbutil::{Bytes32, Color}; use eyre::{bail, ErrReport, Result, WrapErr}; use sha3::{Digest, Keccak256}; -use thiserror::Error; -use wasmer::{ - imports, CompilerConfig, Function, FunctionEnv, FunctionEnvMut, Instance, Memory, Module, - RuntimeError, Store, -}; -use wasmer_compiler_cranelift::Cranelift; - use std::{ collections::{BTreeMap, HashMap}, fs::File, @@ -25,6 +17,12 @@ use std::{ sync::Arc, time::{Duration, Instant}, }; +use thiserror::Error; +use wasmer::{ + imports, CompilerConfig, Function, FunctionEnv, FunctionEnvMut, Instance, Memory, Module, + RuntimeError, Store, +}; +use wasmer_compiler_cranelift::Cranelift; pub fn create(opts: &Opts, env: WasmEnv) -> (Instance, FunctionEnv, Store) { let file = &opts.binary; @@ -68,8 +66,8 @@ pub fn create(opts: &Opts, env: WasmEnv) -> (Instance, FunctionEnv, Sto } let imports = imports! { "arbcompress" => { - "brotliCompress" => func!(arbcompress::brotli_compress), - "brotliDecompress" => func!(arbcompress::brotli_decompress), + "brotli_compress" => func!(arbcompress::brotli_compress), + "brotli_decompress" => func!(arbcompress::brotli_decompress), }, "wavmio" => { "getGlobalStateBytes32" => func!(wavmio::get_global_state_bytes32), @@ -167,10 +165,6 @@ impl Escape { pub fn hostio>(message: S) -> Result { Err(Self::HostIO(message.as_ref().to_string())) } - - pub fn failure>(message: S) -> Result { - Err(Self::Failure(message.as_ref().to_string())) - } } impl From for Escape { diff --git a/arbitrator/jit/src/main.rs b/arbitrator/jit/src/main.rs index ecc5ee7d3..5aa762313 100644 --- a/arbitrator/jit/src/main.rs +++ b/arbitrator/jit/src/main.rs @@ -1,16 +1,14 @@ -// Copyright 2022, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE use crate::machine::{Escape, WasmEnv}; - use arbutil::{color, Color}; use eyre::Result; -use structopt::StructOpt; - use std::path::PathBuf; +use structopt::StructOpt; mod arbcompress; -mod callerenv; +mod caller_env; mod machine; mod program; mod socket; @@ -52,19 +50,18 @@ pub struct Opts { fn main() -> Result<()> { let opts = Opts::from_args(); - let env = match WasmEnv::cli(&opts) { Ok(env) => env, - Err(err) => panic!("{}", err), + Err(err) => panic!("{err}"), }; let (instance, env, mut store) = machine::create(&opts, env); let main = instance.exports.get_function("_start").unwrap(); - let outcome = main.call(&mut store, &vec![]); + let outcome = main.call(&mut store, &[]); let escape = match outcome { Ok(outcome) => { - println!("Go returned values {:?}", outcome); + println!("Go returned values {outcome:?}"); None } Err(outcome) => { diff --git a/arbitrator/jit/src/program.rs b/arbitrator/jit/src/program.rs index c940aefc9..193e6bbc2 100644 --- a/arbitrator/jit/src/program.rs +++ b/arbitrator/jit/src/program.rs @@ -1,11 +1,14 @@ -// Copyright 2022-2023, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -use crate::callerenv::CallerEnv; +#![allow(clippy::too_many_arguments)] + +use crate::caller_env::JitEnv; use crate::machine::{Escape, MaybeEscape, WasmEnvMut}; use crate::stylus_backend::exec_wasm; use arbutil::Bytes32; use arbutil::{evm::EvmData, format::DebugBytes, heapify}; +use caller_env::{GuestPtr, MemAccess}; use eyre::eyre; use prover::programs::prelude::StylusConfig; use prover::{ @@ -13,45 +16,43 @@ use prover::{ programs::{config::PricingParams, prelude::*}, }; -type Uptr = u32; - /// activates a user program pub fn activate( mut env: WasmEnvMut, - wasm_ptr: Uptr, + wasm_ptr: GuestPtr, wasm_size: u32, - pages_ptr: Uptr, - asm_estimate_ptr: Uptr, - init_gas_ptr: Uptr, + pages_ptr: GuestPtr, + asm_estimate_ptr: GuestPtr, + init_gas_ptr: GuestPtr, version: u16, debug: u32, - module_hash_ptr: Uptr, - gas_ptr: Uptr, - err_buf: Uptr, + module_hash_ptr: GuestPtr, + gas_ptr: GuestPtr, + err_buf: GuestPtr, err_buf_len: u32, ) -> Result { - let mut caller_env = CallerEnv::new(&mut env); - let wasm = caller_env.caller_read_slice(wasm_ptr, wasm_size); + let (mut mem, _) = env.jit_env(); + let wasm = mem.read_slice(wasm_ptr, wasm_size as usize); let debug = debug != 0; - let page_limit = caller_env.caller_read_u16(pages_ptr); - let gas_left = &mut caller_env.caller_read_u64(gas_ptr); + let page_limit = mem.read_u16(pages_ptr); + let gas_left = &mut mem.read_u64(gas_ptr); match Module::activate(&wasm, version, page_limit, debug, gas_left) { Ok((module, data)) => { - caller_env.caller_write_u64(gas_ptr, *gas_left); - caller_env.caller_write_u16(pages_ptr, data.footprint); - caller_env.caller_write_u32(asm_estimate_ptr, data.asm_estimate); - caller_env.caller_write_u32(init_gas_ptr, data.init_gas); - caller_env.caller_write_bytes32(module_hash_ptr, module.hash()); + mem.write_u64(gas_ptr, *gas_left); + mem.write_u16(pages_ptr, data.footprint); + mem.write_u32(asm_estimate_ptr, data.asm_estimate); + mem.write_u32(init_gas_ptr, data.init_gas); + mem.write_bytes32(module_hash_ptr, module.hash()); Ok(0) } Err(error) => { let mut err_bytes = error.wrap_err("failed to activate").debug_bytes(); err_bytes.truncate(err_buf_len as usize); - caller_env.caller_write_slice(err_buf, &err_bytes); - caller_env.caller_write_u64(gas_ptr, 0); - caller_env.caller_write_u16(pages_ptr, 0); - caller_env.caller_write_bytes32(module_hash_ptr, Bytes32::default()); + mem.write_slice(err_buf, &err_bytes); + mem.write_u64(gas_ptr, 0); + mem.write_u16(pages_ptr, 0); + mem.write_bytes32(module_hash_ptr, Bytes32::default()); Ok(err_bytes.len() as u32) } } @@ -62,16 +63,16 @@ pub fn activate( /// returns module number pub fn new_program( mut env: WasmEnvMut, - compiled_hash_ptr: Uptr, - calldata_ptr: Uptr, + compiled_hash_ptr: GuestPtr, + calldata_ptr: GuestPtr, calldata_size: u32, stylus_config_handler: u64, evm_data_handler: u64, gas: u64, ) -> Result { - let mut caller_env = CallerEnv::new(&mut env); - let compiled_hash = caller_env.caller_read_bytes32(compiled_hash_ptr); - let calldata = caller_env.caller_read_slice(calldata_ptr, calldata_size); + let (mut mem, exec) = env.jit_env(); + let compiled_hash = mem.read_bytes32(compiled_hash_ptr); + let calldata = mem.read_slice(calldata_ptr, calldata_size as usize); let evm_data: EvmData = unsafe { *Box::from_raw(evm_data_handler as *mut EvmData) }; let config: JitConfig = unsafe { *Box::from_raw(stylus_config_handler as *mut JitConfig) }; @@ -79,11 +80,11 @@ pub fn new_program( let pricing = config.stylus.pricing; let ink = pricing.gas_to_ink(gas); - let Some(module) = caller_env.wenv.module_asms.get(&compiled_hash).cloned() else { + let Some(module) = exec.module_asms.get(&compiled_hash).cloned() else { return Err(Escape::Failure(format!( "module hash {:?} not found in {:?}", compiled_hash, - caller_env.wenv.module_asms.keys() + exec.module_asms.keys() ))); }; @@ -97,81 +98,81 @@ pub fn new_program( ) .unwrap(); - caller_env.wenv.threads.push(cothread); + exec.threads.push(cothread); - Ok(caller_env.wenv.threads.len() as u32) + Ok(exec.threads.len() as u32) } /// starts the program (in jit waits for first request) /// module MUST match last module number returned from new_program /// returns request_id for the first request from the program pub fn start_program(mut env: WasmEnvMut, module: u32) -> Result { - let caller_env = CallerEnv::new(&mut env); + let (_, exec) = env.jit_env(); - if caller_env.wenv.threads.len() as u32 != module || module == 0 { + if exec.threads.len() as u32 != module || module == 0 { return Escape::hostio(format!( "got request for thread {module} but len is {}", - caller_env.wenv.threads.len() + exec.threads.len() )); } - let thread = caller_env.wenv.threads.last_mut().unwrap(); + let thread = exec.threads.last_mut().unwrap(); thread.wait_next_message()?; let msg = thread.last_message()?; Ok(msg.1) } -// gets information about request according to id -// request_id MUST be last request id returned from start_program or send_response -pub fn get_request(mut env: WasmEnvMut, id: u32, len_ptr: u32) -> Result { - let mut caller_env = CallerEnv::new(&mut env); - let thread = caller_env.wenv.threads.last_mut().unwrap(); +/// gets information about request according to id +/// request_id MUST be last request id returned from start_program or send_response +pub fn get_request(mut env: WasmEnvMut, id: u32, len_ptr: GuestPtr) -> Result { + let (mut mem, exec) = env.jit_env(); + let thread = exec.threads.last_mut().unwrap(); let msg = thread.last_message()?; if msg.1 != id { return Escape::hostio("get_request id doesn't match"); }; - caller_env.caller_write_u32(len_ptr, msg.0.req_data.len() as u32); + mem.write_u32(len_ptr, msg.0.req_data.len() as u32); Ok(msg.0.req_type) } // gets data associated with last request. // request_id MUST be last request receieved // data_ptr MUST point to a buffer of at least the length returned by get_request -pub fn get_request_data(mut env: WasmEnvMut, id: u32, data_ptr: u32) -> MaybeEscape { - let caller_env = CallerEnv::new(&mut env); - let thread = caller_env.wenv.threads.last_mut().unwrap(); +pub fn get_request_data(mut env: WasmEnvMut, id: u32, data_ptr: GuestPtr) -> MaybeEscape { + let (mut mem, exec) = env.jit_env(); + let thread = exec.threads.last_mut().unwrap(); let msg = thread.last_message()?; if msg.1 != id { return Escape::hostio("get_request id doesn't match"); }; - caller_env.caller_write_slice(data_ptr, &msg.0.req_data); + mem.write_slice(data_ptr, &msg.0.req_data); Ok(()) } -// sets response for the next request made -// id MUST be the id of last request made +/// sets response for the next request made +/// id MUST be the id of last request made pub fn set_response( mut env: WasmEnvMut, id: u32, gas: u64, - result_ptr: Uptr, + result_ptr: GuestPtr, result_len: u32, - raw_data_ptr: Uptr, + raw_data_ptr: GuestPtr, raw_data_len: u32, ) -> MaybeEscape { - let caller_env = CallerEnv::new(&mut env); - let result = caller_env.caller_read_slice(result_ptr, result_len); - let raw_data = caller_env.caller_read_slice(raw_data_ptr, raw_data_len); + let (mem, exec) = env.jit_env(); + let result = mem.read_slice(result_ptr, result_len as usize); + let raw_data = mem.read_slice(raw_data_ptr, raw_data_len as usize); - let thread = caller_env.wenv.threads.last_mut().unwrap(); + let thread = exec.threads.last_mut().unwrap(); thread.set_response(id, result, raw_data, gas) } -// sends previos response -// MUST be called right after set_response to the same id -// returns request_id for the next request +/// sends previos response +/// MUST be called right after set_response to the same id +/// returns request_id for the next request pub fn send_response(mut env: WasmEnvMut, req_id: u32) -> Result { - let caller_env = CallerEnv::new(&mut env); - let thread = caller_env.wenv.threads.last_mut().unwrap(); + let (_, exec) = env.jit_env(); + let thread = exec.threads.last_mut().unwrap(); let msg = thread.last_message()?; if msg.1 != req_id { return Escape::hostio("get_request id doesn't match"); @@ -181,11 +182,11 @@ pub fn send_response(mut env: WasmEnvMut, req_id: u32) -> Result { Ok(msg.1) } -// removes the last created program +/// removes the last created program pub fn pop(mut env: WasmEnvMut) -> MaybeEscape { - let caller_env = CallerEnv::new(&mut env); + let (_, exec) = env.jit_env(); - match caller_env.wenv.threads.pop() { + match exec.threads.pop() { None => Err(Escape::Child(eyre!("no child"))), Some(mut thread) => thread.wait_done(), } @@ -215,36 +216,35 @@ pub fn create_stylus_config( } /// Creates an `EvmData` handler from its component parts. -/// pub fn create_evm_data( mut env: WasmEnvMut, - block_basefee_ptr: Uptr, + block_basefee_ptr: GuestPtr, chainid: u64, - block_coinbase_ptr: Uptr, + block_coinbase_ptr: GuestPtr, block_gas_limit: u64, block_number: u64, block_timestamp: u64, - contract_address_ptr: Uptr, - msg_sender_ptr: Uptr, - msg_value_ptr: Uptr, - tx_gas_price_ptr: Uptr, - tx_origin_ptr: Uptr, + contract_address_ptr: GuestPtr, + msg_sender_ptr: GuestPtr, + msg_value_ptr: GuestPtr, + tx_gas_price_ptr: GuestPtr, + tx_origin_ptr: GuestPtr, reentrant: u32, ) -> Result { - let mut caller_env = CallerEnv::new(&mut env); + let (mut mem, _) = env.jit_env(); let evm_data = EvmData { - block_basefee: caller_env.caller_read_bytes32(block_basefee_ptr), + block_basefee: mem.read_bytes32(block_basefee_ptr), chainid, - block_coinbase: caller_env.caller_read_bytes20(block_coinbase_ptr), + block_coinbase: mem.read_bytes20(block_coinbase_ptr), block_gas_limit, block_number, block_timestamp, - contract_address: caller_env.caller_read_bytes20(contract_address_ptr), - msg_sender: caller_env.caller_read_bytes20(msg_sender_ptr), - msg_value: caller_env.caller_read_bytes32(msg_value_ptr), - tx_gas_price: caller_env.caller_read_bytes32(tx_gas_price_ptr), - tx_origin: caller_env.caller_read_bytes20(tx_origin_ptr), + contract_address: mem.read_bytes20(contract_address_ptr), + msg_sender: mem.read_bytes20(msg_sender_ptr), + msg_value: mem.read_bytes32(msg_value_ptr), + tx_gas_price: mem.read_bytes32(tx_gas_price_ptr), + tx_origin: mem.read_bytes20(tx_origin_ptr), reentrant, return_data_len: 0, tracing: false, diff --git a/arbitrator/jit/src/socket.rs b/arbitrator/jit/src/socket.rs index f8bd9c9e9..004b8eb44 100644 --- a/arbitrator/jit/src/socket.rs +++ b/arbitrator/jit/src/socket.rs @@ -1,18 +1,16 @@ -// Copyright 2022, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -//use core::slice::SlicePattern; +use arbutil::Bytes32; use std::{ io, io::{BufReader, BufWriter, Read, Write}, net::TcpStream, }; -use arbutil::Bytes32; - pub const SUCCESS: u8 = 0x0; pub const FAILURE: u8 = 0x1; -pub const PREIMAGE: u8 = 0x2; +// pub const PREIMAGE: u8 = 0x2; // not used pub const ANOTHER: u8 = 0x3; pub const READY: u8 = 0x4; diff --git a/arbitrator/jit/src/stylus_backend.rs b/arbitrator/jit/src/stylus_backend.rs index 6b0495a0c..74a8d6ae1 100644 --- a/arbitrator/jit/src/stylus_backend.rs +++ b/arbitrator/jit/src/stylus_backend.rs @@ -1,4 +1,4 @@ -// Copyright 2023, Offchain Labs, Inc. +// Copyright 2023-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE #![allow(clippy::too_many_arguments)] @@ -48,15 +48,13 @@ impl RequestHandler for CothreadRequestor { req_type: EvmApiMethod, req_data: &[u8], ) -> (Vec, VecReader, u64) { - if self - .tx - .send(MessageFromCothread { - req_type: req_type as u32 + EVM_API_METHOD_REQ_OFFSET, - req_data: req_data.to_vec(), - }) - .is_err() - { - panic!("failed sending request from cothread"); + let msg = MessageFromCothread { + req_type: req_type as u32 + EVM_API_METHOD_REQ_OFFSET, + req_data: req_data.to_vec(), + }; + + if let Err(error) = self.tx.send(msg) { + panic!("failed sending request from cothread: {error}"); } match self.rx.recv_timeout(Duration::from_secs(5)) { Ok(response) => ( @@ -80,28 +78,25 @@ impl CothreadHandler { pub fn wait_next_message(&mut self) -> MaybeEscape { let msg = self.rx.recv_timeout(Duration::from_secs(10)); let Ok(msg) = msg else { - return Err(Escape::HostIO("did not receive message".to_string())); + return Escape::hostio("did not receive message"); }; self.last_request = Some((msg, 0x33333333)); // TODO: Ids Ok(()) } pub fn wait_done(&mut self) -> MaybeEscape { - let status = self - .thread - .take() - .ok_or(Escape::Child(eyre!("no child")))? - .join(); + let error = || Escape::Child(eyre!("no child")); + let status = self.thread.take().ok_or_else(error)?.join(); match status { Ok(res) => res, - Err(_) => Err(Escape::HostIO("failed joining child process".to_string())), + Err(_) => Escape::hostio("failed joining child process"), } } pub fn last_message(&self) -> Result<(MessageFromCothread, u32), Escape> { self.last_request .clone() - .ok_or(Escape::HostIO("no message waiting".to_string())) + .ok_or_else(|| Escape::HostIO("no message waiting".to_string())) } pub fn set_response( @@ -117,16 +112,13 @@ impl CothreadHandler { if msg.1 != id { return Escape::hostio("trying to set response for wrong message id"); }; - if self - .tx - .send(MessageToCothread { - result, - raw_data, - cost, - }) - .is_err() - { - return Escape::hostio("failed sending response to stylus thread"); + let msg = MessageToCothread { + result, + raw_data, + cost, + }; + if let Err(err) = self.tx.send(msg) { + return Escape::hostio(format!("failed to send response to stylus thread: {err:?}")); }; Ok(()) } @@ -168,21 +160,23 @@ pub fn exec_wasm( }; let (out_kind, data) = outcome.into_data(); - let gas_left = config.pricing.ink_to_gas(ink_left); - let mut output = gas_left.to_be_bytes().to_vec(); - output.extend(data.iter()); + let mut output = Vec::with_capacity(8 + data.len()); + output.extend(gas_left.to_be_bytes()); + output.extend(data); + + let msg = MessageFromCothread { + req_data: output, + req_type: out_kind as u32, + }; instance .env_mut() .evm_api .request_handler() .tx - .send(MessageFromCothread { - req_data: output, - req_type: out_kind as u32, - }) - .or(Escape::hostio("failed sending messaage to thread")) + .send(msg) + .or_else(|_| Escape::hostio("failed sending messaage to thread")) }); Ok(CothreadHandler { diff --git a/arbitrator/jit/src/wasip1_stub.rs b/arbitrator/jit/src/wasip1_stub.rs index 0e13e6cdf..0b525e6a9 100644 --- a/arbitrator/jit/src/wasip1_stub.rs +++ b/arbitrator/jit/src/wasip1_stub.rs @@ -1,305 +1,162 @@ -// Copyright 2021-2023, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -use crate::callerenv::CallerEnv; -use crate::machine::{Escape, WasmEnvMut}; -use rand::RngCore; - -type Errno = u16; - -type Uptr = u32; +#![allow(clippy::too_many_arguments)] -const ERRNO_SUCCESS: Errno = 0; -const ERRNO_BADF: Errno = 8; -const ERRNO_INTVAL: Errno = 28; +use crate::caller_env::{JitEnv, JitExecEnv}; +use crate::machine::{Escape, WasmEnvMut}; +use caller_env::{self, wasip1_stub::Errno, GuestPtr}; pub fn proc_exit(mut _env: WasmEnvMut, code: u32) -> Result<(), Escape> { Err(Escape::Exit(code)) } -pub fn environ_sizes_get( - mut env: WasmEnvMut, - length_ptr: Uptr, - data_size_ptr: Uptr, -) -> Result { - let mut caller_env = CallerEnv::new(&mut env); - - caller_env.caller_write_u32(length_ptr, 0); - caller_env.caller_write_u32(data_size_ptr, 0); - Ok(ERRNO_SUCCESS) -} - -pub fn fd_write( - mut env: WasmEnvMut, - fd: u32, - iovecs_ptr: Uptr, - iovecs_len: u32, - ret_ptr: Uptr, -) -> Result { - let mut caller_env = CallerEnv::new(&mut env); - - if fd != 1 && fd != 2 { - return Ok(ERRNO_BADF); - } - let mut size = 0; - for i in 0..iovecs_len { - let ptr = iovecs_ptr + i * 8; - let iovec = caller_env.caller_read_u32(ptr); - let len = caller_env.caller_read_u32(ptr + 4); - let data = caller_env.caller_read_string(iovec, len); - eprintln!("JIT: WASM says [{fd}]: {data}"); - size += len; - } - caller_env.caller_write_u32(ret_ptr, size); - Ok(ERRNO_SUCCESS) -} - -pub fn environ_get(mut _env: WasmEnvMut, _: u32, _: u32) -> Result { - Ok(ERRNO_INTVAL) -} - -pub fn fd_close(mut _env: WasmEnvMut, _: u32) -> Result { - Ok(ERRNO_BADF) -} - -pub fn fd_read(mut _env: WasmEnvMut, _: u32, _: u32, _: u32, _: u32) -> Result { - Ok(ERRNO_BADF) -} - -pub fn fd_readdir( - mut _env: WasmEnvMut, - _fd: u32, - _: u32, - _: u32, - _: u64, - _: u32, -) -> Result { - Ok(ERRNO_BADF) -} - -pub fn fd_sync(mut _env: WasmEnvMut, _: u32) -> Result { - Ok(ERRNO_SUCCESS) -} - -pub fn fd_seek( - mut _env: WasmEnvMut, - _fd: u32, - _offset: u64, - _whence: u8, - _filesize: u32, -) -> Result { - Ok(ERRNO_BADF) -} - -pub fn fd_datasync(mut _env: WasmEnvMut, _fd: u32) -> Result { - Ok(ERRNO_BADF) -} - -pub fn path_open( - mut _env: WasmEnvMut, - _: u32, - _: u32, - _: u32, - _: u32, - _: u32, - _: u64, - _: u64, - _: u32, - _: u32, -) -> Result { - Ok(ERRNO_BADF) -} - -pub fn path_create_directory( - mut _env: WasmEnvMut, - _: u32, - _: u32, - _: u32, -) -> Result { - Ok(ERRNO_BADF) -} - -pub fn path_remove_directory( - mut _env: WasmEnvMut, - _: u32, - _: u32, - _: u32, -) -> Result { - Ok(ERRNO_BADF) -} - -pub fn path_readlink( - mut _env: WasmEnvMut, - _: u32, - _: u32, - _: u32, - _: u32, - _: u32, - _: u32, -) -> Result { - Ok(ERRNO_BADF) -} - -pub fn path_rename( - mut _env: WasmEnvMut, - _: u32, - _: u32, - _: u32, - _: u32, - _: u32, - _: u32, -) -> Result { - Ok(ERRNO_BADF) -} - -pub fn path_filestat_get( - mut _env: WasmEnvMut, - _: u32, - _: u32, - _: u32, - _: u32, - _: u32, -) -> Result { - Ok(ERRNO_BADF) -} - -pub fn path_unlink_file(mut _env: WasmEnvMut, _: u32, _: u32, _: u32) -> Result { - Ok(ERRNO_BADF) -} - -pub fn fd_prestat_get(mut _env: WasmEnvMut, _: u32, _: u32) -> Result { - Ok(ERRNO_BADF) -} - -pub fn fd_prestat_dir_name(mut _env: WasmEnvMut, _: u32, _: u32, _: u32) -> Result { - Ok(ERRNO_BADF) -} - -pub fn fd_filestat_get(mut _env: WasmEnvMut, _fd: u32, _filestat: u32) -> Result { - Ok(ERRNO_BADF) -} - -pub fn fd_filestat_set_size(mut _env: WasmEnvMut, _fd: u32, _: u64) -> Result { - Ok(ERRNO_BADF) -} - -pub fn fd_pread( - mut _env: WasmEnvMut, - _fd: u32, - _: u32, - _: u32, - _: u64, - _: u32, -) -> Result { - Ok(ERRNO_BADF) -} - -pub fn fd_pwrite( - mut _env: WasmEnvMut, - _fd: u32, - _: u32, - _: u32, - _: u64, - _: u32, -) -> Result { - Ok(ERRNO_BADF) -} - -pub fn sock_accept(mut _env: WasmEnvMut, _fd: u32, _: u32, _: u32) -> Result { - Ok(ERRNO_BADF) -} - -pub fn sock_shutdown(mut _env: WasmEnvMut, _: u32, _: u32) -> Result { - Ok(ERRNO_BADF) -} - -pub fn sched_yield(mut _env: WasmEnvMut) -> Result { - Ok(ERRNO_SUCCESS) -} - -pub fn clock_time_get( - mut env: WasmEnvMut, - _clock_id: u32, - _precision: u64, - time: Uptr, -) -> Result { - let mut caller_env = CallerEnv::new(&mut env); - caller_env.wenv.go_state.time += caller_env.wenv.go_state.time_interval; - caller_env.caller_write_u32(time, caller_env.wenv.go_state.time as u32); - caller_env.caller_write_u32(time + 4, (caller_env.wenv.go_state.time >> 32) as u32); - Ok(ERRNO_SUCCESS) -} - -pub fn random_get(mut env: WasmEnvMut, mut buf: u32, mut len: u32) -> Result { - let mut caller_env = CallerEnv::new(&mut env); - - while len >= 4 { - let next_rand = caller_env.wenv.go_state.rng.next_u32(); - caller_env.caller_write_u32(buf, next_rand); - buf += 4; - len -= 4; - } - if len > 0 { - let mut rem = caller_env.wenv.go_state.rng.next_u32(); - for _ in 0..len { - caller_env.caller_write_u8(buf, rem as u8); - buf += 1; - rem >>= 8; - } - } - Ok(ERRNO_SUCCESS) -} - -pub fn args_sizes_get( - mut env: WasmEnvMut, - length_ptr: Uptr, - data_size_ptr: Uptr, -) -> Result { - let mut caller_env = CallerEnv::new(&mut env); - caller_env.caller_write_u32(length_ptr, 1); - caller_env.caller_write_u32(data_size_ptr, 4); - Ok(ERRNO_SUCCESS) -} - -pub fn args_get(mut env: WasmEnvMut, argv_buf: Uptr, data_buf: Uptr) -> Result { - let mut caller_env = CallerEnv::new(&mut env); - - caller_env.caller_write_u32(argv_buf, data_buf as u32); - caller_env.caller_write_u32(data_buf, 0x6E6962); // "bin\0" - Ok(ERRNO_SUCCESS) -} - -// we always simulate a timeout -pub fn poll_oneoff( - mut env: WasmEnvMut, - in_subs: Uptr, - out_evt: Uptr, - nsubscriptions: u32, - nevents_ptr: Uptr, -) -> Result { - let mut caller_env = CallerEnv::new(&mut env); - - const SUBSCRIPTION_SIZE: u32 = 48; - for i in 0..nsubscriptions { - let subs_base = in_subs + (SUBSCRIPTION_SIZE * (i as u32)); - let subs_type = caller_env.caller_read_u32(subs_base + 8); - if subs_type != 0 { - // not a clock subscription type - continue; - } - let user_data = caller_env.caller_read_u32(subs_base); - caller_env.caller_write_u32(out_evt, user_data); - caller_env.caller_write_u32(out_evt + 8, 0); - caller_env.caller_write_u32(nevents_ptr, 1); - return Ok(ERRNO_SUCCESS); - } - Ok(ERRNO_INTVAL) -} - -pub fn fd_fdstat_get(mut _env: WasmEnvMut, _: u32, _: u32) -> Result { - Ok(ERRNO_INTVAL) -} - -pub fn fd_fdstat_set_flags(mut _env: WasmEnvMut, _: u32, _: u32) -> Result { - Ok(ERRNO_INTVAL) +macro_rules! wrap { + ($(fn $func_name:ident ($($arg_name:ident : $arg_type:ty),* ) -> $return_type:ty);*) => { + $( + pub fn $func_name(mut src: WasmEnvMut, $($arg_name : $arg_type),*) -> Result<$return_type, Escape> { + let (mut mem, wenv) = src.jit_env(); + + Ok(caller_env::wasip1_stub::$func_name(&mut mem, &mut JitExecEnv { wenv }, $($arg_name),*)) + } + )* + }; +} + +wrap! { + fn clock_time_get( + clock_id: u32, + precision: u64, + time_ptr: GuestPtr + ) -> Errno; + + fn random_get(buf: GuestPtr, len: u32) -> Errno; + + fn environ_get(a: GuestPtr, b: GuestPtr) -> Errno; + fn environ_sizes_get(length_ptr: GuestPtr, data_size_ptr: GuestPtr) -> Errno; + + fn fd_read(a: u32, b: u32, c: u32, d: u32) -> Errno; + fn fd_close(fd: u32) -> Errno; + fn fd_write( + fd: u32, + iovecs_ptr: GuestPtr, + iovecs_len: u32, + ret_ptr: GuestPtr + ) -> Errno; + + fn fd_readdir( + fd: u32, + a: u32, + b: u32, + c: u64, + d: u32 + ) -> Errno; + + fn fd_sync(a: u32) -> Errno; + + fn fd_seek( + fd: u32, + offset: u64, + whence: u8, + filesize: u32 + ) -> Errno; + + fn fd_datasync(_fd: u32) -> Errno; + + fn path_open( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32, + f: u64, + g: u64, + h: u32, + i: u32 + ) -> Errno; + + fn path_create_directory( + a: u32, + b: u32, + c: u32 + ) -> Errno; + + fn path_remove_directory( + a: u32, + b: u32, + c: u32 + ) -> Errno; + + fn path_readlink( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32, + f: u32 + ) -> Errno; + + fn path_rename( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32, + f: u32 + ) -> Errno; + + fn path_filestat_get( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32 + ) -> Errno; + + fn path_unlink_file(a: u32, b: u32, c: u32) -> Errno; + + fn fd_prestat_get(a: u32, b: u32) -> Errno; + fn fd_prestat_dir_name(a: u32, b: u32, c: u32) -> Errno; + + fn fd_filestat_get(fd: u32, _filestat: u32) -> Errno; + fn fd_filestat_set_size(fd: u32, size: u64) -> Errno; + + fn fd_pread( + fd: u32, + a: u32, + b: u32, + c: u64, + d: u32 + ) -> Errno; + + fn fd_pwrite( + fd: u32, + a: u32, + b: u32, + c: u64, + d: u32 + ) -> Errno; + + fn sock_accept(_fd: u32, a: u32, b: u32) -> Errno; + fn sock_shutdown(a: u32, b: u32) -> Errno; + + fn sched_yield() -> Errno; + + fn args_sizes_get( + length_ptr: GuestPtr, + data_size_ptr: GuestPtr + ) -> Errno; + + fn args_get(argv_buf: GuestPtr, data_buf: GuestPtr) -> Errno; + + fn fd_fdstat_get(a: u32, b: u32) -> Errno; + fn fd_fdstat_set_flags(a: u32, b: u32) -> Errno; + + // we always simulate a timeout + fn poll_oneoff( + in_subs: GuestPtr, + out_evt: GuestPtr, + nsubscriptions: u32, + nevents_ptr: GuestPtr + ) -> Errno } diff --git a/arbitrator/jit/src/wavmio.rs b/arbitrator/jit/src/wavmio.rs index 3cb65cd93..e1611d432 100644 --- a/arbitrator/jit/src/wavmio.rs +++ b/arbitrator/jit/src/wavmio.rs @@ -1,13 +1,13 @@ -// Copyright 2022-2023, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE use crate::{ - callerenv::CallerEnv, + caller_env::JitEnv, machine::{Escape, MaybeEscape, WasmEnv, WasmEnvMut}, socket, }; - use arbutil::Color; +use caller_env::{GuestPtr, MemAccess}; use std::{ io, io::{BufReader, BufWriter, ErrorKind}, @@ -15,43 +15,38 @@ use std::{ time::Instant, }; -type Uptr = u32; - -/// Reads 32-bytes of global state -pub fn get_global_state_bytes32(mut env: WasmEnvMut, idx: u32, out_ptr: Uptr) -> MaybeEscape { - let caller_env = CallerEnv::new(&mut env); - ready_hostio(caller_env.wenv)?; +/// Reads 32-bytes of global state. +pub fn get_global_state_bytes32(mut env: WasmEnvMut, idx: u32, out_ptr: GuestPtr) -> MaybeEscape { + let (mut mem, exec) = env.jit_env(); + ready_hostio(exec)?; - let global = match caller_env.wenv.large_globals.get(idx as usize) { - Some(global) => global, - None => return Escape::hostio("global read out of bounds in wavmio.getGlobalStateBytes32"), + let Some(global) = exec.large_globals.get(idx as usize) else { + return Escape::hostio("global read out of bounds in wavmio.getGlobalStateBytes32"); }; - caller_env.caller_write_slice(out_ptr, &global[..32]); + mem.write_slice(out_ptr, &global[..32]); Ok(()) } -/// Writes 32-bytes of global state -pub fn set_global_state_bytes32(mut env: WasmEnvMut, idx: u32, src_ptr: Uptr) -> MaybeEscape { - let caller_env = CallerEnv::new(&mut env); - ready_hostio(caller_env.wenv)?; +/// Writes 32-bytes of global state. +pub fn set_global_state_bytes32(mut env: WasmEnvMut, idx: u32, src_ptr: GuestPtr) -> MaybeEscape { + let (mem, exec) = env.jit_env(); + ready_hostio(exec)?; - let slice = caller_env.caller_read_slice(src_ptr, 32); + let slice = mem.read_slice(src_ptr, 32); let slice = &slice.try_into().unwrap(); - match caller_env.wenv.large_globals.get_mut(idx as usize) { + match exec.large_globals.get_mut(idx as usize) { Some(global) => *global = *slice, - None => { - return Escape::hostio("global write out of bounds in wavmio.setGlobalStateBytes32") - } - } + None => return Escape::hostio("global write oob in wavmio.setGlobalStateBytes32"), + }; Ok(()) } /// Reads 8-bytes of global state pub fn get_global_state_u64(mut env: WasmEnvMut, idx: u32) -> Result { - let caller_env = CallerEnv::new(&mut env); - ready_hostio(caller_env.wenv)?; + let (_, exec) = env.jit_env(); + ready_hostio(exec)?; - match caller_env.wenv.small_globals.get(idx as usize) { + match exec.small_globals.get(idx as usize) { Some(global) => Ok(*global), None => Escape::hostio("global read out of bounds in wavmio.getGlobalStateU64"), } @@ -59,68 +54,66 @@ pub fn get_global_state_u64(mut env: WasmEnvMut, idx: u32) -> Result MaybeEscape { - let caller_env = CallerEnv::new(&mut env); - ready_hostio(caller_env.wenv)?; + let (_, exec) = env.jit_env(); + ready_hostio(exec)?; - match caller_env.wenv.small_globals.get_mut(idx as usize) { - Some(global) => { - *global = val; - Ok(()) - } - None => Escape::hostio("global write out of bounds in wavmio.setGlobalStateU64"), + match exec.small_globals.get_mut(idx as usize) { + Some(global) => *global = val, + None => return Escape::hostio("global write out of bounds in wavmio.setGlobalStateU64"), } + Ok(()) } -/// Reads an inbox message +/// Reads an inbox message. pub fn read_inbox_message( mut env: WasmEnvMut, msg_num: u64, offset: u32, - out_ptr: Uptr, + out_ptr: GuestPtr, ) -> Result { - let caller_env = CallerEnv::new(&mut env); - ready_hostio(caller_env.wenv)?; + let (mut mem, exec) = env.jit_env(); + ready_hostio(exec)?; - let message = match caller_env.wenv.sequencer_messages.get(&msg_num) { + let message = match exec.sequencer_messages.get(&msg_num) { Some(message) => message, None => return Escape::hostio(format!("missing sequencer inbox message {msg_num}")), }; let offset = offset as usize; let len = std::cmp::min(32, message.len().saturating_sub(offset)); let read = message.get(offset..(offset + len)).unwrap_or_default(); - caller_env.caller_write_slice(out_ptr, read); + mem.write_slice(out_ptr, read); Ok(read.len() as u32) } -/// Reads a delayed inbox message +/// Reads a delayed inbox message. pub fn read_delayed_inbox_message( mut env: WasmEnvMut, msg_num: u64, offset: u32, - out_ptr: Uptr, + out_ptr: GuestPtr, ) -> Result { - let caller_env = CallerEnv::new(&mut env); - ready_hostio(caller_env.wenv)?; + let (mut mem, exec) = env.jit_env(); + ready_hostio(exec)?; - let message = match caller_env.wenv.delayed_messages.get(&msg_num) { + let message = match exec.delayed_messages.get(&msg_num) { Some(message) => message, None => return Escape::hostio(format!("missing delayed inbox message {msg_num}")), }; let offset = offset as usize; let len = std::cmp::min(32, message.len().saturating_sub(offset)); let read = message.get(offset..(offset + len)).unwrap_or_default(); - caller_env.caller_write_slice(out_ptr, read); + mem.write_slice(out_ptr, read); Ok(read.len() as u32) } /// Retrieves the preimage of the given hash. pub fn resolve_preimage( mut env: WasmEnvMut, - hash_ptr: Uptr, + hash_ptr: GuestPtr, offset: u32, - out_ptr: Uptr, + out_ptr: GuestPtr, ) -> Result { - let mut caller_env = CallerEnv::new(&mut env); + let (mut mem, exec) = env.jit_env(); let name = "wavmio.resolvePreImage"; @@ -131,13 +124,13 @@ pub fn resolve_preimage( }}; } - let hash = caller_env.caller_read_bytes32(hash_ptr); + let hash = mem.read_bytes32(hash_ptr); let hash_hex = hex::encode(hash); let mut preimage = None; // see if we've cached the preimage - if let Some((key, cached)) = &caller_env.wenv.process.last_preimage { + if let Some((key, cached)) = &exec.process.last_preimage { if *key == hash { preimage = Some(cached); } @@ -145,21 +138,19 @@ pub fn resolve_preimage( // see if this is a known preimage if preimage.is_none() { - preimage = caller_env.wenv.preimages.get(&hash); + preimage = exec.preimages.get(&hash); } - let preimage = match preimage { - Some(preimage) => preimage, - None => error!("Missing requested preimage for hash {hash_hex} in {name}"), + let Some(preimage) = preimage else { + error!("Missing requested preimage for hash {hash_hex} in {name}") }; - let offset = match u32::try_from(offset) { - Ok(offset) => offset as usize, - Err(_) => error!("bad offset {offset} in {name}"), + let Ok(offset) = usize::try_from(offset) else { + error!("bad offset {offset} in {name}") }; let len = std::cmp::min(32, preimage.len().saturating_sub(offset)); let read = preimage.get(offset..(offset + len)).unwrap_or_default(); - caller_env.caller_write_slice(out_ptr, read); + mem.write_slice(out_ptr, read); Ok(read.len() as u32) } diff --git a/arbitrator/prover/src/host.rs b/arbitrator/prover/src/host.rs index 78f4e9dd6..d5ec9154a 100644 --- a/arbitrator/prover/src/host.rs +++ b/arbitrator/prover/src/host.rs @@ -207,7 +207,7 @@ impl Hostio { ty } - pub fn body(&self, prior: usize) -> Vec { + pub fn body(&self, _prior: usize) -> Vec { let mut body = vec![]; macro_rules! opcode { @@ -359,7 +359,7 @@ impl Hostio { opcode!(I32Const, UserOutcomeKind::Failure as u32) } ProgramRequest => { - // caller sees: λ(status)->response + // caller sees: λ(status) → response // code returns status of either ProgramContinue or ProgramCallMain opcode!(LocalGet, 0); // return_data opcode!(MoveFromStackToInternal); diff --git a/arbitrator/prover/src/lib.rs b/arbitrator/prover/src/lib.rs index e1fb64cc7..c8504a397 100644 --- a/arbitrator/prover/src/lib.rs +++ b/arbitrator/prover/src/lib.rs @@ -112,7 +112,7 @@ pub unsafe extern "C" fn arbitrator_load_wavm_binary(binary_path: *const c_char) match Machine::new_from_wavm(binary_path) { Ok(mach) => Box::into_raw(Box::new(mach)), Err(err) => { - eprintln!("Error loading binary: {}", err); + eprintln!("Error loading binary: {err}"); ptr::null_mut() } } diff --git a/arbitrator/prover/src/machine.rs b/arbitrator/prover/src/machine.rs index e03405463..a4601f845 100644 --- a/arbitrator/prover/src/machine.rs +++ b/arbitrator/prover/src/machine.rs @@ -718,10 +718,35 @@ pub struct ModuleState<'a> { memory: Cow<'a, Memory>, } +/// Represents if the machine can recover and where to jump back if so. +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum ThreadState { + /// Execution is in the main thread. Errors are fatal. + Main, + /// Execution is in a cothread. Errors recover to the associated pc with the main thread. + CoThread(ProgramCounter), +} + +impl ThreadState { + fn is_cothread(&self) -> bool { + match self { + ThreadState::Main => false, + ThreadState::CoThread(_) => true, + } + } + + fn serialize(&self) -> Bytes32 { + match self { + ThreadState::Main => Bytes32([0xff; 32]), + ThreadState::CoThread(pc) => (*pc).serialize(), + } + } +} + #[derive(Serialize, Deserialize)] pub struct MachineState<'a> { steps: u64, // Not part of machine hash - main_thread_recovery: Option, + thread_state: ThreadState, status: MachineStatus, value_stacks: Cow<'a, Vec>>, internal_stack: Cow<'a, Vec>, @@ -785,11 +810,7 @@ impl PreimageResolverWrapper { #[derive(Clone, Debug)] pub struct Machine { steps: u64, // Not part of machine hash - // This has double use: - // If None - we are not in a cothread. - // Otherwise - it holds the recovery PC which we'll jump to - // in case the cothread reaches a machine error - main_thread_recovery: Option, + thread_state: ThreadState, status: MachineStatus, value_stacks: Vec>, internal_stack: Vec, @@ -1108,6 +1129,11 @@ impl Machine { let config = CompileConfig::version(version, debug_funcs); let stylus_data = bin.instrument(&config)?; + // enable debug mode if debug funcs are available + if debug_funcs { + self.debug_info = true; + } + let module = Module::from_user_binary(&bin, debug_funcs, Some(stylus_data))?; let hash = module.hash(); self.add_stylus_module(module, hash); @@ -1348,7 +1374,7 @@ impl Machine { let mut mach = Machine { status: MachineStatus::Running, - main_thread_recovery: None, + thread_state: ThreadState::Main, steps: 0, value_stacks: vec![vec![Value::RefNull, Value::I32(0), Value::I32(0)]], internal_stack: Vec::new(), @@ -1403,7 +1429,7 @@ impl Machine { } let mut mach = Machine { status: MachineStatus::Running, - main_thread_recovery: None, + thread_state: ThreadState::Main, steps: 0, value_stacks: vec![vec![Value::RefNull, Value::I32(0), Value::I32(0)]], internal_stack: Vec::new(), @@ -1453,7 +1479,7 @@ impl Machine { .collect(); let state = MachineState { steps: self.steps, - main_thread_recovery: self.main_thread_recovery, + thread_state: self.thread_state, status: self.status, value_stacks: Cow::Borrowed(&self.value_stacks), internal_stack: Cow::Borrowed(&self.internal_stack), @@ -1589,7 +1615,7 @@ impl Machine { } pub fn get_final_result(&self) -> Result> { - if self.main_thread_recovery.is_some() { + if self.thread_state.is_cothread() { bail!("machine in cothread when expecting final result") } if !self.frame_stacks[0].is_empty() { @@ -1701,9 +1727,9 @@ impl Machine { if self.is_halted() { return Ok(()); } - let (mut value_stack, mut frame_stack) = match self.main_thread_recovery { - None => (&mut self.value_stacks[0], &mut self.frame_stacks[0]), - Some(_) => ( + let (mut value_stack, mut frame_stack) = match self.thread_state { + ThreadState::Main => (&mut self.value_stacks[0], &mut self.frame_stacks[0]), + ThreadState::CoThread(_) => ( self.value_stacks.last_mut().unwrap(), self.frame_stacks.last_mut().unwrap(), ), @@ -1713,9 +1739,9 @@ impl Machine { macro_rules! reset_refs { () => { - (value_stack, frame_stack) = match self.main_thread_recovery { - None => (&mut self.value_stacks[0], &mut self.frame_stacks[0]), - Some(_) => ( + (value_stack, frame_stack) = match self.thread_state { + ThreadState::Main => (&mut self.value_stacks[0], &mut self.frame_stacks[0]), + ThreadState::CoThread(_) => ( self.value_stacks.last_mut().unwrap(), self.frame_stacks.last_mut().unwrap(), ), @@ -1737,21 +1763,23 @@ impl Machine { }; ($format:expr $(, $message:expr)*) => {{ flush_module!(); - let print_debug_info = |machine: &Self| { + + if self.debug_info { println!("\n{} {}", "error on line".grey(), line!().pink()); println!($format, $($message.pink()),*); - println!("{}", "Backtrace:".grey()); - machine.print_backtrace(true); - }; + println!("{}", "backtrace:".grey()); + self.print_backtrace(true); + } - if let Some(recovery_pc) = self.main_thread_recovery.take() { - println!("\n{}", "switching to main thread".grey()); + if let ThreadState::CoThread(recovery_pc) = self.thread_state { + self.thread_state = ThreadState::Main; self.pc = recovery_pc; reset_refs!(); - println!("\n{} {:?}", "next opcode: ".grey(), func.code[self.pc.inst()]); + if self.debug_info { + println!("\n{}", "switching to main thread".grey()); + println!("\n{} {:?}", "next opcode: ".grey(), func.code[self.pc.inst()]); + } continue; - } else { - print_debug_info(self); } self.status = MachineStatus::Errored; module = &mut self.modules[self.pc.module()]; @@ -1762,8 +1790,13 @@ impl Machine { for _ in 0..n { self.steps += 1; if self.steps == Self::MAX_STEPS { - error!(); + println!("\n{}", "Machine out of steps".red()); + self.status = MachineStatus::Errored; + self.print_backtrace(true); + module = &mut self.modules[self.pc.module()]; + break; } + let inst = func.code[self.pc.inst()]; self.pc.inst += 1; match inst.opcode { @@ -1790,7 +1823,7 @@ impl Machine { .and_then(|h| h.as_ref()) { if let Err(err) = Self::host_call_hook( - &value_stack, + value_stack, module, &mut self.stdio_output, &hook.0, @@ -2328,7 +2361,7 @@ impl Machine { break; } Opcode::NewCoThread => { - if self.main_thread_recovery.is_some() { + if self.thread_state.is_cothread() { error!("called NewCoThread from cothread") } self.value_stacks.push(Vec::new()); @@ -2336,7 +2369,7 @@ impl Machine { reset_refs!(); } Opcode::PopCoThread => { - if self.main_thread_recovery.is_some() { + if self.thread_state.is_cothread() { error!("called PopCoThread from cothread") } self.value_stacks.pop(); @@ -2345,16 +2378,13 @@ impl Machine { } Opcode::SwitchThread => { let next_recovery = match inst.argument_data { - 0 => None, - offset => { - let offset: u32 = (offset - 1).try_into().unwrap(); - Some(self.pc.add(offset)) - } + 0 => ThreadState::Main, + x => ThreadState::CoThread(self.pc.add((x - 1).try_into().unwrap())), }; - if next_recovery.xor(self.main_thread_recovery).is_none() { - error!("switchthread doesn't switch") + if next_recovery.is_cothread() == self.thread_state.is_cothread() { + error!("SwitchThread doesn't switch") } - self.main_thread_recovery = next_recovery; + self.thread_state = next_recovery; reset_refs!(); } } @@ -2561,13 +2591,6 @@ impl Machine { (frame_stacks, value_stacks, inter_stack) } - pub fn main_thread_recovery_serialized(&self) -> Bytes32 { - match self.main_thread_recovery { - Some(recovery_pc) => recovery_pc.serialize(), - None => Bytes32([255; 32]), - } - } - pub fn hash(&self) -> Bytes32 { let mut h = Keccak256::new(); match self.status { @@ -2582,7 +2605,7 @@ impl Machine { h.update(self.pc.module.to_be_bytes()); h.update(self.pc.func.to_be_bytes()); h.update(self.pc.inst.to_be_bytes()); - h.update(self.main_thread_recovery_serialized()); + h.update(self.thread_state.serialize()); h.update(self.get_modules_root()); } MachineStatus::Finished => { @@ -2617,7 +2640,7 @@ impl Machine { }}; } out!(prove_multistack( - self.main_thread_recovery.is_some(), + self.thread_state.is_cothread(), self.get_data_stacks(), hash_value_stack, hash_multistack, @@ -2633,7 +2656,7 @@ impl Machine { )); out!(prove_multistack( - self.main_thread_recovery.is_some(), + self.thread_state.is_cothread(), self.get_frame_stacks(), hash_stack_frame_stack, hash_multistack, @@ -2650,7 +2673,7 @@ impl Machine { out!(self.pc.func.to_be_bytes()); out!(self.pc.inst.to_be_bytes()); - out!(self.main_thread_recovery_serialized()); + out!(self.thread_state.serialize()); let mod_merkle = self.get_modules_merkle(); out!(mod_merkle.root()); @@ -2880,9 +2903,9 @@ impl Machine { } pub fn get_data_stack(&self) -> &[Value] { - match self.main_thread_recovery { - None => &self.value_stacks[0], - Some(_) => self.value_stacks.last().unwrap(), + match self.thread_state { + ThreadState::Main => &self.value_stacks[0], + ThreadState::CoThread(_) => self.value_stacks.last().unwrap(), } } @@ -2891,16 +2914,16 @@ impl Machine { } fn get_frame_stack(&self) -> &[StackFrame] { - match self.main_thread_recovery { - None => &self.frame_stacks[0], - Some(_) => self.frame_stacks.last().unwrap(), + match self.thread_state { + ThreadState::Main => &self.frame_stacks[0], + ThreadState::CoThread(_) => self.frame_stacks.last().unwrap(), } } fn get_frame_stacks(&self) -> Vec<&[StackFrame]> { self.frame_stacks .iter() - .map(|v: &Vec| v.as_slice()) + .map(|v: &Vec<_>| v.as_slice()) .collect() } diff --git a/arbitrator/prover/src/wavm.rs b/arbitrator/prover/src/wavm.rs index 6f6b8137b..2507ff403 100644 --- a/arbitrator/prover/src/wavm.rs +++ b/arbitrator/prover/src/wavm.rs @@ -171,7 +171,7 @@ pub enum Opcode { NewCoThread, /// pop cothread (cannot be called from cothread) PopCoThread, - /// switch to/from create cothread + /// switch between main and a cothread SwitchThread, } diff --git a/arbitrator/prover/test-cases/go/main.go b/arbitrator/prover/test-cases/go/main.go index d886bd591..ebfe1496c 100644 --- a/arbitrator/prover/test-cases/go/main.go +++ b/arbitrator/prover/test-cases/go/main.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package main diff --git a/arbitrator/stylus/Cargo.toml b/arbitrator/stylus/Cargo.toml index f57f200f3..01867e307 100644 --- a/arbitrator/stylus/Cargo.toml +++ b/arbitrator/stylus/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] arbutil = { path = "../arbutil/" } +caller-env = { path = "../caller-env", features = ["wasmer_traits"] } prover = { path = "../prover/", default-features = false, features = ["native"] } wasmer = { path = "../tools/wasmer/lib/api" } wasmer-vm = { path = "../tools/wasmer/lib/vm/" } diff --git a/arbitrator/stylus/cbindgen.toml b/arbitrator/stylus/cbindgen.toml index 62a040040..b9afbe840 100644 --- a/arbitrator/stylus/cbindgen.toml +++ b/arbitrator/stylus/cbindgen.toml @@ -10,4 +10,4 @@ extra_bindings = ["arbutil", "prover"] prefix_with_name = true [export] -include = ["EvmApiMethod", "EvmApiMethodOffset"] \ No newline at end of file +include = ["EvmApiMethod"] diff --git a/arbitrator/stylus/src/env.rs b/arbitrator/stylus/src/env.rs index 6599026b8..5922d84b0 100644 --- a/arbitrator/stylus/src/env.rs +++ b/arbitrator/stylus/src/env.rs @@ -8,6 +8,7 @@ use arbutil::{ }, pricing, }; +use caller_env::GuestPtr; use derivative::Derivative; use eyre::{eyre, ErrReport}; use prover::programs::{config::PricingParams, meter::OutOfInkError, prelude::*}; @@ -160,7 +161,7 @@ impl<'a, D: DataReader, E: EvmApi> HostioInfo<'a, D, E> { } // TODO: use the unstable array_assum_init - pub fn read_fixed(&self, ptr: u32) -> Result<[u8; N], MemoryAccessError> { + pub fn read_fixed(&self, ptr: GuestPtr) -> Result<[u8; N], MemoryAccessError> { let mut data = [MaybeUninit::uninit(); N]; self.view().read_uninit(ptr.into(), &mut data)?; Ok(data.map(|x| unsafe { x.assume_init() })) diff --git a/arbitrator/stylus/src/evm_api.rs b/arbitrator/stylus/src/evm_api.rs index d31fefac2..752410d32 100644 --- a/arbitrator/stylus/src/evm_api.rs +++ b/arbitrator/stylus/src/evm_api.rs @@ -1,7 +1,7 @@ -// Copyright 2022-2023, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -use crate::{GoSliceData, RustSlice, SendGoSliceData}; +use crate::{GoSliceData, RustSlice}; use arbutil::evm::{ api::{EvmApiMethod, EvmApiStatus, EVM_API_METHOD_REQ_OFFSET}, req::RequestHandler, @@ -15,8 +15,8 @@ pub struct NativeRequestHandler { data: *mut RustSlice, gas_cost: *mut u64, result: *mut GoSliceData, - raw_data: *mut SendGoSliceData, - ) -> EvmApiStatus, // value + raw_data: *mut GoSliceData, + ) -> EvmApiStatus, pub id: usize, } @@ -26,14 +26,14 @@ macro_rules! ptr { }; } -impl RequestHandler for NativeRequestHandler { +impl RequestHandler for NativeRequestHandler { fn handle_request( &mut self, req_type: EvmApiMethod, req_data: &[u8], - ) -> (Vec, SendGoSliceData, u64) { - let mut result = GoSliceData::default(); - let mut raw_data = SendGoSliceData::default(); + ) -> (Vec, GoSliceData, u64) { + let mut result = GoSliceData::null(); + let mut raw_data = GoSliceData::null(); let mut cost = 0; let status = unsafe { (self.handle_request_fptr)( diff --git a/arbitrator/stylus/src/host.rs b/arbitrator/stylus/src/host.rs index cc738ddf1..c7a038818 100644 --- a/arbitrator/stylus/src/host.rs +++ b/arbitrator/stylus/src/host.rs @@ -3,22 +3,29 @@ #![allow(clippy::too_many_arguments)] -use std::fmt::Display; - use crate::env::{Escape, HostioInfo, MaybeEscape, WasmEnv, WasmEnvMut}; use arbutil::{ evm::{ api::{DataReader, EvmApi}, EvmData, }, - Bytes20, Bytes32, Color, + Color, }; -use eyre::{eyre, Result}; +use caller_env::GuestPtr; +use eyre::Result; use prover::value::Value; +use std::{ + fmt::Display, + mem::{self, MaybeUninit}, +}; use user_host_trait::UserHost; use wasmer::{MemoryAccessError, WasmPtr}; -impl<'a, DR: DataReader, A: EvmApi> UserHost for HostioInfo<'a, DR, A> { +impl<'a, DR, A> UserHost for HostioInfo<'a, DR, A> +where + DR: DataReader, + A: EvmApi, +{ type Err = Escape; type MemoryErr = MemoryAccessError; type A = A; @@ -43,39 +50,31 @@ impl<'a, DR: DataReader, A: EvmApi> UserHost for HostioInfo<'a, DR, A> { &mut self.evm_data.return_data_len } - fn read_bytes20(&self, ptr: u32) -> Result { - let data = self.read_fixed(ptr)?; - Ok(data.into()) - } - - fn read_bytes32(&self, ptr: u32) -> Result { - let data = self.read_fixed(ptr)?; - Ok(data.into()) + fn read_fixed( + &self, + ptr: GuestPtr, + ) -> std::result::Result<[u8; N], Self::MemoryErr> { + HostioInfo::read_fixed(self, ptr) } - fn read_slice(&self, ptr: u32, len: u32) -> Result, Self::MemoryErr> { - let mut data = vec![0; len as usize]; - self.view().read(ptr.into(), &mut data)?; - Ok(data) + fn read_slice(&self, ptr: GuestPtr, len: u32) -> Result, Self::MemoryErr> { + let len = len as usize; + let mut data: Vec> = Vec::with_capacity(len); + // SAFETY: read_uninit fills all available space + unsafe { + data.set_len(len); + self.view().read_uninit(ptr.into(), &mut data)?; + Ok(mem::transmute(data)) + } } - fn write_u32(&mut self, ptr: u32, x: u32) -> Result<(), Self::MemoryErr> { - let ptr: WasmPtr = WasmPtr::new(ptr); + fn write_u32(&mut self, ptr: GuestPtr, x: u32) -> Result<(), Self::MemoryErr> { + let ptr: WasmPtr = WasmPtr::new(ptr.into()); ptr.deref(&self.view()).write(x)?; Ok(()) } - fn write_bytes20(&self, ptr: u32, src: Bytes20) -> Result<(), Self::MemoryErr> { - self.write_slice(ptr, &src.0)?; - Ok(()) - } - - fn write_bytes32(&self, ptr: u32, src: Bytes32) -> Result<(), Self::MemoryErr> { - self.write_slice(ptr, &src.0)?; - Ok(()) - } - - fn write_slice(&self, ptr: u32, src: &[u8]) -> Result<(), Self::MemoryErr> { + fn write_slice(&self, ptr: GuestPtr, src: &[u8]) -> Result<(), Self::MemoryErr> { self.view().write(ptr.into(), src) } @@ -83,17 +82,11 @@ impl<'a, DR: DataReader, A: EvmApi> UserHost for HostioInfo<'a, DR, A> { println!("{} {text}", "Stylus says:".yellow()); } - fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], start_ink: u64, end_ink: u64) { + fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], end_ink: u64) { + let start_ink = self.start_ink; self.evm_api .capture_hostio(name, args, outs, start_ink, end_ink); } - - fn start_ink(&self) -> Result { - if !self.env.evm_data.tracing { - return Err(eyre!("recording start ink when not captured").into()); - } - Ok(self.start_ink) - } } macro_rules! hostio { @@ -104,14 +97,14 @@ macro_rules! hostio { pub(crate) fn read_args>( mut env: WasmEnvMut, - ptr: u32, + ptr: GuestPtr, ) -> MaybeEscape { hostio!(env, read_args(ptr)) } pub(crate) fn write_result>( mut env: WasmEnvMut, - ptr: u32, + ptr: GuestPtr, len: u32, ) -> MaybeEscape { hostio!(env, write_result(ptr, len)) @@ -119,28 +112,28 @@ pub(crate) fn write_result>( pub(crate) fn storage_load_bytes32>( mut env: WasmEnvMut, - key: u32, - dest: u32, + key: GuestPtr, + dest: GuestPtr, ) -> MaybeEscape { hostio!(env, storage_load_bytes32(key, dest)) } pub(crate) fn storage_store_bytes32>( mut env: WasmEnvMut, - key: u32, - value: u32, + key: GuestPtr, + value: GuestPtr, ) -> MaybeEscape { hostio!(env, storage_store_bytes32(key, value)) } pub(crate) fn call_contract>( mut env: WasmEnvMut, - contract: u32, - data: u32, + contract: GuestPtr, + data: GuestPtr, data_len: u32, - value: u32, + value: GuestPtr, gas: u64, - ret_len: u32, + ret_len: GuestPtr, ) -> Result { hostio!( env, @@ -150,11 +143,11 @@ pub(crate) fn call_contract>( pub(crate) fn delegate_call_contract>( mut env: WasmEnvMut, - contract: u32, - data: u32, + contract: GuestPtr, + data: GuestPtr, data_len: u32, gas: u64, - ret_len: u32, + ret_len: GuestPtr, ) -> Result { hostio!( env, @@ -164,11 +157,11 @@ pub(crate) fn delegate_call_contract>( pub(crate) fn static_call_contract>( mut env: WasmEnvMut, - contract: u32, - data: u32, + contract: GuestPtr, + data: GuestPtr, data_len: u32, gas: u64, - ret_len: u32, + ret_len: GuestPtr, ) -> Result { hostio!( env, @@ -178,11 +171,11 @@ pub(crate) fn static_call_contract>( pub(crate) fn create1>( mut env: WasmEnvMut, - code: u32, + code: GuestPtr, code_len: u32, - endowment: u32, - contract: u32, - revert_len: u32, + endowment: GuestPtr, + contract: GuestPtr, + revert_len: GuestPtr, ) -> MaybeEscape { hostio!( env, @@ -192,12 +185,12 @@ pub(crate) fn create1>( pub(crate) fn create2>( mut env: WasmEnvMut, - code: u32, + code: GuestPtr, code_len: u32, - endowment: u32, - salt: u32, - contract: u32, - revert_len: u32, + endowment: GuestPtr, + salt: GuestPtr, + contract: GuestPtr, + revert_len: GuestPtr, ) -> MaybeEscape { hostio!( env, @@ -207,7 +200,7 @@ pub(crate) fn create2>( pub(crate) fn read_return_data>( mut env: WasmEnvMut, - dest: u32, + dest: GuestPtr, offset: u32, size: u32, ) -> Result { @@ -222,7 +215,7 @@ pub(crate) fn return_data_size>( pub(crate) fn emit_log>( mut env: WasmEnvMut, - data: u32, + data: GuestPtr, len: u32, topics: u32, ) -> MaybeEscape { @@ -231,47 +224,47 @@ pub(crate) fn emit_log>( pub(crate) fn account_balance>( mut env: WasmEnvMut, - address: u32, - ptr: u32, + address: GuestPtr, + ptr: GuestPtr, ) -> MaybeEscape { hostio!(env, account_balance(address, ptr)) } pub(crate) fn account_code>( mut env: WasmEnvMut, - address: u32, + address: GuestPtr, offset: u32, size: u32, - code: u32, + code: GuestPtr, ) -> Result { hostio!(env, account_code(address, offset, size, code)) } pub(crate) fn account_code_size>( mut env: WasmEnvMut, - address: u32, + address: GuestPtr, ) -> Result { hostio!(env, account_code_size(address)) } pub(crate) fn account_codehash>( mut env: WasmEnvMut, - address: u32, - ptr: u32, + address: GuestPtr, + ptr: GuestPtr, ) -> MaybeEscape { hostio!(env, account_codehash(address, ptr)) } pub(crate) fn block_basefee>( mut env: WasmEnvMut, - ptr: u32, + ptr: GuestPtr, ) -> MaybeEscape { hostio!(env, block_basefee(ptr)) } pub(crate) fn block_coinbase>( mut env: WasmEnvMut, - ptr: u32, + ptr: GuestPtr, ) -> MaybeEscape { hostio!(env, block_coinbase(ptr)) } @@ -302,7 +295,7 @@ pub(crate) fn chainid>( pub(crate) fn contract_address>( mut env: WasmEnvMut, - ptr: u32, + ptr: GuestPtr, ) -> MaybeEscape { hostio!(env, contract_address(ptr)) } @@ -327,30 +320,30 @@ pub(crate) fn msg_reentrant>( pub(crate) fn msg_sender>( mut env: WasmEnvMut, - ptr: u32, + ptr: GuestPtr, ) -> MaybeEscape { hostio!(env, msg_sender(ptr)) } pub(crate) fn msg_value>( mut env: WasmEnvMut, - ptr: u32, + ptr: GuestPtr, ) -> MaybeEscape { hostio!(env, msg_value(ptr)) } pub(crate) fn native_keccak256>( mut env: WasmEnvMut, - input: u32, + input: GuestPtr, len: u32, - output: u32, + output: GuestPtr, ) -> MaybeEscape { hostio!(env, native_keccak256(input, len, output)) } pub(crate) fn tx_gas_price>( mut env: WasmEnvMut, - ptr: u32, + ptr: GuestPtr, ) -> MaybeEscape { hostio!(env, tx_gas_price(ptr)) } @@ -363,7 +356,7 @@ pub(crate) fn tx_ink_price>( pub(crate) fn tx_origin>( mut env: WasmEnvMut, - ptr: u32, + ptr: GuestPtr, ) -> MaybeEscape { hostio!(env, tx_origin(ptr)) } @@ -377,7 +370,7 @@ pub(crate) fn pay_for_memory_grow>( pub(crate) fn console_log_text>( mut env: WasmEnvMut, - ptr: u32, + ptr: GuestPtr, len: u32, ) -> MaybeEscape { hostio!(env, console_log_text(ptr, len)) diff --git a/arbitrator/stylus/src/lib.rs b/arbitrator/stylus/src/lib.rs index 2835ee246..2e681725f 100644 --- a/arbitrator/stylus/src/lib.rs +++ b/arbitrator/stylus/src/lib.rs @@ -1,5 +1,6 @@ // Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + use arbutil::{ evm::{ api::DataReader, @@ -15,7 +16,7 @@ use eyre::ErrReport; use native::NativeInstance; use prover::programs::{prelude::*, StylusData}; use run::RunProgram; -use std::{marker::PhantomData, mem, ptr::null}; +use std::{marker::PhantomData, mem, ptr}; pub use prover; @@ -34,20 +35,23 @@ mod benchmarks; #[derive(Clone, Copy)] #[repr(C)] pub struct GoSliceData { - ptr: *const u8, // stored as pointer for GO + /// Points to data owned by Go. + ptr: *const u8, + /// The length in bytes. len: usize, } -impl Default for GoSliceData { - fn default() -> Self { - GoSliceData { - ptr: null(), +/// The data we're pointing to is owned by Go and has a lifetime no shorter than the current program. +unsafe impl Send for GoSliceData {} + +impl GoSliceData { + pub fn null() -> Self { + Self { + ptr: ptr::null(), len: 0, } } -} -impl GoSliceData { fn slice(&self) -> &[u8] { if self.len == 0 { return &[]; @@ -56,32 +60,12 @@ impl GoSliceData { } } -// same as above, with Send semantics using dirty trickery -// GO will always use GoSliceData so these types must have -// exact same representation, see assert_go_slices_match -#[derive(Clone, Copy, Default)] -#[repr(C)] -pub struct SendGoSliceData { - ptr: usize, // not stored as pointer because rust won't let that be Send - len: usize, -} - -#[allow(dead_code)] -const fn assert_go_slices_match() -> () { - // TODO: this will be stabilized on rust 1.77 - // assert_eq!(mem::offset_of!(GoSliceData, ptr), mem::offset_of!(SendGoSliceData, ptr)); - // assert_eq!(mem::offset_of!(GoSliceData, len), mem::offset_of!(SendGoSliceData, len)); - assert!(mem::size_of::() == mem::size_of::()); -} - -const _: () = assert_go_slices_match(); - -impl DataReader for SendGoSliceData { +impl DataReader for GoSliceData { fn slice(&self) -> &[u8] { if self.len == 0 { return &[]; } - unsafe { std::slice::from_raw_parts(self.ptr as *const u8, self.len) } + unsafe { std::slice::from_raw_parts(self.ptr, self.len) } } } @@ -110,16 +94,6 @@ pub struct RustBytes { } impl RustBytes { - fn new(vec: Vec) -> Self { - let mut rust_vec = Self { - ptr: std::ptr::null_mut(), - len: 0, - cap: 0, - }; - unsafe { rust_vec.write(vec) }; - rust_vec - } - unsafe fn into_vec(self) -> Vec { Vec::from_raw_parts(self.ptr, self.len, self.cap) } @@ -205,14 +179,13 @@ pub unsafe extern "C" fn stylus_call( let module = module.slice(); let calldata = calldata.slice().to_vec(); let compile = CompileConfig::version(config.version, debug_chain != 0); + let evm_api = EvmApiRequestor::new(req_handler); let pricing = config.pricing; let output = &mut *output; let ink = pricing.gas_to_ink(*gas); // Safety: module came from compile_user_wasm and we've paid for memory expansion - let instance = unsafe { - NativeInstance::deserialize(module, compile, EvmApiRequestor::new(req_handler), evm_data) - }; + let instance = unsafe { NativeInstance::deserialize(module, compile, evm_api, evm_data) }; let mut instance = match instance { Ok(instance) => instance, Err(error) => panic!("failed to instantiate program: {error:?}"), @@ -241,17 +214,3 @@ pub unsafe extern "C" fn stylus_drop_vec(vec: RustBytes) { mem::drop(vec.into_vec()) } } - -/// Overwrites the bytes of the vector. -/// -/// # Safety -/// -/// `rust` must not be null. -#[no_mangle] -pub unsafe extern "C" fn stylus_vec_set_bytes(rust: *mut RustBytes, data: GoSliceData) { - let rust = &mut *rust; - let mut vec = Vec::from_raw_parts(rust.ptr, rust.len, rust.cap); - vec.clear(); - vec.extend(data.slice()); - rust.write(vec); -} diff --git a/arbitrator/stylus/src/run.rs b/arbitrator/stylus/src/run.rs index e7cac9051..3b20aba82 100644 --- a/arbitrator/stylus/src/run.rs +++ b/arbitrator/stylus/src/run.rs @@ -107,13 +107,13 @@ impl> RunProgram for NativeInstance { } }; - let env = self.env.as_mut(store); + let env = self.env_mut(); if env.evm_data.tracing { env.evm_api .capture_hostio("user_returned", &[], &status.to_be_bytes(), ink, ink); } - let outs = self.env().outs.clone(); + let outs = env.outs.clone(); Ok(match status { 0 => UserOutcome::Success(outs), _ => UserOutcome::Revert(outs), diff --git a/arbitrator/wasm-libraries/Cargo.lock b/arbitrator/wasm-libraries/Cargo.lock index 114843201..67beb7c93 100644 --- a/arbitrator/wasm-libraries/Cargo.lock +++ b/arbitrator/wasm-libraries/Cargo.lock @@ -107,7 +107,8 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" name = "brotli" version = "0.1.0" dependencies = [ - "arbutil", + "caller-env", + "paste", ] [[package]] @@ -132,6 +133,21 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "caller-env" +version = "0.1.0" +dependencies = [ + "num_enum", + "rand", + "rand_pcg", +] + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -365,7 +381,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "wasi", ] @@ -413,7 +429,7 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" name = "host-io" version = "0.1.0" dependencies = [ - "arbutil", + "caller-env", ] [[package]] @@ -516,6 +532,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -638,18 +660,18 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683751d591e6d81200c39fb0d1032608b77724f34114db54f571ff1317b337c0" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c11e44798ad209ccdd91fc192f0526a369a01234f7373e1b141c96d7cee4f0e" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" dependencies = [ "proc-macro-crate", "proc-macro2", @@ -685,13 +707,19 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", "windows-sys", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "percent-encoding" version = "2.3.0" @@ -744,13 +772,6 @@ dependencies = [ [[package]] name = "program-exec" version = "0.1.0" -dependencies = [ - "arbutil", - "eyre", - "fnv", - "hex", - "prover", -] [[package]] name = "prover" @@ -1202,11 +1223,13 @@ name = "user-host" version = "0.1.0" dependencies = [ "arbutil", + "caller-env", "eyre", "fnv", "hex", "prover", "user-host-trait", + "wasmer-types", ] [[package]] @@ -1214,6 +1237,7 @@ name = "user-host-trait" version = "0.1.0" dependencies = [ "arbutil", + "caller-env", "eyre", "prover", ] @@ -1223,6 +1247,7 @@ name = "user-test" version = "0.1.0" dependencies = [ "arbutil", + "caller-env", "eyre", "fnv", "hex", @@ -1259,8 +1284,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" name = "wasi-stub" version = "0.1.0" dependencies = [ - "rand", - "rand_pcg", + "caller-env", + "paste", + "wee_alloc", ] [[package]] @@ -1317,6 +1343,18 @@ dependencies = [ "wast", ] +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/arbitrator/wasm-libraries/Cargo.toml b/arbitrator/wasm-libraries/Cargo.toml index aae12537f..e9e5aa4b8 100644 --- a/arbitrator/wasm-libraries/Cargo.toml +++ b/arbitrator/wasm-libraries/Cargo.toml @@ -4,9 +4,9 @@ members = [ "wasi-stub", "host-io", "user-host", - "user-host-trait", + "user-host-trait", "user-test", "program-exec", - "forward", + "forward", ] resolver = "2" diff --git a/arbitrator/wasm-libraries/brotli/Cargo.toml b/arbitrator/wasm-libraries/brotli/Cargo.toml index 779929a04..640db7230 100644 --- a/arbitrator/wasm-libraries/brotli/Cargo.toml +++ b/arbitrator/wasm-libraries/brotli/Cargo.toml @@ -8,4 +8,5 @@ publish = false crate-type = ["cdylib"] [dependencies] -arbutil = { path = "../../arbutil", features = ["wavm"] } +paste = { version = "1.0.14" } +caller-env = { path = "../../caller-env/", features = ["static_caller"] } diff --git a/arbitrator/wasm-libraries/brotli/src/lib.rs b/arbitrator/wasm-libraries/brotli/src/lib.rs index 76715eba6..86456bfec 100644 --- a/arbitrator/wasm-libraries/brotli/src/lib.rs +++ b/arbitrator/wasm-libraries/brotli/src/lib.rs @@ -1,91 +1,30 @@ -// Copyright 2021-2023, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -use arbutil::wavm; - -extern "C" { - pub fn BrotliDecoderDecompress( - encoded_size: usize, - encoded_buffer: *const u8, - decoded_size: *mut usize, - decoded_buffer: *mut u8, - ) -> BrotliStatus; - - pub fn BrotliEncoderCompress( - quality: u32, - lgwin: u32, - mode: u32, - input_size: usize, - input_buffer: *const u8, - encoded_size: *mut usize, - encoded_buffer: *mut u8, - ) -> BrotliStatus; -} - -const BROTLI_MODE_GENERIC: u32 = 0; - -type Uptr = usize; - -#[derive(PartialEq)] -#[repr(u32)] -pub enum BrotliStatus { - Failure, - Success, -} - -/// Brotli decompresses a go slice -/// -/// # Safety -/// -/// The output buffer must be sufficiently large enough. -#[no_mangle] -pub unsafe extern "C" fn arbcompress__brotliDecompress(in_buf_ptr: Uptr, in_buf_len: usize, out_buf_ptr: Uptr, out_len_ptr: Uptr) -> BrotliStatus { - let in_slice = wavm::read_slice_usize(in_buf_ptr, in_buf_len); - let orig_output_len = wavm::caller_load32(out_len_ptr) as usize; - let mut output = vec![0u8; orig_output_len as usize]; - let mut output_len = orig_output_len; - let res = BrotliDecoderDecompress( - in_buf_len as usize, - in_slice.as_ptr(), - &mut output_len, - output.as_mut_ptr(), - ); - if (res != BrotliStatus::Success) || (output_len > orig_output_len) { - return BrotliStatus::Failure; - } - wavm::write_slice_usize(&output[..output_len], out_buf_ptr); - wavm::caller_store32(out_len_ptr, output_len as u32); - BrotliStatus::Success +#![allow(clippy::missing_safety_doc)] // TODO: add safety docs + +use caller_env::{self, brotli::BrotliStatus, GuestPtr}; +use paste::paste; + +macro_rules! wrap { + ($(fn $func_name:ident ($($arg_name:ident : $arg_type:ty),* ) -> $return_type:ty);*) => { + paste! { + $( + #[no_mangle] + pub unsafe extern "C" fn []($($arg_name : $arg_type),*) -> $return_type { + caller_env::brotli::$func_name( + &mut caller_env::static_caller::STATIC_MEM, + &mut caller_env::static_caller::STATIC_ENV, + $($arg_name),* + ) + } + )* + } + }; } -/// Brotli compresses a go slice -/// -/// # Safety -/// -/// The go side has the following signature, which must be respected. -/// λ(inBuf []byte, outBuf []byte, level, windowSize uint64) (outLen uint64, status BrotliStatus) -/// -/// The output buffer must be sufficiently large enough. -#[no_mangle] -pub unsafe extern "C" fn arbcompress__brotliCompress(in_buf_ptr: Uptr, in_buf_len: usize, out_buf_ptr: Uptr, out_len_ptr: Uptr, level: u32, window_size: u32) -> BrotliStatus { - let in_slice = wavm::read_slice_usize(in_buf_ptr, in_buf_len); - let orig_output_len = wavm::caller_load32(out_len_ptr) as usize; - let mut output = vec![0u8; orig_output_len]; - let mut output_len = orig_output_len; +wrap! { + fn brotli_decompress(in_buf_ptr: GuestPtr, in_buf_len: u32, out_buf_ptr: GuestPtr, out_len_ptr: GuestPtr) -> BrotliStatus; - let res = BrotliEncoderCompress( - level, - window_size, - BROTLI_MODE_GENERIC, - in_buf_len as usize, - in_slice.as_ptr(), - &mut output_len, - output.as_mut_ptr(), - ); - if (res != BrotliStatus::Success) || (output_len > orig_output_len) { - return BrotliStatus::Failure; - } - wavm::write_slice_usize(&output[..output_len], out_buf_ptr); - wavm::caller_store32(out_len_ptr, output_len as u32); - BrotliStatus::Success + fn brotli_compress(in_buf_ptr: GuestPtr, in_buf_len: u32, out_buf_ptr: GuestPtr, out_len_ptr: GuestPtr, level: u32, window_size: u32) -> BrotliStatus } diff --git a/arbitrator/wasm-libraries/host-io/Cargo.toml b/arbitrator/wasm-libraries/host-io/Cargo.toml index 5ad58b560..1743b1017 100644 --- a/arbitrator/wasm-libraries/host-io/Cargo.toml +++ b/arbitrator/wasm-libraries/host-io/Cargo.toml @@ -8,4 +8,4 @@ publish = false crate-type = ["cdylib"] [dependencies] -arbutil = { path = "../../arbutil/", features = ["wavm"] } +caller-env = { path = "../../caller-env/", features = ["static_caller"] } diff --git a/arbitrator/wasm-libraries/host-io/src/lib.rs b/arbitrator/wasm-libraries/host-io/src/lib.rs index 246294672..90c735c8f 100644 --- a/arbitrator/wasm-libraries/host-io/src/lib.rs +++ b/arbitrator/wasm-libraries/host-io/src/lib.rs @@ -1,7 +1,10 @@ -// Copyright 2021-2023, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -use arbutil::wavm; +#![allow(clippy::missing_safety_doc)] // TODO: add safety docs + +use caller_env::{static_caller::STATIC_MEM, GuestPtr, MemAccess}; +use core::ops::{Deref, DerefMut, Index, RangeTo}; extern "C" { pub fn wavm_get_globalstate_bytes32(idx: u32, ptr: *mut u8); @@ -16,31 +19,52 @@ extern "C" { #[repr(C, align(256))] struct MemoryLeaf([u8; 32]); -type Uptr = usize; +impl Deref for MemoryLeaf { + type Target = [u8; 32]; + + fn deref(&self) -> &[u8; 32] { + &self.0 + } +} + +impl DerefMut for MemoryLeaf { + fn deref_mut(&mut self) -> &mut [u8; 32] { + &mut self.0 + } +} + +impl Index> for MemoryLeaf { + type Output = [u8]; + + fn index(&self, index: RangeTo) -> &[u8] { + &self.0[index] + } +} #[no_mangle] -pub unsafe extern "C" fn wavmio__getGlobalStateBytes32(idx: u32, out_ptr: Uptr) { +pub unsafe extern "C" fn wavmio__getGlobalStateBytes32(idx: u32, out_ptr: GuestPtr) { let mut our_buf = MemoryLeaf([0u8; 32]); - let our_ptr = our_buf.0.as_mut_ptr(); + let our_ptr = our_buf.as_mut_ptr(); assert_eq!(our_ptr as usize % 32, 0); wavm_get_globalstate_bytes32(idx, our_ptr); - wavm::write_slice_usize(&our_buf.0[..32], out_ptr); + STATIC_MEM.write_slice(out_ptr, &our_buf[..32]); } /// Writes 32-bytes of global state #[no_mangle] -pub unsafe extern "C" fn wavmio__setGlobalStateBytes32(idx: u32, src_ptr: Uptr) { +pub unsafe extern "C" fn wavmio__setGlobalStateBytes32(idx: u32, src_ptr: GuestPtr) { let mut our_buf = MemoryLeaf([0u8; 32]); - let value = wavm::read_slice_usize(src_ptr, 32); - our_buf.0.copy_from_slice(&value); - let our_ptr = our_buf.0.as_ptr(); + let value = STATIC_MEM.read_slice(src_ptr, 32); + our_buf.copy_from_slice(&value); + + let our_ptr = our_buf.as_ptr(); assert_eq!(our_ptr as usize % 32, 0); wavm_set_globalstate_bytes32(idx, our_ptr); } /// Reads 8-bytes of global state #[no_mangle] -pub unsafe extern "C" fn wavmio__getGlobalStateU64(idx: u32) -> u64{ +pub unsafe extern "C" fn wavmio__getGlobalStateU64(idx: u32) -> u64 { wavm_get_globalstate_u64(idx) } @@ -52,38 +76,54 @@ pub unsafe extern "C" fn wavmio__setGlobalStateU64(idx: u32, val: u64) { /// Reads an inbox message #[no_mangle] -pub unsafe extern "C" fn wavmio__readInboxMessage(msg_num: u64, offset: usize, out_ptr: Uptr) -> usize { +pub unsafe extern "C" fn wavmio__readInboxMessage( + msg_num: u64, + offset: usize, + out_ptr: GuestPtr, +) -> usize { let mut our_buf = MemoryLeaf([0u8; 32]); - let our_ptr = our_buf.0.as_mut_ptr(); + let our_ptr = our_buf.as_mut_ptr(); assert_eq!(our_ptr as usize % 32, 0); + let read = wavm_read_inbox_message(msg_num, our_ptr, offset); assert!(read <= 32); - wavm::write_slice_usize(&our_buf.0[..read], out_ptr); + STATIC_MEM.write_slice(out_ptr, &our_buf[..read]); read } /// Reads a delayed inbox message #[no_mangle] -pub unsafe extern "C" fn wavmio__readDelayedInboxMessage(msg_num: u64, offset: usize, out_ptr: Uptr) -> usize { +pub unsafe extern "C" fn wavmio__readDelayedInboxMessage( + msg_num: u64, + offset: usize, + out_ptr: GuestPtr, +) -> usize { let mut our_buf = MemoryLeaf([0u8; 32]); - let our_ptr = our_buf.0.as_mut_ptr(); + let our_ptr = our_buf.as_mut_ptr(); assert_eq!(our_ptr as usize % 32, 0); - let read = wavm_read_delayed_inbox_message(msg_num, our_ptr, offset as usize); + + let read = wavm_read_delayed_inbox_message(msg_num, our_ptr, offset); assert!(read <= 32); - wavm::write_slice_usize(&our_buf.0[..read], out_ptr); + STATIC_MEM.write_slice(out_ptr, &our_buf[..read]); read } /// Retrieves the preimage of the given hash. #[no_mangle] -pub unsafe extern "C" fn wavmio__resolvePreImage(hash_ptr: Uptr, offset: usize, out_ptr: Uptr) -> usize { +pub unsafe extern "C" fn wavmio__resolvePreImage( + hash_ptr: GuestPtr, + offset: usize, + out_ptr: GuestPtr, +) -> usize { let mut our_buf = MemoryLeaf([0u8; 32]); - let hash = wavm::read_slice_usize(hash_ptr, 32); - our_buf.0.copy_from_slice(&hash); - let our_ptr = our_buf.0.as_mut_ptr(); + let hash = STATIC_MEM.read_slice(hash_ptr, 32); + our_buf.copy_from_slice(&hash); + + let our_ptr = our_buf.as_mut_ptr(); assert_eq!(our_ptr as usize % 32, 0); + let read = wavm_read_pre_image(our_ptr, offset); assert!(read <= 32); - wavm::write_slice_usize(&our_buf.0[..read], out_ptr); + STATIC_MEM.write_slice(out_ptr, &our_buf[..read]); read } diff --git a/arbitrator/wasm-libraries/program-exec/Cargo.toml b/arbitrator/wasm-libraries/program-exec/Cargo.toml index 701b66799..d45f5fe61 100644 --- a/arbitrator/wasm-libraries/program-exec/Cargo.toml +++ b/arbitrator/wasm-libraries/program-exec/Cargo.toml @@ -7,8 +7,3 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -arbutil = { path = "../../arbutil/", features = ["wavm"] } -prover = { path = "../../prover/", default-features = false } -eyre = "0.6.5" -fnv = "1.0.7" -hex = "0.4.3" diff --git a/arbitrator/wasm-libraries/program-exec/src/lib.rs b/arbitrator/wasm-libraries/program-exec/src/lib.rs index 98b3123f0..841da1349 100644 --- a/arbitrator/wasm-libraries/program-exec/src/lib.rs +++ b/arbitrator/wasm-libraries/program-exec/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE #[link(wasm_import_module = "hostio")] @@ -42,21 +42,17 @@ fn check_program_done(mut req_id: u32) -> u32 { /// module MUST match last module number returned from new_program /// returns request_id for the first request from the program #[no_mangle] -pub unsafe extern "C" fn programs__start_program( - module: u32, -) -> u32 { +pub unsafe extern "C" fn programs__start_program(module: u32) -> u32 { // call the program let args_len = args_len(module); check_program_done(program_call_main(module, args_len)) } -// sends previos response and transfers control to program +// sends previous response and transfers control to program // MUST be called right after set_response to the same id // returns request_id for the next request #[no_mangle] -pub unsafe extern "C" fn programs__send_response( - req_id: u32, -) -> u32 { +pub unsafe extern "C" fn programs__send_response(req_id: u32) -> u32 { // call the program check_program_done(program_continue(req_id)) } diff --git a/arbitrator/wasm-libraries/user-host-trait/Cargo.toml b/arbitrator/wasm-libraries/user-host-trait/Cargo.toml index b9f971220..7e40868f4 100644 --- a/arbitrator/wasm-libraries/user-host-trait/Cargo.toml +++ b/arbitrator/wasm-libraries/user-host-trait/Cargo.toml @@ -5,5 +5,6 @@ edition = "2021" [dependencies] arbutil = { path = "../../arbutil/" } +caller-env = { path = "../../caller-env/" } prover = { path = "../../prover/", default-features = false } eyre = "0.6.5" diff --git a/arbitrator/wasm-libraries/user-host-trait/src/lib.rs b/arbitrator/wasm-libraries/user-host-trait/src/lib.rs index 8f13c59ba..cd9c35653 100644 --- a/arbitrator/wasm-libraries/user-host-trait/src/lib.rs +++ b/arbitrator/wasm-libraries/user-host-trait/src/lib.rs @@ -12,6 +12,7 @@ use arbutil::{ pricing::{EVM_API_INK, HOSTIO_INK, PTR_INK}, Bytes20, Bytes32, }; +pub use caller_env::GuestPtr; use eyre::{eyre, Result}; use prover::{ programs::{meter::OutOfInkError, prelude::*}, @@ -29,12 +30,11 @@ macro_rules! trace { ($name:expr, $env:expr, [$($args:expr),+], [$($outs:expr),+], $ret:expr) => {{ if $env.evm_data().tracing { let end_ink = $env.ink_ready()?; - let start_ink = $env.start_ink()?; let mut args = vec![]; $(args.extend($args);)* let mut outs = vec![]; $(outs.extend($outs);)* - $env.trace($name, &args, &outs, start_ink, end_ink); + $env.trace($name, &args, &outs, end_ink); } Ok($ret) }}; @@ -67,25 +67,36 @@ pub trait UserHost: GasMeteredMachine { fn evm_data(&self) -> &EvmData; fn evm_return_data_len(&mut self) -> &mut u32; - fn read_bytes20(&self, ptr: u32) -> Result; - fn read_bytes32(&self, ptr: u32) -> Result; - fn read_slice(&self, ptr: u32, len: u32) -> Result, Self::MemoryErr>; + fn read_slice(&self, ptr: GuestPtr, len: u32) -> Result, Self::MemoryErr>; + fn read_fixed(&self, ptr: GuestPtr) -> Result<[u8; N], Self::MemoryErr>; - fn write_u32(&mut self, ptr: u32, x: u32) -> Result<(), Self::MemoryErr>; - fn write_bytes20(&self, ptr: u32, src: Bytes20) -> Result<(), Self::MemoryErr>; - fn write_bytes32(&self, ptr: u32, src: Bytes32) -> Result<(), Self::MemoryErr>; - fn write_slice(&self, ptr: u32, src: &[u8]) -> Result<(), Self::MemoryErr>; + fn write_u32(&mut self, ptr: GuestPtr, x: u32) -> Result<(), Self::MemoryErr>; + fn write_slice(&self, ptr: GuestPtr, src: &[u8]) -> Result<(), Self::MemoryErr>; + + fn read_bytes20(&self, ptr: GuestPtr) -> Result { + self.read_fixed(ptr).map(Into::into) + } + + fn read_bytes32(&self, ptr: GuestPtr) -> Result { + self.read_fixed(ptr).map(Into::into) + } - // ink when call stated, only used for tracing, Err if unavailable. - fn start_ink(&self) -> Result; fn say(&self, text: D); - fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], start_ink: u64, end_ink: u64); + fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], end_ink: u64); + + fn write_bytes20(&self, ptr: GuestPtr, src: Bytes20) -> Result<(), Self::MemoryErr> { + self.write_slice(ptr, &src.0) + } + + fn write_bytes32(&self, ptr: GuestPtr, src: Bytes32) -> Result<(), Self::MemoryErr> { + self.write_slice(ptr, &src.0) + } /// Reads the program calldata. The semantics are equivalent to that of the EVM's /// [`CALLDATA_COPY`] opcode when requesting the entirety of the current call's calldata. /// /// [`CALLDATA_COPY`]: https://www.evm.codes/#37 - fn read_args(&mut self, ptr: u32) -> Result<(), Self::Err> { + fn read_args(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { self.buy_ink(HOSTIO_INK)?; self.pay_for_write(self.args().len() as u32)?; self.write_slice(ptr, self.args())?; @@ -95,7 +106,7 @@ pub trait UserHost: GasMeteredMachine { /// Writes the final return data. If not called before the program exists, the return data will /// be 0 bytes long. Note that this hostio does not cause the program to exit, which happens /// naturally when `user_entrypoint` returns. - fn write_result(&mut self, ptr: u32, len: u32) -> Result<(), Self::Err> { + fn write_result(&mut self, ptr: GuestPtr, len: u32) -> Result<(), Self::Err> { self.buy_ink(HOSTIO_INK)?; self.pay_for_read(len)?; self.pay_for_geth_bytes(len)?; // returned after call @@ -109,7 +120,7 @@ pub trait UserHost: GasMeteredMachine { /// set. The semantics, then, are equivalent to that of the EVM's [`SLOAD`] opcode. /// /// [`SLOAD`]: https://www.evm.codes/#54 - fn storage_load_bytes32(&mut self, key: u32, dest: u32) -> Result<(), Self::Err> { + fn storage_load_bytes32(&mut self, key: GuestPtr, dest: GuestPtr) -> Result<(), Self::Err> { self.buy_ink(HOSTIO_INK + 2 * PTR_INK + EVM_API_INK)?; self.require_gas(evm::COLD_SLOAD_GAS)?; let key = self.read_bytes32(key)?; @@ -129,7 +140,7 @@ pub trait UserHost: GasMeteredMachine { /// may exceed this amount, but that's ok because the predominant cost is due to state bloat concerns. /// /// [`SSTORE`]: https://www.evm.codes/#55 - fn storage_store_bytes32(&mut self, key: u32, value: u32) -> Result<(), Self::Err> { + fn storage_store_bytes32(&mut self, key: GuestPtr, value: GuestPtr) -> Result<(), Self::Err> { self.buy_ink(HOSTIO_INK + 2 * PTR_INK + EVM_API_INK)?; self.require_gas(evm::SSTORE_SENTRY_GAS)?; // see operations_acl_arbitrum.go @@ -157,12 +168,12 @@ pub trait UserHost: GasMeteredMachine { /// [`CALL`]: https://www.evm.codes/#f1 fn call_contract( &mut self, - contract: u32, - data: u32, + contract: GuestPtr, + data: GuestPtr, data_len: u32, - value: u32, + value: GuestPtr, gas: u64, - ret_len: u32, + ret_len: GuestPtr, ) -> Result { let value = Some(value); let call = |api: &mut Self::A, contract, data: &_, gas, value: Option<_>| { @@ -187,11 +198,11 @@ pub trait UserHost: GasMeteredMachine { /// [`DELEGATE_CALL`]: https://www.evm.codes/#F4 fn delegate_call_contract( &mut self, - contract: u32, - data: u32, + contract: GuestPtr, + data: GuestPtr, data_len: u32, gas: u64, - ret_len: u32, + ret_len: GuestPtr, ) -> Result { let call = |api: &mut Self::A, contract, data: &_, gas, _| api.delegate_call(contract, data, gas); @@ -216,11 +227,11 @@ pub trait UserHost: GasMeteredMachine { /// [`STATIC_CALL`]: https://www.evm.codes/#FA fn static_call_contract( &mut self, - contract: u32, - data: u32, + contract: GuestPtr, + data: GuestPtr, data_len: u32, gas: u64, - ret_len: u32, + ret_len: GuestPtr, ) -> Result { let call = |api: &mut Self::A, contract, data: &_, gas, _| api.static_call(contract, data, gas); @@ -231,12 +242,12 @@ pub trait UserHost: GasMeteredMachine { /// Note that `value` must only be [`Some`] for normal calls. fn do_call( &mut self, - contract: u32, - calldata: u32, + contract: GuestPtr, + calldata: GuestPtr, calldata_len: u32, - value: Option, + value: Option, mut gas: u64, - return_data_len: u32, + return_data_len: GuestPtr, call: F, name: &str, ) -> Result @@ -293,11 +304,11 @@ pub trait UserHost: GasMeteredMachine { /// [`CREATE`]: https://www.evm.codes/#f0 fn create1( &mut self, - code: u32, + code: GuestPtr, code_len: u32, - endowment: u32, - contract: u32, - revert_data_len: u32, + endowment: GuestPtr, + contract: GuestPtr, + revert_data_len: GuestPtr, ) -> Result<(), Self::Err> { let call = |api: &mut Self::A, code, value, _, gas| api.create1(code, value, gas); self.do_create( @@ -330,12 +341,12 @@ pub trait UserHost: GasMeteredMachine { /// [`CREATE2`]: https://www.evm.codes/#f5 fn create2( &mut self, - code: u32, + code: GuestPtr, code_len: u32, - endowment: u32, - salt: u32, - contract: u32, - revert_data_len: u32, + endowment: GuestPtr, + salt: GuestPtr, + contract: GuestPtr, + revert_data_len: GuestPtr, ) -> Result<(), Self::Err> { let call = |api: &mut Self::A, code, value, salt: Option<_>, gas| { api.create2(code, value, salt.unwrap(), gas) @@ -359,12 +370,12 @@ pub trait UserHost: GasMeteredMachine { /// [`CREATE2`]: https://www.evm.codes/#f5 fn do_create( &mut self, - code: u32, + code: GuestPtr, code_len: u32, - endowment: u32, - salt: Option, - contract: u32, - revert_data_len: u32, + endowment: GuestPtr, + salt: Option, + contract: GuestPtr, + revert_data_len: GuestPtr, cost: u64, call: F, name: &str, @@ -402,23 +413,6 @@ pub trait UserHost: GasMeteredMachine { ) } - fn sub_slice(mut slice: &[u8], offset: u32, size: u32) -> &[u8] { - let slice_len = slice.len() as u32; - let out_size = if offset > slice_len { - 0 - } else if offset + size > slice_len { - slice_len - offset - } else { - size - }; - if out_size > 0 { - slice = &slice[offset as usize..(offset + out_size) as usize]; - } else { - slice = &[]; - } - slice - } - /// Copies the bytes of the last EVM call or deployment return result. Does not revert if out of /// bounds, but rather copies the overlapping portion. The semantics are otherwise equivalent /// to that of the EVM's [`RETURN_DATA_COPY`] opcode. @@ -426,7 +420,12 @@ pub trait UserHost: GasMeteredMachine { /// Returns the number of bytes written. /// /// [`RETURN_DATA_COPY`]: https://www.evm.codes/#3e - fn read_return_data(&mut self, dest: u32, offset: u32, size: u32) -> Result { + fn read_return_data( + &mut self, + dest: GuestPtr, + offset: u32, + size: u32, + ) -> Result { self.buy_ink(HOSTIO_INK + EVM_API_INK)?; // pay for only as many bytes as could possibly be written @@ -434,7 +433,9 @@ pub trait UserHost: GasMeteredMachine { self.pay_for_write(size.min(max))?; let ret_data = self.evm_api().get_return_data(); - let out_slice = Self::sub_slice(ret_data.slice(), offset, size); + let ret_data = ret_data.slice(); + let out_slice = arbutil::slice_with_runoff(&ret_data, offset, offset.saturating_add(size)); + let out_len = out_slice.len() as u32; if out_len > 0 { self.write_slice(dest, out_slice)?; @@ -469,7 +470,7 @@ pub trait UserHost: GasMeteredMachine { /// [`LOG2`]: https://www.evm.codes/#a2 /// [`LOG3`]: https://www.evm.codes/#a3 /// [`LOG4`]: https://www.evm.codes/#a4 - fn emit_log(&mut self, data: u32, len: u32, topics: u32) -> Result<(), Self::Err> { + fn emit_log(&mut self, data: GuestPtr, len: u32, topics: u32) -> Result<(), Self::Err> { self.buy_ink(HOSTIO_INK + EVM_API_INK)?; if topics > 4 || len < topics * 32 { Err(eyre!("bad topic data"))?; @@ -486,7 +487,7 @@ pub trait UserHost: GasMeteredMachine { /// The semantics are equivalent to that of the EVM's [`BALANCE`] opcode. /// /// [`BALANCE`]: https://www.evm.codes/#31 - fn account_balance(&mut self, address: u32, ptr: u32) -> Result<(), Self::Err> { + fn account_balance(&mut self, address: GuestPtr, ptr: GuestPtr) -> Result<(), Self::Err> { self.buy_ink(HOSTIO_INK + 2 * PTR_INK + EVM_API_INK)?; self.require_gas(evm::COLD_ACCOUNT_GAS)?; let address = self.read_bytes20(address)?; @@ -505,26 +506,27 @@ pub trait UserHost: GasMeteredMachine { /// [`EXT_CODE_COPY`]: https://www.evm.codes/#3C fn account_code( &mut self, - address: u32, + address: GuestPtr, offset: u32, size: u32, - dest: u32, + dest: GuestPtr, ) -> Result { self.buy_ink(HOSTIO_INK + EVM_API_INK)?; + self.require_gas(evm::COLD_ACCOUNT_GAS)?; // not necessary since we also check in Go + let address = self.read_bytes20(address)?; let gas = self.gas_left()?; // we pass `gas` to check if there's enough before loading from the db let (code, gas_cost) = self.evm_api().account_code(address, gas); - let code = code.slice(); self.buy_gas(gas_cost)?; - self.pay_for_write(size as u32)?; - let out_slice = Self::sub_slice(code, offset, size); + let code = code.slice(); + self.pay_for_write(code.len() as u32)?; + + let out_slice = arbutil::slice_with_runoff(&code, offset, offset.saturating_add(size)); let out_len = out_slice.len() as u32; - if out_len > 0 { - self.write_slice(dest, out_slice)?; - } + self.write_slice(dest, out_slice)?; trace!( "account_code", @@ -539,16 +541,17 @@ pub trait UserHost: GasMeteredMachine { /// to that of the EVM's [`EXT_CODESIZE`]. /// /// [`EXT_CODESIZE`]: https://www.evm.codes/#3B - fn account_code_size(&mut self, address: u32) -> Result { + fn account_code_size(&mut self, address: GuestPtr) -> Result { self.buy_ink(HOSTIO_INK + EVM_API_INK)?; + self.require_gas(evm::COLD_ACCOUNT_GAS)?; // not necessary since we also check in Go let address = self.read_bytes20(address)?; let gas = self.gas_left()?; // we pass `gas` to check if there's enough before loading from the db let (code, gas_cost) = self.evm_api().account_code(address, gas); self.buy_gas(gas_cost)?; - let code = code.slice(); + let code = code.slice(); trace!("account_code_size", self, address, &[], code.len() as u32) } @@ -558,7 +561,7 @@ pub trait UserHost: GasMeteredMachine { /// `keccak("") = c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470`. /// /// [`EXT_CODEHASH`]: https://www.evm.codes/#3F - fn account_codehash(&mut self, address: u32, ptr: u32) -> Result<(), Self::Err> { + fn account_codehash(&mut self, address: GuestPtr, ptr: GuestPtr) -> Result<(), Self::Err> { self.buy_ink(HOSTIO_INK + 2 * PTR_INK + EVM_API_INK)?; self.require_gas(evm::COLD_ACCOUNT_GAS)?; let address = self.read_bytes20(address)?; @@ -573,7 +576,7 @@ pub trait UserHost: GasMeteredMachine { /// [`BASEFEE`] opcode. /// /// [`BASEFEE`]: https://www.evm.codes/#48 - fn block_basefee(&mut self, ptr: u32) -> Result<(), Self::Err> { + fn block_basefee(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { self.buy_ink(HOSTIO_INK + PTR_INK)?; self.write_bytes32(ptr, self.evm_data().block_basefee)?; trace!("block_basefee", self, &[], self.evm_data().block_basefee) @@ -582,7 +585,7 @@ pub trait UserHost: GasMeteredMachine { /// Gets the coinbase of the current block, which on Arbitrum chains is the L1 batch poster's /// address. This differs from Ethereum where the validator including the transaction /// determines the coinbase. - fn block_coinbase(&mut self, ptr: u32) -> Result<(), Self::Err> { + fn block_coinbase(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { self.buy_ink(HOSTIO_INK + PTR_INK)?; self.write_bytes20(ptr, self.evm_data().block_coinbase)?; trace!("block_coinbase", self, &[], self.evm_data().block_coinbase) @@ -637,7 +640,7 @@ pub trait UserHost: GasMeteredMachine { /// [`ADDRESS`] opcode. /// /// [`ADDRESS`]: https://www.evm.codes/#30 - fn contract_address(&mut self, ptr: u32) -> Result<(), Self::Err> { + fn contract_address(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { self.buy_ink(HOSTIO_INK + PTR_INK)?; self.write_bytes20(ptr, self.evm_data().contract_address)?; trace!( @@ -687,7 +690,7 @@ pub trait UserHost: GasMeteredMachine { /// [`CALLER`]: https://www.evm.codes/#33 /// [`DELEGATE_CALL`]: https://www.evm.codes/#f4 /// [aliasing]: https://developer.arbitrum.io/arbos/l1-to-l2-messaging#address-aliasing - fn msg_sender(&mut self, ptr: u32) -> Result<(), Self::Err> { + fn msg_sender(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { self.buy_ink(HOSTIO_INK + PTR_INK)?; self.write_bytes20(ptr, self.evm_data().msg_sender)?; trace!("msg_sender", self, &[], self.evm_data().msg_sender) @@ -697,7 +700,7 @@ pub trait UserHost: GasMeteredMachine { /// EVM's [`CALLVALUE`] opcode. /// /// [`CALLVALUE`]: https://www.evm.codes/#34 - fn msg_value(&mut self, ptr: u32) -> Result<(), Self::Err> { + fn msg_value(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { self.buy_ink(HOSTIO_INK + PTR_INK)?; self.write_bytes32(ptr, self.evm_data().msg_value)?; trace!("msg_value", self, &[], self.evm_data().msg_value) @@ -708,7 +711,12 @@ pub trait UserHost: GasMeteredMachine { /// /// [`keccak256`]: https://en.wikipedia.org/wiki/SHA-3 /// [`SHA3`]: https://www.evm.codes/#20 - fn native_keccak256(&mut self, input: u32, len: u32, output: u32) -> Result<(), Self::Err> { + fn native_keccak256( + &mut self, + input: GuestPtr, + len: u32, + output: GuestPtr, + ) -> Result<(), Self::Err> { self.pay_for_keccak(len)?; let preimage = self.read_slice(input, len)?; @@ -721,7 +729,7 @@ pub trait UserHost: GasMeteredMachine { /// semantics are equivalent to that of the EVM's [`GAS_PRICE`] opcode. /// /// [`GAS_PRICE`]: https://www.evm.codes/#3A - fn tx_gas_price(&mut self, ptr: u32) -> Result<(), Self::Err> { + fn tx_gas_price(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { self.buy_ink(HOSTIO_INK + PTR_INK)?; self.write_bytes32(ptr, self.evm_data().tx_gas_price)?; trace!("tx_gas_price", self, &[], self.evm_data().tx_gas_price) @@ -741,7 +749,7 @@ pub trait UserHost: GasMeteredMachine { /// EVM's [`ORIGIN`] opcode. /// /// [`ORIGIN`]: https://www.evm.codes/#32 - fn tx_origin(&mut self, ptr: u32) -> Result<(), Self::Err> { + fn tx_origin(&mut self, ptr: GuestPtr) -> Result<(), Self::Err> { self.buy_ink(HOSTIO_INK + PTR_INK)?; self.write_bytes20(ptr, self.evm_data().tx_origin)?; trace!("tx_origin", self, &[], self.evm_data().tx_origin) @@ -759,7 +767,7 @@ pub trait UserHost: GasMeteredMachine { } /// Prints a UTF-8 encoded string to the console. Only available in debug mode. - fn console_log_text(&mut self, ptr: u32, len: u32) -> Result<(), Self::Err> { + fn console_log_text(&mut self, ptr: GuestPtr, len: u32) -> Result<(), Self::Err> { let text = self.read_slice(ptr, len)?; self.say(String::from_utf8_lossy(&text)); trace!("console_log_text", self, text, &[]) diff --git a/arbitrator/wasm-libraries/user-host/Cargo.toml b/arbitrator/wasm-libraries/user-host/Cargo.toml index 6b9a32b64..15174397e 100644 --- a/arbitrator/wasm-libraries/user-host/Cargo.toml +++ b/arbitrator/wasm-libraries/user-host/Cargo.toml @@ -7,9 +7,11 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -arbutil = { path = "../../arbutil/", features = ["wavm"] } +arbutil = { path = "../../arbutil/" } +caller-env = { path = "../../caller-env/", features = ["static_caller"] } prover = { path = "../../prover/", default-features = false } user-host-trait = { path = "../user-host-trait" } +wasmer-types = { path = "../../tools/wasmer/lib/types" } eyre = "0.6.5" fnv = "1.0.7" hex = "0.4.3" diff --git a/arbitrator/wasm-libraries/user-host/src/host.rs b/arbitrator/wasm-libraries/user-host/src/host.rs index bcf48e4f9..39fd89c1c 100644 --- a/arbitrator/wasm-libraries/user-host/src/host.rs +++ b/arbitrator/wasm-libraries/user-host/src/host.rs @@ -1,7 +1,8 @@ -// Copyright 2022-2023, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE use crate::program::Program; +use caller_env::GuestPtr; use user_host_trait::UserHost; #[link(wasm_import_module = "forward")] @@ -22,44 +23,44 @@ macro_rules! hostio { } #[no_mangle] -pub unsafe extern "C" fn user_host__read_args(ptr: u32) { +pub unsafe extern "C" fn user_host__read_args(ptr: GuestPtr) { hostio!(read_args(ptr)) } #[no_mangle] -pub unsafe extern "C" fn user_host__write_result(ptr: u32, len: u32) { +pub unsafe extern "C" fn user_host__write_result(ptr: GuestPtr, len: u32) { hostio!(write_result(ptr, len)) } #[no_mangle] -pub unsafe extern "C" fn user_host__storage_load_bytes32(key: u32, dest: u32) { +pub unsafe extern "C" fn user_host__storage_load_bytes32(key: GuestPtr, dest: GuestPtr) { hostio!(storage_load_bytes32(key, dest)) } #[no_mangle] -pub unsafe extern "C" fn user_host__storage_store_bytes32(key: u32, value: u32) { +pub unsafe extern "C" fn user_host__storage_store_bytes32(key: GuestPtr, value: GuestPtr) { hostio!(storage_store_bytes32(key, value)) } #[no_mangle] pub unsafe extern "C" fn user_host__call_contract( - contract: u32, - data: u32, + contract: GuestPtr, + data: GuestPtr, data_len: u32, - value: u32, + value: GuestPtr, gas: u64, - ret_len: u32, + ret_len: GuestPtr, ) -> u8 { hostio!(call_contract(contract, data, data_len, value, gas, ret_len)) } #[no_mangle] pub unsafe extern "C" fn user_host__delegate_call_contract( - contract: u32, - data: u32, + contract: GuestPtr, + data: GuestPtr, data_len: u32, gas: u64, - ret_len: u32, + ret_len: GuestPtr, ) -> u8 { hostio!(delegate_call_contract( contract, data, data_len, gas, ret_len @@ -68,40 +69,44 @@ pub unsafe extern "C" fn user_host__delegate_call_contract( #[no_mangle] pub unsafe extern "C" fn user_host__static_call_contract( - contract: u32, - data: u32, + contract: GuestPtr, + data: GuestPtr, data_len: u32, gas: u64, - ret_len: u32, + ret_len: GuestPtr, ) -> u8 { hostio!(static_call_contract(contract, data, data_len, gas, ret_len)) } #[no_mangle] pub unsafe extern "C" fn user_host__create1( - code: u32, + code: GuestPtr, code_len: u32, - value: u32, - contract: u32, - revert_len: u32, + value: GuestPtr, + contract: GuestPtr, + revert_len: GuestPtr, ) { hostio!(create1(code, code_len, value, contract, revert_len)) } #[no_mangle] pub unsafe extern "C" fn user_host__create2( - code: u32, + code: GuestPtr, code_len: u32, - value: u32, - salt: u32, - contract: u32, - revert_len: u32, + value: GuestPtr, + salt: GuestPtr, + contract: GuestPtr, + revert_len: GuestPtr, ) { hostio!(create2(code, code_len, value, salt, contract, revert_len)) } #[no_mangle] -pub unsafe extern "C" fn user_host__read_return_data(dest: u32, offset: u32, size: u32) -> u32 { +pub unsafe extern "C" fn user_host__read_return_data( + dest: GuestPtr, + offset: u32, + size: u32, +) -> u32 { hostio!(read_return_data(dest, offset, size)) } @@ -111,41 +116,41 @@ pub unsafe extern "C" fn user_host__return_data_size() -> u32 { } #[no_mangle] -pub unsafe extern "C" fn user_host__emit_log(data: u32, len: u32, topics: u32) { +pub unsafe extern "C" fn user_host__emit_log(data: GuestPtr, len: u32, topics: u32) { hostio!(emit_log(data, len, topics)) } #[no_mangle] -pub unsafe extern "C" fn user_host__account_balance(address: u32, ptr: u32) { +pub unsafe extern "C" fn user_host__account_balance(address: GuestPtr, ptr: GuestPtr) { hostio!(account_balance(address, ptr)) } #[no_mangle] pub unsafe extern "C" fn user_host__account_code( - address: u32, + address: GuestPtr, offset: u32, size: u32, - dest: u32, + dest: GuestPtr, ) -> u32 { hostio!(account_code(address, offset, size, dest)) } #[no_mangle] -pub unsafe extern "C" fn user_host__account_code_size(address: u32) -> u32 { +pub unsafe extern "C" fn user_host__account_code_size(address: GuestPtr) -> u32 { hostio!(account_code_size(address)) } #[no_mangle] -pub unsafe extern "C" fn user_host__account_codehash(address: u32, ptr: u32) { +pub unsafe extern "C" fn user_host__account_codehash(address: GuestPtr, ptr: GuestPtr) { hostio!(account_codehash(address, ptr)) } #[no_mangle] -pub unsafe extern "C" fn user_host__block_basefee(ptr: u32) { +pub unsafe extern "C" fn user_host__block_basefee(ptr: GuestPtr) { hostio!(block_basefee(ptr)) } #[no_mangle] -pub unsafe extern "C" fn user_host__block_coinbase(ptr: u32) { +pub unsafe extern "C" fn user_host__block_coinbase(ptr: GuestPtr) { hostio!(block_coinbase(ptr)) } @@ -170,7 +175,7 @@ pub unsafe extern "C" fn user_host__chainid() -> u64 { } #[no_mangle] -pub unsafe extern "C" fn user_host__contract_address(ptr: u32) { +pub unsafe extern "C" fn user_host__contract_address(ptr: GuestPtr) { hostio!(contract_address(ptr)) } @@ -190,22 +195,22 @@ pub unsafe extern "C" fn user_host__msg_reentrant() -> u32 { } #[no_mangle] -pub unsafe extern "C" fn user_host__msg_sender(ptr: u32) { +pub unsafe extern "C" fn user_host__msg_sender(ptr: GuestPtr) { hostio!(msg_sender(ptr)) } #[no_mangle] -pub unsafe extern "C" fn user_host__msg_value(ptr: u32) { +pub unsafe extern "C" fn user_host__msg_value(ptr: GuestPtr) { hostio!(msg_value(ptr)) } #[no_mangle] -pub unsafe extern "C" fn user_host__native_keccak256(input: u32, len: u32, output: u32) { +pub unsafe extern "C" fn user_host__native_keccak256(input: GuestPtr, len: u32, output: GuestPtr) { hostio!(native_keccak256(input, len, output)) } #[no_mangle] -pub unsafe extern "C" fn user_host__tx_gas_price(ptr: u32) { +pub unsafe extern "C" fn user_host__tx_gas_price(ptr: GuestPtr) { hostio!(tx_gas_price(ptr)) } @@ -215,7 +220,7 @@ pub unsafe extern "C" fn user_host__tx_ink_price() -> u32 { } #[no_mangle] -pub unsafe extern "C" fn user_host__tx_origin(ptr: u32) { +pub unsafe extern "C" fn user_host__tx_origin(ptr: GuestPtr) { hostio!(tx_origin(ptr)) } diff --git a/arbitrator/wasm-libraries/user-host/src/link.rs b/arbitrator/wasm-libraries/user-host/src/link.rs index 39e487214..664d19464 100644 --- a/arbitrator/wasm-libraries/user-host/src/link.rs +++ b/arbitrator/wasm-libraries/user-host/src/link.rs @@ -1,20 +1,14 @@ // Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -use crate::{ - program::Program, -}; +use crate::program::Program; use arbutil::{ evm::{user::UserOutcomeKind, EvmData}, format::DebugBytes, - heapify, wavm, Bytes32, -}; -use prover::{ - machine::Module, - programs::config::{PricingParams, StylusConfig}, + heapify, Bytes20, Bytes32, }; - -type Uptr = usize; +use caller_env::{static_caller::STATIC_MEM, GuestPtr, MemAccess}; +use prover::{machine::Module, programs::config::StylusConfig}; // these hostio methods allow the replay machine to modify itself #[link(wasm_import_module = "hostio")] @@ -36,54 +30,61 @@ extern "C" { #[repr(C, align(256))] struct MemoryLeaf([u8; 32]); -// Instruments and "activates" a user wasm, producing a unique module hash. -// -// Note that this operation costs gas and is limited by the amount supplied via the `gas` pointer. -// The amount left is written back at the end of the call. -// -// pages_ptr: starts pointing to max allowed pages, returns number of pages used +/// Instruments and "activates" a user wasm, producing a unique module hash. +/// +/// Note that this operation costs gas and is limited by the amount supplied via the `gas` pointer. +/// The amount left is written back at the end of the call. +/// +/// pages_ptr: starts pointing to max allowed pages, returns number of pages used #[no_mangle] pub unsafe extern "C" fn programs__activate( - wasm_ptr: Uptr, + wasm_ptr: GuestPtr, wasm_size: usize, - pages_ptr: Uptr, - asm_estimate_ptr: Uptr, - init_gas_ptr: Uptr, + pages_ptr: GuestPtr, + asm_estimate_ptr: GuestPtr, + init_gas_ptr: GuestPtr, version: u16, debug: u32, - module_hash_ptr: Uptr, - gas_ptr: Uptr, - err_buf: Uptr, + module_hash_ptr: GuestPtr, + gas_ptr: GuestPtr, + err_buf: GuestPtr, err_buf_len: usize, ) -> usize { - let wasm = wavm::read_slice_usize(wasm_ptr, wasm_size); + let wasm = STATIC_MEM.read_slice(wasm_ptr, wasm_size); let debug = debug != 0; - let page_limit = wavm::caller_load16(pages_ptr); - let gas_left = &mut wavm::caller_load64(gas_ptr); + let page_limit = STATIC_MEM.read_u16(pages_ptr); + let gas_left = &mut STATIC_MEM.read_u64(gas_ptr); match Module::activate(&wasm, version, page_limit, debug, gas_left) { Ok((module, data)) => { - wavm::caller_store64(gas_ptr, *gas_left); - wavm::caller_store16(pages_ptr, data.footprint); - wavm::caller_store32(asm_estimate_ptr, data.asm_estimate); - wavm::caller_store32(init_gas_ptr, data.init_gas); - wavm::write_bytes32_usize(module_hash_ptr, module.hash()); + STATIC_MEM.write_u64(gas_ptr, *gas_left); + STATIC_MEM.write_u16(pages_ptr, data.footprint); + STATIC_MEM.write_u32(asm_estimate_ptr, data.asm_estimate); + STATIC_MEM.write_u32(init_gas_ptr, data.init_gas); + STATIC_MEM.write_slice(module_hash_ptr, module.hash().as_slice()); 0 - }, + } Err(error) => { let mut err_bytes = error.wrap_err("failed to activate").debug_bytes(); err_bytes.truncate(err_buf_len); - wavm::write_slice_usize(&err_bytes, err_buf); - wavm::caller_store64(gas_ptr, 0); - wavm::caller_store16(pages_ptr, 0); - wavm::caller_store32(asm_estimate_ptr, 0); - wavm::caller_store32(init_gas_ptr, 0); - wavm::write_bytes32_usize(module_hash_ptr, Bytes32::default()); + STATIC_MEM.write_slice(err_buf, &err_bytes); + STATIC_MEM.write_u64(gas_ptr, 0); + STATIC_MEM.write_u16(pages_ptr, 0); + STATIC_MEM.write_u32(asm_estimate_ptr, 0); + STATIC_MEM.write_u32(init_gas_ptr, 0); + STATIC_MEM.write_slice(module_hash_ptr, Bytes32::default().as_slice()); err_bytes.len() - }, + } } } +unsafe fn read_bytes32(ptr: GuestPtr) -> Bytes32 { + STATIC_MEM.read_fixed(ptr).into() +} + +unsafe fn read_bytes20(ptr: GuestPtr) -> Bytes20 { + STATIC_MEM.read_fixed(ptr).into() +} /// Links and creates user program /// consumes both evm_data_handler and config_handler @@ -91,67 +92,77 @@ pub unsafe extern "C" fn programs__activate( /// see program-exec for starting the user program #[no_mangle] pub unsafe extern "C" fn programs__new_program( - compiled_hash_ptr: Uptr, - calldata_ptr: Uptr, + module_hash_ptr: GuestPtr, + calldata_ptr: GuestPtr, calldata_size: usize, config_box: u64, evm_data_box: u64, gas: u64, ) -> u32 { - let compiled_hash = wavm::read_bytes32_usize(compiled_hash_ptr); - let calldata = wavm::read_slice_usize(calldata_ptr, calldata_size); - let config: StylusConfig = *Box::from_raw(config_box as *mut StylusConfig); - let evm_data: EvmData = *Box::from_raw(evm_data_box as *mut EvmData); + let module_hash = read_bytes32(module_hash_ptr); + let calldata = STATIC_MEM.read_slice(calldata_ptr, calldata_size); + let config: StylusConfig = *Box::from_raw(config_box as _); + let evm_data: EvmData = *Box::from_raw(evm_data_box as _); // buy ink let pricing = config.pricing; let ink = pricing.gas_to_ink(gas); // link the program and ready its instrumentation - let module = wavm_link_module(&MemoryLeaf(*compiled_hash)); + let module = wavm_link_module(&MemoryLeaf(*module_hash)); program_set_ink(module, ink); program_set_stack(module, config.max_depth); // provide arguments Program::push_new(calldata, evm_data, module, config); - module } -// gets information about request according to id -// request_id MUST be last request id returned from start_program or send_response +/// Gets information about request according to id. +/// +/// # Safety +/// +/// `request_id` MUST be last request id returned from start_program or send_response. #[no_mangle] -pub unsafe extern "C" fn programs__get_request(id: u32, len_ptr: usize) -> u32 { - let (req_type, len) = Program::current().evm_api.request_handler().get_request_meta(id); - if len_ptr != 0 { - wavm::caller_store32(len_ptr, len as u32); +pub unsafe extern "C" fn programs__get_request(id: u32, len_ptr: GuestPtr) -> u32 { + let (req_type, len) = Program::current().request_handler().get_request_meta(id); + if len_ptr != GuestPtr(0) { + STATIC_MEM.write_u32(len_ptr, len as u32); } req_type } -// gets data associated with last request. -// request_id MUST be last request receieved -// data_ptr MUST point to a buffer of at least the length returned by get_request +/// Gets data associated with last request. +/// +/// # Safety +/// +/// `request_id` MUST be last request receieved +/// `data_ptr` MUST point to a buffer of at least the length returned by `get_request` #[no_mangle] -pub unsafe extern "C" fn programs__get_request_data(id: u32, data_ptr: usize) { - let (_, data) = Program::current().evm_api.request_handler().take_request(id); - wavm::write_slice_usize(&data, data_ptr); +pub unsafe extern "C" fn programs__get_request_data(id: u32, data_ptr: GuestPtr) { + let (_, data) = Program::current().request_handler().take_request(id); + STATIC_MEM.write_slice(data_ptr, &data); } -// sets response for the next request made -// id MUST be the id of last request made -// see program-exec send_response for sending this response to the program +/// sets response for the next request made +/// id MUST be the id of last request made +/// see `program-exec::send_response` for sending this response to the program #[no_mangle] pub unsafe extern "C" fn programs__set_response( id: u32, gas: u64, - result_ptr: Uptr, + result_ptr: GuestPtr, result_len: usize, - raw_data_ptr: Uptr, + raw_data_ptr: GuestPtr, raw_data_len: usize, ) { let program = Program::current(); - program.evm_api.request_handler().set_response(id, wavm::read_slice_usize(result_ptr, result_len), wavm::read_slice_usize(raw_data_ptr, raw_data_len), gas); + program.request_handler().set_response( + id, + STATIC_MEM.read_slice(result_ptr, result_len), + STATIC_MEM.read_slice(raw_data_ptr, raw_data_len), + gas, + ); } // removes the last created program @@ -173,8 +184,8 @@ pub unsafe extern "C" fn program_internal__args_len(module: u32) -> usize { program.args_len() } -// used by program-exec -// sets status of the last program and sends a program_done request +/// used by program-exec +/// sets status of the last program and sends a program_done request #[no_mangle] pub unsafe extern "C" fn program_internal__set_done(mut status: u8) -> u32 { let program = Program::current(); @@ -197,9 +208,13 @@ pub unsafe extern "C" fn program_internal__set_done(mut status: u8) -> u32 { ink_left = 0; } let gas_left = program.config.pricing.ink_to_gas(ink_left); - let mut output = gas_left.to_be_bytes().to_vec(); - output.extend(outs.iter()); - program.evm_api.request_handler().set_request(status as u32, &output) + + let mut output = Vec::with_capacity(8 + outs.len()); + output.extend(gas_left.to_be_bytes()); + output.extend(outs); + program + .request_handler() + .set_request(status as u32, &output) } /// Creates a `StylusConfig` from its component parts. @@ -210,13 +225,7 @@ pub unsafe extern "C" fn programs__create_stylus_config( ink_price: u32, _debug: u32, ) -> u64 { - let config = StylusConfig { - version, - max_depth, - pricing: PricingParams { - ink_price, - }, - }; + let config = StylusConfig::new(version, max_depth, ink_price); heapify(config) as u64 } @@ -224,31 +233,31 @@ pub unsafe extern "C" fn programs__create_stylus_config( /// #[no_mangle] pub unsafe extern "C" fn programs__create_evm_data( - block_basefee_ptr: Uptr, + block_basefee_ptr: GuestPtr, chainid: u64, - block_coinbase_ptr: Uptr, + block_coinbase_ptr: GuestPtr, block_gas_limit: u64, block_number: u64, block_timestamp: u64, - contract_address_ptr: Uptr, - msg_sender_ptr: Uptr, - msg_value_ptr: Uptr, - tx_gas_price_ptr: Uptr, - tx_origin_ptr: Uptr, + contract_address_ptr: GuestPtr, + msg_sender_ptr: GuestPtr, + msg_value_ptr: GuestPtr, + tx_gas_price_ptr: GuestPtr, + tx_origin_ptr: GuestPtr, reentrant: u32, ) -> u64 { let evm_data = EvmData { - block_basefee: wavm::read_bytes32_usize(block_basefee_ptr), + block_basefee: read_bytes32(block_basefee_ptr), chainid, - block_coinbase: wavm::read_bytes20_usize(block_coinbase_ptr), + block_coinbase: read_bytes20(block_coinbase_ptr), block_gas_limit, block_number, block_timestamp, - contract_address: wavm::read_bytes20_usize(contract_address_ptr), - msg_sender: wavm::read_bytes20_usize(msg_sender_ptr), - msg_value: wavm::read_bytes32_usize(msg_value_ptr), - tx_gas_price: wavm::read_bytes32_usize(tx_gas_price_ptr), - tx_origin: wavm::read_bytes20_usize(tx_origin_ptr), + contract_address: read_bytes20(contract_address_ptr), + msg_sender: read_bytes20(msg_sender_ptr), + msg_value: read_bytes32(msg_value_ptr), + tx_gas_price: read_bytes32(tx_gas_price_ptr), + tx_origin: read_bytes20(tx_origin_ptr), reentrant, return_data_len: 0, tracing: false, diff --git a/arbitrator/wasm-libraries/user-host/src/program.rs b/arbitrator/wasm-libraries/user-host/src/program.rs index 50b5cf99f..d456fe198 100644 --- a/arbitrator/wasm-libraries/user-host/src/program.rs +++ b/arbitrator/wasm-libraries/user-host/src/program.rs @@ -1,14 +1,21 @@ -// Copyright 2022-2023, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE -use core::sync::atomic::{compiler_fence, Ordering}; + use arbutil::{ - evm::{req::{EvmApiRequestor, RequestHandler}, EvmData, api::{{VecReader, EvmApiMethod, EVM_API_METHOD_REQ_OFFSET}}}, - wavm, Bytes20, Bytes32, Color, + evm::{ + api::{EvmApiMethod, VecReader, EVM_API_METHOD_REQ_OFFSET}, + req::{EvmApiRequestor, RequestHandler}, + EvmData, + }, + Color, }; -use eyre::{bail, eyre, Result}; +use caller_env::{static_caller::STATIC_MEM, GuestPtr, MemAccess}; +use core::sync::atomic::{compiler_fence, Ordering}; +use eyre::{eyre, Result}; use prover::programs::prelude::*; use std::fmt::Display; use user_host_trait::UserHost; +use wasmer_types::WASM_PAGE_SIZE; // allows introspection into user modules #[link(wasm_import_module = "hostio")] @@ -39,7 +46,7 @@ static mut PROGRAMS: Vec> = vec![]; static mut LAST_REQUEST_ID: u32 = 0x10000; #[derive(Clone)] -pub (crate) struct UserHostRequester { +pub(crate) struct UserHostRequester { data: Option>, answer: Option<(Vec, VecReader, u64)>, req_type: u32, @@ -80,7 +87,13 @@ extern "C" { impl UserHostRequester { #[no_mangle] - pub unsafe fn set_response(&mut self, req_id: u32, result: Vec, raw_data:Vec, gas: u64) { + pub unsafe fn set_response( + &mut self, + req_id: u32, + result: Vec, + raw_data: Vec, + gas: u64, + ) { self.answer = Some((result, VecReader::new(raw_data), gas)); if req_id != self.id { panic!("bad req id returning from send_request") @@ -101,22 +114,26 @@ impl UserHostRequester { if self.id != id { panic!("get_request got wrong id"); } - (self.req_type, self.data.as_ref().expect("no request on get_request_meta").len()) + let size = self.data.as_ref().expect("no data get_request_meta").len(); + (self.req_type, size) } pub unsafe fn take_request(&mut self, id: u32) -> (u32, Vec) { if self.id != id { panic!("get_request got wrong id"); } - (self.req_type, self.data.take().expect("no request on take_request")) + let data = self.data.take().expect("no request on take_request"); + (self.req_type, data) } #[no_mangle] unsafe fn send_request(&mut self, req_type: u32, data: Vec) -> (Vec, VecReader, u64) { let req_id = self.set_request(req_type, &data); compiler_fence(Ordering::SeqCst); + let got_id = program_request(req_id); compiler_fence(Ordering::SeqCst); + if got_id != req_id { panic!("bad req id returning from send_request") } @@ -125,21 +142,23 @@ impl UserHostRequester { } impl RequestHandler for UserHostRequester { - fn handle_request(&mut self, req_type: EvmApiMethod, req_data: &[u8]) -> (Vec, VecReader, u64) { + fn handle_request( + &mut self, + req_type: EvmApiMethod, + req_data: &[u8], + ) -> (Vec, VecReader, u64) { unsafe { - self.send_request(req_type as u32 + EVM_API_METHOD_REQ_OFFSET, req_data.to_vec()) + self.send_request( + req_type as u32 + EVM_API_METHOD_REQ_OFFSET, + req_data.to_vec(), + ) } } } impl Program { /// Adds a new program, making it current. - pub fn push_new( - args: Vec, - evm_data: EvmData, - module: u32, - config: StylusConfig, - ) { + pub fn push_new(args: Vec, evm_data: EvmData, module: u32, config: StylusConfig) { let program = Self { args, outs: vec![], @@ -153,7 +172,9 @@ impl Program { /// Removes the current program pub fn pop() { - unsafe { PROGRAMS.pop().expect("no program"); } + unsafe { + PROGRAMS.pop().expect("no program"); + } } /// Provides a reference to the current program. @@ -166,19 +187,23 @@ impl Program { unsafe { program_memory_size(self.module) } } - /// Reads the program's memory size in pages + /// Provides the length of the program's calldata in bytes. pub fn args_len(&self) -> usize { - return self.args.len() + self.args.len() } /// Ensures an access is within bounds - fn check_memory_access(&self, ptr: u32, bytes: u32) -> Result<(), MemoryBoundsError> { - let last_page = ptr.saturating_add(bytes) / wavm::PAGE_SIZE; + fn check_memory_access(&self, ptr: GuestPtr, bytes: u32) -> Result<(), MemoryBoundsError> { + let last_page = ptr.saturating_add(bytes) / (WASM_PAGE_SIZE as u32); if last_page > self.memory_size() { return Err(MemoryBoundsError); } Ok(()) } + + pub fn request_handler(&mut self) -> &mut UserHostRequester { + self.evm_api.request_handler() + } } #[allow(clippy::unit_arg)] @@ -207,52 +232,33 @@ impl UserHost for Program { &mut self.evm_data.return_data_len } - fn read_bytes20(&self, ptr: u32) -> Result { - self.check_memory_access(ptr, 20)?; - unsafe { Ok(wavm::read_bytes20(ptr)) } - } - - fn read_bytes32(&self, ptr: u32) -> Result { - self.check_memory_access(ptr, 32)?; - unsafe { Ok(wavm::read_bytes32(ptr)) } - } - - fn read_slice(&self, ptr: u32, len: u32) -> Result, MemoryBoundsError> { + fn read_slice(&self, ptr: GuestPtr, len: u32) -> Result, MemoryBoundsError> { self.check_memory_access(ptr, len)?; - unsafe { Ok(wavm::read_slice_u32(ptr, len)) } + unsafe { Ok(STATIC_MEM.read_slice(ptr, len as usize)) } } - fn write_u32(&mut self, ptr: u32, x: u32) -> Result<(), MemoryBoundsError> { - self.check_memory_access(ptr, 4)?; - unsafe { Ok(wavm::caller_store32(ptr as usize, x)) } + fn read_fixed(&self, ptr: GuestPtr) -> Result<[u8; N], MemoryBoundsError> { + self.read_slice(ptr, N as u32) + .map(|x| x.try_into().unwrap()) } - fn write_bytes20(&self, ptr: u32, src: Bytes20) -> Result<(), MemoryBoundsError> { - self.check_memory_access(ptr, 20)?; - unsafe { Ok(wavm::write_bytes20(ptr, src)) } - } - - fn write_bytes32(&self, ptr: u32, src: Bytes32) -> Result<(), MemoryBoundsError> { - self.check_memory_access(ptr, 32)?; - unsafe { Ok(wavm::write_bytes32(ptr, src)) } + fn write_u32(&mut self, ptr: GuestPtr, x: u32) -> Result<(), MemoryBoundsError> { + self.check_memory_access(ptr, 4)?; + unsafe { Ok(STATIC_MEM.write_u32(ptr, x)) } } - fn write_slice(&self, ptr: u32, src: &[u8]) -> Result<(), MemoryBoundsError> { + fn write_slice(&self, ptr: GuestPtr, src: &[u8]) -> Result<(), MemoryBoundsError> { self.check_memory_access(ptr, src.len() as u32)?; - unsafe { Ok(wavm::write_slice_u32(src, ptr)) } + unsafe { Ok(STATIC_MEM.write_slice(ptr, src)) } } fn say(&self, text: D) { println!("{} {text}", "Stylus says:".yellow()); } - fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], _start_ink: u64, _end_ink: u64) { + fn trace(&mut self, name: &str, args: &[u8], outs: &[u8], _end_ink: u64) { let args = hex::encode(args); let outs = hex::encode(outs); println!("Error: unexpected hostio tracing info for {name} while proving: {args}, {outs}"); } - - fn start_ink(&self) -> Result { - bail!("recording start ink while proving") - } } diff --git a/arbitrator/wasm-libraries/user-test/Cargo.toml b/arbitrator/wasm-libraries/user-test/Cargo.toml index e1e43117d..ee4577d4b 100644 --- a/arbitrator/wasm-libraries/user-test/Cargo.toml +++ b/arbitrator/wasm-libraries/user-test/Cargo.toml @@ -7,7 +7,8 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -arbutil = { path = "../../arbutil/", features = ["wavm"] } +arbutil = { path = "../../arbutil/" } +caller-env = { path = "../../caller-env/", features = ["static_caller"] } prover = { path = "../../prover/", default-features = false } eyre = "0.6.5" fnv = "1.0.7" diff --git a/arbitrator/wasm-libraries/user-test/src/caller_env.rs b/arbitrator/wasm-libraries/user-test/src/caller_env.rs new file mode 100644 index 000000000..04555d579 --- /dev/null +++ b/arbitrator/wasm-libraries/user-test/src/caller_env.rs @@ -0,0 +1,21 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use arbutil::Bytes32; +use caller_env::{static_caller::STATIC_MEM, GuestPtr, MemAccess}; + +pub struct UserMem; + +impl UserMem { + pub fn read_bytes32(ptr: GuestPtr) -> Bytes32 { + unsafe { STATIC_MEM.read_fixed(ptr).into() } + } + + pub fn read_slice(ptr: GuestPtr, len: u32) -> Vec { + unsafe { STATIC_MEM.read_slice(ptr, len as usize) } + } + + pub fn write_slice(ptr: GuestPtr, src: &[u8]) { + unsafe { STATIC_MEM.write_slice(ptr, src) } + } +} diff --git a/arbitrator/wasm-libraries/user-test/src/host.rs b/arbitrator/wasm-libraries/user-test/src/host.rs index 391d994f8..d7b4869d5 100644 --- a/arbitrator/wasm-libraries/user-test/src/host.rs +++ b/arbitrator/wasm-libraries/user-test/src/host.rs @@ -1,65 +1,65 @@ -// Copyright 2022-2023, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE #![allow(clippy::missing_safety_doc)] -use crate::{Program, ARGS, EVER_PAGES, KEYS, LOGS, OPEN_PAGES, OUTS}; +use crate::{caller_env::UserMem, Program, ARGS, EVER_PAGES, KEYS, LOGS, OPEN_PAGES, OUTS}; use arbutil::{ crypto, evm, pricing::{EVM_API_INK, HOSTIO_INK, PTR_INK}, - wavm, }; +use caller_env::GuestPtr; use prover::programs::{ memory::MemoryModel, prelude::{GasMeteredMachine, MeteredMachine}, }; #[no_mangle] -pub unsafe extern "C" fn vm_hooks__read_args(ptr: u32) { +pub unsafe extern "C" fn vm_hooks__read_args(ptr: GuestPtr) { let mut program = Program::start(0); program.pay_for_write(ARGS.len() as u32).unwrap(); - wavm::write_slice_u32(&ARGS, ptr); + UserMem::write_slice(ptr, &ARGS); } #[no_mangle] -pub unsafe extern "C" fn vm_hooks__write_result(ptr: u32, len: u32) { +pub unsafe extern "C" fn vm_hooks__write_result(ptr: GuestPtr, len: u32) { let mut program = Program::start(0); program.pay_for_read(len).unwrap(); program.pay_for_geth_bytes(len).unwrap(); - OUTS = wavm::read_slice_u32(ptr, len); + OUTS = UserMem::read_slice(ptr, len); } #[no_mangle] -pub unsafe extern "C" fn vm_hooks__storage_load_bytes32(key: u32, dest: u32) { +pub unsafe extern "C" fn vm_hooks__storage_load_bytes32(key: GuestPtr, dest: GuestPtr) { let mut program = Program::start(2 * PTR_INK + EVM_API_INK); - let key = wavm::read_bytes32(key); + let key = UserMem::read_bytes32(key); let value = KEYS.lock().get(&key).cloned().unwrap_or_default(); program.buy_gas(2100).unwrap(); // pretend it was cold - wavm::write_bytes32(dest, value); + UserMem::write_slice(dest, &value.0); } #[no_mangle] -pub unsafe extern "C" fn vm_hooks__storage_store_bytes32(key: u32, value: u32) { +pub unsafe extern "C" fn vm_hooks__storage_store_bytes32(key: GuestPtr, value: GuestPtr) { let mut program = Program::start(2 * PTR_INK + EVM_API_INK); program.require_gas(evm::SSTORE_SENTRY_GAS).unwrap(); program.buy_gas(22100).unwrap(); // pretend the worst case - let key = wavm::read_bytes32(key); - let value = wavm::read_bytes32(value); + let key = UserMem::read_bytes32(key); + let value = UserMem::read_bytes32(value); KEYS.lock().insert(key, value); } #[no_mangle] -pub unsafe extern "C" fn vm_hooks__emit_log(data: u32, len: u32, topics: u32) { +pub unsafe extern "C" fn vm_hooks__emit_log(data: GuestPtr, len: u32, topics: u32) { let mut program = Program::start(EVM_API_INK); if topics > 4 || len < topics * 32 { panic!("bad topic data"); } - program.pay_for_read(len.into()).unwrap(); + program.pay_for_read(len).unwrap(); program.pay_for_evm_log(topics, len - topics * 32).unwrap(); - let data = wavm::read_slice_u32(data, len); + let data = UserMem::read_slice(data, len); LOGS.push(data) } @@ -78,13 +78,13 @@ pub unsafe extern "C" fn vm_hooks__pay_for_memory_grow(pages: u16) { } #[no_mangle] -pub unsafe extern "C" fn vm_hooks__native_keccak256(bytes: u32, len: u32, output: u32) { +pub unsafe extern "C" fn vm_hooks__native_keccak256(bytes: GuestPtr, len: u32, output: GuestPtr) { let mut program = Program::start(0); program.pay_for_keccak(len).unwrap(); - let preimage = wavm::read_slice_u32(bytes, len); + let preimage = UserMem::read_slice(bytes, len); let digest = crypto::keccak(preimage); - wavm::write_slice_u32(&digest, output); + UserMem::write_slice(output, &digest); } #[no_mangle] diff --git a/arbitrator/wasm-libraries/user-test/src/lib.rs b/arbitrator/wasm-libraries/user-test/src/lib.rs index b93f8c02a..21464d658 100644 --- a/arbitrator/wasm-libraries/user-test/src/lib.rs +++ b/arbitrator/wasm-libraries/user-test/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022-2023, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE #![allow(clippy::missing_safety_doc)] @@ -9,6 +9,7 @@ use lazy_static::lazy_static; use parking_lot::Mutex; use prover::programs::prelude::StylusConfig; +mod caller_env; pub mod host; mod ink; diff --git a/arbitrator/wasm-libraries/wasi-stub/Cargo.toml b/arbitrator/wasm-libraries/wasi-stub/Cargo.toml index 154a438fe..f6079ce2f 100644 --- a/arbitrator/wasm-libraries/wasi-stub/Cargo.toml +++ b/arbitrator/wasm-libraries/wasi-stub/Cargo.toml @@ -8,5 +8,6 @@ publish = false crate-type = ["cdylib"] [dependencies] -rand = { version = "0.8.4", default-features = false } -rand_pcg = { version = "0.3.1", default-features = false } +paste = { version = "1.0.14" } +caller-env = { path = "../../caller-env/", features = ["static_caller"] } +wee_alloc = "0.4.2" diff --git a/arbitrator/wasm-libraries/wasi-stub/src/lib.rs b/arbitrator/wasm-libraries/wasi-stub/src/lib.rs index 05fd24139..2f237dcb4 100644 --- a/arbitrator/wasm-libraries/wasi-stub/src/lib.rs +++ b/arbitrator/wasm-libraries/wasi-stub/src/lib.rs @@ -1,28 +1,20 @@ -// Copyright 2021-2023, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE +#![allow(clippy::missing_safety_doc)] // TODO: require safety docs #![no_std] -use rand::RngCore; -use rand_pcg::Pcg32; +use caller_env::{self, wasip1_stub::Errno, GuestPtr}; +use paste::paste; +use wee_alloc::WeeAlloc; -type Errno = u16; - -type Uptr = usize; - -const ERRNO_SUCCESS: Errno = 0; -const ERRNO_BADF: Errno = 8; -const ERRNO_INTVAL: Errno = 28; - -#[allow(dead_code)] extern "C" { - fn wavm_caller_load8(ptr: Uptr) -> u8; - fn wavm_caller_load32(ptr: Uptr) -> u32; - fn wavm_caller_store8(ptr: Uptr, val: u8); - fn wavm_caller_store32(ptr: Uptr, val: u32); fn wavm_halt_and_set_finished() -> !; } +#[global_allocator] +static ALLOC: WeeAlloc = WeeAlloc::INIT; + #[panic_handler] unsafe fn panic(_: &core::panic::PanicInfo) -> ! { core::arch::wasm32::unreachable() @@ -37,327 +29,157 @@ pub unsafe extern "C" fn wasi_snapshot_preview1__proc_exit(code: u32) -> ! { } } -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__environ_sizes_get( - length_ptr: Uptr, - data_size_ptr: Uptr, -) -> Errno { - wavm_caller_store32(length_ptr, 0); - wavm_caller_store32(data_size_ptr, 0); - ERRNO_SUCCESS -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_write( - fd: usize, - iovecs_ptr: Uptr, - iovecs_len: usize, - ret_ptr: Uptr, -) -> Errno { - if fd != 1 && fd != 2 { - return ERRNO_BADF; - } - let mut size = 0; - for i in 0..iovecs_len { - let ptr = iovecs_ptr + i * 8; - size += wavm_caller_load32(ptr + 4); - } - wavm_caller_store32(ret_ptr, size); - ERRNO_SUCCESS -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__environ_get(_: usize, _: usize) -> Errno { - ERRNO_INTVAL -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_close(_: usize) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_read( - _: usize, - _: usize, - _: usize, - _: usize, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_readdir( - _fd: usize, - _: u32, - _: u32, - _: u64, - _: u32, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_sync( - _: u32, -) -> Errno { - ERRNO_SUCCESS -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_seek( - _fd: usize, - _offset: u64, - _whence: u8, - _filesize: usize, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_datasync(_fd: usize) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__path_open( - _: usize, - _: usize, - _: usize, - _: usize, - _: usize, - _: u64, - _: u64, - _: usize, - _: usize, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__path_create_directory( - _: usize, - _: usize, - _: usize, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__path_remove_directory( - _: usize, - _: usize, - _: usize, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__path_readlink( - _: usize, - _: usize, - _: usize, - _: usize, - _: usize, - _: usize, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__path_rename( - _: usize, - _: usize, - _: usize, - _: usize, - _: usize, - _: usize, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__path_filestat_get( - _: usize, - _: usize, - _: usize, - _: usize, - _: usize, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__path_unlink_file( - _: usize, - _: usize, - _: usize, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_prestat_get(_: usize, _: usize) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_prestat_dir_name( - _: usize, - _: usize, - _: usize, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_filestat_get( - _fd: usize, - _filestat: usize, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_filestat_set_size( - _fd: usize, - _: u64, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_pread( - _fd: usize, - _: u32, - _: u32, - _: u64, - _: u32, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_pwrite( - _fd: usize, - _: u32, - _: u32, - _: u64, - _: u32, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__sock_accept( - _fd: usize, - _: u32, - _: u32, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__sock_shutdown( - _: usize, - _: u32, -) -> Errno { - ERRNO_BADF -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__sched_yield() -> Errno { - ERRNO_SUCCESS -} - -// An increasing clock, measured in nanoseconds. -static mut TIME: u64 = 0; -// The amount of TIME advanced each check. Currently 10 milliseconds. -static TIME_INTERVAL: u64 = 10_000_000; - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__clock_time_get( - _clock_id: usize, - _precision: u64, - time: Uptr, -) -> Errno { - TIME += TIME_INTERVAL; - wavm_caller_store32(time, TIME as u32); - wavm_caller_store32(time + 4, (TIME >> 32) as u32); - ERRNO_SUCCESS -} - -static mut RNG: Option = None; - -unsafe fn get_rng<'a>() -> &'a mut Pcg32 { - RNG.get_or_insert_with(|| Pcg32::new(0xcafef00dd15ea5e5, 0xa02bdbf7bb3c0a7)) -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__random_get(mut buf: usize, mut len: usize) -> Errno { - let rng = get_rng(); - while len >= 4 { - wavm_caller_store32(buf, rng.next_u32()); - buf += 4; - len -= 4; - } - if len > 0 { - let mut rem = rng.next_u32(); - for _ in 0..len { - wavm_caller_store8(buf, rem as u8); - buf += 1; - rem >>= 8; - } - } - ERRNO_SUCCESS -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__args_sizes_get( - length_ptr: Uptr, - data_size_ptr: Uptr, -) -> Errno { - wavm_caller_store32(length_ptr, 1); - wavm_caller_store32(data_size_ptr, 4); - ERRNO_SUCCESS -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__args_get( - argv_buf: Uptr, - data_buf: Uptr -) -> Errno { - wavm_caller_store32(argv_buf, data_buf as u32); - wavm_caller_store32(data_buf, 0x6E6962); // "bin\0" - ERRNO_SUCCESS -} - -#[no_mangle] -// we always simulate a timeout -pub unsafe extern "C" fn wasi_snapshot_preview1__poll_oneoff(in_subs: Uptr, out_evt: Uptr, nsubscriptions: usize, nevents_ptr: Uptr) -> Errno { - const SUBSCRIPTION_SIZE: usize = 48; - for i in 0..nsubscriptions { - let subs_base = in_subs + (SUBSCRIPTION_SIZE * i); - let subs_type = wavm_caller_load32(subs_base + 8); - if subs_type != 0 { - // not a clock subscription type - continue +macro_rules! wrap { + ($(fn $func_name:ident ($($arg_name:ident : $arg_type:ty),* ) -> $return_type:ty);*) => { + paste! { + $( + #[no_mangle] + pub unsafe extern "C" fn []($($arg_name : $arg_type),*) -> $return_type { + caller_env::wasip1_stub::$func_name( + &mut caller_env::static_caller::STATIC_MEM, + &mut caller_env::static_caller::STATIC_ENV, + $($arg_name),* + ) + } + )* } - let user_data = wavm_caller_load32(subs_base); - wavm_caller_store32(out_evt, user_data); - wavm_caller_store32(out_evt + 8, 0); - wavm_caller_store32(nevents_ptr, 1); - return ERRNO_SUCCESS; - } - ERRNO_INTVAL -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_fdstat_get(_: usize, _: usize) -> Errno { - ERRNO_INTVAL -} - -#[no_mangle] -pub unsafe extern "C" fn wasi_snapshot_preview1__fd_fdstat_set_flags(_: usize, _: usize) -> Errno { - ERRNO_INTVAL + }; +} + +wrap! { + fn clock_time_get( + clock_id: u32, + precision: u64, + time_ptr: GuestPtr + ) -> Errno; + + fn random_get(buf: GuestPtr, len: u32) -> Errno; + + fn environ_sizes_get(length_ptr: GuestPtr, data_size_ptr: GuestPtr) -> Errno; + + fn fd_write( + fd: u32, + iovecs_ptr: GuestPtr, + iovecs_len: u32, + ret_ptr: GuestPtr + ) -> Errno; + + fn environ_get(a: GuestPtr, b: GuestPtr) -> Errno; + + fn fd_close(fd: u32) -> Errno; + fn fd_read(a: u32, b: u32, c: u32, d: u32) -> Errno; + fn fd_readdir( + fd: u32, + a: u32, + b: u32, + c: u64, + d: u32 + ) -> Errno; + + fn fd_sync(a: u32) -> Errno; + + fn fd_seek( + fd: u32, + offset: u64, + whence: u8, + filesize: u32 + ) -> Errno; + + fn fd_datasync(fd: u32) -> Errno; + + fn path_open( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32, + f: u64, + g: u64, + h: u32, + i: u32 + ) -> Errno; + + fn path_create_directory( + a: u32, + b: u32, + c: u32 + ) -> Errno; + + fn path_remove_directory( + a: u32, + b: u32, + c: u32 + ) -> Errno; + + fn path_readlink( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32, + f: u32 + ) -> Errno; + + fn path_rename( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32, + f: u32 + ) -> Errno; + + fn path_filestat_get( + a: u32, + b: u32, + c: u32, + d: u32, + e: u32 + ) -> Errno; + + fn path_unlink_file(a: u32, b: u32, c: u32) -> Errno; + + fn fd_prestat_get(a: u32, b: u32) -> Errno; + fn fd_prestat_dir_name(a: u32, b: u32, c: u32) -> Errno; + + fn fd_filestat_get(fd: u32, filestat: u32) -> Errno; + fn fd_filestat_set_size(fd: u32, size: u64) -> Errno; + + fn fd_pread( + fd: u32, + a: u32, + b: u32, + c: u64, + d: u32 + ) -> Errno; + + fn fd_pwrite( + fd: u32, + a: u32, + b: u32, + c: u64, + d: u32 + ) -> Errno; + + fn sock_accept(fd: u32, a: u32, b: u32) -> Errno; + fn sock_shutdown(a: u32, b: u32) -> Errno; + + fn sched_yield() -> Errno; + + fn args_sizes_get( + length_ptr: GuestPtr, + data_size_ptr: GuestPtr + ) -> Errno; + + fn args_get(argv_buf: GuestPtr, data_buf: GuestPtr) -> Errno; + + fn fd_fdstat_get(a: u32, b: u32) -> Errno; + fn fd_fdstat_set_flags(a: u32, b: u32) -> Errno; + + fn poll_oneoff( + in_subs: GuestPtr, + out_evt: GuestPtr, + nsubscriptions: u32, + nevents_ptr: GuestPtr + ) -> Errno } diff --git a/arbos/programs/api.go b/arbos/programs/api.go index 7251b633c..9369cc626 100644 --- a/arbos/programs/api.go +++ b/arbos/programs/api.go @@ -4,7 +4,6 @@ package programs import ( - "encoding/binary" "errors" "math/big" @@ -236,33 +235,66 @@ func newApiClosures( } return func(req RequestType, input []byte) ([]byte, []byte, uint64) { - switch req { - case GetBytes32: - if len(input) != 32 { - log.Crit("bad API call", "request", req, "len", len(input)) + original := input + + crash := func(reason string) { + log.Crit("bad API call", "reason", reason, "request", req, "len", len(original), "remaining", len(input)) + } + takeInput := func(needed int, reason string) []byte { + if len(input) < needed { + crash(reason) + } + data := input[:needed] + input = input[needed:] + return data + } + defer func() { + if len(input) > 0 { + crash("extra input") } - key := common.BytesToHash(input) + }() - out, cost := getBytes32(key) + takeAddress := func() common.Address { + return common.BytesToAddress(takeInput(20, "expected address")) + } + takeHash := func() common.Hash { + return common.BytesToHash(takeInput(32, "expected hash")) + } + takeU256 := func() *big.Int { + return common.BytesToHash(takeInput(32, "expected big")).Big() + } + takeU64 := func() uint64 { + return am.BytesToUint(takeInput(8, "expected u64")) + } + takeU32 := func() uint32 { + return am.BytesToUint32(takeInput(4, "expected u32")) + } + takeU16 := func() uint16 { + return am.BytesToUint16(takeInput(2, "expected u16")) + } + takeFixed := func(needed int) []byte { + return takeInput(needed, "expected value with known length") + } + takeRest := func() []byte { + data := input + input = []byte{} + return data + } + switch req { + case GetBytes32: + key := takeHash() + out, cost := getBytes32(key) return out[:], nil, cost case SetBytes32: - if len(input) != 64 { - log.Crit("bad API call", "request", req, "len", len(input)) - } - key := common.BytesToHash(input[:32]) - value := common.BytesToHash(input[32:]) - + key := takeHash() + value := takeHash() cost, err := setBytes32(key, value) - if err != nil { return []byte{0}, nil, 0 } return []byte{1}, nil, cost case ContractCall, DelegateCall, StaticCall: - if len(input) < 60 { - log.Crit("bad API call", "request", req, "len", len(input)) - } var opcode vm.OpCode switch req { case ContractCall: @@ -274,41 +306,27 @@ func newApiClosures( default: log.Crit("unsupported call type", "opcode", opcode) } - contract := common.BytesToAddress(input[:20]) - value := common.BytesToHash(input[20:52]).Big() - gas := binary.BigEndian.Uint64(input[52:60]) - input = input[60:] - - ret, cost, err := doCall(contract, opcode, input, gas, value) + contract := takeAddress() + value := takeU256() + gas := takeU64() + calldata := takeRest() + ret, cost, err := doCall(contract, opcode, calldata, gas, value) statusByte := byte(0) if err != nil { - statusByte = 2 //TODO: err value + statusByte = 2 // TODO: err value } return []byte{statusByte}, ret, cost case Create1, Create2: - if len(input) < 40 { - log.Crit("bad API call", "request", req, "len", len(input)) - } - gas := binary.BigEndian.Uint64(input[0:8]) - endowment := common.BytesToHash(input[8:40]).Big() - var code []byte + gas := takeU64() + endowment := takeU256() var salt *big.Int - switch req { - case Create1: - code = input[40:] - case Create2: - if len(input) < 72 { - log.Crit("bad API call", "request", req, "len", len(input)) - } - salt = common.BytesToHash(input[40:72]).Big() - code = input[72:] - default: - log.Crit("unsupported create opcode", "request", req) + if req == Create2 { + salt = takeU256() } + code := takeRest() address, retVal, cost, err := create(code, endowment, salt, gas) - if err != nil { res := append([]byte{0}, []byte(err.Error())...) return res, nil, gas @@ -316,83 +334,49 @@ func newApiClosures( res := append([]byte{1}, address.Bytes()...) return res, retVal, cost case EmitLog: - if len(input) < 4 { - log.Crit("bad API call", "request", req, "len", len(input)) - } - topics := binary.BigEndian.Uint32(input[0:4]) - input = input[4:] + topics := takeU32() hashes := make([]common.Hash, topics) - if len(input) < int(topics*32) { - log.Crit("bad API call", "request", req, "len", len(input)) - } for i := uint32(0); i < topics; i++ { - hashes[i] = common.BytesToHash(input[i*32 : (i+1)*32]) + hashes[i] = takeHash() } - err := emitLog(hashes, input[topics*32:]) - + err := emitLog(hashes, takeRest()) if err != nil { return []byte(err.Error()), nil, 0 } return []byte{}, nil, 0 case AccountBalance: - if len(input) != 20 { - log.Crit("bad API call", "request", req, "len", len(input)) - } - address := common.BytesToAddress(input) - + address := takeAddress() balance, cost := accountBalance(address) - return balance[:], nil, cost case AccountCode: - if len(input) != 28 { - log.Crit("bad API call", "request", req, "len", len(input)) - } - address := common.BytesToAddress(input[0:20]) - gas := binary.BigEndian.Uint64(input[20:28]) - + address := takeAddress() + gas := takeU64() code, cost := accountCode(address, gas) - return nil, code, cost case AccountCodeHash: - if len(input) != 20 { - log.Crit("bad API call", "request", req, "len", len(input)) - } - address := common.BytesToAddress(input) - + address := takeAddress() codeHash, cost := accountCodehash(address) - return codeHash[:], nil, cost case AddPages: - if len(input) != 2 { - log.Crit("bad API call", "request", req, "len", len(input)) - } - pages := binary.BigEndian.Uint16(input) - + pages := takeU16() cost := addPages(pages) - return []byte{}, nil, cost case CaptureHostIO: - if len(input) < 22 { - log.Crit("bad API call", "request", req, "len", len(input)) - } if tracingInfo == nil { + takeRest() // drop any input return []byte{}, nil, 0 } - startInk := binary.BigEndian.Uint64(input[:8]) - endInk := binary.BigEndian.Uint64(input[8:16]) - nameLen := binary.BigEndian.Uint16(input[16:18]) - argsLen := binary.BigEndian.Uint16(input[18:20]) - outsLen := binary.BigEndian.Uint16(input[20:22]) - if len(input) != 22+int(nameLen+argsLen+outsLen) { - log.Error("bad API call", "request", req, "len", len(input), "expected", nameLen+argsLen+outsLen) - } - name := string(input[22 : 22+nameLen]) - args := input[22+nameLen : 22+nameLen+argsLen] - outs := input[22+nameLen+argsLen:] + startInk := takeU64() + endInk := takeU64() + nameLen := takeU16() + argsLen := takeU16() + outsLen := takeU16() + name := string(takeFixed(int(nameLen))) + args := takeFixed(int(argsLen)) + outs := takeFixed(int(outsLen)) captureHostio(name, args, outs, startInk, endInk) - return []byte{}, nil, 0 default: log.Crit("unsupported call type", "req", req) diff --git a/arbos/programs/constant_test.go b/arbos/programs/constant_test.go index 294798846..fe29bcf3d 100644 --- a/arbos/programs/constant_test.go +++ b/arbos/programs/constant_test.go @@ -1,3 +1,6 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + package programs import "testing" diff --git a/arbos/programs/native.go b/arbos/programs/native.go index d3e34a56d..198e3cb80 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -21,8 +21,6 @@ import "C" import ( "errors" "fmt" - "math/big" - "runtime" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/state" @@ -113,8 +111,8 @@ func callProgram( } asm := db.GetActivatedAsm(moduleHash) - evmApi, id := newApi(interpreter, tracingInfo, scope, memoryModel) - defer dropApi(id) + evmApi := newApi(interpreter, tracingInfo, scope, memoryModel) + defer evmApi.drop() output := &rustBytes{} status := userStatus(C.stylus_call( @@ -142,37 +140,18 @@ type apiStatus = C.EvmApiStatus const apiSuccess C.EvmApiStatus = C.EvmApiStatus_Success const apiFailure C.EvmApiStatus = C.EvmApiStatus_Failure -func pinAndRef(pinner *runtime.Pinner, data []byte, goSlice *C.GoSliceData) { - if len(data) > 0 { - dataPointer := arbutil.SliceToPointer(data) - pinner.Pin(dataPointer) - goSlice.ptr = (*u8)(dataPointer) - } else { - goSlice.ptr = (*u8)(nil) - } - goSlice.len = usize(len(data)) -} - //export handleReqImpl -func handleReqImpl(apiId usize, req_type u32, data *rustBytes, costPtr *u64, out_response *C.GoSliceData, out_raw_data *C.GoSliceData) apiStatus { +func handleReqImpl(apiId usize, req_type u32, data *rustSlice, costPtr *u64, out_response *C.GoSliceData, out_raw_data *C.GoSliceData) apiStatus { api := getApi(apiId) reqData := data.read() reqType := RequestType(req_type - EvmApiMethodReqOffset) response, raw_data, cost := api.handler(reqType, reqData) *costPtr = u64(cost) - pinAndRef(&api.pinner, response, out_response) - pinAndRef(&api.pinner, raw_data, out_raw_data) + api.pinAndRef(response, out_response) + api.pinAndRef(raw_data, out_raw_data) return apiSuccess } -func (value bytes20) toAddress() common.Address { - addr := common.Address{} - for index, b := range value.bytes { - addr[index] = byte(b) - } - return addr -} - func (value bytes32) toHash() common.Hash { hash := common.Hash{} for index, b := range value.bytes { @@ -181,10 +160,6 @@ func (value bytes32) toHash() common.Hash { return hash } -func (value bytes32) toBig() *big.Int { - return value.toHash().Big() -} - func hashToBytes32(hash common.Hash) bytes32 { value := bytes32{} for index, b := range hash.Bytes() { @@ -219,14 +194,6 @@ func (vec *rustBytes) drop() { C.stylus_drop_vec(*vec) } -func (vec *rustBytes) setString(data string) { - vec.setBytes([]byte(data)) -} - -func (vec *rustBytes) setBytes(data []byte) { - C.stylus_vec_set_bytes(vec, goSlice(data)) -} - func goSlice(slice []byte) C.GoSliceData { return C.GoSliceData{ ptr: (*u8)(arbutil.SliceToPointer(slice)), diff --git a/arbos/programs/native_api.go b/arbos/programs/native_api.go index 03def554c..e66cf07fc 100644 --- a/arbos/programs/native_api.go +++ b/arbos/programs/native_api.go @@ -16,8 +16,8 @@ typedef uint32_t u32; typedef uint64_t u64; typedef size_t usize; -EvmApiStatus handleReqImpl(usize api, u32 req_type, RustBytes *data, u64 *out_cost, GoSliceData *out_result, GoSliceData *out_raw_data); -EvmApiStatus handleReqWrap(usize api, u32 req_type, RustBytes *data, u64 *out_cost, GoSliceData *out_result, GoSliceData *out_raw_data) { +EvmApiStatus handleReqImpl(usize api, u32 req_type, RustSlice *data, u64 *out_cost, GoSliceData *out_result, GoSliceData *out_raw_data); +EvmApiStatus handleReqWrap(usize api, u32 req_type, RustSlice *data, u64 *out_cost, GoSliceData *out_result, GoSliceData *out_raw_data) { return handleReqImpl(api, req_type, data, out_cost, out_result, out_raw_data); } */ @@ -30,6 +30,7 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" "github.com/offchainlabs/nitro/arbos/util" + "github.com/offchainlabs/nitro/arbutil" ) var apiObjects sync.Map @@ -46,7 +47,7 @@ func newApi( tracingInfo *util.TracingInfo, scope *vm.ScopeContext, memoryModel *MemoryModel, -) (NativeApi, usize) { +) NativeApi { handler := newApiClosures(interpreter, tracingInfo, scope, memoryModel) apiId := atomic.AddUintptr(&apiIds, 1) id := usize(apiId) @@ -56,11 +57,11 @@ func newApi( handle_request_fptr: (*[0]byte)(C.handleReqWrap), id: id, }, - // TODO: doesn't seem like pinner needs to be initialized? + pinner: runtime.Pinner{}, } api.pinner.Pin(&api) apiObjects.Store(apiId, api) - return api, id + return api } func getApi(id usize) NativeApi { @@ -75,9 +76,20 @@ func getApi(id usize) NativeApi { return api } -func dropApi(id usize) { - uid := uintptr(id) - api := getApi(id) +// Free the API object, and any saved request payloads. +func (api *NativeApi) drop() { api.pinner.Unpin() - apiObjects.Delete(uid) + apiObjects.Delete(uintptr(api.cNative.id)) +} + +// Pins a slice until program exit during the call to `drop`. +func (api *NativeApi) pinAndRef(data []byte, goSlice *C.GoSliceData) { + if len(data) > 0 { + dataPointer := arbutil.SliceToPointer(data) + api.pinner.Pin(dataPointer) + goSlice.ptr = (*u8)(dataPointer) + } else { + goSlice.ptr = (*u8)(nil) + } + goSlice.len = usize(len(data)) } diff --git a/arbos/programs/testconstants.go b/arbos/programs/testconstants.go index d189f025f..04f40395d 100644 --- a/arbos/programs/testconstants.go +++ b/arbos/programs/testconstants.go @@ -1,5 +1,10 @@ +// Copyright 2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + package programs +// This file exists because cgo isn't allowed in tests + /* #cgo CFLAGS: -g -Wall -I../../target/include/ #include "arbitrator.h" @@ -7,14 +12,16 @@ package programs import "C" import "fmt" -func errIfNotEq(index int, a RequestType, b uint32) error { - if uint32(a) != b { - return fmt.Errorf("constant test %d failed! %d != %d", index, a, b) +func testConstants() error { + + // this closure exists to avoid polluting the package namespace + errIfNotEq := func(index int, a RequestType, b uint32) error { + if uint32(a) != b { + return fmt.Errorf("constant test %d failed! %d != %d", index, a, b) + } + return nil } - return nil -} -func testConstants() error { if err := errIfNotEq(1, GetBytes32, C.EvmApiMethod_GetBytes32); err != nil { return err } diff --git a/arbos/programs/wasm.go b/arbos/programs/wasm.go index 942b87b9c..0995685b0 100644 --- a/arbos/programs/wasm.go +++ b/arbos/programs/wasm.go @@ -7,7 +7,6 @@ package programs import ( - "encoding/binary" "errors" "unsafe" @@ -128,9 +127,8 @@ func callProgram( params *goParams, memoryModel *MemoryModel, ) ([]byte, error) { - // debug := arbmath.UintToBool(params.debugMode) + debug := arbmath.UintToBool(params.debugMode) reqHandler := newApiClosures(interpreter, tracingInfo, scope, memoryModel) - configHandler := params.createHandler() dataHandler := evmData.createHandler() @@ -151,17 +149,23 @@ func callProgram( if reqTypeId < EvmApiMethodReqOffset { popProgram() status := userStatus(reqTypeId) - gasLeft := binary.BigEndian.Uint64(reqData[:8]) + gasLeft := arbmath.BytesToUint(reqData[:8]) scope.Contract.Gas = gasLeft - data, msg, err := status.toResult(reqData[8:], params.debugMode != 0) - if status == userFailure && params.debugMode != 0 { + data, msg, err := status.toResult(reqData[8:], debug) + if status == userFailure && debug { log.Warn("program failure", "err", err, "msg", msg, "program", address) } return data, err } + reqType := RequestType(reqTypeId - EvmApiMethodReqOffset) result, rawData, cost := reqHandler(reqType, reqData) - setResponse(reqId, cost, arbutil.SliceToUnsafePointer(result), uint32(len(result)), arbutil.SliceToUnsafePointer(rawData), uint32(len(rawData))) + setResponse( + reqId, + cost, + arbutil.SliceToUnsafePointer(result), uint32(len(result)), + arbutil.SliceToUnsafePointer(rawData), uint32(len(rawData)), + ) reqId = sendResponse(reqId) } } diff --git a/arbos/programs/wasm_api.go b/arbos/programs/wasm_api.go index 5b4c6c1db..301f5283a 100644 --- a/arbos/programs/wasm_api.go +++ b/arbos/programs/wasm_api.go @@ -1,4 +1,4 @@ -// Copyright 2023, Offchain Labs, Inc. +// Copyright 2023-2024, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE //go:build wasm @@ -52,5 +52,6 @@ func (data *evmData) createHandler() evmDataHandler { arbutil.SliceToUnsafePointer(data.msgValue[:]), arbutil.SliceToUnsafePointer(data.txGasPrice[:]), arbutil.SliceToUnsafePointer(data.txOrigin[:]), - data.reentrant) + data.reentrant, + ) } diff --git a/arbutil/correspondingl1blocknumber.go b/arbutil/correspondingl1blocknumber.go index 5127684be..05323ed18 100644 --- a/arbutil/correspondingl1blocknumber.go +++ b/arbutil/correspondingl1blocknumber.go @@ -1,9 +1,6 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -//go:build !wasm -// +build !wasm - package arbutil import ( diff --git a/arbutil/format.go b/arbutil/format.go index 9cb79aeac..041866e4c 100644 --- a/arbutil/format.go +++ b/arbutil/format.go @@ -1,3 +1,6 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + package arbutil import ( diff --git a/arbutil/transaction_data.go b/arbutil/transaction_data.go index 80d150fb9..7741af6e9 100644 --- a/arbutil/transaction_data.go +++ b/arbutil/transaction_data.go @@ -1,9 +1,6 @@ // Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -//go:build !wasm -// +build !wasm - package arbutil import ( diff --git a/arbutil/unsafe.go b/arbutil/unsafe.go index 5aa1ad422..341aa12c1 100644 --- a/arbutil/unsafe.go +++ b/arbutil/unsafe.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023, Offchain Labs, Inc. +// Copyright 2022-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE package arbutil diff --git a/arbutil/wait_for_l1.go b/arbutil/wait_for_l1.go index c2a5e38bb..ce82501e8 100644 --- a/arbutil/wait_for_l1.go +++ b/arbutil/wait_for_l1.go @@ -1,9 +1,6 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE -//go:build !wasm -// +build !wasm - package arbutil import ( diff --git a/contracts b/contracts index efcc03da9..5666569dc 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit efcc03da9a274147e263b58aba8c12f046ebd870 +Subproject commit 5666569dc3e83903de28fb73cf558535cfe22ecb diff --git a/wavmio/higher.go b/wavmio/higher.go index d9db344db..147b4e6c8 100644 --- a/wavmio/higher.go +++ b/wavmio/higher.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE //go:build wasm diff --git a/wavmio/raw.go b/wavmio/raw.go index 26d7fdf46..2e415350f 100644 --- a/wavmio/raw.go +++ b/wavmio/raw.go @@ -1,4 +1,4 @@ -// Copyright 2021-2022, Offchain Labs, Inc. +// Copyright 2021-2024, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE //go:build wasm