From 884f60dbe1f13dd7650e79483325deb3d4e5dcf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Tue, 24 Oct 2023 10:54:59 +0200 Subject: [PATCH 1/6] contracts: build for `wasm64-unknown-unknown` Resolves: #280 --- Makefile | 8 ++++---- contracts/c-example/build.sh | 4 ++-- contracts/c-example/contract.c | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 91c99309..3533f4f3 100644 --- a/Makefile +++ b/Makefile @@ -13,13 +13,13 @@ contracts: setup-compiler ## Build example contracts --manifest-path=contracts/Cargo.toml \ --color=always \ -Z build-std=core,alloc \ - --target wasm32-unknown-unknown - @contracts/c-example/build.sh + --target wasm64-unknown-unknown @mkdir -p target/stripped - @find target/wasm32-unknown-unknown/release -maxdepth 1 -name "*.wasm" \ + @contracts/c-example/build.sh + @find target/wasm64-unknown-unknown/release -maxdepth 1 -name "*.wasm" \ | xargs -I % basename % \ | xargs -I % wasm-tools strip -a \ - target/wasm32-unknown-unknown/release/% \ + target/wasm64-unknown-unknown/release/% \ -o target/stripped/% test: contracts cold-reboot assert-counter-contract-small ## Run all tests diff --git a/contracts/c-example/build.sh b/contracts/c-example/build.sh index 231a74a6..171ac80c 100755 --- a/contracts/c-example/build.sh +++ b/contracts/c-example/build.sh @@ -3,11 +3,11 @@ BASEDIR=$(dirname "$0") clang -nostdlib -Os \ - --target=wasm32 \ + --target=wasm64 \ -Wl,--allow-undefined \ -Wl,--no-entry \ -Wl,--export=A \ -Wl,--export=increment_and_read \ -Wl,--export=out_of_bounds \ "$BASEDIR/contract.c" \ - -o "$BASEDIR/../../target/wasm32-unknown-unknown/release/c-example.wasm" + -o "$BASEDIR/../../target/wasm64-unknown-unknown/release/c-example.wasm" diff --git a/contracts/c-example/contract.c b/contracts/c-example/contract.c index 0fd18e7a..50a2fb7e 100644 --- a/contracts/c-example/contract.c +++ b/contracts/c-example/contract.c @@ -87,6 +87,6 @@ int32_t increment_and_read(int32_t _arg_len) { // Calls the "hd" extern with an (almost) certainly out of bounds pointer, in an // effort to trigger an error. int32_t out_of_bounds(int32_t _arg_len) { - hd((uint8_t*)4294967295, 2); + hd((uint8_t*)4398046511103, 2); return 0; } From 5ebf3e9bb4ba8f619e2fc94480cc967ef5bfadb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Tue, 24 Oct 2023 11:55:06 +0200 Subject: [PATCH 2/6] piecrust: use "stripped" directory in cold-reboot --- piecrust/tests/cold-reboot/src/main.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/piecrust/tests/cold-reboot/src/main.rs b/piecrust/tests/cold-reboot/src/main.rs index 3c81a3e0..9299e111 100644 --- a/piecrust/tests/cold-reboot/src/main.rs +++ b/piecrust/tests/cold-reboot/src/main.rs @@ -23,9 +23,8 @@ fn initialize_counter>( ) -> Result<(), piecrust::Error> { let mut session = vm.session(SessionData::builder())?; - let counter_bytecode = include_bytes!( - "../../../../target/wasm32-unknown-unknown/release/counter.wasm" - ); + let counter_bytecode = + include_bytes!("../../../../target/stripped/counter.wasm"); session.deploy( counter_bytecode, From 8129ff75880d68c226ad1f59fc709517e0938172 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Tue, 24 Oct 2023 14:48:38 +0200 Subject: [PATCH 3/6] piecrust: support `memory64` Support for the WebAssembly `memory64` proposal is done by properly configuring wasmtime, and adapting the validation of the contracts accordingly. Imports also have to be adapted - in the sense that versions that take pointers as arguments have to now take 64-bit pointers, as opposed to 32-bit. Resolves #281 --- piecrust/CHANGELOG.md | 2 + piecrust/src/imports.rs | 225 +++++---------------------------- piecrust/src/imports/wasm32.rs | 195 ++++++++++++++++++++++++++++ piecrust/src/imports/wasm64.rs | 195 ++++++++++++++++++++++++++++ piecrust/src/instance.rs | 62 +++++---- piecrust/src/vm.rs | 4 +- 6 files changed, 459 insertions(+), 224 deletions(-) create mode 100644 piecrust/src/imports/wasm32.rs create mode 100644 piecrust/src/imports/wasm64.rs diff --git a/piecrust/CHANGELOG.md b/piecrust/CHANGELOG.md index 41c784b0..d9e397f5 100644 --- a/piecrust/CHANGELOG.md +++ b/piecrust/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Added +- Support `memory64` smart contracts [#281] - Add some `Error` variants: * `InvalidFunction` * `InvalidMemory` @@ -281,6 +282,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#234]: https://github.com/dusk-network/piecrust/pull/234 +[#281]: https://github.com/dusk-network/piecrust/issues/281 [#271]: https://github.com/dusk-network/piecrust/issues/271 [#268]: https://github.com/dusk-network/piecrust/issues/268 [#254]: https://github.com/dusk-network/piecrust/issues/254 diff --git a/piecrust/src/imports.rs b/piecrust/src/imports.rs index 8066e97e..87f6f6e0 100644 --- a/piecrust/src/imports.rs +++ b/piecrust/src/imports.rs @@ -4,19 +4,18 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use std::sync::Arc; +mod wasm32; +mod wasm64; use dusk_wasmtime::{ Caller, Extern, Func, Module, Result as WasmtimeResult, Store, }; -use piecrust_uplink::{ - ContractError, ContractId, ARGBUF_LEN, CONTRACT_ID_BYTES, -}; +use piecrust_uplink::{ContractId, ARGBUF_LEN}; use crate::instance::{Env, WrappedInstance}; use crate::Error; -const POINT_PASS_PCT: u64 = 93; +pub const POINT_PASS_PCT: u64 = 93; pub(crate) struct Imports; @@ -25,6 +24,7 @@ impl Imports { pub fn for_module( store: &mut Store, module: &Module, + is_64: bool, ) -> Result, Error> { let max_imports = 12; let mut imports = Vec::with_capacity(max_imports); @@ -32,7 +32,7 @@ impl Imports { for import in module.imports() { let import_name = import.name(); - match Self::import(store, import_name) { + match Self::import(store, import_name, is_64) { None => { return Err(Error::InvalidFunction(import_name.to_string())) } @@ -45,13 +45,25 @@ impl Imports { Ok(imports) } - fn import(store: &mut Store, name: &str) -> Option { + fn import(store: &mut Store, name: &str, is_64: bool) -> Option { Some(match name { "caller" => Func::wrap(store, caller), - "c" => Func::wrap(store, c), - "hq" => Func::wrap(store, hq), - "hd" => Func::wrap(store, hd), - "emit" => Func::wrap(store, emit), + "c" => match is_64 { + false => Func::wrap(store, wasm32::c), + true => Func::wrap(store, wasm64::c), + }, + "hq" => match is_64 { + false => Func::wrap(store, wasm32::hq), + true => Func::wrap(store, wasm64::hq), + }, + "hd" => match is_64 { + false => Func::wrap(store, wasm32::hd), + true => Func::wrap(store, wasm64::hd), + }, + "emit" => match is_64 { + false => Func::wrap(store, wasm32::emit), + true => Func::wrap(store, wasm64::emit), + }, "feed" => Func::wrap(store, feed), "limit" => Func::wrap(store, limit), "spent" => Func::wrap(store, spent), @@ -65,16 +77,13 @@ impl Imports { } } -fn check_ptr( +pub fn check_ptr( instance: &WrappedInstance, - offset: u32, - len: u32, + offset: usize, + len: usize, ) -> Result<(), Error> { let mem_len = instance.with_memory(|mem| mem.len()); - let offset = offset as usize; - let len = len as usize; - if offset + len >= mem_len { return Err(Error::MemoryAccessOutOfBounds { offset, @@ -86,7 +95,10 @@ fn check_ptr( Ok(()) } -fn check_arg(instance: &WrappedInstance, arg_len: u32) -> Result<(), Error> { +pub fn check_arg( + instance: &WrappedInstance, + arg_len: u32, +) -> Result<(), Error> { let mem_len = instance.with_memory(|mem| mem.len()); let arg_ofs = instance.arg_buffer_offset(); @@ -124,183 +136,6 @@ fn caller(env: Caller) { }) } -fn c( - mut fenv: Caller, - mod_id_ofs: u32, - name_ofs: u32, - name_len: u32, - arg_len: u32, - points_limit: u64, -) -> WasmtimeResult { - let env = fenv.data_mut(); - - let instance = env.self_instance(); - - check_ptr(instance, mod_id_ofs, CONTRACT_ID_BYTES as u32)?; - check_ptr(instance, name_ofs, name_len)?; - check_arg(instance, arg_len)?; - - let argbuf_ofs = instance.arg_buffer_offset(); - - let caller_remaining = instance.get_remaining_points(); - - let callee_limit = if points_limit > 0 && points_limit < caller_remaining { - points_limit - } else { - caller_remaining * POINT_PASS_PCT / 100 - }; - - let with_memory = |memory: &mut [u8]| -> Result<_, Error> { - let arg_buf = &memory[argbuf_ofs..][..ARGBUF_LEN]; - - let mut mod_id = ContractId::uninitialized(); - mod_id.as_bytes_mut().copy_from_slice( - &memory[mod_id_ofs as usize..][..std::mem::size_of::()], - ); - - let callee_stack_element = env - .push_callstack(mod_id, callee_limit) - .expect("pushing to the callstack should succeed"); - let callee = env - .instance(&callee_stack_element.contract_id) - .expect("callee instance should exist"); - - callee.snap().map_err(|err| Error::MemorySnapshotFailure { - reason: None, - io: Arc::new(err), - })?; - - let name = core::str::from_utf8( - &memory[name_ofs as usize..][..name_len as usize], - )?; - - let arg = &arg_buf[..arg_len as usize]; - - callee.write_argument(arg); - let ret_len = callee - .call(name, arg.len() as u32, callee_limit) - .map_err(Error::normalize)?; - check_arg(callee, ret_len as u32)?; - - // copy back result - callee.read_argument(&mut memory[argbuf_ofs..][..ret_len as usize]); - - let callee_remaining = callee.get_remaining_points(); - let callee_spent = callee_limit - callee_remaining; - - Ok((ret_len, callee_spent)) - }; - - let ret = match instance.with_memory_mut(with_memory) { - Ok((ret_len, callee_spent)) => { - env.move_up_call_tree(callee_spent); - instance.set_remaining_points(caller_remaining - callee_spent); - ret_len - } - Err(mut err) => { - if let Err(io_err) = env.revert_callstack() { - err = Error::MemorySnapshotFailure { - reason: Some(Arc::new(err)), - io: Arc::new(io_err), - }; - } - env.move_up_prune_call_tree(); - instance.set_remaining_points(caller_remaining - callee_limit); - - ContractError::from(err).into() - } - }; - - Ok(ret) -} - -fn hq( - mut fenv: Caller, - name_ofs: u32, - name_len: u32, - arg_len: u32, -) -> WasmtimeResult { - let env = fenv.data_mut(); - - let instance = env.self_instance(); - - check_ptr(instance, name_ofs, arg_len)?; - check_arg(instance, arg_len)?; - - let name_ofs = name_ofs as usize; - let name_len = name_len as usize; - - let name = instance.with_memory(|buf| { - // performance: use a dedicated buffer here? - core::str::from_utf8(&buf[name_ofs..][..name_len]) - .map(ToOwned::to_owned) - })?; - - Ok(instance - .with_arg_buf_mut(|buf| env.host_query(&name, buf, arg_len)) - .ok_or(Error::MissingHostQuery(name))?) -} - -fn hd( - mut fenv: Caller, - name_ofs: u32, - name_len: u32, -) -> WasmtimeResult { - let env = fenv.data_mut(); - - let instance = env.self_instance(); - - check_ptr(instance, name_ofs, name_len)?; - - let name_ofs = name_ofs as usize; - let name_len = name_len as usize; - - let name = instance.with_memory(|buf| { - // performance: use a dedicated buffer here? - core::str::from_utf8(&buf[name_ofs..][..name_len]) - .map(ToOwned::to_owned) - })?; - - let data = env.meta(&name).unwrap_or_default(); - - instance.with_arg_buf_mut(|buf| { - buf[..data.len()].copy_from_slice(&data); - }); - - Ok(data.len() as u32) -} - -fn emit( - mut fenv: Caller, - topic_ofs: u32, - topic_len: u32, - arg_len: u32, -) -> WasmtimeResult<()> { - let env = fenv.data_mut(); - let instance = env.self_instance(); - - check_ptr(instance, topic_ofs, topic_len)?; - check_arg(instance, arg_len)?; - - let data = instance.with_arg_buf(|buf| { - let arg_len = arg_len as usize; - Vec::from(&buf[..arg_len]) - }); - - let topic_ofs = topic_ofs as usize; - let topic_len = topic_len as usize; - - let topic = instance.with_memory(|buf| { - // performance: use a dedicated buffer here? - core::str::from_utf8(&buf[topic_ofs..][..topic_len]) - .map(ToOwned::to_owned) - })?; - - env.emit(topic, data); - - Ok(()) -} - fn feed(mut fenv: Caller, arg_len: u32) -> WasmtimeResult<()> { let env = fenv.data_mut(); let instance = env.self_instance(); diff --git a/piecrust/src/imports/wasm32.rs b/piecrust/src/imports/wasm32.rs new file mode 100644 index 00000000..203992ed --- /dev/null +++ b/piecrust/src/imports/wasm32.rs @@ -0,0 +1,195 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use std::sync::Arc; + +use dusk_wasmtime::{Caller, Result as WasmtimeResult}; +use piecrust_uplink::{ + ContractError, ContractId, ARGBUF_LEN, CONTRACT_ID_BYTES, +}; + +use crate::imports::{check_arg, check_ptr, POINT_PASS_PCT}; +use crate::instance::Env; +use crate::Error; + +pub(crate) fn hq( + mut fenv: Caller, + name_ofs: u32, + name_len: u32, + arg_len: u32, +) -> WasmtimeResult { + let env = fenv.data_mut(); + + let instance = env.self_instance(); + + let name_ofs = name_ofs as usize; + let name_len = name_len as usize; + + check_ptr(instance, name_ofs, name_len)?; + check_arg(instance, arg_len)?; + + let name = instance.with_memory(|buf| { + // performance: use a dedicated buffer here? + core::str::from_utf8(&buf[name_ofs..][..name_len]) + .map(ToOwned::to_owned) + })?; + + Ok(instance + .with_arg_buf_mut(|buf| env.host_query(&name, buf, arg_len)) + .ok_or(Error::MissingHostQuery(name))?) +} + +pub(crate) fn hd( + mut fenv: Caller, + name_ofs: u32, + name_len: u32, +) -> WasmtimeResult { + let env = fenv.data_mut(); + + let instance = env.self_instance(); + + let name_ofs = name_ofs as usize; + let name_len = name_len as usize; + + check_ptr(instance, name_ofs, name_len)?; + + let name = instance.with_memory(|buf| { + // performance: use a dedicated buffer here? + core::str::from_utf8(&buf[name_ofs..][..name_len]) + .map(ToOwned::to_owned) + })?; + + let data = env.meta(&name).unwrap_or_default(); + + instance.with_arg_buf_mut(|buf| { + buf[..data.len()].copy_from_slice(&data); + }); + + Ok(data.len() as u32) +} + +pub(crate) fn c( + mut fenv: Caller, + mod_id_ofs: u32, + name_ofs: u32, + name_len: u32, + arg_len: u32, + points_limit: u64, +) -> WasmtimeResult { + let env = fenv.data_mut(); + + let instance = env.self_instance(); + + let mod_id_ofs = mod_id_ofs as usize; + let name_ofs = name_ofs as usize; + let name_len = name_len as usize; + + check_ptr(instance, mod_id_ofs, CONTRACT_ID_BYTES)?; + check_ptr(instance, name_ofs, name_len)?; + check_arg(instance, arg_len)?; + + let argbuf_ofs = instance.arg_buffer_offset(); + + let caller_remaining = instance.get_remaining_points(); + + let callee_limit = if points_limit > 0 && points_limit < caller_remaining { + points_limit + } else { + caller_remaining * POINT_PASS_PCT / 100 + }; + + let with_memory = |memory: &mut [u8]| -> Result<_, Error> { + let arg_buf = &memory[argbuf_ofs..][..ARGBUF_LEN]; + + let mut mod_id = ContractId::uninitialized(); + mod_id.as_bytes_mut().copy_from_slice( + &memory[mod_id_ofs..][..std::mem::size_of::()], + ); + + let callee_stack_element = env + .push_callstack(mod_id, callee_limit) + .expect("pushing to the callstack should succeed"); + let callee = env + .instance(&callee_stack_element.contract_id) + .expect("callee instance should exist"); + + callee.snap().map_err(|err| Error::MemorySnapshotFailure { + reason: None, + io: Arc::new(err), + })?; + + let name = core::str::from_utf8(&memory[name_ofs..][..name_len])?; + + let arg = &arg_buf[..arg_len as usize]; + + callee.write_argument(arg); + let ret_len = callee + .call(name, arg.len() as u32, callee_limit) + .map_err(Error::normalize)?; + check_arg(callee, ret_len as u32)?; + + // copy back result + callee.read_argument(&mut memory[argbuf_ofs..][..ret_len as usize]); + + let callee_remaining = callee.get_remaining_points(); + let callee_spent = callee_limit - callee_remaining; + + Ok((ret_len, callee_spent)) + }; + + let ret = match instance.with_memory_mut(with_memory) { + Ok((ret_len, callee_spent)) => { + env.move_up_call_tree(callee_spent); + instance.set_remaining_points(caller_remaining - callee_spent); + ret_len + } + Err(mut err) => { + if let Err(io_err) = env.revert_callstack() { + err = Error::MemorySnapshotFailure { + reason: Some(Arc::new(err)), + io: Arc::new(io_err), + }; + } + env.move_up_prune_call_tree(); + instance.set_remaining_points(caller_remaining - callee_limit); + + ContractError::from(err).into() + } + }; + + Ok(ret) +} + +pub(crate) fn emit( + mut fenv: Caller, + topic_ofs: u32, + topic_len: u32, + arg_len: u32, +) -> WasmtimeResult<()> { + let env = fenv.data_mut(); + let instance = env.self_instance(); + + let topic_ofs = topic_ofs as usize; + let topic_len = topic_len as usize; + + check_ptr(instance, topic_ofs, topic_len)?; + check_arg(instance, arg_len)?; + + let data = instance.with_arg_buf(|buf| { + let arg_len = arg_len as usize; + Vec::from(&buf[..arg_len]) + }); + + let topic = instance.with_memory(|buf| { + // performance: use a dedicated buffer here? + core::str::from_utf8(&buf[topic_ofs..][..topic_len]) + .map(ToOwned::to_owned) + })?; + + env.emit(topic, data); + + Ok(()) +} diff --git a/piecrust/src/imports/wasm64.rs b/piecrust/src/imports/wasm64.rs new file mode 100644 index 00000000..a38ac3de --- /dev/null +++ b/piecrust/src/imports/wasm64.rs @@ -0,0 +1,195 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) DUSK NETWORK. All rights reserved. + +use std::sync::Arc; + +use dusk_wasmtime::{Caller, Result as WasmtimeResult}; +use piecrust_uplink::{ + ContractError, ContractId, ARGBUF_LEN, CONTRACT_ID_BYTES, +}; + +use crate::imports::{check_arg, check_ptr, POINT_PASS_PCT}; +use crate::instance::Env; +use crate::Error; + +pub(crate) fn hq( + mut fenv: Caller, + name_ofs: u64, + name_len: u32, + arg_len: u32, +) -> WasmtimeResult { + let env = fenv.data_mut(); + + let instance = env.self_instance(); + + let name_ofs = name_ofs as usize; + let name_len = name_len as usize; + + check_ptr(instance, name_ofs, name_len)?; + check_arg(instance, arg_len)?; + + let name = instance.with_memory(|buf| { + // performance: use a dedicated buffer here? + core::str::from_utf8(&buf[name_ofs..][..name_len]) + .map(ToOwned::to_owned) + })?; + + Ok(instance + .with_arg_buf_mut(|buf| env.host_query(&name, buf, arg_len)) + .ok_or(Error::MissingHostQuery(name))?) +} + +pub(crate) fn hd( + mut fenv: Caller, + name_ofs: u64, + name_len: u32, +) -> WasmtimeResult { + let env = fenv.data_mut(); + + let instance = env.self_instance(); + + let name_ofs = name_ofs as usize; + let name_len = name_len as usize; + + check_ptr(instance, name_ofs, name_len)?; + + let name = instance.with_memory(|buf| { + // performance: use a dedicated buffer here? + core::str::from_utf8(&buf[name_ofs..][..name_len]) + .map(ToOwned::to_owned) + })?; + + let data = env.meta(&name).unwrap_or_default(); + + instance.with_arg_buf_mut(|buf| { + buf[..data.len()].copy_from_slice(&data); + }); + + Ok(data.len() as u32) +} + +pub(crate) fn c( + mut fenv: Caller, + mod_id_ofs: u64, + name_ofs: u64, + name_len: u32, + arg_len: u32, + points_limit: u64, +) -> WasmtimeResult { + let env = fenv.data_mut(); + + let instance = env.self_instance(); + + let mod_id_ofs = mod_id_ofs as usize; + let name_ofs = name_ofs as usize; + let name_len = name_len as usize; + + check_ptr(instance, mod_id_ofs, CONTRACT_ID_BYTES)?; + check_ptr(instance, name_ofs, name_len)?; + check_arg(instance, arg_len)?; + + let argbuf_ofs = instance.arg_buffer_offset(); + + let caller_remaining = instance.get_remaining_points(); + + let callee_limit = if points_limit > 0 && points_limit < caller_remaining { + points_limit + } else { + caller_remaining * POINT_PASS_PCT / 100 + }; + + let with_memory = |memory: &mut [u8]| -> Result<_, Error> { + let arg_buf = &memory[argbuf_ofs..][..ARGBUF_LEN]; + + let mut mod_id = ContractId::uninitialized(); + mod_id.as_bytes_mut().copy_from_slice( + &memory[mod_id_ofs..][..std::mem::size_of::()], + ); + + let callee_stack_element = env + .push_callstack(mod_id, callee_limit) + .expect("pushing to the callstack should succeed"); + let callee = env + .instance(&callee_stack_element.contract_id) + .expect("callee instance should exist"); + + callee.snap().map_err(|err| Error::MemorySnapshotFailure { + reason: None, + io: Arc::new(err), + })?; + + let name = core::str::from_utf8(&memory[name_ofs..][..name_len])?; + + let arg = &arg_buf[..arg_len as usize]; + + callee.write_argument(arg); + let ret_len = callee + .call(name, arg.len() as u32, callee_limit) + .map_err(Error::normalize)?; + check_arg(callee, ret_len as u32)?; + + // copy back result + callee.read_argument(&mut memory[argbuf_ofs..][..ret_len as usize]); + + let callee_remaining = callee.get_remaining_points(); + let callee_spent = callee_limit - callee_remaining; + + Ok((ret_len, callee_spent)) + }; + + let ret = match instance.with_memory_mut(with_memory) { + Ok((ret_len, callee_spent)) => { + env.move_up_call_tree(callee_spent); + instance.set_remaining_points(caller_remaining - callee_spent); + ret_len + } + Err(mut err) => { + if let Err(io_err) = env.revert_callstack() { + err = Error::MemorySnapshotFailure { + reason: Some(Arc::new(err)), + io: Arc::new(io_err), + }; + } + env.move_up_prune_call_tree(); + instance.set_remaining_points(caller_remaining - callee_limit); + + ContractError::from(err).into() + } + }; + + Ok(ret) +} + +pub(crate) fn emit( + mut fenv: Caller, + topic_ofs: u64, + topic_len: u32, + arg_len: u32, +) -> WasmtimeResult<()> { + let env = fenv.data_mut(); + let instance = env.self_instance(); + + let topic_ofs = topic_ofs as usize; + let topic_len = topic_len as usize; + + check_ptr(instance, topic_ofs, topic_len)?; + check_arg(instance, arg_len)?; + + let data = instance.with_arg_buf(|buf| { + let arg_len = arg_len as usize; + Vec::from(&buf[..arg_len]) + }); + + let topic = instance.with_memory(|buf| { + // performance: use a dedicated buffer here? + core::str::from_utf8(&buf[topic_ofs..][..topic_len]) + .map(ToOwned::to_owned) + })?; + + env.emit(topic, data); + + Ok(()) +} diff --git a/piecrust/src/instance.rs b/piecrust/src/instance.rs index c1277e88..d738abd9 100644 --- a/piecrust/src/instance.rs +++ b/piecrust/src/instance.rs @@ -100,30 +100,6 @@ impl WrappedInstance { unsafe { Module::deserialize(&engine, contract.as_bytes())? }; let mut store = Store::new(&engine, env); - let imports = Imports::for_module(&mut store, &module)?; - let instance = Instance::new(&mut store, &module, &imports)?; - - // Ensure there is a global exported named `A`, whose value is in the - // memory. - let arg_buf_ofs = match instance.get_global(&mut store, "A") { - Some(global) => { - let ty = global.ty(&mut store); - - if ty.mutability() != Mutability::Const { - return Err(Error::InvalidArgumentBuffer); - } - - let val = global.get(&mut store); - - val.i32().ok_or(Error::InvalidArgumentBuffer)? as usize - } - _ => return Err(Error::InvalidArgumentBuffer), - }; - - if arg_buf_ofs + ARGBUF_LEN >= MAX_MEM_SIZE { - return Err(Error::InvalidArgumentBuffer); - } - // Ensure there is at most one memory exported, and that it is called // "memory". let n_memories = module @@ -131,10 +107,16 @@ impl WrappedInstance { .filter(|exp| exp.ty().memory().is_some()) .count(); - if n_memories > 1 { + if n_memories != 1 { return Err(Error::TooManyMemories(n_memories)); } + let is_64 = module + .exports() + .filter_map(|exp| exp.ty().memory().map(|mem_ty| mem_ty.is_64())) + .next() + .unwrap(); + // Ensure that every exported function has a signature that matches the // calling convention `F: I32 -> I32`. for exp in module.exports() { @@ -164,9 +146,33 @@ impl WrappedInstance { } } - let _ = instance - .get_memory(&mut store, "memory") - .ok_or(Error::InvalidMemory)?; + let imports = Imports::for_module(&mut store, &module, is_64)?; + let instance = Instance::new(&mut store, &module, &imports)?; + + // Ensure there is a global exported named `A`, whose value is in the + // memory. + let arg_buf_ofs = match instance.get_global(&mut store, "A") { + Some(global) => { + let ty = global.ty(&mut store); + + if ty.mutability() != Mutability::Const { + return Err(Error::InvalidArgumentBuffer); + } + + let val = global.get(&mut store); + + if is_64 { + val.i64().ok_or(Error::InvalidArgumentBuffer)? as usize + } else { + val.i32().ok_or(Error::InvalidArgumentBuffer)? as usize + } + } + _ => return Err(Error::InvalidArgumentBuffer), + }; + + if arg_buf_ofs + ARGBUF_LEN >= MAX_MEM_SIZE { + return Err(Error::InvalidArgumentBuffer); + } let wrapped = WrappedInstance { store, diff --git a/piecrust/src/vm.rs b/piecrust/src/vm.rs index d6c02aa1..3c28b471 100644 --- a/piecrust/src/vm.rs +++ b/piecrust/src/vm.rs @@ -42,7 +42,6 @@ fn config() -> Config { // config.with_host_memory() config.static_memory_forced(true); - config.static_memory_maximum_size(0x100000000); config.static_memory_guard_size(0); config.dynamic_memory_guard_size(0); config.guard_before_linear_memory(false); @@ -54,6 +53,9 @@ fn config() -> Config { config.generate_address_map(false); config.macos_use_mach_ports(false); + // Support 64-bit memories + config.wasm_memory64(true); + config } From d38d4c425de22aeea167f5b4a45aff96a717f582 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Tue, 24 Oct 2023 16:38:13 +0200 Subject: [PATCH 4/6] piecrust: increase max memory size to 4TiB Resolves #159 --- piecrust/CHANGELOG.md | 3 +++ piecrust/src/store/memory.rs | 2 +- piecrust/src/store/tree.rs | 6 +++--- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/piecrust/CHANGELOG.md b/piecrust/CHANGELOG.md index d9e397f5..331b9ecf 100644 --- a/piecrust/CHANGELOG.md +++ b/piecrust/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Changed +- Change contract tree to be arity 4 and height 17 [#159] +- Maximum contract size is now 4TiB [#159] - Change `Error::RuntimeError` variant to contain `dusk_wasmtime::Error`, and changed `From` implementation - Switch runtime from `wasmer` to `wasmtime` @@ -303,6 +305,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#167]: https://github.com/dusk-network/piecrust/issues/167 [#166]: https://github.com/dusk-network/piecrust/issues/166 [#162]: https://github.com/dusk-network/piecrust/issues/162 +[#159]: https://github.com/dusk-network/piecrust/issues/159 [#158]: https://github.com/dusk-network/piecrust/issues/158 [#136]: https://github.com/dusk-network/piecrust/issues/136 [#93]: https://github.com/dusk-network/piecrust/issues/93 diff --git a/piecrust/src/store/memory.rs b/piecrust/src/store/memory.rs index 1f45e500..ce549a49 100644 --- a/piecrust/src/store/memory.rs +++ b/piecrust/src/store/memory.rs @@ -16,7 +16,7 @@ use crumbles::{LocateFile, Mmap}; use dusk_wasmtime::LinearMemory; pub const PAGE_SIZE: usize = 0x10000; -const WASM_MAX_PAGES: u32 = 0x10000; +const WASM_MAX_PAGES: u32 = 0x4000000; const MIN_PAGES: usize = 4; const MIN_MEM_SIZE: usize = MIN_PAGES * PAGE_SIZE; diff --git a/piecrust/src/store/tree.rs b/piecrust/src/store/tree.rs index 48f63b2c..f42a12d7 100644 --- a/piecrust/src/store/tree.rs +++ b/piecrust/src/store/tree.rs @@ -12,9 +12,9 @@ use rkyv::{Archive, Deserialize, Serialize}; use crate::store::memory::Memory; -// There are `2^16` pages in a memory -const P_HEIGHT: usize = 16; -const P_ARITY: usize = 2; +// There are max `2^26` pages in a memory +const P_HEIGHT: usize = 13; +const P_ARITY: usize = 4; pub type PageTree = dusk_merkle::Tree; From 485c47660d939d0684eaaedc81c09188062ac4ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Wed, 25 Oct 2023 15:33:16 +0200 Subject: [PATCH 5/6] piecrust: add some docs for `memory64` --- piecrust/src/lib.rs | 6 ++++++ piecrust/src/session.rs | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/piecrust/src/lib.rs b/piecrust/src/lib.rs index 91b3e24f..358bf68a 100644 --- a/piecrust/src/lib.rs +++ b/piecrust/src/lib.rs @@ -74,6 +74,12 @@ //! the error-producing call, which returns an error without being actually //! executed. //! +//! # 32 vs 64-bit +//! +//! Contracts can be compiled to either 32 or 64-bit WASM - i.e. the `memory64` +//! proposal. 32-bit contracts have a maximum memory size of 4GiB, while 64-bit +//! contracts have a maximum memory size of 4TiB. +//! //! # Usage //! ``` //! use piecrust::{contract_bytecode, ContractData, SessionData, VM}; diff --git a/piecrust/src/session.rs b/piecrust/src/session.rs index 633d413d..2fcae9f2 100644 --- a/piecrust/src/session.rs +++ b/piecrust/src/session.rs @@ -197,7 +197,9 @@ impl Session { } /// Deploy a contract, returning its [`ContractId`]. The ID is computed - /// using a `blake3` hash of the `bytecode`. + /// using a `blake3` hash of the `bytecode`. Contracts using the `memory64` + /// proposal are accepted in just the same way as 32-bit contracts, and + /// their handling is totally transparent. /// /// Since a deployment may execute some contract initialization code, that /// code will be metered and executed with the given `points_limit`. From a8b9c2143c6b5b5ca006dfdef9966be7f303b8f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eduardo=20Leegwater=20Sim=C3=B5es?= Date: Wed, 25 Oct 2023 15:47:02 +0200 Subject: [PATCH 6/6] piecrust: dedup 32 and 64-bit imports code --- piecrust/src/imports.rs | 180 ++++++++++++++++++++++++++++++++- piecrust/src/imports/wasm32.rs | 173 +++---------------------------- piecrust/src/imports/wasm64.rs | 173 +++---------------------------- 3 files changed, 211 insertions(+), 315 deletions(-) diff --git a/piecrust/src/imports.rs b/piecrust/src/imports.rs index 87f6f6e0..38376916 100644 --- a/piecrust/src/imports.rs +++ b/piecrust/src/imports.rs @@ -7,10 +7,14 @@ mod wasm32; mod wasm64; +use std::sync::Arc; + use dusk_wasmtime::{ Caller, Extern, Func, Module, Result as WasmtimeResult, Store, }; -use piecrust_uplink::{ContractId, ARGBUF_LEN}; +use piecrust_uplink::{ + ContractError, ContractId, ARGBUF_LEN, CONTRACT_ID_BYTES, +}; use crate::instance::{Env, WrappedInstance}; use crate::Error; @@ -123,6 +127,180 @@ pub fn check_arg( Ok(()) } +pub(crate) fn hq( + mut fenv: Caller, + name_ofs: usize, + name_len: u32, + arg_len: u32, +) -> WasmtimeResult { + let env = fenv.data_mut(); + + let instance = env.self_instance(); + + let name_len = name_len as usize; + + check_ptr(instance, name_ofs, name_len)?; + check_arg(instance, arg_len)?; + + let name = instance.with_memory(|buf| { + // performance: use a dedicated buffer here? + core::str::from_utf8(&buf[name_ofs..][..name_len]) + .map(ToOwned::to_owned) + })?; + + Ok(instance + .with_arg_buf_mut(|buf| env.host_query(&name, buf, arg_len)) + .ok_or(Error::MissingHostQuery(name))?) +} + +pub(crate) fn hd( + mut fenv: Caller, + name_ofs: usize, + name_len: u32, +) -> WasmtimeResult { + let env = fenv.data_mut(); + + let instance = env.self_instance(); + + let name_len = name_len as usize; + + check_ptr(instance, name_ofs, name_len)?; + + let name = instance.with_memory(|buf| { + // performance: use a dedicated buffer here? + core::str::from_utf8(&buf[name_ofs..][..name_len]) + .map(ToOwned::to_owned) + })?; + + let data = env.meta(&name).unwrap_or_default(); + + instance.with_arg_buf_mut(|buf| { + buf[..data.len()].copy_from_slice(&data); + }); + + Ok(data.len() as u32) +} + +pub(crate) fn c( + mut fenv: Caller, + mod_id_ofs: usize, + name_ofs: usize, + name_len: u32, + arg_len: u32, + points_limit: u64, +) -> WasmtimeResult { + let env = fenv.data_mut(); + + let instance = env.self_instance(); + + let name_len = name_len as usize; + + check_ptr(instance, mod_id_ofs, CONTRACT_ID_BYTES)?; + check_ptr(instance, name_ofs, name_len)?; + check_arg(instance, arg_len)?; + + let argbuf_ofs = instance.arg_buffer_offset(); + + let caller_remaining = instance.get_remaining_points(); + + let callee_limit = if points_limit > 0 && points_limit < caller_remaining { + points_limit + } else { + caller_remaining * POINT_PASS_PCT / 100 + }; + + let with_memory = |memory: &mut [u8]| -> Result<_, Error> { + let arg_buf = &memory[argbuf_ofs..][..ARGBUF_LEN]; + + let mut mod_id = ContractId::uninitialized(); + mod_id.as_bytes_mut().copy_from_slice( + &memory[mod_id_ofs..][..std::mem::size_of::()], + ); + + let callee_stack_element = env + .push_callstack(mod_id, callee_limit) + .expect("pushing to the callstack should succeed"); + let callee = env + .instance(&callee_stack_element.contract_id) + .expect("callee instance should exist"); + + callee.snap().map_err(|err| Error::MemorySnapshotFailure { + reason: None, + io: Arc::new(err), + })?; + + let name = core::str::from_utf8(&memory[name_ofs..][..name_len])?; + + let arg = &arg_buf[..arg_len as usize]; + + callee.write_argument(arg); + let ret_len = callee + .call(name, arg.len() as u32, callee_limit) + .map_err(Error::normalize)?; + check_arg(callee, ret_len as u32)?; + + // copy back result + callee.read_argument(&mut memory[argbuf_ofs..][..ret_len as usize]); + + let callee_remaining = callee.get_remaining_points(); + let callee_spent = callee_limit - callee_remaining; + + Ok((ret_len, callee_spent)) + }; + + let ret = match instance.with_memory_mut(with_memory) { + Ok((ret_len, callee_spent)) => { + env.move_up_call_tree(callee_spent); + instance.set_remaining_points(caller_remaining - callee_spent); + ret_len + } + Err(mut err) => { + if let Err(io_err) = env.revert_callstack() { + err = Error::MemorySnapshotFailure { + reason: Some(Arc::new(err)), + io: Arc::new(io_err), + }; + } + env.move_up_prune_call_tree(); + instance.set_remaining_points(caller_remaining - callee_limit); + + ContractError::from(err).into() + } + }; + + Ok(ret) +} + +pub(crate) fn emit( + mut fenv: Caller, + topic_ofs: usize, + topic_len: u32, + arg_len: u32, +) -> WasmtimeResult<()> { + let env = fenv.data_mut(); + let instance = env.self_instance(); + + let topic_len = topic_len as usize; + + check_ptr(instance, topic_ofs, topic_len)?; + check_arg(instance, arg_len)?; + + let data = instance.with_arg_buf(|buf| { + let arg_len = arg_len as usize; + Vec::from(&buf[..arg_len]) + }); + + let topic = instance.with_memory(|buf| { + // performance: use a dedicated buffer here? + core::str::from_utf8(&buf[topic_ofs..][..topic_len]) + .map(ToOwned::to_owned) + })?; + + env.emit(topic, data); + + Ok(()) +} + fn caller(env: Caller) { let env = env.data(); diff --git a/piecrust/src/imports/wasm32.rs b/piecrust/src/imports/wasm32.rs index 203992ed..449c4da2 100644 --- a/piecrust/src/imports/wasm32.rs +++ b/piecrust/src/imports/wasm32.rs @@ -4,192 +4,51 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use std::sync::Arc; - use dusk_wasmtime::{Caller, Result as WasmtimeResult}; -use piecrust_uplink::{ - ContractError, ContractId, ARGBUF_LEN, CONTRACT_ID_BYTES, -}; -use crate::imports::{check_arg, check_ptr, POINT_PASS_PCT}; +use crate::imports; use crate::instance::Env; -use crate::Error; pub(crate) fn hq( - mut fenv: Caller, + fenv: Caller, name_ofs: u32, name_len: u32, arg_len: u32, ) -> WasmtimeResult { - let env = fenv.data_mut(); - - let instance = env.self_instance(); - - let name_ofs = name_ofs as usize; - let name_len = name_len as usize; - - check_ptr(instance, name_ofs, name_len)?; - check_arg(instance, arg_len)?; - - let name = instance.with_memory(|buf| { - // performance: use a dedicated buffer here? - core::str::from_utf8(&buf[name_ofs..][..name_len]) - .map(ToOwned::to_owned) - })?; - - Ok(instance - .with_arg_buf_mut(|buf| env.host_query(&name, buf, arg_len)) - .ok_or(Error::MissingHostQuery(name))?) + imports::hq(fenv, name_ofs as usize, name_len, arg_len) } pub(crate) fn hd( - mut fenv: Caller, + fenv: Caller, name_ofs: u32, name_len: u32, ) -> WasmtimeResult { - let env = fenv.data_mut(); - - let instance = env.self_instance(); - - let name_ofs = name_ofs as usize; - let name_len = name_len as usize; - - check_ptr(instance, name_ofs, name_len)?; - - let name = instance.with_memory(|buf| { - // performance: use a dedicated buffer here? - core::str::from_utf8(&buf[name_ofs..][..name_len]) - .map(ToOwned::to_owned) - })?; - - let data = env.meta(&name).unwrap_or_default(); - - instance.with_arg_buf_mut(|buf| { - buf[..data.len()].copy_from_slice(&data); - }); - - Ok(data.len() as u32) + imports::hd(fenv, name_ofs as usize, name_len) } pub(crate) fn c( - mut fenv: Caller, + fenv: Caller, mod_id_ofs: u32, name_ofs: u32, name_len: u32, arg_len: u32, points_limit: u64, ) -> WasmtimeResult { - let env = fenv.data_mut(); - - let instance = env.self_instance(); - - let mod_id_ofs = mod_id_ofs as usize; - let name_ofs = name_ofs as usize; - let name_len = name_len as usize; - - check_ptr(instance, mod_id_ofs, CONTRACT_ID_BYTES)?; - check_ptr(instance, name_ofs, name_len)?; - check_arg(instance, arg_len)?; - - let argbuf_ofs = instance.arg_buffer_offset(); - - let caller_remaining = instance.get_remaining_points(); - - let callee_limit = if points_limit > 0 && points_limit < caller_remaining { - points_limit - } else { - caller_remaining * POINT_PASS_PCT / 100 - }; - - let with_memory = |memory: &mut [u8]| -> Result<_, Error> { - let arg_buf = &memory[argbuf_ofs..][..ARGBUF_LEN]; - - let mut mod_id = ContractId::uninitialized(); - mod_id.as_bytes_mut().copy_from_slice( - &memory[mod_id_ofs..][..std::mem::size_of::()], - ); - - let callee_stack_element = env - .push_callstack(mod_id, callee_limit) - .expect("pushing to the callstack should succeed"); - let callee = env - .instance(&callee_stack_element.contract_id) - .expect("callee instance should exist"); - - callee.snap().map_err(|err| Error::MemorySnapshotFailure { - reason: None, - io: Arc::new(err), - })?; - - let name = core::str::from_utf8(&memory[name_ofs..][..name_len])?; - - let arg = &arg_buf[..arg_len as usize]; - - callee.write_argument(arg); - let ret_len = callee - .call(name, arg.len() as u32, callee_limit) - .map_err(Error::normalize)?; - check_arg(callee, ret_len as u32)?; - - // copy back result - callee.read_argument(&mut memory[argbuf_ofs..][..ret_len as usize]); - - let callee_remaining = callee.get_remaining_points(); - let callee_spent = callee_limit - callee_remaining; - - Ok((ret_len, callee_spent)) - }; - - let ret = match instance.with_memory_mut(with_memory) { - Ok((ret_len, callee_spent)) => { - env.move_up_call_tree(callee_spent); - instance.set_remaining_points(caller_remaining - callee_spent); - ret_len - } - Err(mut err) => { - if let Err(io_err) = env.revert_callstack() { - err = Error::MemorySnapshotFailure { - reason: Some(Arc::new(err)), - io: Arc::new(io_err), - }; - } - env.move_up_prune_call_tree(); - instance.set_remaining_points(caller_remaining - callee_limit); - - ContractError::from(err).into() - } - }; - - Ok(ret) + imports::c( + fenv, + mod_id_ofs as usize, + name_ofs as usize, + name_len, + arg_len, + points_limit, + ) } pub(crate) fn emit( - mut fenv: Caller, + fenv: Caller, topic_ofs: u32, topic_len: u32, arg_len: u32, ) -> WasmtimeResult<()> { - let env = fenv.data_mut(); - let instance = env.self_instance(); - - let topic_ofs = topic_ofs as usize; - let topic_len = topic_len as usize; - - check_ptr(instance, topic_ofs, topic_len)?; - check_arg(instance, arg_len)?; - - let data = instance.with_arg_buf(|buf| { - let arg_len = arg_len as usize; - Vec::from(&buf[..arg_len]) - }); - - let topic = instance.with_memory(|buf| { - // performance: use a dedicated buffer here? - core::str::from_utf8(&buf[topic_ofs..][..topic_len]) - .map(ToOwned::to_owned) - })?; - - env.emit(topic, data); - - Ok(()) + imports::emit(fenv, topic_ofs as usize, topic_len, arg_len) } diff --git a/piecrust/src/imports/wasm64.rs b/piecrust/src/imports/wasm64.rs index a38ac3de..493330e1 100644 --- a/piecrust/src/imports/wasm64.rs +++ b/piecrust/src/imports/wasm64.rs @@ -4,192 +4,51 @@ // // Copyright (c) DUSK NETWORK. All rights reserved. -use std::sync::Arc; - use dusk_wasmtime::{Caller, Result as WasmtimeResult}; -use piecrust_uplink::{ - ContractError, ContractId, ARGBUF_LEN, CONTRACT_ID_BYTES, -}; -use crate::imports::{check_arg, check_ptr, POINT_PASS_PCT}; +use crate::imports; use crate::instance::Env; -use crate::Error; pub(crate) fn hq( - mut fenv: Caller, + fenv: Caller, name_ofs: u64, name_len: u32, arg_len: u32, ) -> WasmtimeResult { - let env = fenv.data_mut(); - - let instance = env.self_instance(); - - let name_ofs = name_ofs as usize; - let name_len = name_len as usize; - - check_ptr(instance, name_ofs, name_len)?; - check_arg(instance, arg_len)?; - - let name = instance.with_memory(|buf| { - // performance: use a dedicated buffer here? - core::str::from_utf8(&buf[name_ofs..][..name_len]) - .map(ToOwned::to_owned) - })?; - - Ok(instance - .with_arg_buf_mut(|buf| env.host_query(&name, buf, arg_len)) - .ok_or(Error::MissingHostQuery(name))?) + imports::hq(fenv, name_ofs as usize, name_len, arg_len) } pub(crate) fn hd( - mut fenv: Caller, + fenv: Caller, name_ofs: u64, name_len: u32, ) -> WasmtimeResult { - let env = fenv.data_mut(); - - let instance = env.self_instance(); - - let name_ofs = name_ofs as usize; - let name_len = name_len as usize; - - check_ptr(instance, name_ofs, name_len)?; - - let name = instance.with_memory(|buf| { - // performance: use a dedicated buffer here? - core::str::from_utf8(&buf[name_ofs..][..name_len]) - .map(ToOwned::to_owned) - })?; - - let data = env.meta(&name).unwrap_or_default(); - - instance.with_arg_buf_mut(|buf| { - buf[..data.len()].copy_from_slice(&data); - }); - - Ok(data.len() as u32) + imports::hd(fenv, name_ofs as usize, name_len) } pub(crate) fn c( - mut fenv: Caller, + fenv: Caller, mod_id_ofs: u64, name_ofs: u64, name_len: u32, arg_len: u32, points_limit: u64, ) -> WasmtimeResult { - let env = fenv.data_mut(); - - let instance = env.self_instance(); - - let mod_id_ofs = mod_id_ofs as usize; - let name_ofs = name_ofs as usize; - let name_len = name_len as usize; - - check_ptr(instance, mod_id_ofs, CONTRACT_ID_BYTES)?; - check_ptr(instance, name_ofs, name_len)?; - check_arg(instance, arg_len)?; - - let argbuf_ofs = instance.arg_buffer_offset(); - - let caller_remaining = instance.get_remaining_points(); - - let callee_limit = if points_limit > 0 && points_limit < caller_remaining { - points_limit - } else { - caller_remaining * POINT_PASS_PCT / 100 - }; - - let with_memory = |memory: &mut [u8]| -> Result<_, Error> { - let arg_buf = &memory[argbuf_ofs..][..ARGBUF_LEN]; - - let mut mod_id = ContractId::uninitialized(); - mod_id.as_bytes_mut().copy_from_slice( - &memory[mod_id_ofs..][..std::mem::size_of::()], - ); - - let callee_stack_element = env - .push_callstack(mod_id, callee_limit) - .expect("pushing to the callstack should succeed"); - let callee = env - .instance(&callee_stack_element.contract_id) - .expect("callee instance should exist"); - - callee.snap().map_err(|err| Error::MemorySnapshotFailure { - reason: None, - io: Arc::new(err), - })?; - - let name = core::str::from_utf8(&memory[name_ofs..][..name_len])?; - - let arg = &arg_buf[..arg_len as usize]; - - callee.write_argument(arg); - let ret_len = callee - .call(name, arg.len() as u32, callee_limit) - .map_err(Error::normalize)?; - check_arg(callee, ret_len as u32)?; - - // copy back result - callee.read_argument(&mut memory[argbuf_ofs..][..ret_len as usize]); - - let callee_remaining = callee.get_remaining_points(); - let callee_spent = callee_limit - callee_remaining; - - Ok((ret_len, callee_spent)) - }; - - let ret = match instance.with_memory_mut(with_memory) { - Ok((ret_len, callee_spent)) => { - env.move_up_call_tree(callee_spent); - instance.set_remaining_points(caller_remaining - callee_spent); - ret_len - } - Err(mut err) => { - if let Err(io_err) = env.revert_callstack() { - err = Error::MemorySnapshotFailure { - reason: Some(Arc::new(err)), - io: Arc::new(io_err), - }; - } - env.move_up_prune_call_tree(); - instance.set_remaining_points(caller_remaining - callee_limit); - - ContractError::from(err).into() - } - }; - - Ok(ret) + imports::c( + fenv, + mod_id_ofs as usize, + name_ofs as usize, + name_len, + arg_len, + points_limit, + ) } pub(crate) fn emit( - mut fenv: Caller, + fenv: Caller, topic_ofs: u64, topic_len: u32, arg_len: u32, ) -> WasmtimeResult<()> { - let env = fenv.data_mut(); - let instance = env.self_instance(); - - let topic_ofs = topic_ofs as usize; - let topic_len = topic_len as usize; - - check_ptr(instance, topic_ofs, topic_len)?; - check_arg(instance, arg_len)?; - - let data = instance.with_arg_buf(|buf| { - let arg_len = arg_len as usize; - Vec::from(&buf[..arg_len]) - }); - - let topic = instance.with_memory(|buf| { - // performance: use a dedicated buffer here? - core::str::from_utf8(&buf[topic_ofs..][..topic_len]) - .map(ToOwned::to_owned) - })?; - - env.emit(topic, data); - - Ok(()) + imports::emit(fenv, topic_ofs as usize, topic_len, arg_len) }