diff --git a/Cargo.lock b/Cargo.lock index 3a3076578..f0c2e831c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -540,6 +540,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "intx" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f38a50a899dc47a6d0ed5508e7f601a2e34c3a85303514b5d137f3c10a0c75" + [[package]] name = "itertools" version = "0.10.5" @@ -622,12 +628,6 @@ 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 = "meval" version = "0.2.0" @@ -734,7 +734,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", - "num-bigint", "num-integer", "num-traits", ] @@ -1049,6 +1048,12 @@ dependencies = [ "wide", ] +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + [[package]] name = "soroban-env-common" version = "0.0.16" @@ -1148,24 +1153,26 @@ version = "0.0.16" [[package]] name = "soroban-wasmi" -version = "0.16.0-soroban2" -source = "git+https://github.com/stellar/wasmi?rev=862b32f5#862b32f53f9c6223911e79e0b0fc8592fb3bb04c" +version = "0.30.0-soroban" +source = "git+https://github.com/stellar/wasmi?rev=1a2bc7f#1a2bc7f3801c565c2dab22021255a164c05a7f76" dependencies = [ + "intx", + "smallvec", "soroban-wasmi_core", "spin", + "wasmi_arena", "wasmparser-nostd", ] [[package]] name = "soroban-wasmi_core" -version = "0.16.0-soroban2" -source = "git+https://github.com/stellar/wasmi?rev=862b32f5#862b32f53f9c6223911e79e0b0fc8592fb3bb04c" +version = "0.30.0-soroban" +source = "git+https://github.com/stellar/wasmi?rev=1a2bc7f#1a2bc7f3801c565c2dab22021255a164c05a7f76" dependencies = [ "downcast-rs", "libm", - "memory_units", - "num-rational", "num-traits", + "paste", ] [[package]] @@ -1558,6 +1565,11 @@ dependencies = [ "leb128", ] +[[package]] +name = "wasmi_arena" +version = "0.4.0" +source = "git+https://github.com/stellar/wasmi?rev=1a2bc7f#1a2bc7f3801c565c2dab22021255a164c05a7f76" + [[package]] name = "wasmparser" version = "0.106.0" @@ -1570,9 +1582,9 @@ dependencies = [ [[package]] name = "wasmparser-nostd" -version = "0.90.0" +version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92a94fbf4c521b038f41382df2056cf47099d3b7a0faa5a6e46f7771fd7c84a6" +checksum = "9157cab83003221bfd385833ab587a039f5d6fa7304854042ba358a3b09e0724" dependencies = [ "indexmap-nostd", ] diff --git a/Cargo.toml b/Cargo.toml index 68f8688fc..45df58a83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,16 +32,15 @@ default-features = false [workspace.dependencies.wasmi] package = "soroban-wasmi" -version = "0.16.0-soroban2" +version = "0.30.0-soroban" git = "https://github.com/stellar/wasmi" -rev = "862b32f5" +rev = "1a2bc7f" # [patch."https://github.com/stellar/rs-stellar-xdr"] # stellar-xdr = { path = "../rs-stellar-xdr/" } - # [patch."https://github.com/stellar/wasmi"] -# soroban-wasmi = { path = "../wasmi/wasmi_v1/" } -# soroban-wasmi_core = { path = "../wasmi/core/" } +# soroban-wasmi = { path = "../wasmi/crates/wasmi/" } +# soroban-wasmi_core = { path = "../wasmi/crates/core/" } [profile.release] codegen-units = 1 diff --git a/soroban-env-common/src/error.rs b/soroban-env-common/src/error.rs index a4e02ef69..4cbdc20b4 100644 --- a/soroban-env-common/src/error.rs +++ b/soroban-env-common/src/error.rs @@ -134,35 +134,38 @@ impl From for Error { } } +#[cfg(feature = "wasmi")] +impl From for Error { + fn from(code: wasmi::core::TrapCode) -> Self { + let ec = match code { + wasmi::core::TrapCode::UnreachableCodeReached => ScErrorCode::InternalError, + + wasmi::core::TrapCode::MemoryOutOfBounds | wasmi::core::TrapCode::TableOutOfBounds => { + ScErrorCode::IndexBounds + } + + wasmi::core::TrapCode::IndirectCallToNull => ScErrorCode::MissingValue, + + wasmi::core::TrapCode::IntegerDivisionByZero + | wasmi::core::TrapCode::IntegerOverflow + | wasmi::core::TrapCode::BadConversionToInteger => ScErrorCode::ArithDomain, + + wasmi::core::TrapCode::BadSignature => ScErrorCode::UnexpectedType, + + wasmi::core::TrapCode::StackOverflow | wasmi::core::TrapCode::OutOfFuel => { + return Error::from_type_and_code(ScErrorType::Budget, ScErrorCode::ExceededLimit) + } + }; + return Error::from_type_and_code(ScErrorType::WasmVm, ec); + } +} + #[cfg(feature = "wasmi")] impl From for Error { fn from(e: wasmi::Error) -> Self { if let wasmi::Error::Trap(trap) = e { - if let Some(code) = trap.as_code() { - let ec = match code { - wasmi::core::TrapCode::Unreachable => ScErrorCode::InternalError, - - wasmi::core::TrapCode::MemoryAccessOutOfBounds - | wasmi::core::TrapCode::TableAccessOutOfBounds => ScErrorCode::IndexBounds, - - wasmi::core::TrapCode::ElemUninitialized => ScErrorCode::MissingValue, - - wasmi::core::TrapCode::DivisionByZero - | wasmi::core::TrapCode::IntegerOverflow - | wasmi::core::TrapCode::InvalidConversionToInt => ScErrorCode::ArithDomain, - - wasmi::core::TrapCode::UnexpectedSignature => ScErrorCode::UnexpectedType, - - wasmi::core::TrapCode::StackOverflow - | wasmi::core::TrapCode::MemLimitExceeded - | wasmi::core::TrapCode::CpuLimitExceeded => { - return Error::from_type_and_code( - ScErrorType::Budget, - ScErrorCode::ExceededLimit, - ) - } - }; - return Error::from_type_and_code(ScErrorType::WasmVm, ec); + if let Some(code) = trap.trap_code() { + return code.into(); } } Error::from_type_and_code(ScErrorType::WasmVm, ScErrorCode::InternalError) diff --git a/soroban-env-common/src/lib.rs b/soroban-env-common/src/lib.rs index 46b0e015d..2614e530e 100644 --- a/soroban-env-common/src/lib.rs +++ b/soroban-env-common/src/lib.rs @@ -73,6 +73,8 @@ pub use num::{I256, U256}; pub use stellar_xdr as xdr; // RawVal is the 64-bit transparent type. +#[cfg(feature = "wasmi")] +pub use raw_val::WasmiMarshal; pub use raw_val::{ AddressObject, ContractExecutableObject, LedgerKeyNonceObject, MapObject, VecObject, }; diff --git a/soroban-env-common/src/raw_val.rs b/soroban-env-common/src/raw_val.rs index 068f4b52f..4f7245de0 100644 --- a/soroban-env-common/src/raw_val.rs +++ b/soroban-env-common/src/raw_val.rs @@ -383,20 +383,53 @@ impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible!(i32); impl_tryfroms_and_tryfromvals_delegating_to_rawvalconvertible!(Error); #[cfg(feature = "wasmi")] -impl wasmi::core::FromValue for RawVal { - fn from_value(val: wasmi::core::Value) -> Option { - if let wasmi::core::Value::I64(i) = val { +pub trait WasmiMarshal: Sized { + fn try_marshal_from_value(v: wasmi::Value) -> Option; + fn marshal_from_self(self) -> wasmi::Value; +} + +#[cfg(feature = "wasmi")] +impl WasmiMarshal for RawVal { + fn try_marshal_from_value(v: wasmi::Value) -> Option { + if let wasmi::Value::I64(i) = v { Some(RawVal::from_payload(i as u64)) } else { None } } + + fn marshal_from_self(self) -> wasmi::Value { + wasmi::Value::I64(self.get_payload() as i64) + } } #[cfg(feature = "wasmi")] -impl From for wasmi::core::Value { - fn from(v: RawVal) -> Self { - wasmi::core::Value::I64(v.get_payload() as i64) +impl WasmiMarshal for u64 { + fn try_marshal_from_value(v: wasmi::Value) -> Option { + if let wasmi::Value::I64(i) = v { + Some(i as u64) + } else { + None + } + } + + fn marshal_from_self(self) -> wasmi::Value { + wasmi::Value::I64(self as i64) + } +} + +#[cfg(feature = "wasmi")] +impl WasmiMarshal for i64 { + fn try_marshal_from_value(v: wasmi::Value) -> Option { + if let wasmi::Value::I64(i) = v { + Some(i) + } else { + None + } + } + + fn marshal_from_self(self) -> wasmi::Value { + wasmi::Value::I64(self) } } diff --git a/soroban-env-common/src/wrapper_macros.rs b/soroban-env-common/src/wrapper_macros.rs index 35264d35a..6be58ab5d 100644 --- a/soroban-env-common/src/wrapper_macros.rs +++ b/soroban-env-common/src/wrapper_macros.rs @@ -84,28 +84,21 @@ macro_rules! impl_wrapper_wasmi_conversions { ($wrapper:ty) => { // wasmi / VM argument support #[cfg(feature = "wasmi")] - impl wasmi::core::FromValue for $wrapper { - fn from_value(val: wasmi::core::Value) -> Option { - let maybe: Option = ::from_value(val); - match maybe { - Some(u) => { - let raw = $crate::RawVal::from_payload(u); - if ::is_val_type(raw) { - Some(unsafe { - ::unchecked_from_val(raw) - }) - } else { - None - } + impl $crate::WasmiMarshal for $wrapper { + fn try_marshal_from_value(v: wasmi::Value) -> Option { + if let wasmi::Value::I64(i) = v { + let raw = $crate::RawVal::from_payload(i as u64); + if ::is_val_type(raw) { + return Some(unsafe { + ::unchecked_from_val(raw) + }); } - None => None, } + None } - } - #[cfg(feature = "wasmi")] - impl From<$wrapper> for wasmi::core::Value { - fn from(v: $wrapper) -> Self { - wasmi::core::Value::I64(v.as_raw().get_payload() as i64) + + fn marshal_from_self(self) -> wasmi::Value { + wasmi::Value::I64(self.as_raw().get_payload() as i64) } } }; diff --git a/soroban-env-host/src/budget.rs b/soroban-env-host/src/budget.rs index 3f0850abc..86c834c45 100644 --- a/soroban-env-host/src/budget.rs +++ b/soroban-env-host/src/budget.rs @@ -11,6 +11,8 @@ use crate::{ Host, HostError, }; +use wasmi::FuelCosts; + /// We provide a "cost model" object that evaluates a linear expression: /// /// f(x) = a + b * Option @@ -41,7 +43,6 @@ pub trait HostCostModel { impl HostCostModel for ContractCostParamEntry { fn evaluate(&self, input: Option) -> Result { if self.const_term < 0 || self.linear_term < 0 { - // TODO: consider more concrete error code "invalid input" return Err((ScErrorType::Context, ScErrorCode::InvalidInput).into()); } @@ -151,6 +152,10 @@ impl BudgetDimension { self.limit } + pub fn get_remaining(&self) -> u64 { + self.limit.saturating_sub(self.total_count) + } + pub fn reset(&mut self, limit: u64) { self.limit = limit; self.total_count = 0; @@ -233,8 +238,8 @@ impl BudgetImpl { // type -- we leave the input as `None`, otherwise, we initialize the input to 0. let i = ct as usize; match ct { - ContractCostType::WasmInsnExec => (), - ContractCostType::WasmMemAlloc => self.tracker[i].1 = Some(0), // number of pages in wasm linear memory to allocate (each page is 64kB) + ContractCostType::WasmInsnExec => self.tracker[i].1 = Some(0), // number of "fuel" units wasmi consumes + ContractCostType::WasmMemAlloc => self.tracker[i].1 = Some(0), // number of "memory fuel" units wasmi consumes ContractCostType::HostMemAlloc => self.tracker[i].1 = Some(0), // number of bytes in host memory to allocate ContractCostType::HostMemCpy => self.tracker[i].1 = Some(0), // number of bytes in host to copy ContractCostType::HostMemCmp => self.tracker[i].1 = Some(0), // number of bytes in host to compare @@ -450,6 +455,15 @@ impl Budget { self.charge_in_bulk(ty, 1, input) } + pub fn apply_wasmi_fuels(&self, cpu_fuel: u64, mem_fuel: u64) -> Result<(), HostError> { + self.mut_budget(|mut b| { + b.cpu_insns + .charge(ContractCostType::WasmInsnExec, 1, Some(cpu_fuel))?; + b.mem_bytes + .charge(ContractCostType::WasmMemAlloc, 1, Some(mem_fuel)) + }) + } + /// Performs a bulk charge to the budget under the specified [`CostType`]. /// The `iterations` is the batch size. The caller needs to ensure: /// 1. the batched charges have identical costs (having the same @@ -496,14 +510,22 @@ impl Budget { f(&mut self.0.borrow_mut().tracker[ty as usize]) } - pub fn get_cpu_insns_count(&self) -> u64 { + pub fn get_cpu_insns_consumed(&self) -> u64 { self.0.borrow().cpu_insns.get_total_count() } - pub fn get_mem_bytes_count(&self) -> u64 { + pub fn get_mem_bytes_consumed(&self) -> u64 { self.0.borrow().mem_bytes.get_total_count() } + pub fn get_cpu_insns_remaining(&self) -> u64 { + self.0.borrow().cpu_insns.get_remaining() + } + + pub fn get_mem_bytes_remaining(&self) -> u64 { + self.0.borrow().mem_bytes.get_remaining() + } + pub fn reset_default(&self) { *self.0.borrow_mut() = BudgetImpl::default() } @@ -558,6 +580,65 @@ impl Budget { }) .unwrap(); // impossible to panic } + + fn get_cpu_insns_remaining_as_fuel(&self) -> Result { + let cpu_remaining = self.get_cpu_insns_remaining(); + let cpu_per_fuel = self + .0 + .borrow() + .cpu_insns + .get_cost_model(ContractCostType::WasmInsnExec) + .linear_term; + + if cpu_per_fuel < 0 { + return Err((ScErrorType::Context, ScErrorCode::InvalidInput).into()); + } + let cpu_per_fuel = (cpu_per_fuel as u64).max(1); + // Due to rounding, the amount of cpu converted to fuel will be slightly + // less than the total cpu available. This is okay because 1. that rounded-off + // amount should be very small (less than the cpu_per_fuel) 2. it does + // not cumulate over host function calls (each time the Vm returns back + // to the host, the host gets back the unspent fuel amount converged + // back to the cpu). The only way this rounding difference is observable + // is if the Vm traps due to `OutOfFuel`, this tiny amount would still + // be withheld from the host. And this may not be the only source of + // unspendable residual budget (see the other comment in `vm::wrapped_func_call`). + // So it should be okay. + Ok(cpu_remaining / cpu_per_fuel) + } + + fn get_mem_bytes_remaining_as_fuel(&self) -> Result { + let bytes_remaining = self.get_mem_bytes_remaining(); + let bytes_per_fuel = self + .0 + .borrow() + .mem_bytes + .get_cost_model(ContractCostType::WasmMemAlloc) + .linear_term; + + if bytes_per_fuel < 0 { + return Err((ScErrorType::Context, ScErrorCode::InvalidInput).into()); + } + let bytes_per_fuel = (bytes_per_fuel as u64).max(1); + // See comment about rounding above. + Ok(bytes_remaining / bytes_per_fuel) + } + + pub fn get_fuels_budget(&self) -> Result<(u64, u64), HostError> { + let cpu_fuel = self.get_cpu_insns_remaining_as_fuel()?; + let mem_fuel = self.get_mem_bytes_remaining_as_fuel()?; + Ok((cpu_fuel, mem_fuel)) + } + + // generate a wasmi fuel cost schedule based on our calibration + pub fn wasmi_fuel_costs(&self) -> FuelCosts { + let mut costs = FuelCosts::default(); + costs.base = 1; + costs.entity = 1; + costs.store = 1; + costs.call = 10; + costs + } } /// Default settings for local/sandbox testing only. The actual operations will use parameters @@ -575,12 +656,21 @@ impl Default for BudgetImpl { // define the cpu cost model parameters let cpu = &mut b.cpu_insns.get_cost_model_mut(ct); match ct { + // This is the host cpu insn cost per wasm "fuel". Every "base" wasm + // instruction costs 1 fuel (by default), and some particular types of + // instructions may cost additional amount of fuel based on + // wasmi's config setting. ContractCostType::WasmInsnExec => { - cpu.const_term = 22; - cpu.linear_term = 0; - } + cpu.const_term = 0; + cpu.linear_term = 22; // this is calibrated + } + // Host cpu insns per wasm "memory fuel". This has to be zero since + // the fuel (representing cpu cost) has been covered by `WasmInsnExec`. + // The extra cost of mem processing is accounted for by wasmi's + // `config.memory_bytes_per_fuel` parameter. + // This type is designated to the mem cost. ContractCostType::WasmMemAlloc => { - cpu.const_term = 521; + cpu.const_term = 0; cpu.linear_term = 0; } ContractCostType::HostMemAlloc => { @@ -667,13 +757,15 @@ impl Default for BudgetImpl { // define the memory cost model parameters let mem = b.mem_bytes.get_cost_model_mut(ct); match ct { + // This type is designated to the cpu cost. ContractCostType::WasmInsnExec => { mem.const_term = 0; mem.linear_term = 0; } + // Bytes per wasmi "memory fuel". ContractCostType::WasmMemAlloc => { - mem.const_term = 66136; - mem.linear_term = 1; + mem.const_term = 66136; // TODO: this seems to be 1 page overhead? + mem.linear_term = 1; // this is always 1 (1-to-1 between bytes in the host and in Wasmi) } ContractCostType::HostMemAlloc => { mem.const_term = 8; diff --git a/soroban-env-host/src/host.rs b/soroban-env-host/src/host.rs index 6f5fb9382..e16681921 100644 --- a/soroban-env-host/src/host.rs +++ b/soroban-env-host/src/host.rs @@ -219,9 +219,9 @@ impl Host { /// Helper for mutating the [`Budget`] held in this [`Host`], either to /// allocate it on contract creation or to deplete it on callbacks from /// the VM or host functions. - pub fn with_budget(&self, f: F) -> T + pub(crate) fn with_budget(&self, f: F) -> Result where - F: FnOnce(Budget) -> T, + F: FnOnce(Budget) -> Result, { f(self.0.budget.clone()) } diff --git a/soroban-env-host/src/host/declared_size.rs b/soroban-env-host/src/host/declared_size.rs index a717112ec..af6705ea1 100644 --- a/soroban-env-host/src/host/declared_size.rs +++ b/soroban-env-host/src/host/declared_size.rs @@ -96,7 +96,7 @@ impl_declared_size_type!(SymbolStr, SCSYMBOL_LIMIT); impl_declared_size_type!(SymbolSmallIter, 8); impl_declared_size_type!(U256, 32); impl_declared_size_type!(I256, 32); -impl_declared_size_type!(HostObject, 40); +impl_declared_size_type!(HostObject, 48); // xdr types impl_declared_size_type!(TimePoint, 8); impl_declared_size_type!(Duration, 8); diff --git a/soroban-env-host/src/host/error.rs b/soroban-env-host/src/host/error.rs index 1ee442600..4a4a7bc52 100644 --- a/soroban-env-host/src/host/error.rs +++ b/soroban-env-host/src/host/error.rs @@ -218,6 +218,15 @@ impl Host { ) } + pub(crate) fn err_wasmi_fuel_metering_disabled(&self) -> HostError { + self.err( + ScErrorType::WasmVm, + ScErrorCode::InternalError, + "wasmi fuel metering is disabled", + &[], + ) + } + /// Given a result carrying some error type that can be converted to an /// [Error] and supports [core::fmt::Debug], calls [Host::error] with the /// error when there's an error, also passing the result of diff --git a/soroban-env-host/src/test/basic.rs b/soroban-env-host/src/test/basic.rs index d4e4528bb..621c16e15 100644 --- a/soroban-env-host/src/test/basic.rs +++ b/soroban-env-host/src/test/basic.rs @@ -76,3 +76,12 @@ fn tuple_roundtrip() -> Result<(), HostError> { assert_eq!(t0, t0_back); Ok(()) } + +#[test] +fn f32_does_not_work() -> Result<(), HostError> { + use soroban_env_common::xdr::Hash; + let host = Host::default(); + let hash = Hash::from([0; 32]); + assert!(crate::vm::Vm::new(&host, hash, soroban_test_wasms::ADD_F32).is_err()); + Ok(()) +} diff --git a/soroban-env-host/src/test/budget_metering.rs b/soroban-env-host/src/test/budget_metering.rs index 6cdf0a262..72e214cef 100644 --- a/soroban-env-host/src/test/budget_metering.rs +++ b/soroban-env-host/src/test/budget_metering.rs @@ -35,9 +35,10 @@ fn xdr_object_conversion() -> Result<(), HostError> { // we wind up double-counting the conversion of "objects". // Possibly this should be improved in the future. assert_eq!(budget.get_tracker(ContractCostType::ValXdrConv).0, 6); - assert_eq!(budget.get_cpu_insns_count(), 60); - assert_eq!(budget.get_mem_bytes_count(), 6); - }); + assert_eq!(budget.get_cpu_insns_consumed(), 60); + assert_eq!(budget.get_mem_bytes_consumed(), 6); + Ok(()) + })?; Ok(()) } @@ -63,9 +64,10 @@ fn vm_hostfn_invocation() -> Result<(), HostError> { budget.get_tracker(ContractCostType::InvokeHostFunction).0, 2 ); - assert_eq!(budget.get_cpu_insns_count(), 30); - assert_eq!(budget.get_mem_bytes_count(), 3); - }); + assert_eq!(budget.get_cpu_insns_consumed(), 30); + assert_eq!(budget.get_mem_bytes_consumed(), 3); + Ok(()) + })?; Ok(()) } @@ -96,7 +98,8 @@ fn metered_xdr() -> Result<(), HostError> { budget.get_tracker(ContractCostType::ValSer).1, Some(w.len() as u64) ); - }); + Ok(()) + })?; host.metered_from_xdr::(w.as_slice())?; host.with_budget(|budget| { @@ -104,7 +107,8 @@ fn metered_xdr() -> Result<(), HostError> { budget.get_tracker(ContractCostType::ValDeser).1, Some(w.len() as u64) ); - }); + Ok(()) + })?; Ok(()) } @@ -155,7 +159,8 @@ fn map_insert_key_vec_obj() -> Result<(), HostError> { assert_eq!(budget.get_tracker(ContractCostType::VisitObject).0, 4); // upper bound of number of map-accesses, counting both binary-search and point-access. assert_eq!(budget.get_tracker(ContractCostType::MapEntry).0, 5); - }); + Ok(()) + })?; Ok(()) } @@ -221,7 +226,7 @@ fn total_amount_charged_from_random_inputs() -> Result<(), HostError> { let host = Host::default(); let tracker: Vec<(u64, Option)> = vec![ - (246, None), + (1, Some(246)), (1, Some(184)), (1, Some(152)), (1, Some(65)), @@ -250,12 +255,12 @@ fn total_amount_charged_from_random_inputs() -> Result<(), HostError> { let actual = format!("{:?}", host.as_budget()); expect![[r#" ===================================================================================================================================================================== - Cpu limit: 40000000; used: 8426807 + Cpu limit: 40000000; used: 8426286 Mem limit: 52428800; used: 1219916 ===================================================================================================================================================================== CostType iterations input cpu_insns mem_bytes const_term_cpu lin_term_cpu const_term_mem lin_term_mem - WasmInsnExec 246 None 5412 0 22 0 0 0 - WasmMemAlloc 1 Some(184) 521 66320 521 0 66136 1 + WasmInsnExec 1 Some(246) 5412 0 0 22 0 0 + WasmMemAlloc 1 Some(184) 0 66320 0 0 66136 1 HostMemAlloc 1 Some(152) 883 160 883 0 8 1 HostMemCpy 1 Some(65) 24 0 24 0 0 0 HostMemCmp 1 Some(74) 116 0 42 1 0 0 diff --git a/soroban-env-host/src/test/hostile.rs b/soroban-env-host/src/test/hostile.rs index fc1e41276..0e758f1f2 100644 --- a/soroban-env-host/src/test/hostile.rs +++ b/soroban-env-host/src/test/hostile.rs @@ -89,8 +89,8 @@ fn hostile_objs_traps() -> Result<(), HostError> { let args: ScVec = host.test_scvec::(&[])?; host.set_diagnostic_level(crate::DiagnosticLevel::Debug); - host.with_budget(|b| b.reset_default()); - host.with_budget(|b| b.reset_unlimited_cpu()); + host.with_budget(|b| Ok(b.reset_default()))?; + host.with_budget(|b| Ok(b.reset_unlimited_cpu()))?; // This one should just run out of memory let res = vm.invoke_function(&host, "objs", &args); diff --git a/soroban-env-host/src/test/invocation.rs b/soroban-env-host/src/test/invocation.rs index f19768b9f..0c5c4119f 100644 --- a/soroban-env-host/src/test/invocation.rs +++ b/soroban-env-host/src/test/invocation.rs @@ -134,7 +134,7 @@ fn invoke_cross_contract_indirect_err() -> Result<(), HostError> { assert_eq!(status.get_payload(), exp.to_raw().get_payload()); let events = host.get_events()?.0; - assert_eq!(events.len(), 3); + assert_eq!(events.len(), 2); let last_event = events.last().unwrap(); // run `UPDATE_EXPECT=true cargo test` to update this. let expected = expect![[ @@ -148,7 +148,7 @@ fn invoke_cross_contract_indirect_err() -> Result<(), HostError> { assert!(HostError::result_matches_err(res, code)); let events = host.get_events()?.0; - assert_eq!(events.len(), 6); + assert_eq!(events.len(), 4); let last_event = events.last().unwrap(); // run `UPDATE_EXPECT=true cargo test` to update this. let expected = expect![[ diff --git a/soroban-env-host/src/test/util.rs b/soroban-env-host/src/test/util.rs index 7fb086f28..092d404ee 100644 --- a/soroban-env-host/src/test/util.rs +++ b/soroban-env-host/src/test/util.rs @@ -70,7 +70,9 @@ impl Host { self.with_budget(|budget| { budget.reset_limits(cpu, mem); // something big but finite that we may exceed budget.reset_models(); - }); + Ok(()) + }) + .unwrap(); self } @@ -108,7 +110,9 @@ impl Host { .mem_bytes .get_cost_model_mut(ty) .linear_term = lin_mem as i64; - }); + Ok(()) + }) + .unwrap(); self } diff --git a/soroban-env-host/src/vm.rs b/soroban-env-host/src/vm.rs index 3d9c014d7..dddb3f43c 100644 --- a/soroban-env-host/src/vm.rs +++ b/soroban-env-host/src/vm.rs @@ -9,59 +9,33 @@ //! the [wasmi](https://github.com/paritytech/wasmi) project. mod dispatch; +mod fuel_refillable; mod func_info; -use crate::{ - err, - host::{Frame, HostImpl}, - xdr::ContractCostType, - HostError, VmCaller, -}; +use crate::{budget::AsBudget, err, host::Frame, xdr::ContractCostType, HostError}; use std::{cell::RefCell, io::Cursor, rc::Rc}; use super::{xdr::Hash, Host, RawVal, Symbol}; +use fuel_refillable::FuelRefillable; use func_info::HOST_FUNCTIONS; use soroban_env_common::{ meta::{self, get_ledger_protocol_version, get_pre_release_version}, xdr::{ReadXdr, ScEnvMetaEntry, ScErrorCode, ScErrorType}, - ConversionError, SymbolStr, TryIntoVal, + ConversionError, SymbolStr, TryIntoVal, WasmiMarshal, }; -use wasmi::{ - core::Value, Caller, Engine, Instance, Linker, Memory, Module, StepMeter, Store, - StoreContextMut, -}; +use wasmi::{Engine, FuelConsumptionMode, Func, Instance, Linker, Memory, Module, Store, Value}; #[cfg(any(test, feature = "testutils"))] -use soroban_env_common::{ +use crate::{ xdr::{ScVal, ScVec}, - TryFromVal, + TryFromVal, VmCaller, }; +#[cfg(any(test, feature = "testutils"))] +use wasmi::{Caller, StoreContextMut}; impl wasmi::core::HostError for HostError {} -impl StepMeter for HostImpl { - fn max_insn_step(&self) -> u64 { - 256 - } - - fn charge_cpu(&self, insns: u64) -> Result<(), wasmi::core::TrapCode> { - // TODO reconcile TrapCode with HostError better. - self.budget - .clone() - .batched_charge(ContractCostType::WasmInsnExec, insns, None) - .map_err(|_| wasmi::core::TrapCode::CpuLimitExceeded) - } - - // each page is 64kB - fn charge_mem(&self, pages: u64) -> Result<(), wasmi::core::TrapCode> { - self.budget - .clone() - .charge(ContractCostType::WasmMemAlloc, Some(pages)) - .map_err(|_| wasmi::core::TrapCode::MemLimitExceeded) - } -} - /// A [Vm] is a thin wrapper around an instance of [wasmi::Module]. Multiple /// [Vm]s may be held in a single [Host], and each contains a single WASM module /// instantiation. @@ -172,16 +146,18 @@ impl Vm { )?; let mut config = wasmi::Config::default(); + let fuel_costs = host.as_budget().wasmi_fuel_costs(); // Turn off all optional wasm features. - config.wasm_multi_value(false); - config.wasm_mutable_global(false); - config.wasm_saturating_float_to_int(false); - config.wasm_sign_extension(false); - - // This should always be true, and it enforces wasmi's notion of "deterministic only" - // execution, which excludes all floating point ops. Double check to be sure. - assert!(config.wasm_features().deterministic_only); + config + .wasm_multi_value(false) + .wasm_mutable_global(false) + .wasm_saturating_float_to_int(false) + .wasm_sign_extension(false) + .floats(false) + .consume_fuel(true) + .fuel_consumption_mode(FuelConsumptionMode::Eager) + .set_fuel_costs(fuel_costs); let engine = Engine::new(&config); let module = host.map_err(Module::new(&engine, module_wasm_code))?; @@ -189,8 +165,7 @@ impl Vm { Self::check_meta_section(host, &module)?; let mut store = Store::new(&engine, host.clone()); - store.set_step_meter(host.0.clone()); - let mut linker = >::new(); + let mut linker = >::new(&engine); for hf in HOST_FUNCTIONS { let func = (hf.wrap)(&mut store); @@ -215,11 +190,13 @@ impl Vm { None }; - let store = RefCell::new(store); + // Here we do _not_ supply the store with any fuel. Fuel is supplied + // right before the VM is being run, i.e., before crossing the host->VM + // boundary. Ok(Rc::new(Self { contract_id, module, - store, + store: RefCell::new(store), instance, memory, })) @@ -237,6 +214,66 @@ impl Vm { } } + // Wrapper for the [`Func`] call, mostly taking care of fuel supply from + // host to the VM and applying VM fuel consumption back to the host budget. + // This is where the host->VM->host boundaries are crossed. + fn wrapped_func_call( + self: &Rc, + host: &Host, + func_sym: &Symbol, + func: &Func, + inputs: &[Value], + ) -> Result { + let mut wasm_ret: [Value; 1] = [Value::I64(0)]; + self.store.borrow_mut().fill_fuels(host)?; + let res = func.call(&mut *self.store.borrow_mut(), inputs, &mut wasm_ret); + // Due to the way wasmi's fuel metering works (it does `remaining.checked_sub(delta).ok_or(Trap)`), + // there may be a small amount of fuel (less than delta -- the fuel cost of that failing + // wasmi instruction) remaining when the `OutOfFuel` trap occurs. This is only observable + // if the contract traps with `OutOfFuel`, which may appear confusing if they look closely + // at the budget amount consumed. So it should be fine. + self.store.borrow_mut().return_fuels(host)?; + + if let Err(e) = res { + // When a call fails with a wasmi::Error::Trap that carries a HostError + // we propagate that HostError as is, rather than producing something new. + + match e { + wasmi::Error::Trap(trap) => { + if let Some(code) = trap.trap_code() { + return Err(code.into()); + } + if let Some(he) = trap.downcast::() { + host.log_diagnostics( + "VM call trapped with HostError", + &[func_sym.to_raw(), he.error.to_raw()], + )?; + return Err(he); + } + return Err(host.err( + ScErrorType::WasmVm, + ScErrorCode::InternalError, + "VM trapped with HostError but propagation failed", + &[], + )); + } + e => { + return Err(if host.is_debug() { + // With diagnostics on: log as much detail as we can from wasmi. + let msg = format!("VM call failed: {:?}", &e); + host.error(e.into(), &msg, &[func_sym.to_raw()]) + } else { + host.error(e.into(), "VM call failed", &[func_sym.to_raw()]) + }); + } + } + } + Ok( + <_ as WasmiMarshal>::try_marshal_from_value(wasm_ret[0].clone()) + .ok_or(ConversionError)?, + ) + } + pub(crate) fn invoke_function_raw( self: &Rc, host: &Host, @@ -251,7 +288,6 @@ impl Vm { .iter() .map(|i| Value::I64(i.get_payload() as i64)) .collect(); - let mut wasm_ret: [Value; 1] = [Value::I64(0)]; let func_ss: SymbolStr = func_sym.try_into_val(host)?; let ext = match self .instance @@ -279,48 +315,7 @@ impl Vm { Some(e) => e, }; - let res = func.call( - &mut *self.store.borrow_mut(), - wasm_args.as_slice(), - &mut wasm_ret, - ); - - if let Err(e) = res { - // When a call fails with a wasmi::Error::Trap that carries a HostError - // we propagate that HostError as is, rather than producing something new. - - match e { - wasmi::Error::Trap(trap) if trap.is_host() => { - if let Some(he) = trap.into_host() { - if let Ok(he) = he.downcast::() { - host.log_diagnostics( - "VM call trapped with HostError", - &[func_sym.to_raw(), he.error.to_raw()], - )?; - return Err(*he); - } - } - return Err(host.err( - ScErrorType::WasmVm, - ScErrorCode::InternalError, - "VM trapped with HostError but propagation failed", - &[], - )); - } - e => { - return Err(if host.is_debug() { - // With diagnostics on: log as much detail as we can from wasmi. - let msg = format!("VM call failed: {:?}", &e); - host.error(e.into(), &msg, &[func_sym.to_raw()]) - } else { - host.error(e.into(), "VM call failed", &[func_sym.to_raw()]) - }); - } - } - } - wasm_ret[0].try_into().ok_or_else(|| { - host.error(ConversionError.into(), "converting result of VM call", &[]) - }) + self.wrapped_func_call(host, func_sym, &func, wasm_args.as_slice()) }, ) } @@ -355,7 +350,7 @@ impl Vm { pub fn functions(&self) -> Vec { let mut res = Vec::new(); for e in self.module.exports() { - if let wasmi::ExportItemKind::Func(f) = e.kind() { + if let wasmi::ExternType::Func(f) = e.ty() { res.push(VmFunction { name: e.name().to_string(), param_count: f.params().len(), @@ -385,13 +380,14 @@ impl Vm { /// Utility function that synthesizes a `VmCaller` configured to point /// to this VM's `Store` and `Instance`, and calls the provided function /// back with it. Mainly used for testing. + #[cfg(any(test, feature = "testutils"))] pub fn with_vmcaller(&self, f: F) -> T where F: FnOnce(&mut VmCaller) -> T, { let store: &mut Store = &mut self.store.borrow_mut(); let mut ctx: StoreContextMut = store.into(); - let caller: Caller = Caller::new(&mut ctx, Some(self.instance)); + let caller: Caller = Caller::new(&mut ctx, Some(&self.instance)); let mut vmcaller: VmCaller = VmCaller(Some(caller)); f(&mut vmcaller) } diff --git a/soroban-env-host/src/vm/dispatch.rs b/soroban-env-host/src/vm/dispatch.rs index 88ab1982e..074525ef7 100644 --- a/soroban-env-host/src/vm/dispatch.rs +++ b/soroban-env-host/src/vm/dispatch.rs @@ -1,10 +1,14 @@ +use super::FuelRefillable; use crate::{xdr::ContractCostType, Host, HostError, VmCaller, VmCallerEnv}; use crate::{ AddressObject, BytesObject, Error, I128Object, I256Object, I64Object, MapObject, RawVal, StringObject, Symbol, SymbolObject, U128Object, U256Object, U32Val, U64Object, VecObject, }; -use soroban_env_common::call_macro_with_all_host_functions; -use wasmi::core::{FromValue, Trap, TrapCode::UnexpectedSignature, Value}; +use soroban_env_common::{call_macro_with_all_host_functions, WasmiMarshal}; +use wasmi::{ + core::{Trap, TrapCode::BadSignature}, + Value, +}; /////////////////////////////////////////////////////////////////////////////// /// X-macro use: dispatch functions @@ -61,13 +65,19 @@ macro_rules! generate_dispatch_functions { // expansion, flattening all functions from all 'mod' blocks // into a set of functions. $(#[$fn_attr])* - pub(crate) fn $fn_id(caller: wasmi::Caller, $($arg:i64),*) -> + pub(crate) fn $fn_id(mut caller: wasmi::Caller, $($arg:i64),*) -> Result<(i64,), Trap> { // Notes on metering: a flat charge per host function invocation. // This does not account for the actual work being done in those functions, // which are accounted for individually at the operation level. - let host = caller.host_data().clone(); + let host = caller.data().clone(); + + // This is where the VM -> Host boundary is crossed. + // We first return all fuels from the VM back to the host such that + // the host maintains control of the budget. + FuelRefillable::return_fuels(&mut caller, &host).map_err(|he| Trap::from(he))?; + host.charge_budget(ContractCostType::InvokeHostFunction, None)?; let mut vmcaller = VmCaller(Some(caller)); // The odd / seemingly-redundant use of `wasmi::Value` here @@ -79,9 +89,17 @@ macro_rules! generate_dispatch_functions { // happens to be a natural switching point for that: we have // conversions to and from both RawVal and i64 / u64 for // wasmi::Value. - let res: Result<_, HostError> = host.$fn_id(&mut vmcaller, $(<$type as FromValue>::from_value(Value::I64($arg)).ok_or(UnexpectedSignature)?),*); - let res: Value = match res { - Ok(ok) => ok.into(), + let res: Result<_, HostError> = host.$fn_id(&mut vmcaller, $(<$type>::try_marshal_from_value(Value::I64($arg)).ok_or(BadSignature)?),*); + + let res = match res { + Ok(ok) => { + let val: Value = ok.marshal_from_self(); + if let Value::I64(v) = val { + Ok((v,)) + } else { + Err(BadSignature.into()) + } + }, Err(hosterr) => { // We make a new HostError here to capture the escalation event itself. let escalation: HostError = @@ -89,14 +107,16 @@ macro_rules! generate_dispatch_functions { concat!("escalating error to VM trap from failed host function call: ", stringify!($fn_id)), &[]); let trap: Trap = escalation.into(); - return Err(trap) + Err(trap) } }; - if let Value::I64(v) = res { - Ok((v,)) - } else { - Err(UnexpectedSignature.into()) - } + + // This is where the Host->VM boundary is crossed. + // We supply the remaining host budget as fuel to the VM. + let caller = vmcaller.try_mut().map_err(|e| Trap::from(HostError::from(e)))?; + FuelRefillable::fill_fuels(caller, &host).map_err(|he| Trap::from(he))?; + + res } )* )* diff --git a/soroban-env-host/src/vm/fuel_refillable.rs b/soroban-env-host/src/vm/fuel_refillable.rs new file mode 100644 index 000000000..ccf98837c --- /dev/null +++ b/soroban-env-host/src/vm/fuel_refillable.rs @@ -0,0 +1,84 @@ +use crate::{ + budget::AsBudget, + xdr::{ScErrorCode, ScErrorType}, + Host, HostError, +}; + +use wasmi::{errors::FuelError, Caller, Store}; + +pub(crate) trait FuelRefillable { + fn fuels_consumed(&self) -> Result<(u64, u64), HostError>; + + fn fuels_total(&self) -> Result<(u64, u64), HostError>; + + fn add_fuels(&mut self, cpu: u64, mem: u64) -> Result<(), HostError>; + + fn reset_fuels(&mut self) -> Result<(), HostError>; + + fn is_clean(&self) -> Result { + Ok(self.fuels_consumed()? == (0, 0) && self.fuels_total()? == (0, 0)) + } + + fn fill_fuels(&mut self, host: &Host) -> Result<(), HostError> { + if !self.is_clean()? { + return Err(host.err( + ScErrorType::WasmVm, + ScErrorCode::InternalError, + "VM fuel must be clean before we supply to it", + &[], + )); + } + let (cpu, mem) = host.as_budget().get_fuels_budget()?; + self.add_fuels(cpu, mem) + } + + fn return_fuels(&mut self, host: &Host) -> Result<(), HostError> { + let (cpu, mem) = self.fuels_consumed()?; + host.as_budget().apply_wasmi_fuels(cpu, mem)?; + self.reset_fuels() + } +} +//wasmi::Store +macro_rules! impl_refillable_for_store { + ($store: ty) => { + impl<'a> FuelRefillable for $store { + fn fuels_consumed(&self) -> Result<(u64, u64), HostError> { + let cpu = self.fuel_consumed().ok_or_else(|| { + HostError::from(wasmi::Error::Store(FuelError::FuelMeteringDisabled)) + })?; + let mem = self.mem_fuel_consumed().ok_or_else(|| { + HostError::from(wasmi::Error::Store(FuelError::FuelMeteringDisabled)) + })?; + Ok((cpu, mem)) + } + + fn fuels_total(&self) -> Result<(u64, u64), HostError> { + let cpu = self.fuel_total().ok_or_else(|| { + HostError::from(wasmi::Error::Store(FuelError::FuelMeteringDisabled)) + })?; + let mem = self.mem_fuel_total().ok_or_else(|| { + HostError::from(wasmi::Error::Store(FuelError::FuelMeteringDisabled)) + })?; + Ok((cpu, mem)) + } + + fn add_fuels(&mut self, cpu: u64, mem: u64) -> Result<(), HostError> { + self.add_fuel(cpu) + .map_err(|fe| HostError::from(wasmi::Error::Store(fe)))?; + self.add_mem_fuel(mem) + .map_err(|fe| HostError::from(wasmi::Error::Store(fe)))?; + Ok(()) + } + + fn reset_fuels(&mut self) -> Result<(), HostError> { + self.reset_fuel() + .map_err(|fe| HostError::from(wasmi::Error::Store(fe)))?; + self.reset_mem_fuel() + .map_err(|fe| HostError::from(wasmi::Error::Store(fe)))?; + Ok(()) + } + } + }; +} +impl_refillable_for_store!(Store); +impl_refillable_for_store!(Caller<'a, Host>); diff --git a/soroban-test-wasms/src/lib.rs b/soroban-test-wasms/src/lib.rs index bb0a869af..b76563fb4 100644 --- a/soroban-test-wasms/src/lib.rs +++ b/soroban-test-wasms/src/lib.rs @@ -43,6 +43,7 @@ //! the host here. pub const ADD_I32: &[u8] = include_bytes!("../wasm-workspace/opt/example_add_i32.wasm").as_slice(); +pub const ADD_F32: &[u8] = include_bytes!("../wasm-workspace/opt/example_add_f32.wasm").as_slice(); pub const CREATE_CONTRACT: &[u8] = include_bytes!("../wasm-workspace/opt/example_create_contract.wasm").as_slice(); pub const CONTRACT_STORAGE: &[u8] = diff --git a/soroban-test-wasms/wasm-workspace/Cargo.lock b/soroban-test-wasms/wasm-workspace/Cargo.lock index 86ce4e031..640a94506 100644 --- a/soroban-test-wasms/wasm-workspace/Cargo.lock +++ b/soroban-test-wasms/wasm-workspace/Cargo.lock @@ -274,6 +274,13 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0198b9d0078e0f30dedc7acbb21c974e838fc8fae3ee170128658a98cb2c1c04" +[[package]] +name = "example_add_f32" +version = "0.0.0" +dependencies = [ + "soroban-sdk", +] + [[package]] name = "example_add_i32" version = "0.0.0" @@ -452,6 +459,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" +[[package]] +name = "intx" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f38a50a899dc47a6d0ed5508e7f601a2e34c3a85303514b5d137f3c10a0c75" + [[package]] name = "itertools" version = "0.10.5" @@ -503,12 +516,6 @@ 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 = "miniz_oxide" version = "0.6.2" @@ -550,18 +557,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.15" @@ -592,6 +587,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "paste" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -757,6 +758,12 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + [[package]] name = "soroban-env-common" version = "0.0.16" @@ -768,7 +775,7 @@ dependencies = [ "soroban-env-macros", "soroban-wasmi", "static_assertions", - "stellar-xdr 0.0.16 (git+https://github.com/stellar/rs-stellar-xdr?rev=979c0a5dbae204a2efe7bf52c1229081f685c2a0)", + "stellar-xdr 0.0.16 (git+https://github.com/stellar/rs-stellar-xdr?rev=a2f370c930bb94f7bc0c9ea0426ecef3700b90a9)", ] [[package]] @@ -809,7 +816,7 @@ dependencies = [ "quote", "serde", "serde_json", - "stellar-xdr 0.0.16 (git+https://github.com/stellar/rs-stellar-xdr?rev=979c0a5dbae204a2efe7bf52c1229081f685c2a0)", + "stellar-xdr 0.0.16 (git+https://github.com/stellar/rs-stellar-xdr?rev=a2f370c930bb94f7bc0c9ea0426ecef3700b90a9)", "syn 2.0.16", "thiserror", ] @@ -886,24 +893,26 @@ dependencies = [ [[package]] name = "soroban-wasmi" -version = "0.16.0-soroban2" -source = "git+https://github.com/stellar/wasmi?rev=862b32f5#862b32f53f9c6223911e79e0b0fc8592fb3bb04c" +version = "0.30.0-soroban" +source = "git+https://github.com/stellar/wasmi?rev=1a2bc7f#1a2bc7f3801c565c2dab22021255a164c05a7f76" dependencies = [ + "intx", + "smallvec", "soroban-wasmi_core", "spin", + "wasmi_arena", "wasmparser-nostd", ] [[package]] name = "soroban-wasmi_core" -version = "0.16.0-soroban2" -source = "git+https://github.com/stellar/wasmi?rev=862b32f5#862b32f53f9c6223911e79e0b0fc8592fb3bb04c" +version = "0.30.0-soroban" +source = "git+https://github.com/stellar/wasmi?rev=1a2bc7f#1a2bc7f3801c565c2dab22021255a164c05a7f76" dependencies = [ "downcast-rs", "libm", - "memory_units", - "num-rational", "num-traits", + "paste", ] [[package]] @@ -930,7 +939,7 @@ dependencies = [ [[package]] name = "stellar-xdr" version = "0.0.16" -source = "git+https://github.com/stellar/rs-stellar-xdr?rev=979c0a5dbae204a2efe7bf52c1229081f685c2a0#979c0a5dbae204a2efe7bf52c1229081f685c2a0" +source = "git+https://github.com/stellar/rs-stellar-xdr?rev=a2f370c930bb94f7bc0c9ea0426ecef3700b90a9#a2f370c930bb94f7bc0c9ea0426ecef3700b90a9" dependencies = [ "arbitrary", "base64 0.13.1", @@ -1110,6 +1119,11 @@ version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +[[package]] +name = "wasmi_arena" +version = "0.4.0" +source = "git+https://github.com/stellar/wasmi?rev=1a2bc7f#1a2bc7f3801c565c2dab22021255a164c05a7f76" + [[package]] name = "wasmparser" version = "0.88.0" @@ -1121,9 +1135,9 @@ dependencies = [ [[package]] name = "wasmparser-nostd" -version = "0.90.0" +version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92a94fbf4c521b038f41382df2056cf47099d3b7a0faa5a6e46f7771fd7c84a6" +checksum = "9157cab83003221bfd385833ab587a039f5d6fa7304854042ba358a3b09e0724" dependencies = [ "indexmap-nostd", ] diff --git a/soroban-test-wasms/wasm-workspace/Cargo.toml b/soroban-test-wasms/wasm-workspace/Cargo.toml index 3e97f20e4..727d50202 100644 --- a/soroban-test-wasms/wasm-workspace/Cargo.toml +++ b/soroban-test-wasms/wasm-workspace/Cargo.toml @@ -18,6 +18,7 @@ resolver = "2" members = [ "add_i32", + "add_f32", "auth", "fib", "contract_data", diff --git a/soroban-test-wasms/wasm-workspace/add_f32/Cargo.toml b/soroban-test-wasms/wasm-workspace/add_f32/Cargo.toml new file mode 100644 index 000000000..d81f20c8e --- /dev/null +++ b/soroban-test-wasms/wasm-workspace/add_f32/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "example_add_f32" +version = "0.0.0" +authors = ["Stellar Development Foundation "] +license = "Apache-2.0" +edition = "2021" +publish = false +rust-version = "1.65" + +[lib] +crate-type = ["cdylib", "rlib"] +doctest = false + +[dependencies] +soroban-sdk = { workspace = true } diff --git a/soroban-test-wasms/wasm-workspace/add_f32/src/lib.rs b/soroban-test-wasms/wasm-workspace/add_f32/src/lib.rs new file mode 100644 index 000000000..fc4355fca --- /dev/null +++ b/soroban-test-wasms/wasm-workspace/add_f32/src/lib.rs @@ -0,0 +1,14 @@ +#![no_std] +use soroban_sdk::{contractimpl, Env, Symbol}; + +pub struct Contract; + +#[contractimpl] +impl Contract { + pub fn add(env: Env, a: i32, b: i32) -> i32 { + env.events().publish((Symbol::short("add"),), (a, b)); + let a = a as f32; + let b = b as f32; + (a + b) as i32 + } +} diff --git a/soroban-test-wasms/wasm-workspace/opt/example_add_f32.wasm b/soroban-test-wasms/wasm-workspace/opt/example_add_f32.wasm new file mode 100644 index 000000000..d0f0c78d3 Binary files /dev/null and b/soroban-test-wasms/wasm-workspace/opt/example_add_f32.wasm differ