lyquid 0.4.4

Lyquid Development Kit (LDK).
Documentation
//! Guest-side development kit for Lyquid WASM modules.
//!
//! `lyquid` is the crate Lyquid authors import inside contract crates. It exposes the ABI tags,
//! call context, result and error types, LyteMemory layout constants, HTTP request shapes, guest
//! memory helpers, runtime host imports, and the `method` proc-macro facade. Those pieces line up
//! with the metadata decoded by `lyquor-wasm` and the entry points executed by `lyquor-vm`: method
//! macros generate network, instance, Ethereum-exported, and UPC entry points, while runtime
//! helpers perform host calls from inside the WASM guest.
//!
//! - [Litepaper](https://docs.lyquor.dev/docs/litepaper/arch)
//! - [Tutorial](https://docs.lyquor.dev/docs/tutorial/)
//! - [Lyquor Development Kit Documentation](https://docs.lyquor.dev/docs/ldk/)

#[cfg(feature = "ldk")] pub use hashbrown;
#[cfg(feature = "ldk")] pub use lyquor_primitives;

/// Method metadata categories and WASM custom-section encoding helpers.
pub mod consts;
/// Guest runtime support for memory, calls, oracle, UPC, and synchronization.
#[cfg(feature = "ldk")]
pub mod runtime;
#[cfg(feature = "ldk")] pub use runtime::prelude;

pub use alloy_sol_types;
/// HTTP request and response types exposed to Lyquid instance functions.
pub mod http;
/// Stable hostnames and endpoint URLs exposed by the Lyquor runtime.
pub mod well_known {
    /// Hostname routed by the node to its configured sequencing backend HTTP endpoint.
    pub const SEQUENCER_RPC_HOST: &str = "sequencer.lyquor.internal";
    /// Stable HTTP JSON-RPC path routed by the node to its configured sequencing backend.
    pub const SEQUENCER_RPC_PATH: &str = "/api";
    /// Internal sequencer egress origin.
    pub const SEQUENCER_RPC: &str = "http://sequencer.lyquor.internal";
    /// URL Lyquid instance functions can use with `lyquor_api::http_request` for sequencer JSON-RPC reads.
    pub const SEQUENCER_RPC_API: &str = "http://sequencer.lyquor.internal/api";
    /// Hostname routed by the node to its own public API.
    pub const NODE_API_HOST: &str = "node.lyquor.internal";
    /// Internal node API origin for Lyquid instance functions.
    pub const NODE_API: &str = "http://node.lyquor.internal";
}
/// Guest pointer and memory-layout helpers.
pub mod mem;

use lyquor_primitives::{Address, Bytes, LyquidID, NodeID};
use serde::{Deserialize, Serialize};
use thiserror::Error;

/// Lyquid method syntax (attribute macros).
///
/// Lyquid functions are defined with attribute macros. These methods may execute with network or
/// instance context, and can include UPC procedures. Define them as top-level functions in your
/// crate (module-level items, not inside `impl`/`trait` blocks). All functions are exported into a
/// single global namespace keyed by `<category>`, `<group>`, and `<method_name>`.
///
/// ### Constructor (optional)
/// The constructor is invoked atomically once at deployment (or code upgrade). It must be named
/// `constructor`, must not return a value, and must use `#[lyquid::method::network]` with no
/// attribute arguments.
///
/// ```ignore
/// #[lyquid::method::network]
/// fn constructor(ctx: &mut _, greeting: String) {
///     *ctx.network.greeting = greeting.into();
/// }
/// ```
///
/// ### Standard Methods
///
/// #### Network method (defaults to `main` group)
/// ```ignore
/// #[lyquid::method::network]
/// fn set_greeting(ctx: &mut _, greeting: String) -> LyquidResult<bool> {
///     *ctx.network.greeting = greeting.into();
///     Ok(true)
/// }
/// ```
///
/// #### Network method with explicit group
/// ```ignore
/// #[lyquid::method::network(group = node)]
/// fn join(ctx: &mut _, node: NodeID) -> LyquidResult<()> {
///     ctx.network.nodes.push(node);
///     Ok(())
/// }
/// ```
///
/// #### Instance method
/// ```ignore
/// #[lyquid::method::instance]
/// fn get_price(ctx: &_) -> LyquidResult<U256> {
///     Ok(*ctx.instance.price.read())
/// }
/// ```
///
/// #### Ethereum export creator guard
/// ```ignore
/// #[lyquid::method::network(export = eth, eth_guard = creator)]
/// fn setup(ctx: &mut _, value: U256) -> LyquidResult<()> {
///     *ctx.network.value = value;
///     Ok(())
/// }
/// ```
/// `eth_guard = creator` adds a `msg.sender == creator` check to the generated EVM transaction
/// wrapper for mutable `main` or `node` network exports. It does not protect calls that arrive
/// through other Lyquor paths; methods that require runtime authorization should still validate
/// the call context inside the method body.
///
/// ### UPC Methods
///
/// UPC expands into three instance functions using dedicated groups.
///
/// #### 1. UPC callee selection
/// ```ignore
/// #[lyquid::method::instance(upc(prepare))]
/// fn ping(ctx: &_) -> LyquidResult<Vec<NodeID>> {
///     Ok(Vec::from(&ctx.network.nodes[..]))
/// }
/// ```
///
/// #### 2. UPC request handler
/// ```ignore
/// #[lyquid::method::instance(upc(request))]
/// fn ping(ctx: &mut _, msg: String) -> LyquidResult<String> {
///     let from = ctx.from;
///     let id = ctx.id;
///     Ok(format!("pong: {msg} ({from:?}, {id})"))
/// }
/// ```
///
/// #### 3. UPC response aggregator
/// ```ignore
/// #[lyquid::method::instance(upc(response))]
/// fn ping(ctx: &_, response: LyquidResult<String>) -> LyquidResult<Option<String>> {
///     let resp = response?;
///     let from = ctx.from;
///     Ok(Some(format!("from {from:?}: {resp}")))
/// }
/// ```
///
/// ### Notes on Categories and Context
/// - `network` methods are deterministic and can read/write `network` state. They cannot perform
///   nondeterministic operations (UPC, timers, etc.).
/// - `instance` methods are event-driven and can read/write `instance` state and read `network`
///   state, but cannot mutate shared `network` state.
/// - The context parameter must be a reference like `ctx: &mut _` or `ctx: &_`. The concrete
///   context type depends on the method category (network/instance/UPC).
/// - UPC `response` functions are optional. If omitted, UPC behaves like a request-response call
///   that returns the first result.
#[cfg(feature = "ldk")]
pub mod method {
    pub use lyquid_proc::instance_function as instance;
    pub use lyquid_proc::network_function as network;
}

/// Invocation context supplied by the host for one Lyquid method call.
#[cfg_attr(feature = "ldk", doc(hidden))]
#[derive(Serialize, Deserialize, Clone)]
pub struct CallContext {
    pub origin: Address,
    pub caller: Address,
    pub input: Bytes,
    pub lyquid_id: LyquidID,
    pub node_id: Option<NodeID>,
}

/// Error type shared by generated Lyquid wrappers and host-facing runtime helpers.
#[derive(Serialize, Deserialize, Debug, Error)]
pub enum LyquidError {
    #[error("Fail to initialize Lyquid.")]
    Init,
    #[error("Invalid input given from the host.")]
    LyquorInput,
    #[error("Invalid output returned by the host call.")]
    LyquorOutput,
    #[error("Invalid input given from the Lyquid.")]
    LyquidInput,
    #[error("Invalid output returned by the Lyquid.")]
    LyquidOutput,
    #[error("Host runtime: {0}")]
    LyquorRuntime(String),
    #[error("Lyquid runtime: {0}")]
    LyquidRuntime(String),
    #[error("Invalid certificate for the input.")]
    InputCert,
    #[error("Oracle error: {0}")]
    OracleError(String),
}

/// Numeric ABI tag for Ethereum-compatible call payloads.
pub const ABI_ETH: u32 = 0x1;
/// Numeric ABI tag for native Lyquor call payloads.
pub const ABI_LYQUOR: u32 = 0x0;

/// Standard result type for Lyquid runtime and generated wrapper operations.
pub type LyquidResult<T> = Result<T, LyquidError>;

/// The starting address for stacks used by Lyquid.
pub const LYTESTACK_BASE: usize = 0x30000000;
/// The base address for LyteMemory.
/// Volatile's upper address is below next to this address. Everything from this base to
/// [NETWORK_MEMSIZE_IN_MB] and [INSTANCE_MEMSIZE_IN_MB] are persistent.
pub const LYTEMEM_BASE: usize = 0x80000000;
/// Total size of the memory in megabytes.
pub const LYTEMEM_SIZE_IN_MB: usize = 4096; // 4GB (WASM limit)
/// Size cap for the addressable LyteMemory that is globally viewed (and persisted) by all Lyquid instances.
pub const NETWORK_MEMSIZE_IN_MB: usize = 1024; // 1GB
/// Size cap for the addressable LyteMemory that is locally viewed (and persisted) for one Lyquid instance.
pub const INSTANCE_MEMSIZE_IN_MB: usize = 1024; // 1GB
/// Size cap for the volatile memory that can be used by each function call.
pub const VOLATILE_MEMSIZE_IN_MB: usize = 1024; // 1GB

/// Prefix bytes used for varaiable catalog in versioned state.
pub const VAR_CATALOG_PREFIX: [u8; 1] = [0x2a];
/// Prefix bytes used for runtime-owned state in versioned state.
pub const INTERNAL_STATE_PREFIX: [u8; 1] = [0x20];
/// Prefix bytes used for lite pages in versioned state.
pub const LYTEMEM_PAGE_PREFIX: [u8; 1] = [0x00];

/// Exported guest initialization function name.
pub const WASM_INIT_FUNC: &str = "__lyquid_initialize";
/// Exported guest state-variable initialization function name.
pub const WASM_INIT_VAR_FUNC: &str = "__lyquid_initialize_state_variables";
/// Exported guest function name that marks Lyquid state uninitialized; the VM calls it to reset
/// network state when a new Lyquid image is loaded.
pub const WASM_NUKE_STATE_FUNC: &str = "__lyquid_nuke_state";

/// Exported guest volatile-memory allocation function name.
pub const WASM_VOLATILE_ALLOC_FUNC: &str = "__lyquid_volatile_alloc";
/// Exported guest volatile-memory deallocation function name.
pub const WASM_VOLATILE_DEALLOC_FUNC: &str = "__lyquid_volatile_dealloc";
/// WASM global name for the guest stack pointer.
pub const WASM_STACK_POINTER: &str = "__stack_pointer";
/// Prefix for exported network method entry points.
pub const WASM_NETWORK_METHOD_PREFIX: &str = "__lyquid_method_network";
/// Prefix for exported instance method entry points.
pub const WASM_INSTANCE_METHOD_PREFIX: &str = "__lyquid_method_instance";
/// The maximum size of a stack per call.
pub const WASM_CALLSTACK_LIMIT: u32 = 0x100000; // 1M
/// Default stack base address for Lyquid guest modules.
pub const WASM_DEFAULT_STACK_BASE: u32 = 0x100000;

/// Ethereum export metadata for a Lyquid method.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct FuncEthInfo {
    pub decl: String,
    pub canonical: String,
}

/// Method metadata decoded from Lyquid WASM custom sections.
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct FuncInfo {
    pub eth: Option<FuncEthInfo>,
    pub mutable: bool, // &mut ctx or &ctx
}

pub mod upc {
    use super::*;
    use lyquor_primitives::NodeID;

    /// UPC request payload passed from the host to a receiving Lyquid instance method.
    #[derive(Serialize, Deserialize)]
    pub struct RequestInput {
        pub from: NodeID,
        pub id: u64,
        pub input: Vec<u8>,
    }

    // TODO: refactor request output to be a struct with more sophisticated error handling
    /// Raw UPC request output bytes returned to the caller.
    pub type RequestOutput = Vec<u8>;

    /// Opaque pointer to response cache state owned by a UPC continuation.
    pub type CachePtr = u64;

    /// UPC response payload passed back to the initiating Lyquid instance method.
    #[derive(Serialize, Deserialize)]
    pub struct ResponseInput {
        pub from: NodeID,
        pub id: u64,
        pub returned: Vec<u8>,
        pub cache: Option<CachePtr>,
    }

    /// The ResponseOutput is similar to std::ops::ControlFlow, we don't depend on std::ops::ControlFlow because it is
    /// not serializable.
    #[derive(Serialize, Deserialize)]
    pub enum ResponseOutput {
        Continue(Option<CachePtr>),
        Return(Vec<u8>),
    }

    /// UPC prepare payload containing the original client call parameters.
    #[derive(Serialize, Deserialize)]
    pub struct PrepareInput {
        pub client_params: Bytes,
    }

    /// UPC prepare output containing selected nodes and optional continuation cache.
    #[derive(Serialize, Deserialize)]
    pub struct PrepareOutput {
        pub result: Vec<NodeID>,
        pub cache: Option<CachePtr>,
    }
}