diff --git a/README.md b/README.md index 4e24e75bdf..f8a06666b1 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ The imports provided to give the contract access to the environment are: // TODO: use feature switches to enable precompile dependencies in the future, // so contracts that need less extern "C" { - fn db_read(key: *const c_void, value: *mut c_void) -> i32; + fn db_read(key: *const c_void) -> i32; fn db_write(key: *const c_void, value: *mut c_void) -> i32; fn db_remove(key: *const c_void) -> i32; fn canonicalize_address(human: *const c_void, canonical: *mut c_void) -> i32; diff --git a/packages/std/src/imports.rs b/packages/std/src/imports.rs index d4f35425fc..fa4994846f 100644 --- a/packages/std/src/imports.rs +++ b/packages/std/src/imports.rs @@ -12,9 +12,6 @@ use crate::traits::{Api, Querier, QuerierResponse, ReadonlyStorage, Storage}; use crate::traits::{Order, KV}; use crate::types::{CanonicalAddr, HumanAddr}; -// this is the buffer we pre-allocate in get - we should configure this somehow later -static MAX_READ: usize = 2000; - // this is the maximum allowed size for bech32 // https://github.com/bitcoin/bips/blob/master/bip-0173.mediawiki#bech32 static ADDR_BUFFER: usize = 90; @@ -26,7 +23,7 @@ static QUERY_BUFFER: usize = 4000; // A complete documentation those functions is available in the VM that provides them: // https://github.com/confio/cosmwasm/blob/0.7/lib/vm/src/instance.rs#L43 extern "C" { - fn db_read(key: *const c_void, value: *mut c_void) -> i32; + fn db_read(key: *const c_void) -> i32; fn db_write(key: *const c_void, value: *mut c_void) -> i32; fn db_remove(key: *const c_void) -> i32; @@ -58,24 +55,17 @@ impl ReadonlyStorage for ExternalStorage { fn get(&self, key: &[u8]) -> Option> { let key = build_region(key); let key_ptr = &*key as *const Region as *const c_void; - let value = alloc(MAX_READ); - - let read = unsafe { db_read(key_ptr, value) }; - if read == -1000002 { - panic!("Allocated memory too small to hold the database value for the given key. \ - If this is causing trouble for you, have a look at https://github.com/confio/cosmwasm/issues/126"); - } else if read < 0 { - panic!("An unknown error occurred in the db_read call.") + + let read = unsafe { db_read(key_ptr) }; + if read < 0 { + panic!("An unknown error occurred in the db_read call"); + } else if read == 0 { + return None; } - match unsafe { consume_region(value) } { - Ok(data) => { - if data.len() == 0 { - None - } else { - Some(data) - } - } + let value_ptr = read as u32; + match unsafe { consume_region(value_ptr as *mut c_void) } { + Ok(data) => Some(data), // TODO: do we really want to convert errors to None? Err(_) => None, } diff --git a/packages/vm/src/context.rs b/packages/vm/src/context.rs index 8efcaf811c..adb247e721 100644 --- a/packages/vm/src/context.rs +++ b/packages/vm/src/context.rs @@ -3,8 +3,13 @@ use std::collections::HashMap; use std::ffi::c_void; use std::mem; +use std::ptr::NonNull; -use wasmer_runtime_core::vm::Ctx; +use snafu::ResultExt; +use wasmer_runtime_core::{ + typed_func::{Func, Wasm, WasmTypeList}, + vm::Ctx, +}; #[cfg(feature = "iterator")] use cosmwasm_std::KV; @@ -13,9 +18,8 @@ use cosmwasm_std::{ QuerierResponse, QueryRequest, Storage, }; -#[cfg(feature = "iterator")] -use crate::conversion::to_i32; -use crate::errors::{Error, Result, UninitializedContextData}; +use crate::conversion::{to_i32, to_u32}; +use crate::errors::{Error, Result, UninitializedContextData, WasmerRuntimeErr}; use crate::memory::{read_region, write_region}; use crate::serde::{from_slice, to_vec}; #[cfg(feature = "iterator")] @@ -49,7 +53,7 @@ pub static ERROR_NO_CONTEXT_DATA: i32 = -1_000_501; static ERROR_DB_UNKNOWN: i32 = -1_000_502; /// Reads a storage entry from the VM's storage into Wasm memory -pub fn do_read(ctx: &Ctx, key_ptr: u32, value_ptr: u32) -> i32 { +pub fn do_read(ctx: &Ctx, key_ptr: u32) -> i32 { let key = match read_region(ctx, key_ptr, MAX_LENGTH_DB_KEY) { Ok(data) => data, Err(Error::RegionLengthTooBigErr { .. }) => return ERROR_REGION_READ_LENGTH_TOO_BIG, @@ -61,13 +65,36 @@ pub fn do_read(ctx: &Ctx, key_ptr: u32, value_ptr: u32) Err(Error::UninitializedContextData { .. }) => return ERROR_NO_CONTEXT_DATA, Err(_) => return ERROR_DB_UNKNOWN, }; - match value { - Some(buf) => match write_region(ctx, value_ptr, &buf) { - Ok(()) => SUCCESS, - Err(Error::RegionTooSmallErr { .. }) => ERROR_REGION_WRITE_TOO_SMALL, - Err(_) => ERROR_REGION_WRITE_UNKNOWN, + + let data = match value { + Some(d) => d, + None => return SUCCESS, + }; + + println!("Data len: {}", data.len()); + + let value_ptr = + match with_func_from_context::(ctx, "allocate", |allocate_func| { + println!("Found allocate function"); + let size = to_u32(data.len())?; + allocate_func.call(size).context(WasmerRuntimeErr {}) + }) { + Ok(ptr) => ptr, + Err(_) => { + println!("Error executing allocate"); + return -34567890; // error executing allocate + } + }; + + println!("value_ptr = {}", value_ptr); + + match write_region(ctx, value_ptr, &data) { + Ok(()) => match to_i32(value_ptr) { + Ok(iptr) => iptr, + Err(_) => return -98765, // Error converting to i32 range }, - None => SUCCESS, + Err(Error::RegionTooSmallErr { .. }) => ERROR_REGION_WRITE_TOO_SMALL, + Err(_) => ERROR_REGION_WRITE_UNKNOWN, } } @@ -318,6 +345,7 @@ mod iter_support { struct ContextData { storage: Option, querier: Option, + wasmer_instance: Option>, #[cfg(feature = "iterator")] iter: HashMap>>, } @@ -333,6 +361,7 @@ fn create_unmanaged_context_data() -> *mut c_void { let data = ContextData:: { storage: None, querier: None, + wasmer_instance: None, #[cfg(feature = "iterator")] iter: HashMap::new(), }; @@ -348,6 +377,17 @@ fn destroy_unmanaged_context_data(ptr: *mut c_void) { } } +/// Creates a back reference from a contact to its partent instance +pub fn set_wasmer_instance( + ctx: &Ctx, + wasmer_instance: NonNull, +) { + let context_data = ctx.data as *mut ContextData; + unsafe { + (*context_data).wasmer_instance = Some(wasmer_instance); + } +} + unsafe fn get_data(ptr: *mut c_void) -> Box> { Box::from_raw(ptr as *mut ContextData) } @@ -363,6 +403,47 @@ fn free_iterator(context: &mut ContextData) { #[cfg(not(feature = "iterator"))] fn free_iterator(_context: &mut ContextData) {} +pub(crate) fn with_func_from_context( + ctx: &Ctx, + name: &str, + mut callback: Callback, +) -> Result +where + S: Storage, + Q: Querier, + Args: WasmTypeList, + Rets: WasmTypeList, + Callback: FnMut(Func) -> Result, +{ + let context_data = ctx.data as *mut ContextData; + println!("Got context"); + match unsafe { (*context_data).wasmer_instance } { + Some(ptr) => { + println!("Got wasmer instance pointer"); + let instance = unsafe { ptr.as_ref() }; + println!("Got wasmer instance ref"); + let _context = instance.context(); + println!("Could execute context function on wasmer instance ref"); + let func = match instance.func(name) { + Ok(func) => { + println!("Found func"); + func + } + Err(e) => { + println!("Error in func(): {:?}", e); + panic!("Error in func(): {:?}", e); + } + }; // .context(ResolveErr {})?; + println!("Executing func ..."); + callback(func) + } + None => UninitializedContextData { + kind: "wasmer_instance", + } + .fail(), + } +} + pub(crate) fn with_storage_from_context(ctx: &Ctx, mut func: F) -> Result where S: Storage, @@ -451,7 +532,7 @@ mod test { let import_obj = imports! { || { setup_context::() }, "env" => { - "db_read" => Func::new(|_a: i32, _b: i32| -> i32 { 0 }), + "db_read" => Func::new(|_a: i32| -> i32 { 0 }), "db_write" => Func::new(|_a: i32, _b: i32| -> i32 { 0 }), "db_remove" => Func::new(|_a: i32| -> i32 { 0 }), "db_scan" => Func::new(|_a: i32, _b: i32, _c: i32| -> i32 { 0 }), @@ -497,6 +578,39 @@ mod test { assert!(endq.is_none()); } + #[test] + fn with_func_from_context_works() { + let mut instance = make_instance(); + leave_default_data(&instance); + let instance_ptr = NonNull::from(&mut instance); + set_wasmer_instance::(instance.context(), instance_ptr); + + let ctx = instance.context(); + let _ptr = with_func_from_context::(ctx, "allocate", |alloc_func| { + alloc_func.call(10).context(WasmerRuntimeErr {}) + }) + .expect("with_func_from_context should not fail"); + } + + #[test] + fn with_func_from_context_fails_for_missing_instance() { + let instance = make_instance(); + leave_default_data(&instance); + let ctx = instance.context(); + + let res = with_func_from_context::(ctx, "allocate", |alloc_func| { + alloc_func.call(10).expect("error calling allocate"); + Ok(()) + }); + match res { + Ok(_) => panic!("this must not succeed"), + Err(Error::UninitializedContextData { kind, .. }) => { + assert_eq!(kind, "wasmer_instance") + } + Err(e) => panic!("Unexpected error: {}", e), + } + } + #[test] fn with_storage_set_get() { // this creates an instance diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index c2c12f7077..9c3be3b70c 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -1,4 +1,5 @@ use std::marker::PhantomData; +use std::ptr::NonNull; use snafu::ResultExt; pub use wasmer_runtime_core::typed_func::Func; @@ -14,7 +15,8 @@ use cosmwasm_std::{Api, Extern, Querier, Storage}; use crate::backends::{compile, get_gas, set_gas}; use crate::context::{ do_canonicalize_address, do_humanize_address, do_query_chain, do_read, do_remove, do_write, - move_from_context, move_into_context, setup_context, with_storage_from_context, + move_from_context, move_into_context, set_wasmer_instance, setup_context, + with_storage_from_context, }; #[cfg(feature = "iterator")] use crate::context::{do_next, do_scan}; @@ -23,7 +25,7 @@ use crate::errors::{ResolveErr, Result, WasmerErr, WasmerRuntimeErr}; use crate::memory::{read_region, write_region}; pub struct Instance { - wasmer_instance: wasmer_runtime_core::instance::Instance, + wasmer_instance: Box, pub api: A, // This does not store data but only fixes type information type_storage: PhantomData, @@ -53,8 +55,8 @@ where // Returns 0 on success. Returns negative value on error. An incomplete list of error codes is: // value region too small: -1000002 // Ownership of both input and output pointer is not transferred to the host. - "db_read" => Func::new(move |ctx: &mut Ctx, key_ptr: u32, value_ptr: u32| -> i32 { - do_read::(ctx, key_ptr, value_ptr) + "db_read" => Func::new(move |ctx: &mut Ctx, key_ptr: u32| -> i32 { + do_read::(ctx, key_ptr) }), // Writes the given value into the database entry at the given key. // Ownership of both input and output pointer is not transferred to the host. @@ -119,6 +121,15 @@ where gas_limit: u64, ) -> Self { set_gas(&mut wasmer_instance, gas_limit); + + // The pointer shenanigans below are sound because: + // 1. Boxing the wasmer instance gives it a constant address, + // 2. We never move out of this Box before we finish using this `Instance` + // 3. We provide the context a mut pointer to the wasmer context, + // and we only use it when we actually have unique access to it. + let mut wasmer_instance = Box::new(wasmer_instance); + let instance_ptr = NonNull::from(&mut *wasmer_instance); + set_wasmer_instance::(wasmer_instance.context(), instance_ptr); move_from_context(wasmer_instance.context(), deps.storage, deps.querier); Instance { wasmer_instance, @@ -142,7 +153,7 @@ where } else { None }; - (instance.wasmer_instance, ext) + (*instance.wasmer_instance, ext) } /// Returns the currently remaining gas