#[cfg(target_arch = "wasm32")]
pub(crate) use wasm::*;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use native::*;
#[cfg(target_arch = "wasm32")]
pub(crate) mod abi {
use std::alloc::{Layout, alloc, dealloc};
use serde::Serialize;
use serde::de::DeserializeOwned;
use crate::{Error, Result};
#[unsafe(no_mangle)]
pub extern "C" fn __cotyledon_alloc(len: i32) -> i32 {
if len <= 0 {
return 0;
}
unsafe { alloc(Layout::from_size_align_unchecked(len as usize, 1)) as i32 }
}
#[unsafe(no_mangle)]
pub extern "C" fn __cotyledon_dealloc(ptr: i32, len: i32) {
if ptr == 0 || len <= 0 {
return;
}
unsafe { dealloc(ptr as *mut u8, Layout::from_size_align_unchecked(len as usize, 1)) }
}
pub fn pack(ptr: i32, len: i32) -> i64 {
(((ptr as u32 as u64) << 32) | (len as u32 as u64)) as i64
}
pub fn unpack(packed: i64) -> (i32, i32) {
let p = packed as u64;
((p >> 32) as i32, (p & 0xffff_ffff) as i32)
}
pub fn read_owned(ptr: i32, len: i32) -> Vec<u8> {
if ptr == 0 || len <= 0 {
return Vec::new();
}
let owned = unsafe { std::slice::from_raw_parts(ptr as *const u8, len as usize) }.to_vec();
__cotyledon_dealloc(ptr, len);
owned
}
pub fn decode<T: DeserializeOwned>(bytes: &[u8]) -> Result<T> {
let env: serde_json::Value =
serde_json::from_slice(bytes).map_err(|e| Error::Codec(e.to_string()))?;
if env.get("ok").and_then(|v| v.as_bool()).unwrap_or(false) {
let value = env.get("value").cloned().unwrap_or(serde_json::Value::Null);
serde_json::from_value(value).map_err(|e| Error::Codec(e.to_string()))
} else {
let err = env.get("error");
let kind = err
.and_then(|e| e.get("kind"))
.and_then(|v| v.as_str())
.unwrap_or("Host");
let message = err
.and_then(|e| e.get("message"))
.and_then(|v| v.as_str())
.unwrap_or("unknown host error")
.to_owned();
Err(match kind {
"Denied" => Error::Denied(message),
"Codec" => Error::Codec(message),
_ => Error::Host(message),
})
}
}
pub unsafe fn call<R: DeserializeOwned>(
import: unsafe extern "C" fn(i32, i32) -> i64,
req: impl Serialize,
) -> Result<R> {
let bytes = serde_json::to_vec(&req).map_err(|e| Error::Codec(e.to_string()))?;
let packed = unsafe { import(bytes.as_ptr() as i32, bytes.len() as i32) };
let (ptr, len) = unpack(packed);
decode(&read_owned(ptr, len))
}
pub unsafe fn send(import: unsafe extern "C" fn(i32, i32), req: impl Serialize) {
let Ok(bytes) = serde_json::to_vec(&req) else {
return;
};
unsafe { import(bytes.as_ptr() as i32, bytes.len() as i32) };
}
}
#[cfg(target_arch = "wasm32")]
mod imports {
#[link(wasm_import_module = "cotyledon")]
unsafe extern "C" {
pub fn host_config(ptr: i32, len: i32) -> i64;
pub fn host_price(ptr: i32, len: i32) -> i64;
pub fn host_wallet_address(ptr: i32, len: i32) -> i64;
pub fn host_wallet_balance(ptr: i32, len: i32) -> i64;
pub fn host_sign(ptr: i32, len: i32) -> i64;
pub fn host_submit_tx(ptr: i32, len: i32) -> i64;
pub fn host_state_get(ptr: i32, len: i32) -> i64;
pub fn host_state_set(ptr: i32, len: i32) -> i64;
pub fn host_emit(ptr: i32, len: i32);
pub fn host_metric(ptr: i32, len: i32);
}
}
#[cfg(target_arch = "wasm32")]
mod wasm {
use base64::Engine as _;
use base64::prelude::BASE64_STANDARD;
use serde_json::json;
use super::{abi, imports};
use crate::{Error, Result};
fn b64(bytes: &[u8]) -> String {
BASE64_STANDARD.encode(bytes)
}
fn unb64(s: &str) -> Result<Vec<u8>> {
BASE64_STANDARD.decode(s).map_err(|e| Error::Codec(e.to_string()))
}
pub(crate) fn config_raw() -> Vec<u8> {
let packed = unsafe { imports::host_config(0, 0) };
let (ptr, len) = abi::unpack(packed);
abi::read_owned(ptr, len)
}
pub(crate) async fn price(token: &str) -> Result<f64> {
unsafe { abi::call(imports::host_price, json!({ "token": token })) }
}
pub(crate) async fn wallet_address() -> Result<String> {
unsafe { abi::call(imports::host_wallet_address, json!({})) }
}
pub(crate) async fn wallet_balance(token: &str) -> Result<f64> {
unsafe { abi::call(imports::host_wallet_balance, json!({ "token": token })) }
}
pub(crate) async fn sign(payload: &[u8]) -> Result<Vec<u8>> {
let sig: String =
unsafe { abi::call(imports::host_sign, json!({ "payload": b64(payload) }))? };
unb64(&sig)
}
pub(crate) async fn submit_tx(signed_tx: &[u8]) -> Result<String> {
unsafe { abi::call(imports::host_submit_tx, json!({ "signed_tx": b64(signed_tx) })) }
}
pub(crate) async fn state_get(key: &str) -> Result<Option<Vec<u8>>> {
let v: Option<String> =
unsafe { abi::call(imports::host_state_get, json!({ "key": key }))? };
match v {
Some(b) => Ok(Some(unb64(&b)?)),
None => Ok(None),
}
}
pub(crate) async fn state_set(key: &str, value: &[u8]) -> Result<()> {
unsafe {
abi::call(
imports::host_state_set,
json!({ "key": key, "value": b64(value) }),
)
}
}
pub(crate) fn emit(event_json: &[u8]) {
unsafe { imports::host_emit(event_json.as_ptr() as i32, event_json.len() as i32) };
}
pub(crate) fn metric(name: &str, value: f64) {
unsafe { abi::send(imports::host_metric, json!({ "name": name, "value": value })) };
}
}
#[cfg(not(target_arch = "wasm32"))]
mod native {
use crate::Result;
pub(crate) fn config_raw() -> Vec<u8> {
todo!("host ABI: config")
}
pub(crate) async fn price(_token: &str) -> Result<f64> {
todo!("host ABI: price")
}
pub(crate) async fn wallet_address() -> Result<String> {
todo!("host ABI: wallet_address")
}
pub(crate) async fn wallet_balance(_token: &str) -> Result<f64> {
todo!("host ABI: wallet_balance")
}
pub(crate) async fn sign(_payload: &[u8]) -> Result<Vec<u8>> {
todo!("host ABI: sign")
}
pub(crate) async fn submit_tx(_signed_tx: &[u8]) -> Result<String> {
todo!("host ABI: submit_tx")
}
pub(crate) async fn state_get(_key: &str) -> Result<Option<Vec<u8>>> {
todo!("host ABI: state_get")
}
pub(crate) async fn state_set(_key: &str, _value: &[u8]) -> Result<()> {
todo!("host ABI: state_set")
}
pub(crate) fn emit(_event_json: &[u8]) {
todo!("host ABI: emit")
}
pub(crate) fn metric(_name: &str, _value: f64) {
todo!("host ABI: metric")
}
}