Skip to main content

thruput_sdk/
mintable.rs

1use crate::xdr::*;
2
3use crate::types::AuthTokenNode;
4
5#[cfg(feature = "alloc")]
6use crate::types::AuthTokenPayload;
7
8/// Marker trait for types that have a pre-signing (preimage) representation.
9///
10/// `Preimage` is intentionally unbound — the `WriteXDR + ReadXDR` requirement
11/// is deferred to the `Mintable` trait so that recursive contract_types can
12/// implement `MintableType` without triggering trait-solver overflow.
13pub trait MintableType: Sized + WriteXDR + for<'r> ReadXDR<'r> {
14    type Preimage;
15}
16
17pub struct AuthTokenPreimage<T> {
18    pub consumer: crate::types::ContractAddress,
19    pub data: T,
20}
21
22// Hand-written because the `ReadXDR`/`WriteXDR` derives don't synthesize
23// bounds for generic type params (`T: WriteXDR` on the impl); switch to
24// derives if xdrs grows serde-style bound synthesis for them.
25impl<T: WriteXDR> WriteXDR for AuthTokenPreimage<T> {
26    fn write_xdr<F, W: Writer<F>>(&self, w: &mut W) -> WriteResult<()> {
27        self.consumer.write_xdr(w)?;
28        self.data.write_xdr(w)
29    }
30
31    unsafe fn write_xdr_unchecked<F, W: WriterUnchecked<F>>(&self, w: &mut W) {
32        unsafe {
33            self.consumer.write_xdr_unchecked(w);
34            self.data.write_xdr_unchecked(w);
35        }
36    }
37}
38
39impl<'r, T: ReadXDR<'r>> ReadXDR<'r> for AuthTokenPreimage<T> {
40    fn read_xdr<R: Reader<'r>>(r: &mut R) -> ReadResult<Self> {
41        Ok(Self {
42            consumer: crate::types::ContractAddress::read_xdr(r)?,
43            data: T::read_xdr(r)?,
44        })
45    }
46}
47
48const AUTH_TOKEN_MAX_CHILDREN: usize =
49    thruput_xdr::auth_token::kAuthTokenMaxChildNodes as usize;
50
51pub type ChildNodeBuf = heapless::Vec<AuthTokenNode, AUTH_TOKEN_MAX_CHILDREN>;
52
53pub trait Mintable: MintableType
54where
55    Self::Preimage: WriteXDR + for<'r> ReadXDR<'r>,
56{
57    fn mint_into_parent(
58        preimage: Self::Preimage,
59        children: &mut ChildNodeBuf,
60    ) -> Self;
61}
62
63/// Build the [`AuthTokenNode`] tree for `preimage`: the payload is the
64/// encoded `T` (nested `Signed` fields become child nodes), and the node's
65/// consumer is `preimage.consumer`.
66#[cfg(feature = "alloc")]
67pub fn build_auth_token_node<T: Mintable>(
68    preimage: AuthTokenPreimage<T::Preimage>,
69) -> AuthTokenNode
70where
71    T::Preimage: WriteXDR + for<'r> ReadXDR<'r>,
72{
73    let mut children = ChildNodeBuf::new();
74    let payload_value = T::mint_into_parent(preimage.data, &mut children);
75    AuthTokenNode {
76        consumer_address: preimage.consumer,
77        payload: encode_payload(&payload_value),
78        child_nodes: children_to_vecm(children),
79    }
80}
81
82#[cfg(feature = "alloc")]
83pub fn encode_payload<T: WriteXDR>(value: &T) -> AuthTokenPayload {
84    let buf = crate::internal::encode_xdr(value).unwrap_or_else(|e| {
85        crate::exn::library_throw(crate::AuthTokenError::MintEncodeFailure(
86            crate::exn::FaultString::from_display(e),
87        ))
88    });
89    let bytes: xdrs::BytesM<{ thruput_xdr::auth_token::kAuthTokenMaxLen }> =
90        buf.as_slice().try_into().unwrap_or_else(|_| {
91            crate::exn::library_throw(
92                crate::AuthTokenError::MintEncodeFailure(
93                    crate::exn::FaultString::from_display(
94                        "auth token payload exceeds max length",
95                    ),
96                ),
97            )
98        });
99    AuthTokenPayload::from(bytes)
100}
101
102#[cfg(feature = "alloc")]
103fn children_to_vecm(
104    children: ChildNodeBuf,
105) -> xdrs::VecM<
106    AuthTokenNode,
107    { thruput_xdr::auth_token::kAuthTokenMaxChildNodes },
108> {
109    children.as_slice().try_into().unwrap_or_else(|_| {
110        crate::exn::library_throw(crate::AuthTokenError::MintEncodeFailure(
111            crate::exn::FaultString::from_display(format_args!(
112                "too many auth token children (max {})",
113                thruput_xdr::auth_token::kAuthTokenMaxChildNodes
114            )),
115        ))
116    })
117}
118
119#[cfg(feature = "alloc")]
120pub fn encode_auth_token_node(node: &AuthTokenNode) -> alloc::vec::Vec<u8> {
121    xdrs::txs::encode(node).unwrap_or_else(|e| {
122        crate::exn::library_throw(crate::AuthTokenError::MintEncodeFailure(
123            crate::exn::FaultString::from_display(e),
124        ))
125    })
126}
127
128// ── MintableType leaf impls ──
129
130macro_rules! impl_mintable_type_leaf {
131    ($($ty:ty),+ $(,)?) => {
132        $(
133            impl MintableType for $ty {
134                type Preimage = $ty;
135            }
136        )+
137    };
138}
139
140impl_mintable_type_leaf!(
141    u16,
142    u32,
143    u64,
144    u128,
145    i16,
146    i32,
147    i64,
148    i128,
149    bool,
150    (),
151    crate::types::ContractAddress,
152    crate::types::SHA256Hash,
153    crate::types::Ed25519PublicKey,
154    crate::types::Ed25519Signature,
155    crate::types::MlDsa65PublicKey,
156    crate::types::MlDsa65Signature,
157    crate::types::PublicKey,
158    crate::types::Signature,
159    crate::types::BlockHeight,
160    thruput_u256::U256,
161    thruput_u256::I256,
162    crate::types::ContractEntrypointID,
163);
164
165impl<const MAX: u32> MintableType for BytesM<MAX> {
166    type Preimage = BytesM<MAX>;
167}
168
169impl<const N: usize> MintableType for [u8; N] {
170    type Preimage = [u8; N];
171}
172
173impl<const N: usize, T: MintableType> MintableType for [T; N] {
174    type Preimage = [T::Preimage; N];
175}
176
177impl<T: MintableType> MintableType for Option<T> {
178    type Preimage = Option<T::Preimage>;
179}
180
181impl<const MAX: usize> MintableType for heapless::String<MAX> {
182    type Preimage = Self;
183}
184
185impl<const N: usize> MintableType for heapless::Vec<u8, N> {
186    type Preimage = Self;
187}
188
189#[cfg(feature = "alloc")]
190impl<T: MintableType + 'static, const MAX: u32> MintableType for VecM<T, MAX> {
191    type Preimage = VecM<T::Preimage, MAX>;
192}
193
194#[cfg(feature = "alloc")]
195impl<const MAX: u32> MintableType for StringM<MAX> {
196    type Preimage = StringM<MAX>;
197}
198
199#[cfg(feature = "alloc")]
200impl<T: MintableType> MintableType for alloc::vec::Vec<T> {
201    type Preimage = alloc::vec::Vec<T::Preimage>;
202}
203
204#[cfg(feature = "alloc")]
205impl<T: MintableType> MintableType for alloc::boxed::Box<T> {
206    type Preimage = alloc::boxed::Box<T::Preimage>;
207}
208
209// ── Mintable leaf impls ──
210
211macro_rules! impl_mintable_leaf {
212    ($($ty:ty),+ $(,)?) => {
213        $(
214            impl Mintable for $ty {
215                fn mint_into_parent(preimage: $ty, _children: &mut ChildNodeBuf) -> Self {
216                    preimage
217                }
218            }
219        )+
220    };
221}
222
223impl_mintable_leaf!(
224    u16,
225    u32,
226    u64,
227    u128,
228    i16,
229    i32,
230    i64,
231    i128,
232    bool,
233    (),
234    crate::types::ContractAddress,
235    crate::types::SHA256Hash,
236    crate::types::Ed25519PublicKey,
237    crate::types::Ed25519Signature,
238    crate::types::MlDsa65PublicKey,
239    crate::types::MlDsa65Signature,
240    crate::types::PublicKey,
241    crate::types::Signature,
242    crate::types::BlockHeight,
243    thruput_u256::U256,
244    thruput_u256::I256,
245    crate::types::ContractEntrypointID,
246);
247
248impl<const MAX: u32> Mintable for BytesM<MAX> {
249    fn mint_into_parent(
250        preimage: BytesM<MAX>,
251        _children: &mut ChildNodeBuf,
252    ) -> Self {
253        preimage
254    }
255}
256
257#[cfg(feature = "alloc")]
258impl<const MAX: u32> Mintable for StringM<MAX> {
259    fn mint_into_parent(
260        preimage: StringM<MAX>,
261        _children: &mut ChildNodeBuf,
262    ) -> Self {
263        preimage
264    }
265}
266
267impl<const MAX: usize> Mintable for heapless::String<MAX> {
268    fn mint_into_parent(preimage: Self, _children: &mut ChildNodeBuf) -> Self {
269        preimage
270    }
271}
272
273impl<const N: usize> Mintable for heapless::Vec<u8, N> {
274    fn mint_into_parent(preimage: Self, _children: &mut ChildNodeBuf) -> Self {
275        preimage
276    }
277}
278
279impl<const N: usize> Mintable for [u8; N] {
280    fn mint_into_parent(
281        preimage: [u8; N],
282        _children: &mut ChildNodeBuf,
283    ) -> Self {
284        preimage
285    }
286}
287
288impl<T: Mintable> Mintable for Option<T>
289where
290    T::Preimage: WriteXDR + for<'r> ReadXDR<'r>,
291    Option<T::Preimage>: WriteXDR + for<'r> ReadXDR<'r>,
292{
293    fn mint_into_parent(
294        preimage: Option<T::Preimage>,
295        children: &mut ChildNodeBuf,
296    ) -> Self {
297        preimage.map(|inner| T::mint_into_parent(inner, children))
298    }
299}
300
301#[cfg(feature = "alloc")]
302impl<T: Mintable + 'static, const MAX: u32> Mintable for VecM<T, MAX>
303where
304    T::Preimage: WriteXDR + for<'r> ReadXDR<'r> + 'static,
305    VecM<T::Preimage, MAX>: WriteXDR + for<'r> ReadXDR<'r>,
306{
307    fn mint_into_parent(
308        preimage: VecM<T::Preimage, MAX>,
309        children: &mut ChildNodeBuf,
310    ) -> Self {
311        let v: alloc::vec::Vec<T> = preimage
312            .0
313            .into_iter()
314            .map(|item| T::mint_into_parent(item, children))
315            .collect();
316        VecM(v)
317    }
318}
319
320impl<const N: usize, T: Mintable> Mintable for [T; N]
321where
322    T::Preimage: WriteXDR + for<'r> ReadXDR<'r>,
323    [T::Preimage; N]: WriteXDR + for<'r> ReadXDR<'r>,
324{
325    fn mint_into_parent(
326        preimage: [T::Preimage; N],
327        children: &mut ChildNodeBuf,
328    ) -> Self {
329        preimage.map(|inner| T::mint_into_parent(inner, children))
330    }
331}
332
333#[cfg(feature = "alloc")]
334impl<T: Mintable> Mintable for alloc::vec::Vec<T>
335where
336    T::Preimage: WriteXDR + for<'r> ReadXDR<'r>,
337    alloc::vec::Vec<T::Preimage>: WriteXDR + for<'r> ReadXDR<'r>,
338{
339    fn mint_into_parent(
340        preimage: alloc::vec::Vec<T::Preimage>,
341        children: &mut ChildNodeBuf,
342    ) -> Self {
343        preimage
344            .into_iter()
345            .map(|item| T::mint_into_parent(item, children))
346            .collect()
347    }
348}
349
350#[cfg(feature = "alloc")]
351impl<T: Mintable> Mintable for alloc::boxed::Box<T>
352where
353    T::Preimage: Sized + WriteXDR + for<'r> ReadXDR<'r>,
354    alloc::boxed::Box<T::Preimage>: WriteXDR + for<'r> ReadXDR<'r>,
355{
356    fn mint_into_parent(
357        preimage: alloc::boxed::Box<T::Preimage>,
358        children: &mut ChildNodeBuf,
359    ) -> Self {
360        alloc::boxed::Box::new(T::mint_into_parent(*preimage, children))
361    }
362}
363
364#[cfg(test)]
365mod tests {
366    use super::*;
367    use crate::signer::Signed;
368
369    #[test]
370    fn primitive_mintable_builds_leaf_node() {
371        let node = build_auth_token_node::<u64>(AuthTokenPreimage {
372            consumer: crate::types::ContractAddress::default(),
373            data: 42u64,
374        });
375        assert!(node.child_nodes.is_empty());
376    }
377
378    #[test]
379    fn build_node_sets_consumer_from_preimage() {
380        let consumer = crate::types::ContractAddress::from([0xAB; 32]);
381        let node = build_auth_token_node::<u64>(AuthTokenPreimage {
382            consumer,
383            data: 42u64,
384        });
385        assert_eq!(node.consumer_address, consumer);
386    }
387
388    #[test]
389    fn primitive_mint_into_parent_no_children() {
390        let mut children = ChildNodeBuf::new();
391        let val = u64::mint_into_parent(42u64, &mut children);
392        assert_eq!(val, 42);
393        assert!(children.is_empty());
394    }
395
396    #[test]
397    fn signed_mint_into_parent_pushes_child() {
398        let mut children = ChildNodeBuf::new();
399        let consumer = crate::types::ContractAddress::default();
400        let val = <Signed<u64>>::mint_into_parent(
401            AuthTokenPreimage {
402                consumer,
403                data: 42u64,
404            },
405            &mut children,
406        );
407        assert_eq!(val.handle(), u32::MAX);
408        assert_eq!(children.len(), 1);
409    }
410
411    #[test]
412    fn option_none_no_children() {
413        let mut children = ChildNodeBuf::new();
414        let val = <Option<Signed<u64>>>::mint_into_parent(None, &mut children);
415        assert!(val.is_none());
416        assert!(children.is_empty());
417    }
418
419    #[test]
420    fn option_some_signed_pushes_child() {
421        let mut children = ChildNodeBuf::new();
422        let consumer = crate::types::ContractAddress::default();
423        let val = <Option<Signed<u64>>>::mint_into_parent(
424            Some(AuthTokenPreimage {
425                consumer,
426                data: 42u64,
427            }),
428            &mut children,
429        );
430        assert!(val.is_some());
431        assert_eq!(children.len(), 1);
432    }
433}