Skip to main content

Security and Patterns

Security and Correctness

Determinism

Network methods must be deterministic. Do not use node-local time, randomness, HTTP, local files, environment variables, or host-specific data to decide network state changes.

The runtime enforces this for the host APIs that are inherently nondeterministic: systime, random_bytes, http_request, sign, submit_call, upc, and fetch_oracle_info are instance only, and calling them from a network context returns LyquidError::LyquorRuntime instead of executing. See Host APIs for the full context map. Determinism hazards that the runtime cannot see — local files, environment variables, or nondeterministic third-party crates — are still your responsibility.

If a state change depends on off-chain data, use an oracle/certified-call flow so validators approve the data before it reaches network state.

Secrets

Do not put secrets in network state. Use instance state for node-local credentials such as API keys. Remember that instance state is persistent on the node.

Input Validation

Validate at the boundary:

  • addresses are not Address::ZERO unless that is intentional
  • amounts and indexes are in range
  • required Lyquids are configured
  • callback Lyquid IDs and target addresses are authorized
  • oracle-certified calls target the expected method/input
  • UPC responses are from expected nodes

Caller and Origin

ctx.origin is the original external origin. ctx.caller is the direct caller. Use the field that matches the trust boundary. Do not use origin as a replacement for capability checks without a reason.

Interactions

Network call! has atomic abort semantics. Treat it like part of the same sequenced transition.

UPC is instance-side and not a direct network mutation mechanism. If UPC results should affect network state, route them through a network call, certified call, or another sequenced path.

Compatibility

Lyquor currently prioritizes architectural cleanliness and correctness over preserving legacy interfaces for the Network API, Lyquid syntax, UPC, and Rust API. Pin the LDK version your Lyquid builds against, and re-check your code against this reference and the Rustdoc when you upgrade.

Patterns

Read-Only Network Query

#[method::network(export = eth)]
fn balanceOf(ctx: &_, account: Address) -> LyquidResult<U256> {
Ok(ctx.network.balances.get(&account).cloned().unwrap_or_default())
}

Use ctx: &_ when the method should not mutate network state.

Node-Local Cache

state! {
network tracked_symbols: Vec<String> = Vec::new();
instance latest_prices: HashMap<String, U256> = new_hashmap();
}

#[method::instance(export = eth)]
fn cached_price(ctx: &_, symbol: String) -> LyquidResult<U256> {
Ok(ctx.instance.latest_prices.read().get(&symbol).cloned().unwrap_or_default())
}

Use instance state for data that can differ by node.

Network to Instance Follow-Up

#[method::network(export = eth)]
fn request_refresh(ctx: &mut _, symbol: String) -> LyquidResult<bool> {
trigger!(refresh_price(symbol: String = symbol), TriggerMode::Commit);
Ok(true)
}

#[method::instance]
fn refresh_price(ctx: &mut _, symbol: String) -> LyquidResult<bool> {
let mut prices = ctx.instance.latest_prices.write();
prices.insert(symbol, U256::ZERO);
Ok(true)
}

Use commit-time triggers for work that should start after a network state transition commits.

ERC20-Compatible Shape

Use export = eth and Solidity-compatible names/types:

#[method::network(export = eth)]
fn transfer(ctx: &mut _, to: Address, amount: U256) -> LyquidResult<bool> {
let from = ctx.caller;
transfer_balance(&mut ctx.network, from, to, amount)?;
Ok(true)
}

#[method::network(export = eth)]
fn totalSupply(ctx: &_) -> LyquidResult<U256> {
Ok(*ctx.network.total_supply)
}

Oracle-Certified State Update

Use this shape when local nodes observe data and certify a network update:

  1. keep local observations in instance state
  2. declare network oracle <topic>;
  3. implement validation/aggregation instance methods
  4. implement oracle::certified::<topic> network update methods
  5. submit the certified call through the oracle protocol

Reference Tables

Prelude Exports

The LDK prelude exports common types, helpers, and macros:

KindItems
Primitive IDs/typesAddress, LyquidID, NodeID, LyquidNumber, RequiredLyquid
Integer/bytes typesBytes, U64, U128, U256, Hash
Call & trigger typesCallParams, TriggerMode, ChainPos
Literal macrosaddress!, uint!
Encoding helpersencode_by_fields!, decode_by_fields!, encode_object, decode_object
ETH ABI helpersencode_eth_call_params!, decode_eth_call_params!
CollectionsHashMap, HashSet, new_hashmap, new_hashset
Sync typesMutex, RwLock, guards
Hashingblake3
Results/errorsLyquidResult, LyquidError
Method macrosmethod::network, method::instance, state!
Runtime macroscall!, upc!, trigger!, submit_certified_call!
Output/loggingprint!, println!, eprint!, eprintln!, log!
Oracle typesCertifiedCallParams, OracleTarget, OracleServiceTarget
HTTP model typeshttp module types for host HTTP requests

Method Support Matrix

CapabilityNetworkInstance
Read network stateyesyes
Write network statemutable context onlyno
Read instance statenoyes
Write instance statenomutable context only
Use call!yesno
Use upc!noyes
Use log!yesno
Use trigger!yes, including TriggerMode::Commityes, for Once, Recurrent, and Stop
Export as ETH ABIyesyes
Use nondeterministic host APIsnoyes, where supported

Unsupported Method Syntax

Do not use these for Lyquid methods:

async fn bad(...) -> LyquidResult<bool> { ... }
const fn bad(...) -> LyquidResult<bool> { ... }
extern "C" fn bad(...) -> LyquidResult<bool> { ... }
fn bad<T>(...) -> LyquidResult<T> { ... }
fn bad(ctx: Context, ...) -> LyquidResult<bool> { ... }
fn bad(_: &mut _, ...) -> LyquidResult<bool> { ... }
fn bad(&self, ...) -> LyquidResult<bool> { ... }

Use ordinary non-generic top-level functions with named context references.

Documentation References

Useful docs:

Glossary

TermMeaning
LyquidWASM smart contract/service written with the LDK and hosted by Lyquor
LDKLyquor Development Kit, exposed through the lyquid crate's ldk feature
Network methodDeterministic, sequenced method that can mutate network state
Instance methodNode-local method that can mutate instance state
Network stateGlobally agreed persistent Lyquid state
Instance stateNode-local persistent Lyquid state
LyteMemoryFixed linear-memory layout backing volatile, network, and instance memory
LyquidNumberVersion number for network state
UPCUniversal Procedure Call, the instance-side inter-Lyquid call mechanism
Certified callSequenced call authorized by oracle certificate
Oracle topicNamed oracle state and validation domain
RequiredLyquidTyped LyquidID wrapper marking a dependency Lyquid; ABI-encoded as address
export = ethMethod export metadata for Ethereum ABI compatibility
shakerLyquor tooling binary used to build, deploy, and inspect Lyquids
Lyquid packOCI-style package containing WASM, EVM bytecode, metadata, and assets