From 698b07653ab6ee11f4ebb853c69453a5401d0165 Mon Sep 17 00:00:00 2001 From: Rachel Bousfield Date: Wed, 13 Mar 2024 02:56:20 -0600 Subject: [PATCH] VM storage cache --- arbitrator/arbutil/src/evm/api.rs | 31 ++- arbitrator/arbutil/src/evm/mod.rs | 8 +- arbitrator/arbutil/src/evm/req.rs | 56 +++- arbitrator/arbutil/src/evm/storage.rs | 64 +++++ arbitrator/arbutil/src/format.rs | 13 + arbitrator/stylus/cbindgen.toml | 2 +- arbitrator/stylus/src/env.rs | 2 +- arbitrator/stylus/src/evm_api.rs | 7 +- arbitrator/stylus/src/host.rs | 11 +- arbitrator/stylus/src/native.rs | 6 +- arbitrator/stylus/src/test/api.rs | 10 +- arbitrator/wasm-libraries/Cargo.lock | 1 + arbitrator/wasm-libraries/forward/src/main.rs | 5 +- .../wasm-libraries/user-host-trait/src/lib.rs | 44 ++- .../wasm-libraries/user-host/src/host.rs | 9 +- .../wasm-libraries/user-test/Cargo.toml | 1 + .../user-test/src/caller_env.rs | 21 -- .../wasm-libraries/user-test/src/host.rs | 252 ++++++++++++++---- .../wasm-libraries/user-test/src/ink.rs | 18 +- .../wasm-libraries/user-test/src/lib.rs | 8 +- .../wasm-libraries/user-test/src/program.rs | 209 +++++++++++++++ arbos/programs/api.go | 58 ++-- arbos/programs/native.go | 8 +- arbos/programs/native_api.go | 4 +- arbos/programs/testconstants.go | 28 +- 25 files changed, 690 insertions(+), 186 deletions(-) create mode 100644 arbitrator/arbutil/src/evm/storage.rs delete mode 100644 arbitrator/wasm-libraries/user-test/src/caller_env.rs create mode 100644 arbitrator/wasm-libraries/user-test/src/program.rs diff --git a/arbitrator/arbutil/src/evm/api.rs b/arbitrator/arbutil/src/evm/api.rs index a7968fcc8..e9886d0cd 100644 --- a/arbitrator/arbutil/src/evm/api.rs +++ b/arbitrator/arbutil/src/evm/api.rs @@ -3,28 +3,24 @@ use crate::{evm::user::UserOutcomeKind, Bytes20, Bytes32}; use eyre::Result; +use num_enum::IntoPrimitive; use std::sync::Arc; -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, IntoPrimitive)] #[repr(u8)] pub enum EvmApiStatus { Success, Failure, -} - -impl From for UserOutcomeKind { - fn from(value: EvmApiStatus) -> Self { - match value { - EvmApiStatus::Success => UserOutcomeKind::Success, - EvmApiStatus::Failure => UserOutcomeKind::Revert, - } - } + OutOfGas, + WriteProtection, } impl From for EvmApiStatus { fn from(value: u8) -> Self { match value { 0 => Self::Success, + 2 => Self::OutOfGas, + 3 => Self::WriteProtection, _ => Self::Failure, } } @@ -34,7 +30,7 @@ impl From for EvmApiStatus { #[repr(u32)] pub enum EvmApiMethod { GetBytes32, - SetBytes32, + SetTrieSlots, ContractCall, DelegateCall, StaticCall, @@ -81,10 +77,13 @@ pub trait EvmApi: Send + 'static { /// Analogous to `vm.SLOAD`. fn get_bytes32(&mut self, key: Bytes32) -> (Bytes32, u64); - /// Stores the given value at the given key in the EVM state trie. - /// Returns the access cost on success. - /// Analogous to `vm.SSTORE`. - fn set_bytes32(&mut self, key: Bytes32, value: Bytes32) -> Result; + /// Stores the given value at the given key in Stylus VM's cache of the EVM state trie. + /// Note that the actual values only get written after calls to `set_trie_slots`. + fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> u64; + + /// Persists any dirty values in the storage cache to the EVM state trie, dropping the cache entirely if requested. + /// Analogous to repeated invocations of `vm.SSTORE`. + fn flush_storage_cache(&mut self, clear: bool, gas_left: u64) -> Result; /// Calls the contract at the given address. /// Returns the EVM return data's length, the gas cost, and whether the call succeeded. @@ -141,7 +140,7 @@ pub trait EvmApi: Send + 'static { ) -> (eyre::Result, u32, u64); /// Returns the EVM return data. - /// Analogous to `vm.RETURNDATASIZE`. + /// Analogous to `vm.RETURNDATA`. fn get_return_data(&self) -> D; /// Emits an EVM log with the given number of topics and data, the first bytes of which should be the topic data. diff --git a/arbitrator/arbutil/src/evm/mod.rs b/arbitrator/arbutil/src/evm/mod.rs index c99e488d3..72d0ec6e9 100644 --- a/arbitrator/arbutil/src/evm/mod.rs +++ b/arbitrator/arbutil/src/evm/mod.rs @@ -1,10 +1,11 @@ -// 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::{Bytes20, Bytes32}; pub mod api; pub mod req; +pub mod storage; pub mod user; // params.SstoreSentryGasEIP2200 @@ -13,9 +14,12 @@ pub const SSTORE_SENTRY_GAS: u64 = 2300; // params.ColdAccountAccessCostEIP2929 pub const COLD_ACCOUNT_GAS: u64 = 2600; -// params.ColdSloadCostEIP2929 +// params.WarmStorageReadCostEIP2929 pub const COLD_SLOAD_GAS: u64 = 2100; +// params.WarmSloadCostEIP2929; +pub const WARM_SLOAD_GAS: u64 = 100; + // params.LogGas and params.LogDataGas pub const LOG_TOPIC_GAS: u64 = 375; pub const LOG_DATA_GAS: u64 = 8; diff --git a/arbitrator/arbutil/src/evm/req.rs b/arbitrator/arbutil/src/evm/req.rs index bafd0eb73..d7cab0071 100644 --- a/arbitrator/arbutil/src/evm/req.rs +++ b/arbitrator/arbutil/src/evm/req.rs @@ -3,12 +3,16 @@ use crate::{ evm::{ - api::{DataReader, EvmApi, EvmApiMethod}, + api::{DataReader, EvmApi, EvmApiMethod, EvmApiStatus}, + storage::{StorageCache, StorageWord}, user::UserOutcomeKind, }, + format::Utf8OrHex, + pricing::EVM_API_INK, Bytes20, Bytes32, }; use eyre::{bail, eyre, Result}; +use std::collections::hash_map::Entry; pub trait RequestHandler: Send + 'static { fn handle_request(&mut self, req_type: EvmApiMethod, req_data: &[u8]) -> (Vec, D, u64); @@ -18,6 +22,7 @@ pub struct EvmApiRequestor> { handler: H, last_code: Option<(Bytes20, D)>, last_return_data: Option, + storage_cache: StorageCache, } impl> EvmApiRequestor { @@ -26,6 +31,7 @@ impl> EvmApiRequestor { handler, last_code: None, last_return_data: None, + storage_cache: StorageCache::default(), } } @@ -93,20 +99,45 @@ impl> EvmApiRequestor { impl> EvmApi for EvmApiRequestor { fn get_bytes32(&mut self, key: Bytes32) -> (Bytes32, u64) { - let (res, _, cost) = self.handle_request(EvmApiMethod::GetBytes32, key.as_slice()); - (res.try_into().unwrap(), cost) + let cache = &mut self.storage_cache; + let mut cost = cache.read_gas(); + + let value = cache.entry(key).or_insert_with(|| { + let (res, _, gas) = self + .handler + .handle_request(EvmApiMethod::GetBytes32, key.as_slice()); + cost = cost.saturating_add(gas).saturating_add(EVM_API_INK); + StorageWord::known(res.try_into().unwrap()) + }); + (value.value, cost) } - fn set_bytes32(&mut self, key: Bytes32, value: Bytes32) -> Result { - 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") + fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> u64 { + match self.storage_cache.entry(key) { + Entry::Occupied(mut key) => key.get_mut().value = value, + Entry::Vacant(slot) => drop(slot.insert(StorageWord::unknown(value))), + }; + self.storage_cache.write_gas() + } + + fn flush_storage_cache(&mut self, clear: bool, gas_left: u64) -> Result { + let mut data = Vec::with_capacity(64 * self.storage_cache.len() + 8); + data.extend(gas_left.to_be_bytes()); + + for (key, value) in &mut self.storage_cache.slots { + if value.dirty() { + data.extend(*key); + data.extend(*value.value); + value.known = Some(value.value); + } } - if res[0] != 1 { - bail!("write protected") + if clear { + self.storage_cache.clear(); + } + + let (res, _, cost) = self.handle_request(EvmApiMethod::SetTrieSlots, &data); + if res[0] != EvmApiStatus::Success.into() { + bail!("{}", String::from_utf8_or_hex(res)); } Ok(cost) } @@ -175,6 +206,7 @@ impl> EvmApi for EvmApiRequestor { } fn emit_log(&mut self, data: Vec, topics: u32) -> Result<()> { + // TODO: remove copy let mut request = Vec::with_capacity(4 + data.len()); request.extend(topics.to_be_bytes()); request.extend(data); diff --git a/arbitrator/arbutil/src/evm/storage.rs b/arbitrator/arbutil/src/evm/storage.rs new file mode 100644 index 000000000..7fc4a6911 --- /dev/null +++ b/arbitrator/arbutil/src/evm/storage.rs @@ -0,0 +1,64 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/nitro/blob/master/LICENSE + +use crate::Bytes32; +use std::{ + collections::HashMap, + ops::{Deref, DerefMut}, +}; + +/// Represents the EVM word at a given key. +pub struct StorageWord { + /// The current value of the slot. + pub value: Bytes32, + /// The value in Geth, if known. + pub known: Option, +} + +impl StorageWord { + pub fn known(value: Bytes32) -> Self { + let known = Some(value); + Self { value, known } + } + + pub fn unknown(value: Bytes32) -> Self { + Self { value, known: None } + } + + pub fn dirty(&self) -> bool { + Some(self.value) != self.known + } +} + +#[derive(Default)] +pub struct StorageCache { + pub(crate) slots: HashMap, +} + +impl StorageCache { + pub const REQUIRED_ACCESS_GAS: u64 = crate::evm::COLD_SLOAD_GAS; + + pub fn read_gas(&self) -> u64 { + //self.slots.len().ilog2() as u64 + self.slots.len() as u64 + } + + pub fn write_gas(&self) -> u64 { + //self.slots.len().ilog2() as u64 + self.slots.len() as u64 + } +} + +impl Deref for StorageCache { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.slots + } +} + +impl DerefMut for StorageCache { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.slots + } +} diff --git a/arbitrator/arbutil/src/format.rs b/arbitrator/arbutil/src/format.rs index 069421256..99e8b31b5 100644 --- a/arbitrator/arbutil/src/format.rs +++ b/arbitrator/arbutil/src/format.rs @@ -50,3 +50,16 @@ impl DebugBytes for T { format!("{:?}", self).as_bytes().to_vec() } } + +pub trait Utf8OrHex { + fn from_utf8_or_hex(data: impl Into>) -> String; +} + +impl Utf8OrHex for String { + fn from_utf8_or_hex(data: impl Into>) -> String { + match String::from_utf8(data.into()) { + Ok(string) => string, + Err(error) => hex::encode(error.as_bytes()), + } + } +} diff --git a/arbitrator/stylus/cbindgen.toml b/arbitrator/stylus/cbindgen.toml index b9afbe840..466972da7 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"] +include = ["EvmApiMethod", "EvmApiStatus"] diff --git a/arbitrator/stylus/src/env.rs b/arbitrator/stylus/src/env.rs index edf8cfb55..69d542070 100644 --- a/arbitrator/stylus/src/env.rs +++ b/arbitrator/stylus/src/env.rs @@ -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 use arbutil::{ diff --git a/arbitrator/stylus/src/evm_api.rs b/arbitrator/stylus/src/evm_api.rs index 752410d32..fdcc5b16a 100644 --- a/arbitrator/stylus/src/evm_api.rs +++ b/arbitrator/stylus/src/evm_api.rs @@ -3,7 +3,7 @@ use crate::{GoSliceData, RustSlice}; use arbutil::evm::{ - api::{EvmApiMethod, EvmApiStatus, EVM_API_METHOD_REQ_OFFSET}, + api::{EvmApiMethod, EVM_API_METHOD_REQ_OFFSET}, req::RequestHandler, }; @@ -16,7 +16,7 @@ pub struct NativeRequestHandler { gas_cost: *mut u64, result: *mut GoSliceData, raw_data: *mut GoSliceData, - ) -> EvmApiStatus, + ), pub id: usize, } @@ -35,7 +35,7 @@ impl RequestHandler for NativeRequestHandler { let mut result = GoSliceData::null(); let mut raw_data = GoSliceData::null(); let mut cost = 0; - let status = unsafe { + unsafe { (self.handle_request_fptr)( self.id, req_type as u32 + EVM_API_METHOD_REQ_OFFSET, @@ -45,7 +45,6 @@ impl RequestHandler for NativeRequestHandler { ptr!(raw_data), ) }; - assert_eq!(status, EvmApiStatus::Success); (result.slice().to_vec(), raw_data, cost) } } diff --git a/arbitrator/stylus/src/host.rs b/arbitrator/stylus/src/host.rs index 6bf4a9045..130b84a51 100644 --- a/arbitrator/stylus/src/host.rs +++ b/arbitrator/stylus/src/host.rs @@ -126,12 +126,19 @@ pub(crate) fn storage_load_bytes32>( hostio!(env, storage_load_bytes32(key, dest)) } -pub(crate) fn storage_store_bytes32>( +pub(crate) fn storage_cache_bytes32>( mut env: WasmEnvMut, key: GuestPtr, value: GuestPtr, ) -> MaybeEscape { - hostio!(env, storage_store_bytes32(key, value)) + hostio!(env, storage_cache_bytes32(key, value)) +} + +pub(crate) fn storage_flush_cache>( + mut env: WasmEnvMut, + clear: u32, +) -> MaybeEscape { + hostio!(env, storage_flush_cache(clear != 0)) } pub(crate) fn call_contract>( diff --git a/arbitrator/stylus/src/native.rs b/arbitrator/stylus/src/native.rs index c2def7b0a..1b14763c3 100644 --- a/arbitrator/stylus/src/native.rs +++ b/arbitrator/stylus/src/native.rs @@ -130,7 +130,8 @@ impl> NativeInstance { "write_result" => func!(host::write_result), "exit_early" => func!(host::exit_early), "storage_load_bytes32" => func!(host::storage_load_bytes32), - "storage_store_bytes32" => func!(host::storage_store_bytes32), + "storage_cache_bytes32" => func!(host::storage_cache_bytes32), + "storage_flush_cache" => func!(host::storage_flush_cache), "call_contract" => func!(host::call_contract), "delegate_call_contract" => func!(host::delegate_call_contract), "static_call_contract" => func!(host::static_call_contract), @@ -339,7 +340,8 @@ pub fn module(wasm: &[u8], compile: CompileConfig) -> Result> { "write_result" => stub!(|_: u32, _: u32|), "exit_early" => stub!(|_: u32|), "storage_load_bytes32" => stub!(|_: u32, _: u32|), - "storage_store_bytes32" => stub!(|_: u32, _: u32|), + "storage_cache_bytes32" => stub!(|_: u32, _: u32|), + "storage_flush_cache" => stub!(|_: u32|), "call_contract" => stub!(u8 <- |_: u32, _: u32, _: u32, _: u32, _: u64, _: u32|), "delegate_call_contract" => stub!(u8 <- |_: u32, _: u32, _: u32, _: u64, _: u32|), "static_call_contract" => stub!(u8 <- |_: u32, _: u32, _: u32, _: u64, _: u32|), diff --git a/arbitrator/stylus/src/test/api.rs b/arbitrator/stylus/src/test/api.rs index 1c418ab65..798fee79d 100644 --- a/arbitrator/stylus/src/test/api.rs +++ b/arbitrator/stylus/src/test/api.rs @@ -74,11 +74,17 @@ impl EvmApi for TestEvmApi { (value, 2100) // pretend worst case } - fn set_bytes32(&mut self, key: Bytes32, value: Bytes32) -> Result { + fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> u64 { let storage = &mut self.storage.lock(); let storage = storage.get_mut(&self.program).unwrap(); storage.insert(key, value); - Ok(22100) // pretend worst case + 0 + } + + fn flush_storage_cache(&mut self, _clear: bool, _gas_left: u64) -> Result { + let storage = &mut self.storage.lock(); + let storage = storage.get_mut(&self.program).unwrap(); + Ok(22100 * storage.len() as u64) // pretend worst case } /// Simulates a contract call. diff --git a/arbitrator/wasm-libraries/Cargo.lock b/arbitrator/wasm-libraries/Cargo.lock index 67beb7c93..c54553da3 100644 --- a/arbitrator/wasm-libraries/Cargo.lock +++ b/arbitrator/wasm-libraries/Cargo.lock @@ -1254,6 +1254,7 @@ dependencies = [ "lazy_static", "parking_lot", "prover", + "user-host-trait", ] [[package]] diff --git a/arbitrator/wasm-libraries/forward/src/main.rs b/arbitrator/wasm-libraries/forward/src/main.rs index 7f6f24699..632054bcb 100644 --- a/arbitrator/wasm-libraries/forward/src/main.rs +++ b/arbitrator/wasm-libraries/forward/src/main.rs @@ -6,12 +6,13 @@ use std::{fs::File, io::Write, path::PathBuf}; use structopt::StructOpt; /// order matters! -const HOSTIOS: [[&str; 3]; 34] = [ +const HOSTIOS: [[&str; 3]; 35] = [ ["read_args", "i32", ""], ["write_result", "i32 i32", ""], ["exit_early", "i32", ""], ["storage_load_bytes32", "i32 i32", ""], - ["storage_store_bytes32", "i32 i32", ""], + ["storage_cache_bytes32", "i32 i32", ""], + ["storage_flush_cache", "i32", ""], ["call_contract", "i32 i32 i32 i32 i64 i32", "i32"], ["delegate_call_contract", "i32 i32 i32 i64 i32", "i32"], ["static_call_contract", "i32 i32 i32 i64 i32", "i32"], diff --git a/arbitrator/wasm-libraries/user-host-trait/src/lib.rs b/arbitrator/wasm-libraries/user-host-trait/src/lib.rs index 74f8d2924..c9e1e049b 100644 --- a/arbitrator/wasm-libraries/user-host-trait/src/lib.rs +++ b/arbitrator/wasm-libraries/user-host-trait/src/lib.rs @@ -6,6 +6,7 @@ use arbutil::{ evm::{ self, api::{DataReader, EvmApi}, + storage::StorageCache, user::UserOutcomeKind, EvmData, }, @@ -132,10 +133,14 @@ pub trait UserHost: GasMeteredMachine { /// value stored in the EVM state trie at offset `key`, which will be `0` when not previously /// set. The semantics, then, are equivalent to that of the EVM's [`SLOAD`] opcode. /// + /// Note: the Stylus VM implements storage caching. This means that repeated calls to the same key + /// will cost less than in the EVM. + /// /// [`SLOAD`]: https://www.evm.codes/#54 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)?; + self.buy_ink(HOSTIO_INK + 2 * PTR_INK)?; + self.require_gas(evm::COLD_SLOAD_GAS + EVM_API_INK + StorageCache::REQUIRED_ACCESS_GAS)?; // cache-miss case + let key = self.read_bytes32(key)?; let (value, gas_cost) = self.evm_api().get_bytes32(key); @@ -144,25 +149,40 @@ pub trait UserHost: GasMeteredMachine { trace!("storage_load_bytes32", self, key, value) } - /// Stores a 32-byte value to permanent storage. Stylus's storage format is identical to that - /// of the EVM. This means that, under the hood, this hostio is storing a 32-byte value into - /// the EVM state trie at offset `key`. Furthermore, refunds are tabulated exactly as in the - /// EVM. The semantics, then, are equivalent to that of the EVM's [`SSTORE`] opcode. + /// Writes a 32-byte value to the permanent storage cache. Stylus's storage format is identical to that + /// of the EVM. This means that, under the hood, this hostio represents storing a 32-byte value into + /// the EVM state trie at offset `key`. Refunds are tabulated exactly as in the EVM. The semantics, then, + /// are equivalent to that of the EVM's [`SSTORE`] opcode. + /// + /// Note: because this value is cached, one must call `storage_flush_cache` to persist the value. /// - /// Note: we require the [`SSTORE`] sentry per EVM rules. The `gas_cost` returned by the EVM API + /// Auditor's note: we require the [`SSTORE`] sentry per EVM rules. The `gas_cost` returned by the EVM API /// 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: 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 + fn storage_cache_bytes32(&mut self, key: GuestPtr, value: GuestPtr) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + 2 * PTR_INK)?; + self.require_gas(evm::SSTORE_SENTRY_GAS + StorageCache::REQUIRED_ACCESS_GAS)?; // see operations_acl_arbitrum.go let key = self.read_bytes32(key)?; let value = self.read_bytes32(value)?; - let gas_cost = self.evm_api().set_bytes32(key, value)?; + let gas_cost = self.evm_api().cache_bytes32(key, value); self.buy_gas(gas_cost)?; - trace!("storage_store_bytes32", self, [key, value], &[]) + trace!("storage_cache_bytes32", self, [key, value], &[]) + } + + /// Persists any dirty values in the storage cache to the EVM state trie, dropping the cache entirely if requested. + /// Analogous to repeated invocations of [`SSTORE`]. + /// + /// [`SSTORE`]: https://www.evm.codes/#55 + fn storage_flush_cache(&mut self, clear: bool) -> Result<(), Self::Err> { + self.buy_ink(HOSTIO_INK + EVM_API_INK)?; + self.require_gas(evm::SSTORE_SENTRY_GAS)?; // see operations_acl_arbitrum.go + + let gas_left = self.gas_left()?; + self.evm_api().flush_storage_cache(clear, gas_left)?; + trace!("storage_flush_cache", self, [be!(clear as u8)], &[]) } /// Calls the contract at the given address with options for passing value and to limit the diff --git a/arbitrator/wasm-libraries/user-host/src/host.rs b/arbitrator/wasm-libraries/user-host/src/host.rs index 8b4d12240..64320b61a 100644 --- a/arbitrator/wasm-libraries/user-host/src/host.rs +++ b/arbitrator/wasm-libraries/user-host/src/host.rs @@ -49,8 +49,13 @@ pub unsafe extern "C" fn user_host__storage_load_bytes32(key: GuestPtr, dest: Gu } #[no_mangle] -pub unsafe extern "C" fn user_host__storage_store_bytes32(key: GuestPtr, value: GuestPtr) { - hostio!(storage_store_bytes32(key, value)) +pub unsafe extern "C" fn user_host__storage_cache_bytes32(key: GuestPtr, value: GuestPtr) { + hostio!(storage_cache_bytes32(key, value)) +} + +#[no_mangle] +pub unsafe extern "C" fn user_host__storage_flush_cache(clear: u32) { + hostio!(storage_flush_cache(clear != 0)) } #[no_mangle] diff --git a/arbitrator/wasm-libraries/user-test/Cargo.toml b/arbitrator/wasm-libraries/user-test/Cargo.toml index ee4577d4b..aad9d8ec2 100644 --- a/arbitrator/wasm-libraries/user-test/Cargo.toml +++ b/arbitrator/wasm-libraries/user-test/Cargo.toml @@ -10,6 +10,7 @@ crate-type = ["cdylib"] arbutil = { path = "../../arbutil/" } caller-env = { path = "../../caller-env/", features = ["static_caller"] } prover = { path = "../../prover/", default-features = false } +user-host-trait = { path = "../user-host-trait" } eyre = "0.6.5" fnv = "1.0.7" hex = "0.4.3" diff --git a/arbitrator/wasm-libraries/user-test/src/caller_env.rs b/arbitrator/wasm-libraries/user-test/src/caller_env.rs deleted file mode 100644 index 04555d579..000000000 --- a/arbitrator/wasm-libraries/user-test/src/caller_env.rs +++ /dev/null @@ -1,21 +0,0 @@ -// 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 d7b4869d5..a4f7912f5 100644 --- a/arbitrator/wasm-libraries/user-test/src/host.rs +++ b/arbitrator/wasm-libraries/user-test/src/host.rs @@ -1,94 +1,232 @@ // Copyright 2022-2024, Offchain Labs, Inc. -// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE +// For license information, see https://github.com/nitro/blob/master/LICENSE -#![allow(clippy::missing_safety_doc)] - -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}, -}; +use crate::program::Program; use caller_env::GuestPtr; -use prover::programs::{ - memory::MemoryModel, - prelude::{GasMeteredMachine, MeteredMachine}, -}; +use user_host_trait::UserHost; + +macro_rules! hostio { + ($($func:tt)*) => { + match Program::current().$($func)* { + Ok(value) => value, + Err(error) => panic!("{error}"), + } + }; +} #[no_mangle] 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(); - UserMem::write_slice(ptr, &ARGS); + hostio!(read_args(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__exit_early(status: u32) { + hostio!(exit_early(status)); } #[no_mangle] 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 = UserMem::read_slice(ptr, len); + hostio!(write_result(ptr, len)) } #[no_mangle] 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 = UserMem::read_bytes32(key); + hostio!(storage_load_bytes32(key, dest)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__storage_cache_bytes32(key: GuestPtr, value: GuestPtr) { + hostio!(storage_cache_bytes32(key, value)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__storage_flush_cache(clear: u32) { + hostio!(storage_flush_cache(clear != 0)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__call_contract( + contract: GuestPtr, + data: GuestPtr, + data_len: u32, + value: GuestPtr, + gas: u64, + ret_len: GuestPtr, +) -> u8 { + hostio!(call_contract(contract, data, data_len, value, gas, ret_len)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__delegate_call_contract( + contract: GuestPtr, + data: GuestPtr, + data_len: u32, + gas: u64, + ret_len: GuestPtr, +) -> u8 { + hostio!(delegate_call_contract( + contract, data, data_len, gas, ret_len + )) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__static_call_contract( + contract: GuestPtr, + data: GuestPtr, + data_len: u32, + gas: u64, + ret_len: GuestPtr, +) -> u8 { + hostio!(static_call_contract(contract, data, data_len, gas, ret_len)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__create1( + code: GuestPtr, + code_len: u32, + value: GuestPtr, + contract: GuestPtr, + revert_len: GuestPtr, +) { + hostio!(create1(code, code_len, value, contract, revert_len)) +} - let value = KEYS.lock().get(&key).cloned().unwrap_or_default(); - program.buy_gas(2100).unwrap(); // pretend it was cold - UserMem::write_slice(dest, &value.0); +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__create2( + code: GuestPtr, + code_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 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 +pub unsafe extern "C" fn vm_hooks__read_return_data( + dest: GuestPtr, + offset: u32, + size: u32, +) -> u32 { + hostio!(read_return_data(dest, offset, size)) +} - 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__return_data_size() -> u32 { + hostio!(return_data_size()) } #[no_mangle] 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).unwrap(); - program.pay_for_evm_log(topics, len - topics * 32).unwrap(); + hostio!(emit_log(data, len, topics)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__account_balance(address: GuestPtr, ptr: GuestPtr) { + hostio!(account_balance(address, ptr)) +} +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__account_code( + address: GuestPtr, + offset: u32, + size: u32, + dest: GuestPtr, +) -> u32 { + hostio!(account_code(address, offset, size, dest)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__account_code_size(address: GuestPtr) -> u32 { + hostio!(account_code_size(address)) +} - let data = UserMem::read_slice(data, len); - LOGS.push(data) +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__account_codehash(address: GuestPtr, ptr: GuestPtr) { + hostio!(account_codehash(address, ptr)) } #[no_mangle] -pub unsafe extern "C" fn vm_hooks__pay_for_memory_grow(pages: u16) { - let mut program = Program::start_free(); - if pages == 0 { - return program.buy_ink(HOSTIO_INK).unwrap(); - } - let model = MemoryModel::new(2, 1000); +pub unsafe extern "C" fn vm_hooks__block_basefee(ptr: GuestPtr) { + hostio!(block_basefee(ptr)) +} - let (open, ever) = (OPEN_PAGES, EVER_PAGES); - OPEN_PAGES = OPEN_PAGES.saturating_add(pages); - EVER_PAGES = EVER_PAGES.max(OPEN_PAGES); - program.buy_gas(model.gas_cost(pages, open, ever)).unwrap(); +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__block_coinbase(ptr: GuestPtr) { + hostio!(block_coinbase(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__block_gas_limit() -> u64 { + hostio!(block_gas_limit()) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__block_number() -> u64 { + hostio!(block_number()) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__block_timestamp() -> u64 { + hostio!(block_timestamp()) } #[no_mangle] -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(); +pub unsafe extern "C" fn vm_hooks__chainid() -> u64 { + hostio!(chainid()) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__contract_address(ptr: GuestPtr) { + hostio!(contract_address(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__evm_gas_left() -> u64 { + hostio!(evm_gas_left()) +} - let preimage = UserMem::read_slice(bytes, len); - let digest = crypto::keccak(preimage); - UserMem::write_slice(output, &digest); +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__evm_ink_left() -> u64 { + hostio!(evm_ink_left()) } #[no_mangle] pub unsafe extern "C" fn vm_hooks__msg_reentrant() -> u32 { - let _ = Program::start(0); - 0 + hostio!(msg_reentrant()) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__msg_sender(ptr: GuestPtr) { + hostio!(msg_sender(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__msg_value(ptr: GuestPtr) { + hostio!(msg_value(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__native_keccak256(input: GuestPtr, len: u32, output: GuestPtr) { + hostio!(native_keccak256(input, len, output)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__tx_gas_price(ptr: GuestPtr) { + hostio!(tx_gas_price(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__tx_ink_price() -> u32 { + hostio!(tx_ink_price()) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__tx_origin(ptr: GuestPtr) { + hostio!(tx_origin(ptr)) +} + +#[no_mangle] +pub unsafe extern "C" fn vm_hooks__pay_for_memory_grow(pages: u16) { + hostio!(pay_for_memory_grow(pages)) } diff --git a/arbitrator/wasm-libraries/user-test/src/ink.rs b/arbitrator/wasm-libraries/user-test/src/ink.rs index ab9a5045f..fca658e59 100644 --- a/arbitrator/wasm-libraries/user-test/src/ink.rs +++ b/arbitrator/wasm-libraries/user-test/src/ink.rs @@ -1,14 +1,12 @@ -// 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 arbutil::pricing; +use crate::{program::Program, CONFIG}; use prover::programs::{ config::PricingParams, prelude::{GasMeteredMachine, MachineMeter, MeteredMachine}, }; -use crate::{Program, CONFIG}; - #[link(wasm_import_module = "hostio")] extern "C" { fn user_ink_left() -> u64; @@ -38,15 +36,3 @@ impl GasMeteredMachine for Program { unsafe { CONFIG.unwrap().pricing } } } - -impl Program { - pub fn start(cost: u64) -> Self { - let mut program = Self::start_free(); - program.buy_ink(pricing::HOSTIO_INK + cost).unwrap(); - program - } - - pub fn start_free() -> Self { - Self - } -} diff --git a/arbitrator/wasm-libraries/user-test/src/lib.rs b/arbitrator/wasm-libraries/user-test/src/lib.rs index 21464d658..7fd771cf3 100644 --- a/arbitrator/wasm-libraries/user-test/src/lib.rs +++ b/arbitrator/wasm-libraries/user-test/src/lib.rs @@ -3,15 +3,15 @@ #![allow(clippy::missing_safety_doc)] -use arbutil::Bytes32; +use arbutil::{Bytes32, evm::EvmData}; use fnv::FnvHashMap as HashMap; use lazy_static::lazy_static; use parking_lot::Mutex; use prover::programs::prelude::StylusConfig; -mod caller_env; pub mod host; mod ink; +mod program; pub(crate) static mut ARGS: Vec = vec![]; pub(crate) static mut OUTS: Vec = vec![]; @@ -22,11 +22,9 @@ pub(crate) static mut EVER_PAGES: u16 = 0; lazy_static! { static ref KEYS: Mutex> = Mutex::new(HashMap::default()); + static ref EVM_DATA: EvmData = EvmData::default(); } -/// Mock type representing a `user_host::Program` -pub struct Program; - #[no_mangle] pub unsafe extern "C" fn user_test__prepare( len: usize, diff --git a/arbitrator/wasm-libraries/user-test/src/program.rs b/arbitrator/wasm-libraries/user-test/src/program.rs new file mode 100644 index 000000000..592317719 --- /dev/null +++ b/arbitrator/wasm-libraries/user-test/src/program.rs @@ -0,0 +1,209 @@ +// Copyright 2022-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +use crate::{ARGS, EVER_PAGES, KEYS, LOGS, OPEN_PAGES, OUTS, EVM_DATA}; +use arbutil::{ + evm::{ + api::{EvmApi, VecReader}, + user::UserOutcomeKind, + EvmData, + }, + Bytes20, Bytes32, Color, +}; +use caller_env::{static_caller::STATIC_MEM, GuestPtr, MemAccess}; +use eyre::{eyre, Result}; +use prover::programs::memory::MemoryModel; +use std::fmt::Display; +use user_host_trait::UserHost; + +/// Signifies an out-of-bounds memory access was requested. +pub struct MemoryBoundsError; + +impl From for eyre::ErrReport { + fn from(_: MemoryBoundsError) -> Self { + eyre!("memory access out of bounds") + } +} + +/// Mock type representing a `user_host::Program` +pub struct Program { + evm_api: MockEvmApi, +} + +#[allow(clippy::unit_arg)] +impl UserHost for Program { + type Err = eyre::ErrReport; + type MemoryErr = MemoryBoundsError; + type A = MockEvmApi; + + fn args(&self) -> &[u8] { + unsafe { &ARGS } + } + + fn outs(&mut self) -> &mut Vec { + unsafe { &mut OUTS } + } + + fn evm_api(&mut self) -> &mut Self::A { + &mut self.evm_api + } + + fn evm_data(&self) -> &EvmData { + &EVM_DATA + } + + fn evm_return_data_len(&mut self) -> &mut u32 { + unimplemented!() + } + + fn read_slice(&self, ptr: GuestPtr, len: u32) -> Result, MemoryBoundsError> { + self.check_memory_access(ptr, len)?; + unsafe { Ok(STATIC_MEM.read_slice(ptr, len as usize)) } + } + + 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_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: GuestPtr, src: &[u8]) -> Result<(), MemoryBoundsError> { + self.check_memory_access(ptr, src.len() as u32)?; + 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], _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}"); + } +} + +impl Program { + pub fn current() -> Self { + Self { + evm_api: MockEvmApi, + } + } + + fn check_memory_access(&self, _ptr: GuestPtr, _bytes: u32) -> Result<(), MemoryBoundsError> { + Ok(()) // pretend we did a check + } +} + +pub struct MockEvmApi; + +impl EvmApi for MockEvmApi { + fn get_bytes32(&mut self, key: Bytes32) -> (Bytes32, u64) { + let value = KEYS.lock().get(&key).cloned().unwrap_or_default(); + (value, 2100) // pretend worst case + } + + fn cache_bytes32(&mut self, key: Bytes32, value: Bytes32) -> u64 { + KEYS.lock().insert(key, value); + 0 + } + + fn flush_storage_cache(&mut self, _clear: bool, _gas_left: u64) -> Result { + Ok(22100 * KEYS.lock().len() as u64) // pretend worst case + } + + /// Simulates a contract call. + /// Note: this call function is for testing purposes only and deviates from onchain behavior. + fn contract_call( + &mut self, + _contract: Bytes20, + _calldata: &[u8], + _gas: u64, + _value: Bytes32, + ) -> (u32, u64, UserOutcomeKind) { + unimplemented!() + } + + fn delegate_call( + &mut self, + _contract: Bytes20, + _calldata: &[u8], + _gas: u64, + ) -> (u32, u64, UserOutcomeKind) { + unimplemented!() + } + + fn static_call( + &mut self, + _contract: Bytes20, + _calldata: &[u8], + _gas: u64, + ) -> (u32, u64, UserOutcomeKind) { + unimplemented!() + } + + fn create1( + &mut self, + _code: Vec, + _endowment: Bytes32, + _gas: u64, + ) -> (Result, u32, u64) { + unimplemented!() + } + + fn create2( + &mut self, + _code: Vec, + _endowment: Bytes32, + _salt: Bytes32, + _gas: u64, + ) -> (Result, u32, u64) { + unimplemented!() + } + + fn get_return_data(&self) -> VecReader { + unimplemented!() + } + + fn emit_log(&mut self, data: Vec, _topics: u32) -> Result<()> { + unsafe { LOGS.push(data) }; + Ok(()) // pretend a log was emitted + } + + fn account_balance(&mut self, _address: Bytes20) -> (Bytes32, u64) { + unimplemented!() + } + + fn account_code(&mut self, _address: Bytes20, _gas_left: u64) -> (VecReader, u64) { + unimplemented!() + } + + fn account_codehash(&mut self, _address: Bytes20) -> (Bytes32, u64) { + unimplemented!() + } + + fn add_pages(&mut self, pages: u16) -> u64 { + let model = MemoryModel::new(2, 1000); + unsafe { + let (open, ever) = (OPEN_PAGES, EVER_PAGES); + OPEN_PAGES = OPEN_PAGES.saturating_add(pages); + EVER_PAGES = EVER_PAGES.max(OPEN_PAGES); + model.gas_cost(pages, open, ever) + } + } + + fn capture_hostio( + &mut self, + _name: &str, + _args: &[u8], + _outs: &[u8], + _start_ink: u64, + _end_ink: u64, + ) { + unimplemented!() + } +} diff --git a/arbos/programs/api.go b/arbos/programs/api.go index 9369cc626..73c1915da 100644 --- a/arbos/programs/api.go +++ b/arbos/programs/api.go @@ -24,7 +24,7 @@ type RequestType int const ( GetBytes32 RequestType = iota - SetBytes32 + SetTrieSlots ContractCall DelegateCall StaticCall @@ -38,6 +38,19 @@ const ( CaptureHostIO ) +type apiStatus uint8 + +const ( + Success apiStatus = iota + Failure + OutOfGas + WriteProtection +) + +func (s apiStatus) to_slice() []byte { + return []byte{uint8(s)} +} + const EvmApiMethodReqOffset = 0x10000000 func newApiClosures( @@ -60,16 +73,28 @@ func newApiClosures( cost := vm.WasmStateLoadCost(db, actingAddress, key) return db.GetState(actingAddress, key), cost } - setBytes32 := func(key, value common.Hash) (uint64, error) { - if tracingInfo != nil { - tracingInfo.RecordStorageSet(key, value) - } - if readOnly { - return 0, vm.ErrWriteProtection + setTrieSlots := func(data []byte, gasLeft *uint64) apiStatus { + for len(data) > 0 { + key := common.BytesToHash(data[:32]) + value := common.BytesToHash(data[32:64]) + data = data[64:] + + if tracingInfo != nil { + tracingInfo.RecordStorageSet(key, value) + } + if readOnly { + return WriteProtection + } + + cost := vm.WasmStateStoreCost(db, actingAddress, key, value) + if cost > *gasLeft { + *gasLeft = 0 + return OutOfGas + } + *gasLeft -= cost + db.SetState(actingAddress, key, value) } - cost := vm.WasmStateStoreCost(db, actingAddress, key, value) - db.SetState(actingAddress, key, value) - return cost, nil + return Success } doCall := func( contract common.Address, opcode vm.OpCode, input []byte, gas uint64, value *big.Int, @@ -286,14 +311,11 @@ func newApiClosures( key := takeHash() out, cost := getBytes32(key) return out[:], nil, cost - case SetBytes32: - key := takeHash() - value := takeHash() - cost, err := setBytes32(key, value) - if err != nil { - return []byte{0}, nil, 0 - } - return []byte{1}, nil, cost + case SetTrieSlots: + gasLeft := takeU64() + gas := gasLeft + status := setTrieSlots(takeRest(), &gas) + return status.to_slice(), nil, gasLeft - gas case ContractCall, DelegateCall, StaticCall: var opcode vm.OpCode switch req { diff --git a/arbos/programs/native.go b/arbos/programs/native.go index 198e3cb80..a41606366 100644 --- a/arbos/programs/native.go +++ b/arbos/programs/native.go @@ -135,13 +135,8 @@ func callProgram( return data, err } -type apiStatus = C.EvmApiStatus - -const apiSuccess C.EvmApiStatus = C.EvmApiStatus_Success -const apiFailure C.EvmApiStatus = C.EvmApiStatus_Failure - //export handleReqImpl -func handleReqImpl(apiId usize, req_type u32, data *rustSlice, 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) { api := getApi(apiId) reqData := data.read() reqType := RequestType(req_type - EvmApiMethodReqOffset) @@ -149,7 +144,6 @@ func handleReqImpl(apiId usize, req_type u32, data *rustSlice, costPtr *u64, out *costPtr = u64(cost) api.pinAndRef(response, out_response) api.pinAndRef(raw_data, out_raw_data) - return apiSuccess } func (value bytes32) toHash() common.Hash { diff --git a/arbos/programs/native_api.go b/arbos/programs/native_api.go index e66cf07fc..136f74c96 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, 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) { +void handleReqImpl(usize api, u32 req_type, RustSlice *data, u64 *out_cost, GoSliceData *out_result, GoSliceData *out_raw_data); +void 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); } */ diff --git a/arbos/programs/testconstants.go b/arbos/programs/testconstants.go index 04f40395d..f37ccb4b0 100644 --- a/arbos/programs/testconstants.go +++ b/arbos/programs/testconstants.go @@ -25,7 +25,7 @@ func testConstants() error { if err := errIfNotEq(1, GetBytes32, C.EvmApiMethod_GetBytes32); err != nil { return err } - if err := errIfNotEq(2, SetBytes32, C.EvmApiMethod_SetBytes32); err != nil { + if err := errIfNotEq(2, SetTrieSlots, C.EvmApiMethod_SetTrieSlots); err != nil { return err } if err := errIfNotEq(3, ContractCall, C.EvmApiMethod_ContractCall); err != nil { @@ -61,5 +61,29 @@ func testConstants() error { if err := errIfNotEq(14, CaptureHostIO, C.EvmApiMethod_CaptureHostIO); err != nil { return err } - return errIfNotEq(15, EvmApiMethodReqOffset, C.EVM_API_METHOD_REQ_OFFSET) + if err := errIfNotEq(15, EvmApiMethodReqOffset, C.EVM_API_METHOD_REQ_OFFSET); err != nil { + return err + } + + assertEq := func(index int, a apiStatus, b uint32) error { + if uint32(a) != b { + return fmt.Errorf("constant test %d failed! %d != %d", index, a, b) + } + return nil + } + + if err := assertEq(0, Success, C.EvmApiStatus_Success); err != nil { + return err + } + if err := assertEq(1, Failure, C.EvmApiStatus_Failure); err != nil { + return err + } + if err := assertEq(2, OutOfGas, C.EvmApiStatus_OutOfGas); err != nil { + return err + } + if err := assertEq(3, WriteProtection, C.EvmApiStatus_WriteProtection); err != nil { + return err + } + + return nil }