Skip to content

Commit

Permalink
contract-sdk: use C abi instead of wasm
Browse files Browse the repository at this point in the history
  • Loading branch information
ptrus committed Oct 21, 2021
1 parent 9ad3543 commit 7896fb2
Show file tree
Hide file tree
Showing 13 changed files with 199 additions and 49 deletions.
2 changes: 0 additions & 2 deletions contract-sdk/specs/oas20/contract/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
//! An OAS20 contract.
#![feature(wasm_abi)]

extern crate alloc;

use oasis_contract_sdk::{self as sdk};
Expand Down
2 changes: 1 addition & 1 deletion contract-sdk/src/abi/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Crypto helpers ABI.
#[link(wasm_import_module = "crypto")]
extern "wasm" {
extern "C" {
#[link_name = "ecdsa_recover"]
pub(crate) fn crypto_ecdsa_recover(
input_ptr: u32,
Expand Down
40 changes: 20 additions & 20 deletions contract-sdk/src/abi/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ where
(ctx, request)
}

fn handle_result<R, E>(ctx: Context, result: Result<Option<R>, E>) -> HostRegion
fn handle_result<R, E>(ctx: Context, result: Result<Option<R>, E>) -> *const HostRegion
where
R: cbor::Encode,
E: error::Error,
Expand All @@ -116,7 +116,7 @@ where
Err(err) => err.to_execution_result(),
};

HostRegion::from_vec(cbor::to_vec(result))
Box::into_raw(Box::new(HostRegion::from_vec(cbor::to_vec(result))))
}

/// Internal helper for calling the contract's `instantiate` function.
Expand All @@ -126,7 +126,7 @@ pub fn instantiate<C: Contract>(
ctx_len: u32,
request_ptr: u32,
request_len: u32,
) -> HostRegion {
) -> *const HostRegion {
let (mut ctx, request) = load_request_context(ctx_ptr, ctx_len, request_ptr, request_len);
let result = C::instantiate(&mut ctx, request).map(Option::Some);
handle_result(ctx, result)
Expand All @@ -139,7 +139,7 @@ pub fn call<C: Contract>(
ctx_len: u32,
request_ptr: u32,
request_len: u32,
) -> HostRegion {
) -> *const HostRegion {
let (mut ctx, request) = load_request_context(ctx_ptr, ctx_len, request_ptr, request_len);
let result = C::call(&mut ctx, request).map(Option::Some);
handle_result(ctx, result)
Expand All @@ -152,7 +152,7 @@ pub fn handle_reply<C: Contract>(
ctx_len: u32,
request_ptr: u32,
request_len: u32,
) -> HostRegion {
) -> *const HostRegion {
let (mut ctx, reply) = load_request_context(ctx_ptr, ctx_len, request_ptr, request_len);
let result = C::handle_reply(&mut ctx, reply);
handle_result(ctx, result)
Expand All @@ -165,7 +165,7 @@ pub fn pre_upgrade<C: Contract>(
ctx_len: u32,
request_ptr: u32,
request_len: u32,
) -> HostRegion {
) -> *const HostRegion {
let (mut ctx, request) = load_request_context(ctx_ptr, ctx_len, request_ptr, request_len);
let result = C::pre_upgrade(&mut ctx, request).map(Option::Some);
handle_result(ctx, result)
Expand All @@ -178,7 +178,7 @@ pub fn post_upgrade<C: Contract>(
ctx_len: u32,
request_ptr: u32,
request_len: u32,
) -> HostRegion {
) -> *const HostRegion {
let (mut ctx, request) = load_request_context(ctx_ptr, ctx_len, request_ptr, request_len);
let result = C::post_upgrade(&mut ctx, request).map(Option::Some);
handle_result(ctx, result)
Expand All @@ -191,7 +191,7 @@ pub fn query<C: Contract>(
ctx_len: u32,
request_ptr: u32,
request_len: u32,
) -> HostRegion {
) -> *const HostRegion {
let (mut ctx, request) = load_request_context(ctx_ptr, ctx_len, request_ptr, request_len);
let result = C::query(&mut ctx, request).map(Option::Some);
handle_result(ctx, result)
Expand All @@ -202,62 +202,62 @@ pub fn query<C: Contract>(
macro_rules! create_contract {
($name:ty) => {
#[no_mangle]
pub extern "wasm" fn instantiate(
pub extern "C" fn instantiate(
ctx_ptr: u32,
ctx_len: u32,
request_ptr: u32,
request_len: u32,
) -> $crate::memory::HostRegion {
) -> *const $crate::memory::HostRegion {
$crate::abi::dispatch::instantiate::<$name>(ctx_ptr, ctx_len, request_ptr, request_len)
}

#[no_mangle]
pub extern "wasm" fn call(
pub extern "C" fn call(
ctx_ptr: u32,
ctx_len: u32,
request_ptr: u32,
request_len: u32,
) -> $crate::memory::HostRegion {
) -> *const $crate::memory::HostRegion {
$crate::abi::dispatch::call::<$name>(ctx_ptr, ctx_len, request_ptr, request_len)
}

#[no_mangle]
pub extern "wasm" fn handle_reply(
pub extern "C" fn handle_reply(
ctx_ptr: u32,
ctx_len: u32,
request_ptr: u32,
request_len: u32,
) -> $crate::memory::HostRegion {
) -> *const $crate::memory::HostRegion {
$crate::abi::dispatch::handle_reply::<$name>(ctx_ptr, ctx_len, request_ptr, request_len)
}

#[no_mangle]
pub extern "wasm" fn pre_upgrade(
pub extern "C" fn pre_upgrade(
ctx_ptr: u32,
ctx_len: u32,
request_ptr: u32,
request_len: u32,
) -> $crate::memory::HostRegion {
) -> *const $crate::memory::HostRegion {
$crate::abi::dispatch::pre_upgrade::<$name>(ctx_ptr, ctx_len, request_ptr, request_len)
}

#[no_mangle]
pub extern "wasm" fn post_upgrade(
pub extern "C" fn post_upgrade(
ctx_ptr: u32,
ctx_len: u32,
request_ptr: u32,
request_len: u32,
) -> $crate::memory::HostRegion {
) -> *const $crate::memory::HostRegion {
$crate::abi::dispatch::post_upgrade::<$name>(ctx_ptr, ctx_len, request_ptr, request_len)
}

#[no_mangle]
pub extern "wasm" fn query(
pub extern "C" fn query(
ctx_ptr: u32,
ctx_len: u32,
request_ptr: u32,
request_len: u32,
) -> $crate::memory::HostRegion {
) -> *const $crate::memory::HostRegion {
$crate::abi::dispatch::query::<$name>(ctx_ptr, ctx_len, request_ptr, request_len)
}
};
Expand Down
7 changes: 4 additions & 3 deletions contract-sdk/src/abi/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ use crate::{
};

#[link(wasm_import_module = "env")]
extern "wasm" {
extern "C" {
#[link_name = "query"]
fn env_query(query_ptr: u32, query_len: u32) -> HostRegion;
fn env_query(query_ptr: u32, query_len: u32) -> *const HostRegion;

#[link_name = "address_for_instance"]
fn env_address_for_instance(instance_id: u64, dst_ptr: u32, dst_len: u32);
Expand All @@ -26,7 +26,8 @@ extern "wasm" {
pub fn query(query: QueryRequest) -> QueryResponse {
let query_data = cbor::to_vec(query);
let query_region = HostRegionRef::from_slice(&query_data);
let rsp_region = unsafe { env_query(query_region.offset, query_region.length) };
let rsp_ptr = unsafe { env_query(query_region.offset, query_region.length) };
let rsp_region = unsafe { HostRegion::deref(rsp_ptr) };

// We expect the host to produce valid responses and abort otherwise.
cbor::from_slice(&rsp_region.into_vec()).unwrap()
Expand Down
4 changes: 2 additions & 2 deletions contract-sdk/src/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ pub mod env;
pub mod storage;

#[no_mangle]
pub extern "wasm" fn allocate(length: u32) -> u32 {
pub extern "C" fn allocate(length: u32) -> u32 {
memory::allocate_host(length)
}

#[no_mangle]
pub extern "wasm" fn deallocate(offset: u32, length: u32) {
pub extern "C" fn deallocate(offset: u32, length: u32) {
memory::deallocate_host(offset, length)
}
13 changes: 7 additions & 6 deletions contract-sdk/src/abi/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use crate::{
};

#[link(wasm_import_module = "storage")]
extern "wasm" {
extern "C" {
#[link_name = "get"]
fn storage_get(store: u32, key_ptr: u32, key_len: u32) -> HostRegion;
fn storage_get(store: u32, key_ptr: u32, key_len: u32) -> *const HostRegion;

#[link_name = "insert"]
fn storage_insert(store: u32, key_ptr: u32, key_len: u32, value_ptr: u32, value_len: u32);
Expand All @@ -20,13 +20,14 @@ extern "wasm" {
/// Fetches a given key from contract storage.
pub fn get(store: StoreKind, key: &[u8]) -> Option<Vec<u8>> {
let key_region = HostRegionRef::from_slice(key);
let value_region = unsafe { storage_get(store as u32, key_region.offset, key_region.length) };
// Special value of (0, 0) is treated as if the key doesn't exist.
if value_region.offset == 0 && value_region.length == 0 {
let rsp_ptr = unsafe { storage_get(store as u32, key_region.offset, key_region.length) };

// Special value of 0 is treated as if the key doesn't exist.
if rsp_ptr as u32 == 0 {
return None;
}

Some(value_region.into_vec())
Some(unsafe { HostRegion::deref(rsp_ptr) }.into_vec())
}

/// Inserts a given key/value pair into contract storage.
Expand Down
15 changes: 15 additions & 0 deletions contract-sdk/src/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,21 @@ impl HostRegion {

unsafe { Vec::from_raw_parts(ptr, self.length as usize, self.length as usize) }
}

/// Returns a new region by dereferencing a pointer to the region.
///
/// This does not yet transfer memory ownership from the host.
///
/// # Safety
///
/// This is safe as long as the pointer is a valid pointer to the region struct.
pub unsafe fn deref(arg: *const HostRegion) -> Self {
let hr = &*arg;
HostRegion {
offset: hr.offset,
length: hr.length,
}
}
}

/// Reference to a host region.
Expand Down
6 changes: 2 additions & 4 deletions runtime-sdk/modules/contracts/src/abi/oasis/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl<Cfg: Config> OasisV1<Cfg> {
let _ = instance.link_function(
"env",
"query",
|ctx, query: (u32, u32)| -> Result<(u32, u32), wasm3::Trap> {
|ctx, query: (u32, u32)| -> Result<u32, wasm3::Trap> {
// Make sure function was called in valid context.
let ec = ctx.context.ok_or(wasm3::Trap::Abort)?;

Expand All @@ -51,9 +51,7 @@ impl<Cfg: Config> OasisV1<Cfg> {
//
// This makes sure that the call context is unset to avoid any potential issues
// with reentrancy as attempting to re-enter one of the linked function will fail.
let result_region = Self::serialize_and_allocate(ctx.instance, result)?;

Ok(result_region.to_arg())
Self::serialize_and_allocate_as_ptr(ctx.instance, result).map_err(|err| err.into())
},
);

Expand Down
99 changes: 98 additions & 1 deletion runtime-sdk/modules/contracts/src/abi/oasis/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,28 @@ impl Region {
}
}

/// Dereferences a pointer to the region.
pub fn deref(memory: &wasm3::Memory<'_>, arg: u32) -> Result<Self, RegionError> {
let arg = arg as usize;

// Make sure the pointer is within WASM memory.
if arg + 8 > memory.size() {
return Err(RegionError::BadPointer);
}

// WASM uses little-endian encoding.
let dst = memory.as_slice();
let offset = u32::from_le_bytes(dst[arg..arg + 4].try_into().unwrap()) as usize;
let length = u32::from_le_bytes(dst[arg + 4..arg + 8].try_into().unwrap()) as usize;

// Ensure that the dereferenced region fits in WASM memory.
if offset + length > memory.size() {
return Err(RegionError::BadPointer);
}

Ok(Region { offset, length })
}

/// Copies slice content into a previously allocated WASM memory region.
pub fn copy_from_slice(
&self,
Expand Down Expand Up @@ -116,7 +138,19 @@ impl<Cfg: Config> OasisV1<Cfg> {
offset: offset as usize,
length: length as usize,
};
// TODO: Validate region early.

// Validate returned region.
instance
.runtime()
.try_with_memory(|memory| -> Result<(), RegionError> {
// Make sure the region fits in WASM memory.
if (region.offset + region.length) > memory.size() {
return Err(RegionError::BadPointer);
}
Ok(())
})
.unwrap()?;

Ok(region)
}

Expand Down Expand Up @@ -152,4 +186,67 @@ impl<Cfg: Config> OasisV1<Cfg> {
let data = cbor::to_vec(data);
Self::allocate_and_copy(instance, &data)
}

/// Serializes the given type into CBOR, allocates a chunk of memory inside the WASM instance
/// and copies the region and serialized data into it. Returns a pointer to the serialized region.
///
/// This method is useful when you need to a pointer to the region of the serialized data,
/// since it avoids an additional allocation for the region itself as it pre-allocates it with the data.
/// This is an optimized version of calling `serialize_and_allocate` followed by `allocate_region`
/// which does two separate allocations.
pub fn serialize_and_allocate_as_ptr<C, T>(
instance: &wasm3::Instance<'_, '_, ExecutionContext<'_, C>>,
data: T,
) -> Result<u32, RegionError>
where
C: Context,
T: cbor::Encode,
{
let data = cbor::to_vec(data);
// Allocate enough for the data and the serialized region.
let outer = Self::allocate(instance, data.len() + 8)?;
// First 8 bytes are reserved for the region itself. Inner is the region
// for the actual data.
let inner = Region {
offset: outer.offset + 8,
length: outer.length - 8,
};

instance
.runtime()
.try_with_memory(|mut memory| -> Result<(), RegionError> {
inner.copy_from_slice(&mut memory, &data)?;

let dst = &mut memory.as_slice_mut()[outer.offset..outer.offset + 8];
dst[..4].copy_from_slice(&(inner.offset as u32).to_le_bytes());
dst[4..].copy_from_slice(&(inner.length as u32).to_le_bytes());

Ok(())
})
.unwrap()?;

Ok(outer.offset as u32)
}

/// Allocates a region in the memory and returns a pointer to it.
pub fn allocate_region<C: Context>(
instance: &wasm3::Instance<'_, '_, ExecutionContext<'_, C>>,
region: Region,
) -> Result<u32, RegionError> {
let mut data = [0u8; 8];
data[..4].copy_from_slice(&(region.offset as u32).to_le_bytes());
data[4..].copy_from_slice(&(region.length as u32).to_le_bytes());

// Allocate memory for the destination buffer.
let dst = Self::allocate(instance, data.len())?;
instance
.runtime()
.try_with_memory(|mut memory| -> Result<(), RegionError> {
dst.copy_from_slice(&mut memory, &data)?;
Ok(())
})
.unwrap()?;

Ok(dst.offset as u32)
}
}
Loading

0 comments on commit 7896fb2

Please sign in to comment.