Skip to main content

thruput_contract_spec/
lib.rs

1//! The contract spec model shared by the macros, the SDK and spec tooling.
2//!
3//! It defines the traits through which contract types and events describe
4//! themselves ([`ContractTypeSpec`], [`ContractEventSpec`],
5//! [`ContractTypeName`]), zero-copy views of spec fragments, and the raw spec
6//! section's wire-format versioning ([`format`]). With the `owned-model`
7//! feature it also re-exports the owned spec types from `thruput-spec`.
8
9#![no_std]
10
11#[cfg(feature = "alloc")]
12extern crate alloc;
13#[cfg(feature = "alloc")]
14use alloc::{string::String, vec::Vec};
15
16pub mod format;
17pub mod view;
18
19pub use format::{
20    FINAL_SECTION_NAME, RAW_SECTION_NAME, RAW_SECTION_PREFIX, RAW_SPEC_FORMAT,
21    SpecSection, classify_section,
22};
23
24#[cfg(feature = "owned-model")]
25pub use thruput_spec::{
26    ContractArrayType, ContractEnumDef, ContractEnumVariant, ContractEventDef,
27    ContractExceptionType, ContractField, ContractMethodEffect,
28    ContractSignedTokenPreimage, ContractSpec, ContractSpecId,
29    ContractSpecInterface, ContractSpecMethod, ContractSpecString,
30    ContractStructDef, ContractType, ContractTypeDef, ContractTypeDefKind,
31    ContractTypeKind, ContractUnionCase, ContractUnionCaseKind,
32    ContractUnionCaseType, ContractUnionDef, ContractVectorType,
33};
34#[cfg(feature = "owned-model")]
35mod fragment;
36#[cfg(feature = "owned-model")]
37pub use fragment::*;
38pub use view::*;
39#[cfg(feature = "alloc")]
40use xdrs::VecM;
41
42mod thruput_xdr_types;
43
44/// Implemented by `#[contract_type]` types to provide their spec type definition.
45pub trait ContractTypeSpec {
46    /// The type's definition (struct, enum, or union) as a const view.
47    const TYPE_DEF: view::ContractTypeDefView<'static>;
48}
49
50/// Implemented by `#[derive(Event)]` types to provide their spec event
51/// definition. The definition is emitted into the raw spec section only
52/// where the event is declared (`emits(...)` on an interface) or exported
53/// (`#[event_spec(export)]` on the derive).
54#[diagnostic::on_unimplemented(
55    message = "`{Self}` is not a contract event",
56    label = "not a contract event",
57    note = "add `#[derive(Event)]` to `{Self}` — `emits(...)` in \
58            `#[contract(...)]` requires it"
59)]
60pub trait ContractEventSpec {
61    /// The event's definition (name, signature, topics, fields) as a const view.
62    const EVENT_DEF: view::ContractEventDefView<'static>;
63
64    /// Hidden root symbol used to retain raw typedef fragments for the
65    /// event's field types.
66    #[doc(hidden)]
67    const __THRUPUT_SPEC_ANCHOR_ROOT: fn() -> usize =
68        Self::__thruput_spec_anchor;
69
70    /// Hidden linker-anchor hook used to retain raw typedef fragments.
71    #[doc(hidden)]
72    #[cfg_attr(target_arch = "riscv32", inline(never))]
73    #[cfg_attr(
74        target_arch = "riscv32",
75        unsafe(link_section = "thruputspecanchortextv0")
76    )]
77    fn __thruput_spec_anchor() -> usize {
78        0
79    }
80}
81
82/// Provides the spec-visible name for a contract type.
83pub trait ContractTypeName {
84    /// The fully qualified id this type is known by in the contract spec.
85    ///
86    /// This is an identity claim, not a label. A bare ident cannot tell two
87    /// user-defined types apart — two crates, two modules of one crate, or two
88    /// major versions of one crate may each define a `Meta` — so every
89    /// reference in a spec resolves through this const, and two types agree on
90    /// an id only if they are the same type. The crate and major-version
91    /// segments are load-bearing: shortening an id to its declared name for
92    /// display is each codegen's business, not the spec's.
93    ///
94    /// The segments travel apart rather than joined into
95    /// `CRATE@MAJOR::module::path::Name`, because the joined form cannot be
96    /// split back: `name = "..."` substitutes its value verbatim, `::` and
97    /// all, so the boundary between module path and declared name is not
98    /// recoverable from the string.
99    ///
100    /// `#[contract_type]` and `#[contract_exception]` derive it at the
101    /// declaration, where `module_path!()` and the declaring crate's name and
102    /// version are all in scope, so it cannot be taken from the wrong crate. A
103    /// hand-written impl (for a type this crate does not declare) is asserting
104    /// the id itself and is responsible for its accuracy, segment by segment.
105    const NAME: view::ContractSpecIdView<'static>;
106}
107
108/// Bundles a type reference for use in contract spec signatures.
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub struct ContractTypeInfo<'a> {
111    /// The type reference (e.g. `U32`, `Named("Foo")`, `Option(&U64)`).
112    pub ty: view::ContractTypeView<'a>,
113}
114
115/// Trait for types that can appear in contract spec signatures.
116/// Implemented for primitives, collections, and `#[contract_type]` types.
117#[diagnostic::on_unimplemented(
118    message = "`{Self}` cannot appear in a contract signature",
119    label = "not a contract-spec type",
120    note = "annotate the defining type with `#[contract_type]` (error \
121            types: `#[contract_exception]`, events: `#[derive(Event)]`)"
122)]
123pub trait ContractTypeRefSpec {
124    /// The type reference. Transitive type definitions are not carried here;
125    /// they are retained via the linker-anchor mechanism
126    /// (`__THRUPUT_SPEC_ANCHOR_ROOT`) and resolved by spec-tool.
127    const INFO: ContractTypeInfo<'static>;
128
129    /// How this type appears as a signed-token preimage
130    /// (`ContractTypeView::SignedToken`'s payload): the type itself by
131    /// default, or `None` for a type-erased payload (the SDK's `Opaque`
132    /// overrides this).
133    const SIGNED_PREIMAGE: Option<&'static view::ContractTypeView<'static>> =
134        Some(&Self::INFO.ty);
135
136    /// Hidden root symbol used to retain imported raw typedef fragments.
137    #[doc(hidden)]
138    const __THRUPUT_SPEC_ANCHOR_ROOT: fn() -> usize =
139        Self::__thruput_spec_anchor;
140
141    /// Hidden linker-anchor hook used to retain raw typedef fragments.
142    #[doc(hidden)]
143    #[cfg_attr(target_arch = "riscv32", inline(never))]
144    #[cfg_attr(
145        target_arch = "riscv32",
146        unsafe(link_section = "thruputspecanchortextv0")
147    )]
148    fn __thruput_spec_anchor() -> usize {
149        0
150    }
151}
152
153#[doc(hidden)]
154#[inline(always)]
155pub fn mix_anchor_fn(current: usize, anchor: fn() -> usize) -> usize {
156    current ^ core::hint::black_box(anchor as usize)
157}
158
159impl ContractTypeRefSpec for () {
160    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
161        ty: view::ContractTypeView::Void,
162    };
163}
164
165impl ContractTypeRefSpec for bool {
166    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
167        ty: view::ContractTypeView::Bool,
168    };
169}
170
171impl ContractTypeRefSpec for u8 {
172    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
173        ty: view::ContractTypeView::U8,
174    };
175}
176
177impl ContractTypeRefSpec for u32 {
178    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
179        ty: view::ContractTypeView::U32,
180    };
181}
182
183impl ContractTypeRefSpec for i32 {
184    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
185        ty: view::ContractTypeView::I32,
186    };
187}
188
189impl ContractTypeRefSpec for u64 {
190    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
191        ty: view::ContractTypeView::U64,
192    };
193}
194
195impl ContractTypeRefSpec for i64 {
196    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
197        ty: view::ContractTypeView::I64,
198    };
199}
200
201impl ContractTypeRefSpec for u128 {
202    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
203        ty: view::ContractTypeView::U128,
204    };
205}
206
207impl ContractTypeRefSpec for i128 {
208    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
209        ty: view::ContractTypeView::I128,
210    };
211}
212
213impl ContractTypeRefSpec for str {
214    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
215        ty: view::ContractTypeView::String,
216    };
217}
218
219// Byte payloads deliberately lower to `Vector(U8, ...)`, never to
220// `ContractType::Bytes`, so the spec has a single canonical spelling for
221// byte sequences (the macro tests assert `Bytes` never appears). The
222// `Bytes` wire variant still exists; other-language SDKs must also avoid
223// emitting it, or identical interfaces will hash/compare unequal.
224//
225// A slice is an unbounded vector of its element; for `[u8]` that is the
226// byte-payload spelling above.
227impl<T> ContractTypeRefSpec for [T]
228where
229    T: ContractTypeRefSpec,
230{
231    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
232        ty: view::ContractTypeView::Vector(view::ContractVectorTypeView {
233            element: &type_ref::<T>(),
234            max_len: u32::MAX,
235        }),
236    };
237
238    #[cfg_attr(target_arch = "riscv32", inline(never))]
239    #[cfg_attr(
240        target_arch = "riscv32",
241        unsafe(link_section = "thruputspecanchortextv0")
242    )]
243    fn __thruput_spec_anchor() -> usize {
244        mix_anchor_fn(
245            0,
246            <T as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT,
247        )
248    }
249}
250
251impl ContractTypeRefSpec for &[u8] {
252    const INFO: ContractTypeInfo<'static> =
253        <[u8] as ContractTypeRefSpec>::INFO;
254}
255
256impl ContractTypeRefSpec for &str {
257    const INFO: ContractTypeInfo<'static> = <str as ContractTypeRefSpec>::INFO;
258}
259
260impl<const MAX: u32> ContractTypeRefSpec for xdr_common::StringM<MAX> {
261    const INFO: ContractTypeInfo<'static> = <str as ContractTypeRefSpec>::INFO;
262}
263
264impl<const MAX: usize> ContractTypeRefSpec for heapless::String<MAX> {
265    const INFO: ContractTypeInfo<'static> = <str as ContractTypeRefSpec>::INFO;
266}
267
268impl<const MAX: u32> ContractTypeRefSpec for xdr_common::BytesM<MAX> {
269    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
270        ty: view::ContractTypeView::Vector(view::ContractVectorTypeView {
271            element: &view::ContractTypeView::U8,
272            max_len: MAX,
273        }),
274    };
275}
276
277/// Bounded vector with no allocator: the same wire shape and spec as
278/// `VecM<T, N>` (for `u8`, as `BytesM<N>`), but decodable in a no-`alloc`
279/// guest.
280impl<T, const N: usize> ContractTypeRefSpec for heapless::Vec<T, N>
281where
282    T: ContractTypeRefSpec,
283{
284    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
285        ty: view::ContractTypeView::Vector(view::ContractVectorTypeView {
286            element: &type_ref::<T>(),
287            max_len: N as u32,
288        }),
289    };
290
291    #[cfg_attr(target_arch = "riscv32", inline(never))]
292    #[cfg_attr(
293        target_arch = "riscv32",
294        unsafe(link_section = "thruputspecanchortextv0")
295    )]
296    fn __thruput_spec_anchor() -> usize {
297        mix_anchor_fn(
298            0,
299            <T as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT,
300        )
301    }
302}
303
304impl<T, const N: usize> ContractTypeRefSpec for [T; N]
305where
306    T: ContractTypeRefSpec,
307{
308    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
309        ty: view::ContractTypeView::Array(view::ContractArrayTypeView {
310            element: &type_ref::<T>(),
311            len: N as u32,
312        }),
313    };
314
315    #[cfg_attr(target_arch = "riscv32", inline(never))]
316    #[cfg_attr(
317        target_arch = "riscv32",
318        unsafe(link_section = "thruputspecanchortextv0")
319    )]
320    fn __thruput_spec_anchor() -> usize {
321        mix_anchor_fn(
322            0,
323            <T as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT,
324        )
325    }
326}
327
328#[cfg(feature = "alloc")]
329impl ContractTypeRefSpec for String {
330    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
331        ty: view::ContractTypeView::String,
332    };
333}
334
335#[cfg(feature = "alloc")]
336impl<T> ContractTypeRefSpec for Vec<T>
337where
338    T: ContractTypeRefSpec,
339{
340    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
341        ty: view::ContractTypeView::Vector(view::ContractVectorTypeView {
342            element: &type_ref::<T>(),
343            max_len: u32::MAX,
344        }),
345    };
346
347    #[cfg_attr(target_arch = "riscv32", inline(never))]
348    #[cfg_attr(
349        target_arch = "riscv32",
350        unsafe(link_section = "thruputspecanchortextv0")
351    )]
352    fn __thruput_spec_anchor() -> usize {
353        mix_anchor_fn(
354            0,
355            <T as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT,
356        )
357    }
358}
359
360#[cfg(feature = "alloc")]
361impl<T, const N: u32> ContractTypeRefSpec for VecM<T, N>
362where
363    T: ContractTypeRefSpec,
364{
365    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
366        ty: view::ContractTypeView::Vector(view::ContractVectorTypeView {
367            element: &type_ref::<T>(),
368            max_len: N,
369        }),
370    };
371
372    #[cfg_attr(target_arch = "riscv32", inline(never))]
373    #[cfg_attr(
374        target_arch = "riscv32",
375        unsafe(link_section = "thruputspecanchortextv0")
376    )]
377    fn __thruput_spec_anchor() -> usize {
378        mix_anchor_fn(
379            0,
380            <T as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT,
381        )
382    }
383}
384
385impl<T> ContractTypeRefSpec for Option<T>
386where
387    T: ContractTypeRefSpec,
388{
389    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
390        ty: view::ContractTypeView::Option(&type_ref::<T>()),
391    };
392
393    #[cfg_attr(target_arch = "riscv32", inline(never))]
394    #[cfg_attr(
395        target_arch = "riscv32",
396        unsafe(link_section = "thruputspecanchortextv0")
397    )]
398    fn __thruput_spec_anchor() -> usize {
399        mix_anchor_fn(
400            0,
401            <T as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT,
402        )
403    }
404}
405
406#[cfg(feature = "alloc")]
407impl<T> ContractTypeRefSpec for alloc::boxed::Box<T>
408where
409    T: ContractTypeRefSpec,
410{
411    const INFO: ContractTypeInfo<'static> = T::INFO;
412
413    #[cfg_attr(target_arch = "riscv32", inline(never))]
414    #[cfg_attr(
415        target_arch = "riscv32",
416        unsafe(link_section = "thruputspecanchortextv0")
417    )]
418    fn __thruput_spec_anchor() -> usize {
419        mix_anchor_fn(
420            0,
421            <T as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT,
422        )
423    }
424}
425
426macro_rules! impl_tuple_type_ref_spec {
427    ($(($($name:ident),+)),+ $(,)?) => {
428        $(
429            impl<$($name),+> ContractTypeRefSpec for ($($name,)+)
430            where
431                $($name: ContractTypeRefSpec),+
432            {
433                const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
434                    ty: view::ContractTypeView::Tuple(&[$(type_ref::<$name>()),+]),
435                };
436
437                #[cfg_attr(target_arch = "riscv32", inline(never))]
438                #[cfg_attr(
439                    target_arch = "riscv32",
440                    unsafe(link_section = "thruputspecanchortextv0")
441                )]
442                fn __thruput_spec_anchor() -> usize {
443                    let mut acc = 0usize;
444                    $(acc = mix_anchor_fn(acc, <$name as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT);)+
445                    acc
446                }
447            }
448        )+
449    };
450}
451
452impl_tuple_type_ref_spec!(
453    (A),
454    (A, B),
455    (A, B, C),
456    (A, B, C, D),
457    (A, B, C, D, E),
458    (A, B, C, D, E, F),
459    (A, B, C, D, E, F, G),
460    (A, B, C, D, E, F, G, H),
461    (A, B, C, D, E, F, G, H, I),
462    (A, B, C, D, E, F, G, H, I, J),
463    (A, B, C, D, E, F, G, H, I, J, K),
464    (A, B, C, D, E, F, G, H, I, J, K, L),
465    (A, B, C, D, E, F, G, H, I, J, K, L, M),
466    (A, B, C, D, E, F, G, H, I, J, K, L, M, N),
467    (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O),
468    (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P),
469);
470
471/// Returns the spec id a `ContractTypeSpec` type names itself by.
472///
473/// The id, not a display name: read [`ContractSpecIdView::declared`] for the
474/// bare identifier, and see that type on why the segments travel apart.
475pub const fn symbol<T: ContractTypeSpec>() -> view::ContractSpecIdView<'static>
476{
477    T::TYPE_DEF.symbol()
478}
479
480/// Returns the spec type reference for `T` (e.g. `U32`, `Named("Foo")`).
481pub const fn type_ref<T: ContractTypeRefSpec + ?Sized>()
482-> view::ContractTypeView<'static> {
483    T::INFO.ty
484}