extern crate alloc;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use core::cell::UnsafeCell;
use core::{ptr, slice};
use crate::ConvertibleContract;
#[repr(i32)]
pub enum ErrorCode {
Ok = 0,
InvalidInput = 1,
OperationError = 2,
DriverNotInitialized = 3,
BufferTooSmall = 4,
}
static mut CONTRACT_DRIVER: Option<&'static dyn ConvertibleContract> = None;
struct LastError {
value: Option<String>,
}
struct GlobalLastError(UnsafeCell<LastError>);
unsafe impl Sync for GlobalLastError {}
static LAST_ERROR: GlobalLastError =
GlobalLastError(UnsafeCell::new(LastError { value: None }));
fn set_last_error(err: String) {
unsafe {
(*LAST_ERROR.0.get()).value = Some(err);
}
}
fn take_last_error() -> Option<String> {
unsafe { (*LAST_ERROR.0.get()).value.take() }
}
unsafe fn write_to_wasm_buffer(
data: &[u8],
out_ptr: *mut u8,
out_buf_size: usize,
) -> Result<(), ErrorCode> {
let len =
u32::try_from(data.len()).map_err(|_| ErrorCode::BufferTooSmall)?;
if data.len() + 4 > out_buf_size {
return Err(ErrorCode::BufferTooSmall);
}
let len_bytes = len.to_le_bytes();
unsafe {
ptr::copy_nonoverlapping(len_bytes.as_ptr(), out_ptr, 4);
ptr::copy_nonoverlapping(data.as_ptr(), out_ptr.add(4), data.len());
}
Ok(())
}
unsafe fn run_wasm_export<F>(
out_ptr: *mut u8,
out_buf_size: usize,
f: F,
) -> ErrorCode
where
F: FnOnce(&dyn ConvertibleContract) -> Result<Vec<u8>, String>,
{
let Some(driver) = (unsafe { CONTRACT_DRIVER }) else {
set_last_error("Contract driver not initialized".into());
return ErrorCode::DriverNotInitialized;
};
match f(driver) {
Ok(data) => {
match unsafe { write_to_wasm_buffer(&data, out_ptr, out_buf_size) }
{
Ok(()) => ErrorCode::Ok,
Err(e) => {
set_last_error("Output buffer too small".into());
e
}
}
}
Err(msg) => {
set_last_error(msg);
ErrorCode::OperationError
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn get_last_error(
out_ptr: *mut u8,
out_buf_size: usize,
) -> ErrorCode {
let err = take_last_error().unwrap_or_default();
match unsafe { write_to_wasm_buffer(err.as_bytes(), out_ptr, out_buf_size) }
{
Ok(()) => ErrorCode::Ok,
Err(e) => e,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn encode_input_fn(
fn_name_ptr: *mut u8,
fn_name_size: usize,
json_ptr: *mut u8,
json_size: usize,
out_ptr: *mut u8,
out_buf_size: usize,
) -> ErrorCode {
unsafe {
run_wasm_export(out_ptr, out_buf_size, |driver| {
let fn_name_slice =
core::slice::from_raw_parts(fn_name_ptr, fn_name_size);
let fn_name = core::str::from_utf8(fn_name_slice)
.map_err(|e| format!("Invalid fn_name UTF-8: {e}"))?;
let json_slice = core::slice::from_raw_parts(json_ptr, json_size);
let json = core::str::from_utf8(json_slice)
.map_err(|e| format!("Invalid json UTF-8: {e}"))?;
driver
.encode_input_fn(fn_name, json)
.map_err(|e| format!("{e:?}"))
})
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn decode_input_fn(
fn_name_ptr: *mut u8,
fn_name_size: usize,
rkyv_ptr: *const u8,
rkyv_len: usize,
out_ptr: *mut u8,
out_buf_size: usize,
) -> ErrorCode {
unsafe {
run_wasm_export(out_ptr, out_buf_size, |driver| {
let fn_name_slice =
core::slice::from_raw_parts(fn_name_ptr, fn_name_size);
let fn_name = core::str::from_utf8(fn_name_slice)
.map_err(|e| format!("Invalid fn_name UTF-8: {e}"))?;
let rkyv = slice::from_raw_parts(rkyv_ptr, rkyv_len);
driver
.decode_input_fn(fn_name, rkyv)
.map(|v| v.to_string().into_bytes())
.map_err(|e| format!("{e:?}"))
})
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn decode_output_fn(
fn_name_ptr: *mut u8,
fn_name_size: usize,
rkyv_ptr: *const u8,
rkyv_len: usize,
out_ptr: *mut u8,
out_buf_size: usize,
) -> ErrorCode {
unsafe {
run_wasm_export(out_ptr, out_buf_size, |driver| {
let fn_name_slice =
core::slice::from_raw_parts(fn_name_ptr, fn_name_size);
let fn_name = core::str::from_utf8(fn_name_slice)
.map_err(|e| format!("Invalid fn_name UTF-8: {e}"))?;
let rkyv = slice::from_raw_parts(rkyv_ptr, rkyv_len);
driver
.decode_output_fn(fn_name, rkyv)
.map(|v| v.to_string().into_bytes())
.map_err(|e| format!("{e:?}"))
})
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn decode_event(
event_name_ptr: *mut u8,
event_name_size: usize,
rkyv_ptr: *const u8,
rkyv_len: usize,
out_ptr: *mut u8,
out_buf_size: usize,
) -> ErrorCode {
unsafe {
run_wasm_export(out_ptr, out_buf_size, |driver| {
let event_name_slice =
core::slice::from_raw_parts(event_name_ptr, event_name_size);
let event_name = core::str::from_utf8(event_name_slice)
.map_err(|e| format!("Invalid event_name UTF-8: {e}"))?;
let rkyv = slice::from_raw_parts(rkyv_ptr, rkyv_len);
driver
.decode_event(event_name, rkyv)
.map(|v| v.to_string().into_bytes())
.map_err(|e| format!("{e:?}"))
})
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn get_schema(
out_ptr: *mut u8,
out_buf_size: usize,
) -> ErrorCode {
unsafe {
run_wasm_export(out_ptr, out_buf_size, |driver| {
Ok(driver.get_schema().into_bytes())
})
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn get_version(
out_ptr: *mut u8,
out_buf_size: usize,
) -> ErrorCode {
unsafe {
run_wasm_export(out_ptr, out_buf_size, |driver| {
Ok(driver.get_version().to_string().into_bytes())
})
}
}
#[macro_export]
macro_rules! generate_wasm_entrypoint {
($driver_type:ty) => {
static mut INSTANCE: Option<$driver_type> = None;
#[unsafe(no_mangle)]
#[doc = "Initializes and registers the contract driver.\n\n\
# Safety\n\
Must be called exactly once at module startup before using any FFI functions."]
pub unsafe extern "C" fn init() {
unsafe {
INSTANCE = Some(<$driver_type>::default());
$crate::wasm::init_contract_driver(INSTANCE.as_ref().unwrap());
}
}
};
}
pub unsafe fn init_contract_driver(
driver: &'static dyn ConvertibleContract,
) -> ErrorCode {
unsafe { CONTRACT_DRIVER = Some(driver) };
ErrorCode::Ok
}