thruput-contract-spec 0.58.0

Canonical contract spec model for Thruput contracts
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
//! The contract spec model shared by the macros, the SDK and spec tooling.
//!
//! It defines the traits through which contract types and events describe
//! themselves ([`ContractTypeSpec`], [`ContractEventSpec`],
//! [`ContractTypeName`]), zero-copy views of spec fragments, and the raw spec
//! section's wire-format versioning ([`format`]). With the `owned-model`
//! feature it also re-exports the owned spec types from `thruput-spec`.

#![no_std]

#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};

pub mod format;
pub mod view;

pub use format::{
    FINAL_SECTION_NAME, RAW_SECTION_NAME, RAW_SECTION_PREFIX, RAW_SPEC_FORMAT,
    SpecSection, classify_section,
};

#[cfg(feature = "owned-model")]
pub use thruput_spec::{
    ContractArrayType, ContractEnumDef, ContractEnumVariant, ContractEventDef,
    ContractExceptionType, ContractField, ContractMethodEffect,
    ContractSignedTokenPreimage, ContractSpec, ContractSpecId,
    ContractSpecInterface, ContractSpecMethod, ContractSpecString,
    ContractStructDef, ContractType, ContractTypeDef, ContractTypeDefKind,
    ContractTypeKind, ContractUnionCase, ContractUnionCaseKind,
    ContractUnionCaseType, ContractUnionDef, ContractVectorType,
};
#[cfg(feature = "owned-model")]
mod fragment;
#[cfg(feature = "owned-model")]
pub use fragment::*;
pub use view::*;
#[cfg(feature = "alloc")]
use xdrs::VecM;

mod thruput_xdr_types;

/// Implemented by `#[contract_type]` types to provide their spec type definition.
pub trait ContractTypeSpec {
    /// The type's definition (struct, enum, or union) as a const view.
    const TYPE_DEF: view::ContractTypeDefView<'static>;
}

/// Implemented by `#[derive(Event)]` types to provide their spec event
/// definition. The definition is emitted into the raw spec section only
/// where the event is declared (`emits(...)` on an interface) or exported
/// (`#[event_spec(export)]` on the derive).
#[diagnostic::on_unimplemented(
    message = "`{Self}` is not a contract event",
    label = "not a contract event",
    note = "add `#[derive(Event)]` to `{Self}` — `emits(...)` in \
            `#[contract(...)]` requires it"
)]
pub trait ContractEventSpec {
    /// The event's definition (name, signature, topics, fields) as a const view.
    const EVENT_DEF: view::ContractEventDefView<'static>;

    /// Hidden root symbol used to retain raw typedef fragments for the
    /// event's field types.
    #[doc(hidden)]
    const __THRUPUT_SPEC_ANCHOR_ROOT: fn() -> usize =
        Self::__thruput_spec_anchor;

    /// Hidden linker-anchor hook used to retain raw typedef fragments.
    #[doc(hidden)]
    #[cfg_attr(target_arch = "riscv32", inline(never))]
    #[cfg_attr(
        target_arch = "riscv32",
        unsafe(link_section = "thruputspecanchortextv0")
    )]
    fn __thruput_spec_anchor() -> usize {
        0
    }
}

/// Provides the spec-visible name for a contract type.
pub trait ContractTypeName {
    /// The fully qualified id this type is known by in the contract spec.
    ///
    /// This is an identity claim, not a label. A bare ident cannot tell two
    /// user-defined types apart — two crates, two modules of one crate, or two
    /// major versions of one crate may each define a `Meta` — so every
    /// reference in a spec resolves through this const, and two types agree on
    /// an id only if they are the same type. The crate and major-version
    /// segments are load-bearing: shortening an id to its declared name for
    /// display is each codegen's business, not the spec's.
    ///
    /// The segments travel apart rather than joined into
    /// `CRATE@MAJOR::module::path::Name`, because the joined form cannot be
    /// split back: `name = "..."` substitutes its value verbatim, `::` and
    /// all, so the boundary between module path and declared name is not
    /// recoverable from the string.
    ///
    /// `#[contract_type]` and `#[contract_exception]` derive it at the
    /// declaration, where `module_path!()` and the declaring crate's name and
    /// version are all in scope, so it cannot be taken from the wrong crate. A
    /// hand-written impl (for a type this crate does not declare) is asserting
    /// the id itself and is responsible for its accuracy, segment by segment.
    const NAME: view::ContractSpecIdView<'static>;
}

/// Bundles a type reference for use in contract spec signatures.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ContractTypeInfo<'a> {
    /// The type reference (e.g. `U32`, `Named("Foo")`, `Option(&U64)`).
    pub ty: view::ContractTypeView<'a>,
}

/// Trait for types that can appear in contract spec signatures.
/// Implemented for primitives, collections, and `#[contract_type]` types.
#[diagnostic::on_unimplemented(
    message = "`{Self}` cannot appear in a contract signature",
    label = "not a contract-spec type",
    note = "annotate the defining type with `#[contract_type]` (error \
            types: `#[contract_exception]`, events: `#[derive(Event)]`)"
)]
pub trait ContractTypeRefSpec {
    /// The type reference. Transitive type definitions are not carried here;
    /// they are retained via the linker-anchor mechanism
    /// (`__THRUPUT_SPEC_ANCHOR_ROOT`) and resolved by spec-tool.
    const INFO: ContractTypeInfo<'static>;

    /// How this type appears as a signed-token preimage
    /// (`ContractTypeView::SignedToken`'s payload): the type itself by
    /// default, or `None` for a type-erased payload (the SDK's `Opaque`
    /// overrides this).
    const SIGNED_PREIMAGE: Option<&'static view::ContractTypeView<'static>> =
        Some(&Self::INFO.ty);

    /// Hidden root symbol used to retain imported raw typedef fragments.
    #[doc(hidden)]
    const __THRUPUT_SPEC_ANCHOR_ROOT: fn() -> usize =
        Self::__thruput_spec_anchor;

    /// Hidden linker-anchor hook used to retain raw typedef fragments.
    #[doc(hidden)]
    #[cfg_attr(target_arch = "riscv32", inline(never))]
    #[cfg_attr(
        target_arch = "riscv32",
        unsafe(link_section = "thruputspecanchortextv0")
    )]
    fn __thruput_spec_anchor() -> usize {
        0
    }
}

#[doc(hidden)]
#[inline(always)]
pub fn mix_anchor_fn(current: usize, anchor: fn() -> usize) -> usize {
    current ^ core::hint::black_box(anchor as usize)
}

impl ContractTypeRefSpec for () {
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::Void,
    };
}

impl ContractTypeRefSpec for bool {
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::Bool,
    };
}

impl ContractTypeRefSpec for u8 {
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::U8,
    };
}

impl ContractTypeRefSpec for u32 {
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::U32,
    };
}

impl ContractTypeRefSpec for i32 {
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::I32,
    };
}

impl ContractTypeRefSpec for u64 {
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::U64,
    };
}

impl ContractTypeRefSpec for i64 {
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::I64,
    };
}

impl ContractTypeRefSpec for u128 {
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::U128,
    };
}

impl ContractTypeRefSpec for i128 {
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::I128,
    };
}

impl ContractTypeRefSpec for str {
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::String,
    };
}

// Byte payloads deliberately lower to `Vector(U8, ...)`, never to
// `ContractType::Bytes`, so the spec has a single canonical spelling for
// byte sequences (the macro tests assert `Bytes` never appears). The
// `Bytes` wire variant still exists; other-language SDKs must also avoid
// emitting it, or identical interfaces will hash/compare unequal.
//
// A slice is an unbounded vector of its element; for `[u8]` that is the
// byte-payload spelling above.
impl<T> ContractTypeRefSpec for [T]
where
    T: ContractTypeRefSpec,
{
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::Vector(view::ContractVectorTypeView {
            element: &type_ref::<T>(),
            max_len: u32::MAX,
        }),
    };

    #[cfg_attr(target_arch = "riscv32", inline(never))]
    #[cfg_attr(
        target_arch = "riscv32",
        unsafe(link_section = "thruputspecanchortextv0")
    )]
    fn __thruput_spec_anchor() -> usize {
        mix_anchor_fn(
            0,
            <T as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT,
        )
    }
}

impl ContractTypeRefSpec for &[u8] {
    const INFO: ContractTypeInfo<'static> =
        <[u8] as ContractTypeRefSpec>::INFO;
}

impl ContractTypeRefSpec for &str {
    const INFO: ContractTypeInfo<'static> = <str as ContractTypeRefSpec>::INFO;
}

impl<const MAX: u32> ContractTypeRefSpec for xdr_common::StringM<MAX> {
    const INFO: ContractTypeInfo<'static> = <str as ContractTypeRefSpec>::INFO;
}

impl<const MAX: usize> ContractTypeRefSpec for heapless::String<MAX> {
    const INFO: ContractTypeInfo<'static> = <str as ContractTypeRefSpec>::INFO;
}

impl<const MAX: u32> ContractTypeRefSpec for xdr_common::BytesM<MAX> {
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::Vector(view::ContractVectorTypeView {
            element: &view::ContractTypeView::U8,
            max_len: MAX,
        }),
    };
}

/// Bounded vector with no allocator: the same wire shape and spec as
/// `VecM<T, N>` (for `u8`, as `BytesM<N>`), but decodable in a no-`alloc`
/// guest.
impl<T, const N: usize> ContractTypeRefSpec for heapless::Vec<T, N>
where
    T: ContractTypeRefSpec,
{
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::Vector(view::ContractVectorTypeView {
            element: &type_ref::<T>(),
            max_len: N as u32,
        }),
    };

    #[cfg_attr(target_arch = "riscv32", inline(never))]
    #[cfg_attr(
        target_arch = "riscv32",
        unsafe(link_section = "thruputspecanchortextv0")
    )]
    fn __thruput_spec_anchor() -> usize {
        mix_anchor_fn(
            0,
            <T as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT,
        )
    }
}

impl<T, const N: usize> ContractTypeRefSpec for [T; N]
where
    T: ContractTypeRefSpec,
{
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::Array(view::ContractArrayTypeView {
            element: &type_ref::<T>(),
            len: N as u32,
        }),
    };

    #[cfg_attr(target_arch = "riscv32", inline(never))]
    #[cfg_attr(
        target_arch = "riscv32",
        unsafe(link_section = "thruputspecanchortextv0")
    )]
    fn __thruput_spec_anchor() -> usize {
        mix_anchor_fn(
            0,
            <T as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT,
        )
    }
}

#[cfg(feature = "alloc")]
impl ContractTypeRefSpec for String {
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::String,
    };
}

#[cfg(feature = "alloc")]
impl<T> ContractTypeRefSpec for Vec<T>
where
    T: ContractTypeRefSpec,
{
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::Vector(view::ContractVectorTypeView {
            element: &type_ref::<T>(),
            max_len: u32::MAX,
        }),
    };

    #[cfg_attr(target_arch = "riscv32", inline(never))]
    #[cfg_attr(
        target_arch = "riscv32",
        unsafe(link_section = "thruputspecanchortextv0")
    )]
    fn __thruput_spec_anchor() -> usize {
        mix_anchor_fn(
            0,
            <T as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT,
        )
    }
}

#[cfg(feature = "alloc")]
impl<T, const N: u32> ContractTypeRefSpec for VecM<T, N>
where
    T: ContractTypeRefSpec,
{
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::Vector(view::ContractVectorTypeView {
            element: &type_ref::<T>(),
            max_len: N,
        }),
    };

    #[cfg_attr(target_arch = "riscv32", inline(never))]
    #[cfg_attr(
        target_arch = "riscv32",
        unsafe(link_section = "thruputspecanchortextv0")
    )]
    fn __thruput_spec_anchor() -> usize {
        mix_anchor_fn(
            0,
            <T as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT,
        )
    }
}

impl<T> ContractTypeRefSpec for Option<T>
where
    T: ContractTypeRefSpec,
{
    const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
        ty: view::ContractTypeView::Option(&type_ref::<T>()),
    };

    #[cfg_attr(target_arch = "riscv32", inline(never))]
    #[cfg_attr(
        target_arch = "riscv32",
        unsafe(link_section = "thruputspecanchortextv0")
    )]
    fn __thruput_spec_anchor() -> usize {
        mix_anchor_fn(
            0,
            <T as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT,
        )
    }
}

#[cfg(feature = "alloc")]
impl<T> ContractTypeRefSpec for alloc::boxed::Box<T>
where
    T: ContractTypeRefSpec,
{
    const INFO: ContractTypeInfo<'static> = T::INFO;

    #[cfg_attr(target_arch = "riscv32", inline(never))]
    #[cfg_attr(
        target_arch = "riscv32",
        unsafe(link_section = "thruputspecanchortextv0")
    )]
    fn __thruput_spec_anchor() -> usize {
        mix_anchor_fn(
            0,
            <T as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT,
        )
    }
}

macro_rules! impl_tuple_type_ref_spec {
    ($(($($name:ident),+)),+ $(,)?) => {
        $(
            impl<$($name),+> ContractTypeRefSpec for ($($name,)+)
            where
                $($name: ContractTypeRefSpec),+
            {
                const INFO: ContractTypeInfo<'static> = ContractTypeInfo {
                    ty: view::ContractTypeView::Tuple(&[$(type_ref::<$name>()),+]),
                };

                #[cfg_attr(target_arch = "riscv32", inline(never))]
                #[cfg_attr(
                    target_arch = "riscv32",
                    unsafe(link_section = "thruputspecanchortextv0")
                )]
                fn __thruput_spec_anchor() -> usize {
                    let mut acc = 0usize;
                    $(acc = mix_anchor_fn(acc, <$name as ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT);)+
                    acc
                }
            }
        )+
    };
}

impl_tuple_type_ref_spec!(
    (A),
    (A, B),
    (A, B, C),
    (A, B, C, D),
    (A, B, C, D, E),
    (A, B, C, D, E, F),
    (A, B, C, D, E, F, G),
    (A, B, C, D, E, F, G, H),
    (A, B, C, D, E, F, G, H, I),
    (A, B, C, D, E, F, G, H, I, J),
    (A, B, C, D, E, F, G, H, I, J, K),
    (A, B, C, D, E, F, G, H, I, J, K, L),
    (A, B, C, D, E, F, G, H, I, J, K, L, M),
    (A, B, C, D, E, F, G, H, I, J, K, L, M, N),
    (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O),
    (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P),
);

/// Returns the spec id a `ContractTypeSpec` type names itself by.
///
/// The id, not a display name: read [`ContractSpecIdView::declared`] for the
/// bare identifier, and see that type on why the segments travel apart.
pub const fn symbol<T: ContractTypeSpec>() -> view::ContractSpecIdView<'static>
{
    T::TYPE_DEF.symbol()
}

/// Returns the spec type reference for `T` (e.g. `U32`, `Named("Foo")`).
pub const fn type_ref<T: ContractTypeRefSpec + ?Sized>()
-> view::ContractTypeView<'static> {
    T::INFO.ty
}