Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ members = [
"crates/shepherd-cow-host",
"crates/shepherd-sdk",
"crates/shepherd-sdk-test",
"crates/videre-host",
"modules/ethflow-watcher",
"modules/example",
"modules/examples/balance-tracker",
Expand Down
3 changes: 0 additions & 3 deletions crates/nexum-runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@ tokio.workspace = true
# Task lifecycle and graceful shutdown; the sole crate that raw-spawns
# tokio tasks. Every engine task routes through its executor.
nexum-tasks = { path = "../nexum-tasks" }
# Encoder for the opaque status body the host `event` stream carries;
# the router lowers an adapter-reported status through it.
nexum-status-body = { path = "../nexum-status-body" }

# Manifest parsing.
serde.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion crates/nexum-runtime/examples/embed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//!
//! [`CoreRuntime`] is the domain-free preset: it bundles the reference core
//! backends (chain provider pool, local redb store, empty extension slot) and
//! the Prometheus add-on. A domain capability such as cow-api is added by
//! the Prometheus add-on. A domain capability is added by
//! writing a preset that names its extension builder in the `Ext` slot and
//! returns its linker extensions, or by dropping to the explicit
//! `with_components` builder path. The returned [`RuntimeHandle`] carries the
Expand Down
247 changes: 8 additions & 239 deletions crates/nexum-runtime/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@
//! The core host binds the `nexum:host/event-module` world: the six core
//! primitives. Outbound HTTP is not a `nexum:host` interface: it is
//! wasi:http, linked separately; clocks are ambient wasi:clocks. Domain
//! extensions such as cow-api bind their own world and wire themselves in
//! at the composition root; they are not part of this core surface.
//! extensions bind their own world and wire themselves in at the
//! composition root; they are not part of this core surface.
//!
//! Every `Host` trait impl in `crate::host::impls` consumes types
//! generated here.
//!
//! `nexum:host` is a leaf package: the host `event` variant carries an
//! intent-status transition as opaque bytes, so the core world resolves
//! against `wit/nexum-host` alone. The videre vocabulary generates in the
//! adapter bindgen below, and the client bindgen remaps onto it with
//! `with`, so one Rust type serves the registry and the adapter face
//! alike. `PartialEq` is derived so the registry can compare a polled
//! status against the last delivered one.
//! `nexum:host` is a leaf package: the host `event` variant carries a
//! status transition as opaque bytes, so the core world resolves against
//! `wit/nexum-host` alone. An extension's bindgen remaps onto the shared
//! interfaces here with `with`, so the `Host` impls and the `fault` type
//! its components see are the very ones the core host constructs.
//! `PartialEq` is derived so extension services can compare event payloads.

wasmtime::component::bindgen!({
path: ["../../wit/nexum-host"],
Expand All @@ -24,233 +23,3 @@ wasmtime::component::bindgen!({
exports: { default: async },
additional_derives: [PartialEq],
});

/// WIT bindings for the second component kind: the
/// `videre:venue/venue-adapter` world. An adapter imports only the scoped
/// transport it needs (chain and messaging; outbound HTTP is wasi:http,
/// linked and allowlisted separately as for event-module) and exports the
/// `videre:venue/adapter` face plus `init`. The shared `nexum:host`
/// interfaces are reused from the `event-module` bindings above via
/// `with`, so the `chain`/`messaging` `Host` impls and the `fault` type
/// an adapter sees are the very ones the core host constructs. The
/// `videre:types` and `videre:value-flow` types generate here (the leaf
/// host world no longer reaches them) and the client bindgen below remaps
/// onto them.
mod venue_adapter {
wasmtime::component::bindgen!({
path: [
"../../wit/videre-value-flow",
"../../wit/videre-types",
"../../wit/nexum-host",
"../../wit/videre-venue",
],
world: "videre:venue/venue-adapter",
imports: { default: async },
exports: { default: async },
with: {
"nexum:host/types": super::nexum::host::types,
"nexum:host/chain": super::nexum::host::chain,
"nexum:host/messaging": super::nexum::host::messaging,
},
additional_derives: [PartialEq],
});
}

pub use venue_adapter::VenueAdapter;

/// The keeper-facing `videre:venue/client` import bound host-side. The
/// world imports the interface a module calls; the videre types it uses
/// are reused from the adapter bindings above via `with`, so the
/// `SubmitOutcome` and `VenueError` the registry hands back to a module
/// are the very ones an adapter's `submit` produced - no lift between two
/// structurally identical copies. Async, because the `Host` impl awaits the
/// per-adapter mutex and the adapter's own async guest calls.
mod client_host {
wasmtime::component::bindgen!({
inline: "
package videre:client-host;
world client-host {
import videre:venue/client@0.1.0;
}
",
path: [
"../../wit/videre-value-flow",
"../../wit/videre-types",
"../../wit/nexum-host",
"../../wit/videre-venue",
],
imports: { default: async },
with: {
"videre:value-flow/types": super::venue_adapter::videre::value_flow::types,
"videre:types/types": super::venue_adapter::videre::types::types,
},
});
}

/// The host-bound client interface: the `Host` trait the registry implements
/// and the `add_to_linker` the module linker calls.
pub use client_host::videre::venue::client;
/// The registry-observed status transition delivered through the `event`
/// variant, re-exported at the plain spelling the registry names.
pub use nexum::host::types::IntentStatusUpdate;
/// The shared intent ontology, re-exported at the plain spellings the
/// registry and the `client::Host` impl name.
pub use venue_adapter::videre::types::types::{
AuthScheme, IntentHeader, IntentStatus, Quotation, RateLimit, Settlement, SubmitOutcome,
UnsignedTx, VenueError,
};
/// The value-flow vocabulary the header is expressed in.
pub use venue_adapter::videre::value_flow::types as value_flow;

/// Bindgen smoke for the `videre:value-flow` types package, compiled under
/// test through a throwaway world that imports the interface. Its value is
/// the identifier-hygiene gate: the test names every generated type,
/// variant, and field by its plain Rust spelling, so a WIT id that collided
/// with a Rust keyword would surface as an `r#` escape and fail to compile
/// here rather than in a downstream binding.
#[cfg(test)]
mod value_flow_smoke {
wasmtime::component::bindgen!({
inline: "
package videre:value-flow-smoke;
world smoke {
import videre:value-flow/types@0.1.0;
}
",
path: ["../../wit/videre-value-flow"],
});

#[test]
fn identifiers_bind_unescaped() {
use videre::value_flow::types::{Asset, AssetAmount, Erc20};

let erc20 = Erc20 {
token: vec![0u8; 20],
};
let _ = Asset::Native;
let asset = Asset::Erc20(erc20);

let amount = AssetAmount {
asset,
amount: Vec::new(),
};
assert!(amount.amount.is_empty());
}
}

/// Bindgen smoke for the `videre:types` and `videre:venue` packages,
/// mirroring the value-flow smoke above: a throwaway world imports the
/// client interface and, transitively, the types interface and its
/// value-flow dependency. The test names every generated type, case, and
/// field by its plain Rust spelling, and a dummy `client` host impl pins
/// the four function signatures, so a keyword collision or an accidental
/// signature change fails this build rather than a downstream binding.
#[cfg(test)]
mod client_smoke {
wasmtime::component::bindgen!({
inline: "
package videre:client-smoke;
world smoke {
import videre:venue/client@0.1.0;
}
",
path: [
"../../wit/videre-value-flow",
"../../wit/videre-types",
"../../wit/nexum-host",
"../../wit/videre-venue",
],
});

use videre::types::types::{
AuthScheme, IntentHeader, IntentStatus, Quotation, RateLimit, Settlement, SubmitOutcome,
UnsignedTx, VenueError,
};
use videre::value_flow::types::{Asset, AssetAmount};

struct DummyClient;

impl videre::venue::client::Host for DummyClient {
fn quote(&mut self, _venue: String, _body: Vec<u8>) -> Result<Quotation, VenueError> {
Err(VenueError::UnknownVenue)
}

fn submit(&mut self, _venue: String, _body: Vec<u8>) -> Result<SubmitOutcome, VenueError> {
Err(VenueError::UnknownVenue)
}

fn status(
&mut self,
_venue: String,
_receipt: Vec<u8>,
) -> Result<IntentStatus, VenueError> {
Err(VenueError::UnknownVenue)
}

fn cancel(&mut self, _venue: String, _receipt: Vec<u8>) -> Result<(), VenueError> {
Err(VenueError::UnknownVenue)
}
}

fn amount(bytes: Vec<u8>) -> AssetAmount {
AssetAmount {
asset: Asset::Native,
amount: bytes,
}
}

#[test]
fn identifiers_bind_unescaped() {
use videre::venue::client::Host;

let _ = AuthScheme::Eip1271;
let _ = AuthScheme::Eip712;

let header = IntentHeader {
gives: amount(vec![1]),
wants: amount(Vec::new()),
settlement: Settlement { chain: 1 },
authorisation: AuthScheme::Eip712,
};
assert!(header.wants.amount.is_empty());

let _ = IntentStatus::Pending;
let _ = IntentStatus::Open;
let _ = IntentStatus::Fulfilled;
let _ = IntentStatus::Cancelled;
let _ = IntentStatus::Expired;

let tx = UnsignedTx {
chain: 1,
to: Vec::new(),
value: Vec::new(),
data: Vec::new(),
};
let _ = SubmitOutcome::Accepted(Vec::new());
let _ = SubmitOutcome::RequiresSigning(tx);

let quotation = Quotation {
gives: amount(vec![1]),
wants: amount(Vec::new()),
fee: amount(Vec::new()),
valid_until_ms: 0,
};
assert!(quotation.fee.amount.is_empty());

let _ = VenueError::UnknownVenue;
let _ = VenueError::InvalidBody(String::new());
let _ = VenueError::Unsupported;
let _ = VenueError::Denied(String::new());
let _ = VenueError::RateLimited(RateLimit {
retry_after_ms: Some(250),
});
let _ = VenueError::Unavailable(String::new());
let _ = VenueError::Timeout;

let mut client = DummyClient;
assert!(client.quote(String::new(), Vec::new()).is_err());
assert!(client.submit(String::new(), Vec::new()).is_err());
assert!(client.status(String::new(), Vec::new()).is_err());
assert!(client.cancel(String::new(), Vec::new()).is_err());
}
}
2 changes: 1 addition & 1 deletion crates/nexum-runtime/src/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
//!
//! Parameterised over the [`RuntimeTypes`] lattice. The composition root
//! builds the concrete [`Components`] and the extension list (including any
//! domain extension such as cow-api) and hands them here; this thin wrapper
//! domain extension) and hands them here; this thin wrapper
//! forwards to the [`builder`](crate::builder) launcher and blocks until the
//! event loop returns. A launcher that wants the
//! [`RuntimeHandle`](crate::builder::RuntimeHandle) back drives
Expand Down
Loading
Loading