mod allocator;
#[doc(hidden)] pub mod ethabi;
#[doc(hidden)] pub mod internal;
pub mod oracle;
pub mod prelude;
#[doc(hidden)] pub mod sync;
#[doc(hidden)] pub mod syntax;
pub mod upc;
use std::alloc;
use allocator::Talck;
use lyquor_primitives::{Cipher, ConsoleSink, LyteLog, StateCategory};
use talc::{base::Talc, source::Manual};
use super::http;
use super::{
CallContext, INSTANCE_MEMSIZE_IN_MB, LYTEMEM_BASE, LyquidResult, NETWORK_MEMSIZE_IN_MB, VOLATILE_MEMSIZE_IN_MB,
};
use internal::StateAccessor;
use prelude::*;
#[cfg(all(target_arch = "wasm32", target_pointer_width = "32"))]
pub type NativeGuestAbi = crate::mem::Wasm32;
#[cfg(all(target_arch = "wasm64", target_pointer_width = "64"))]
pub type NativeGuestAbi = crate::mem::Wasm64;
pub type GuestUsize = <NativeGuestAbi as crate::mem::Guest>::Usize;
pub type GuestIsize = <NativeGuestAbi as crate::mem::Guest>::Isize;
pub type GuestSlice = crate::mem::Slice<NativeGuestAbi>;
const VOLATILE_SEGMENT_SIZE: usize = VOLATILE_MEMSIZE_IN_MB << 20;
const NETWORK_SEGMENT_SIZE: usize = NETWORK_MEMSIZE_IN_MB << 20;
const INSTANCE_SEGMENT_SIZE: usize = INSTANCE_MEMSIZE_IN_MB << 20;
const VOLATILE_HEADER_SIZE: usize = core::mem::size_of::<VolatileSegmentHeader>();
const NETWORK_HEADER_SIZE: usize = core::mem::size_of::<NetworkSegmentHeader>();
const INSTANCE_HEADER_SIZE: usize = core::mem::size_of::<InstanceSegmentHeader>();
const VOLATILE_HEAP_BASE: usize = LYTEMEM_BASE - VOLATILE_SEGMENT_SIZE;
const VOLATILE_HEADER_BASE: usize = LYTEMEM_BASE - VOLATILE_HEADER_SIZE;
const NETWORK_HEADER_BASE: usize = LYTEMEM_BASE;
const NETWORK_HEAP_BASE: usize = NETWORK_HEADER_BASE + NETWORK_HEADER_SIZE;
const INSTANCE_HEADER_BASE: usize = LYTEMEM_BASE + NETWORK_SEGMENT_SIZE;
const INSTANCE_HEAP_BASE: usize = INSTANCE_HEADER_BASE + INSTANCE_HEADER_SIZE;
#[repr(C)]
struct VolatileSegmentHeader {
category: u8,
allocator: Talck,
}
#[repr(C)]
struct NetworkSegmentHeader {
allocator: Talck,
initialized: bool,
}
#[repr(C)]
struct InstanceSegmentHeader {
allocator: Talck,
}
#[inline(always)]
fn volatile_segment_header() -> &'static mut VolatileSegmentHeader {
unsafe { &mut *(VOLATILE_HEADER_BASE as *mut VolatileSegmentHeader) }
}
#[inline(always)]
fn network_segment_header() -> &'static mut NetworkSegmentHeader {
unsafe { &mut *(NETWORK_HEADER_BASE as *mut NetworkSegmentHeader) }
}
#[inline(always)]
fn instance_segment_header() -> &'static mut InstanceSegmentHeader {
unsafe { &mut *(INSTANCE_HEADER_BASE as *mut InstanceSegmentHeader) }
}
#[inline(always)]
pub unsafe fn set_allocator_category(category: u8) {
volatile_segment_header().category = category;
}
#[derive(Clone, Default)]
struct MuxAlloc;
#[global_allocator]
static ALLOCATOR: MuxAlloc = MuxAlloc;
impl MuxAlloc {
#[inline(always)]
unsafe fn zero_memory(ptr: *mut u8, layout: alloc::Layout) {
unsafe {
ptr.write_bytes(0, layout.size());
std::hint::black_box(ptr);
}
}
}
unsafe impl alloc::GlobalAlloc for MuxAlloc {
unsafe fn alloc(&self, layout: alloc::Layout) -> *mut u8 {
let header = volatile_segment_header();
unsafe {
match header.category {
0x1 => &instance_segment_header().allocator,
0x2 => &network_segment_header().allocator,
_ => &volatile_segment_header().allocator,
}
.alloc(layout)
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: alloc::Layout) {
let header = volatile_segment_header();
unsafe {
Self::zero_memory(ptr, layout);
match header.category {
0x1 => &instance_segment_header().allocator,
0x2 => &network_segment_header().allocator,
_ => &volatile_segment_header().allocator,
}
.dealloc(ptr, layout)
}
}
}
#[unsafe(no_mangle)]
fn __lyquid_initialize(category: u32) -> u32 {
initialize_volatile_heap(category as u8);
match initialize_persistent_heap() {
Some(init) => 0x10 | init,
None => 0,
}
}
#[unsafe(no_mangle)]
fn __lyquid_nuke_state() {
let network_header = network_segment_header();
network_header.initialized = false;
}
#[unsafe(no_mangle)]
fn __lyquid_volatile_alloc(size: GuestUsize, align: GuestUsize) -> GuestUsize {
use alloc::GlobalAlloc;
let allocator = &volatile_segment_header().allocator;
unsafe { allocator.alloc(alloc::Layout::from_size_align(size as usize, align as usize).unwrap()) as GuestUsize }
}
#[unsafe(no_mangle)]
fn __lyquid_volatile_dealloc(base: GuestUsize, size: GuestUsize, align: GuestUsize) {
use alloc::GlobalAlloc;
let allocator = &volatile_segment_header().allocator;
unsafe {
allocator.dealloc(
base as *mut u8,
alloc::Layout::from_size_align(size as usize, align as usize).unwrap(),
)
}
}
#[unsafe(no_mangle)]
static FORCE_SHARED_MEMORY: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
#[inline(always)]
fn initialize_volatile_heap(category: u8) {
let volatile_header = volatile_segment_header();
volatile_header.category = category;
volatile_header.allocator = Talck::new(Talc::new(Manual));
unsafe {
let _ = volatile_header.allocator.lock().claim(
VOLATILE_HEAP_BASE as *mut u8,
VOLATILE_SEGMENT_SIZE - VOLATILE_HEADER_SIZE,
);
}
}
#[inline(always)]
fn initialize_persistent_heap() -> Option<u32> {
let network_header = network_segment_header();
let instance_header = instance_segment_header();
let ret = if network_header.initialized {
0
} else {
let network_size = NETWORK_SEGMENT_SIZE - NETWORK_HEADER_SIZE;
let instance_size = INSTANCE_SEGMENT_SIZE - INSTANCE_HEADER_SIZE - 1;
network_header.allocator = Talck::new(Talc::new(Manual));
instance_header.allocator = Talck::new(Talc::new(Manual));
unsafe {
network_header
.allocator
.lock()
.claim(NETWORK_HEAP_BASE as *mut u8, network_size)?;
instance_header
.allocator
.lock()
.claim(INSTANCE_HEAP_BASE as *mut u8, instance_size)?;
}
network_header.initialized = true;
1
};
Some(ret)
}
macro_rules! host_api {
($fn:ident($($param:ident: $type:ty),*) -> $rt:ty; $($rest:tt)*) => {
pub fn $fn($($param:$type),*) -> Result<$rt, $crate::LyquidError> {
use $crate::prelude::decode_object;
let output = {
#[link(wasm_import_module = "lyquor_api")]
unsafe extern "C" {
fn $fn(
base: $crate::runtime::GuestUsize,
len: $crate::runtime::GuestUsize,
) -> $crate::runtime::GuestUsize;
}
let raw = $crate::prelude::encode_by_fields!($($param: $type),*);
let output_block = unsafe {
$fn(raw.as_ptr() as $crate::runtime::GuestUsize, raw.len() as $crate::runtime::GuestUsize)
};
let host_output = unsafe { $crate::runtime::internal::HostOutput::read(output_block)? };
let output_raw = unsafe { host_output.as_slice() };
decode_object::<Result<$rt, $crate::LyquidError>>(output_raw).ok_or($crate::LyquidError::LyquorOutput)
};
output?
}
host_api!($($rest)*);
};
($fn:ident($($param:ident: $type:ty),*); $($rest:tt)*) => {
host_api!($fn($($param:$type),*) -> (); $($rest)*);
};
() => {};
}
pub mod lyquor_api {
use super::*;
pub use crate::well_known;
host_api!(
state_set(cat: StateCategory, key: Vec<u8>, value: Option<Vec<u8>>);
state_get(cat: StateCategory, key: Vec<u8>) -> Option<Vec<u8>>;
version() -> LyquidNumber;
chain_pos() -> ChainPos;
log(record: LyteLog);
console_output(output: ConsoleSink, s: String);
universal_procedural_call(
target: LyquidID,
group: Option<String>,
method: String,
input: Vec<u8>,
client_params: Option<Bytes>,
timeout_ms: Option<u64>
) -> Vec<u8>;
inter_lyquid_call(target: LyquidID, method: String, input: Vec<u8>) -> Vec<u8>;
submit_call(params: lyquor_primitives::CallParams, signed: bool) -> Vec<u8>;
sign(msg: Bytes, cipher: Cipher) -> Bytes;
verify(msg: Bytes, cipher: Cipher, sig: Bytes, pubkey: Bytes) -> bool;
random_bytes(length: usize) -> Vec<u8>;
systime() -> u64;
http_request(request: http::Request, options: Option<http::RequestOptions>) -> http::Response;
get_ed25519_qxy(pubkey: [u8; 32]) -> (U256, U256);
get_address_by_ed25519(pubkey: [u8; 32]) -> Option<Address>;
get_ed25519_by_address(address: Address) -> Option<NodeID>;
eth_contract() -> Option<Address>;
sequence_backend_id() -> lyquor_primitives::SequenceBackendID;
fetch_oracle_info(topic: String, target: lyquor_primitives::oracle::OracleTarget, full_config: bool) -> Option<lyquor_primitives::oracle::OracleEpochInfo>;
trigger(group: String, method: String, input: Vec<u8>, mode: lyquor_primitives::TriggerMode);
);
}
#[macro_export]
macro_rules! print {
($($arg:tt)*) => {
lyquor_api::console_output($crate::lyquor_primitives::ConsoleSink::StdOut, format!($($arg)*)).unwrap();
};
}
#[macro_export]
macro_rules! println {
($($arg:tt)*) => {
lyquor_api::console_output($crate::lyquor_primitives::ConsoleSink::StdOut, format!($($arg)*) + "\n").unwrap();
};
}
#[macro_export]
macro_rules! eprint {
($($arg:tt)*) => {
lyquor_api::console_output($crate::lyquor_primitives::ConsoleSink::StdErr, format!($($arg)*)).unwrap();
};
}
#[macro_export]
macro_rules! eprintln {
($($arg:tt)*) => {
lyquor_api::console_output($crate::lyquor_primitives::ConsoleSink::StdErr, format!($($arg)*) + "\n").unwrap();
};
}
#[macro_export]
macro_rules! log {
($tag: ident, $v: expr) => {{
lyquor_api::log($crate::lyquor_primitives::LyteLog::new_from_tagged_value(
stringify!($tag),
$v,
))?
}};
}
#[macro_export]
macro_rules! call {
(($service: expr).$method :ident($($var:ident: $type:ty = $val: expr),*) -> ($($ovar:ident: $otype:ty),*)) => {{
let returned = match lyquor_api::inter_lyquid_call(
$service,
stringify!($method).to_string().into(),
Vec::from(&$crate::prelude::encode_by_fields!($($var: $type = $val),*)[..]),
) {
Ok(returned) => returned,
Err(err) => $crate::runtime::internal::abort_atomic_inter_call(err),
};
match $crate::prelude::decode_by_fields!(&returned, $($ovar: $otype),*) {
Some(decoded) => decoded,
None => $crate::runtime::internal::abort_atomic_inter_call($crate::LyquidError::LyquorOutput),
}
}};
}
#[macro_export]
macro_rules! submit_certified_call {
($cert:expr) => {{
lyquor_api::submit_call($cert, false)
}};
($cert:expr, $signed:expr) => {{ lyquor_api::submit_call($cert, $signed) }};
}
#[macro_export]
macro_rules! trigger {
(($($group:ident)::*) $method:ident($($param:ident: $type:ty $(= $default:expr)?),*), $mode:expr) => {
$crate::runtime::lyquor_api::trigger(
$crate::__lyquid_group_string!($($group)::+).to_string(),
stringify!($method).to_string(),
$crate::prelude::encode_by_fields!($($param: $type $(= $default)?),*),
$mode,
).map_err(|e| $crate::LyquidError::LyquidRuntime(format!("Failed to trigger {} :{e:?}.", stringify!($method))))?
};
($method:ident($($param:ident: $type:ty $(= $default:expr)?),*), $mode:expr) => {
$crate::runtime::lyquor_api::trigger(
$crate::lyquor_primitives::GROUP_DEFAULT.to_string(),
stringify!($method).to_string(),
$crate::prelude::encode_by_fields!($($param: $type $(= $default)?),*),
$mode,
).map_err(|e| $crate::LyquidError::LyquidRuntime(format!("Failed to trigger {} :{e:?}.", stringify!($method).to_string())))?
}
}
#[macro_export]
macro_rules! upc {
(($network: expr).$method: ident($($var:ident: $type:ty = $val: expr),*) -> ($($ovar:ident: $otype:ty),*)) => {
lyquor_api::universal_procedural_call(
$network,
None, // TODO: allow user to specify group with upc macro
stringify!($method).to_string().into(),
Vec::from(&$crate::prelude::encode_by_fields!($($var: $type = $val),*)[..]),
None,
None,
).and_then(|r| $crate::prelude::decode_by_fields!(&r, $($ovar: $otype),*).ok_or(LyquidError::LyquorOutput))
};
(($network: expr).$method: ident[$($params:ident: $params_type:ty = $params_val: expr),*]($($var:ident: $type:ty = $val: expr),*) -> ($($ovar:ident: $otype:ty),*)) => {
lyquor_api::universal_procedural_call(
$network,
None, stringify!($method).to_string().into(),
Vec::from(&$crate::prelude::encode_by_fields!($($var: $type = $val),*)[..]),
Some($crate::prelude::encode_by_fields!($($params: $params_type = $params_val),*).into()),
None,
).and_then(|r| $crate::prelude::decode_by_fields!(&r, $($ovar: $otype),*).ok_or(LyquidError::LyquorOutput))
};
}
pub struct Immutable<T>(T);
impl<T> Immutable<T> {
pub fn new(inner: T) -> Self {
Self(inner)
}
}
impl<T> std::ops::Deref for Immutable<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
pub struct Mutable<T>(T);
impl<T> Mutable<T> {
pub fn new(inner: T) -> Self {
Self(inner)
}
}
impl<T> std::ops::Deref for Mutable<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> std::ops::DerefMut for Mutable<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
pub struct NetworkContextImpl<S>
where
S: StateAccessor,
{
pub lyquid_id: LyquidID,
pub origin: Address,
pub caller: Address,
pub input: Bytes,
pub network: Mutable<S>,
}
impl<S> NetworkContextImpl<S>
where
S: StateAccessor,
{
pub fn new(ctx: CallContext) -> LyquidResult<Self> {
Ok(Self {
lyquid_id: ctx.lyquid_id,
origin: ctx.origin,
caller: ctx.caller,
input: ctx.input,
network: Mutable::new(S::new()?),
})
}
}
pub struct CertifiedContextImpl<S>
where
S: StateAccessor,
{
pub lyquid_id: LyquidID,
pub origin: Address,
pub caller: Address,
pub input: Bytes,
pub network: Mutable<S>,
topic: &'static str,
pub cert: oracle::OracleCert,
}
impl<S> CertifiedContextImpl<S>
where
S: StateAccessor,
{
pub fn new(ctx: CallContext, cert: oracle::OracleCert, topic: &'static str) -> LyquidResult<Self> {
Ok(Self {
lyquid_id: ctx.lyquid_id,
origin: ctx.origin,
caller: ctx.caller,
input: ctx.input,
network: Mutable::new(S::new()?),
topic,
cert,
})
}
}
impl<S> CertifiedContextImpl<S>
where
S: StateAccessor,
{
pub fn signer_node_id(&mut self, id: u64) -> Option<NodeID> {
let sid: oracle::SignerID = id.try_into().ok()?;
internal::builtin_network_state()
.oracle_dest(self.topic)
.signer_node_id(sid)
}
#[doc(hidden)]
pub fn __lyquid_verify_oracle_finalize_cert(&mut self, params: &CallParams) -> bool {
internal::builtin_network_state()
.oracle_src(self.topic)
.is_some_and(|oracle| oracle.verify_finalize_cert(self.lyquid_id, params, &self.cert))
}
#[doc(hidden)]
pub fn __lyquid_verify_oracle_dest_cert(&mut self, params: CallParams) -> bool {
internal::builtin_network_state()
.oracle_dest(self.topic)
.verify(self.lyquid_id, params, &self.cert)
}
}
pub struct ImmutableNetworkContextImpl<S>
where
S: StateAccessor,
{
pub lyquid_id: LyquidID,
pub origin: Address,
pub caller: Address,
pub input: Bytes,
pub network: Immutable<S>,
}
impl<S> ImmutableNetworkContextImpl<S>
where
S: StateAccessor,
{
pub fn new(ctx: CallContext) -> LyquidResult<Self> {
Ok(Self {
lyquid_id: ctx.lyquid_id,
origin: ctx.origin,
caller: ctx.caller,
input: ctx.input,
network: Immutable::new(S::new()?),
})
}
}
pub struct InstanceContextImpl<S, I>
where
S: StateAccessor,
I: StateAccessor,
{
pub lyquid_id: LyquidID,
pub node_id: NodeID,
pub origin: Address,
pub caller: Address,
pub input: Bytes,
pub network: Immutable<S>,
pub instance: Mutable<I>,
}
impl<S, I> InstanceContextImpl<S, I>
where
S: StateAccessor,
I: StateAccessor,
{
pub fn new(ctx: CallContext) -> LyquidResult<Self> {
Ok(Self {
lyquid_id: ctx.lyquid_id,
node_id: ctx.node_id.unwrap(), origin: ctx.origin,
caller: ctx.caller,
input: ctx.input,
network: Immutable::new(S::new()?),
instance: Mutable::new(I::new()?),
})
}
}
pub struct ImmutableInstanceContextImpl<S, I>
where
S: StateAccessor,
I: StateAccessor,
{
pub lyquid_id: LyquidID,
pub node_id: NodeID,
pub origin: Address,
pub caller: Address,
pub input: Bytes,
pub network: Immutable<S>,
pub instance: Immutable<I>,
}
impl<S, I> ImmutableInstanceContextImpl<S, I>
where
S: StateAccessor,
I: StateAccessor,
{
pub fn new(ctx: CallContext) -> LyquidResult<Self> {
Ok(Self {
lyquid_id: ctx.lyquid_id,
node_id: ctx.node_id.unwrap(), origin: ctx.origin,
caller: ctx.caller,
input: ctx.input,
network: Immutable::new(S::new()?),
instance: Immutable::new(I::new()?),
})
}
}