From 6d89d42314508cb2ee52a6ebbe02ba90b0ca7628 Mon Sep 17 00:00:00 2001 From: mfw78 Date: Fri, 17 Jul 2026 05:05:27 +0000 Subject: [PATCH] sdk: make the IntentBody derive no_std The derive reaches Vec and ToString through the venue SDK's __private alloc re-export instead of ::std, so a #![no_std] consumer needs no extern crate alloc. cow-venue flips to no_std (tests aside) and a compile-only no-std-probe crate keeps the contract under the workspace gate. --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + crates/cow-venue/src/composable.rs | 2 ++ crates/cow-venue/src/lib.rs | 11 +++++++---- crates/nexum-macros/src/intent_body.rs | 14 +++++++++----- crates/nexum-venue-sdk/src/body.rs | 5 ++++- crates/no-std-probe/Cargo.toml | 18 ++++++++++++++++++ crates/no-std-probe/src/lib.rs | 14 ++++++++++++++ 8 files changed, 62 insertions(+), 10 deletions(-) create mode 100644 crates/no-std-probe/Cargo.toml create mode 100644 crates/no-std-probe/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 99a57b72..c0d10b14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3722,6 +3722,13 @@ dependencies = [ "toml 1.1.2+spec-1.1.0", ] +[[package]] +name = "no-std-probe" +version = "0.1.0" +dependencies = [ + "nexum-venue-sdk", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" diff --git a/Cargo.toml b/Cargo.toml index 0d85dead..6b11a451 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "crates/nexum-venue-sdk", "crates/nexum-venue-test", "crates/nexum-world", + "crates/no-std-probe", "crates/shepherd", "crates/shepherd-backtest", "crates/shepherd-cow-host", diff --git a/crates/cow-venue/src/composable.rs b/crates/cow-venue/src/composable.rs index c3535865..5e7a94a4 100644 --- a/crates/cow-venue/src/composable.rs +++ b/crates/cow-venue/src/composable.rs @@ -8,6 +8,8 @@ //! `static_input` is opaque to the venue; only the named handler parses //! it, so this crate never inspects its bytes. +use alloc::vec::Vec; + use borsh::{BorshDeserialize, BorshSerialize}; use crate::order::Address; diff --git a/crates/cow-venue/src/lib.rs b/crates/cow-venue/src/lib.rs index 1fa8a521..230f6779 100644 --- a/crates/cow-venue/src/lib.rs +++ b/crates/cow-venue/src/lib.rs @@ -9,10 +9,9 @@ //! venue SDK (for the [`IntentBody`](nexum_venue_sdk::IntentBody) derive) //! and borsh, so a venue adapter component or a strategy module can carry //! the body types and codec without dragging in the host-side CoW -//! machinery. The one non-obvious constraint: the derive's generated code -//! names `::std`, so the slice links std and is not a bare-metal -//! `#![no_std]` crate; it is guest-consumable on the runtime's -//! std-bearing wasm target rather than target-free. +//! machinery. The crate is `#![no_std]` (tests aside): the derive's +//! generated code reaches `alloc` through the venue SDK re-export, never +//! `::std`. //! //! With `--no-default-features` the slice drops out entirely and the //! crate compiles empty, so a consumer can depend on a future slice @@ -26,9 +25,13 @@ //! so an adapter or a module that wants only the body types stays //! dependency-light. +#![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), warn(unused_crate_dependencies))] #![warn(missing_docs)] +#[cfg(feature = "body")] +extern crate alloc; + #[cfg(feature = "body")] pub mod body; diff --git a/crates/nexum-macros/src/intent_body.rs b/crates/nexum-macros/src/intent_body.rs index 79b074cd..cc8fe54b 100644 --- a/crates/nexum-macros/src/intent_body.rs +++ b/crates/nexum-macros/src/intent_body.rs @@ -12,7 +12,9 @@ //! //! Generated code names the venue SDK by its crate path //! (`::nexum_venue_sdk`), so the derive is only usable through that -//! crate's re-export. +//! crate's re-export. The expansion names only `::core` and the SDK's +//! `__private` re-exports (borsh, `alloc`), so a `#![no_std]` consumer +//! needs no `extern crate alloc`. use proc_macro2::TokenStream; use quote::quote; @@ -80,12 +82,12 @@ pub(crate) fn expand(input: &DeriveInput) -> syn::Result { encode_arms.push(quote! { Self::#ident(payload) => { - let mut out = ::std::vec::Vec::new(); + let mut out = ::nexum_venue_sdk::body::__private::alloc::vec::Vec::new(); out.push(#tag); ::nexum_venue_sdk::body::__private::borsh::to_writer(&mut out, payload).map_err( |err| ::nexum_venue_sdk::body::BodyError::Encode { version: #tag, - detail: ::std::string::ToString::to_string(&err), + detail: ::nexum_venue_sdk::body::__private::alloc::string::ToString::to_string(&err), }, )?; ::core::result::Result::Ok(out) @@ -96,7 +98,9 @@ pub(crate) fn expand(input: &DeriveInput) -> syn::Result { ::nexum_venue_sdk::body::__private::borsh::from_slice::<#payload_ty>(payload) .map_err(|err| ::nexum_venue_sdk::body::BodyError::Malformed { version: #tag, - detail: ::std::string::ToString::to_string(&err), + detail: ::nexum_venue_sdk::body::__private::alloc::string::ToString::to_string( + &err, + ), })?, )), }); @@ -108,7 +112,7 @@ pub(crate) fn expand(input: &DeriveInput) -> syn::Result { fn to_bytes( &self, ) -> ::core::result::Result< - ::std::vec::Vec, + ::nexum_venue_sdk::body::__private::alloc::vec::Vec, ::nexum_venue_sdk::body::BodyError, > { match self { diff --git a/crates/nexum-venue-sdk/src/body.rs b/crates/nexum-venue-sdk/src/body.rs index 1f202ce2..58a64e12 100644 --- a/crates/nexum-venue-sdk/src/body.rs +++ b/crates/nexum-venue-sdk/src/body.rs @@ -86,8 +86,11 @@ impl From for VenueError { } /// Re-exports for `#[derive(IntentBody)]` generated code only; not a -/// public surface. +/// public surface. `alloc` rides along so the expansion resolves in a +/// `#![no_std]` consumer without its own `extern crate alloc`. #[doc(hidden)] pub mod __private { + pub extern crate alloc; + pub use borsh; } diff --git a/crates/no-std-probe/Cargo.toml b/crates/no-std-probe/Cargo.toml new file mode 100644 index 00000000..88fb4444 --- /dev/null +++ b/crates/no-std-probe/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "no-std-probe" +version = "0.1.0" +edition.workspace = true +license.workspace = true +repository.workspace = true +description = "Compile-only #![no_std] probe: the IntentBody derive must expand without the consumer's std prelude." + +[lib] +# Never shipped. Living in the workspace keeps the derive's no_std +# contract under the workspace check/clippy gate. + +[lints] +workspace = true + +[dependencies] +# Source of the `IntentBody` derive and trait the probe enum implements. +nexum-venue-sdk = { path = "../nexum-venue-sdk" } diff --git a/crates/no-std-probe/src/lib.rs b/crates/no-std-probe/src/lib.rs new file mode 100644 index 00000000..2b91da39 --- /dev/null +++ b/crates/no-std-probe/src/lib.rs @@ -0,0 +1,14 @@ +//! Compile-only `#![no_std]` probe: `#[derive(IntentBody)]` must expand +//! without the consumer's std prelude or an `extern crate alloc`. + +#![no_std] +#![warn(missing_docs)] + +use nexum_venue_sdk::IntentBody; + +/// The probe schema: one published version over a bare byte payload. +#[derive(IntentBody, Clone, Debug, PartialEq, Eq)] +pub enum ProbeBody { + /// First published version. + V1(u8), +}