1use thruput_xdr::types::ContractAddress;
2
3use crate::mintable::MintableType;
4use crate::{AuthTokenError, FaultString, xdr::*};
5use core::marker::PhantomData;
6
7#[derive(Debug)]
12pub struct Opaque(());
13
14#[derive(Debug)]
15pub struct Signed<T> {
16 handle: u32,
17 _phantom: PhantomData<T>,
18}
19
20impl<T> WriteXDR for Signed<T> {
21 #[inline(always)]
22 fn write_xdr<F, W: Writer<F>>(&self, _w: &mut W) -> WriteResult<()> {
23 Ok(())
24 }
25
26 #[inline(always)]
27 unsafe fn write_xdr_unchecked<F, W: WriterUnchecked<F>>(
28 &self,
29 _w: &mut W,
30 ) {
31 }
32}
33
34impl<'r, T> ReadXDR<'r> for Signed<T> {
35 #[inline(always)]
36 fn read_xdr<R: Reader<'r>>(_r: &mut R) -> ReadResult<Self> {
37 Ok(Self {
38 handle: u32::MAX, _phantom: PhantomData,
40 })
41 }
42}
43
44impl<T> Signed<T> {
45 #[cfg(feature = "alloc")]
46 pub(crate) fn from_handle(handle: u32) -> Self {
47 Self {
48 handle,
49 _phantom: PhantomData,
50 }
51 }
52
53 #[doc(hidden)]
65 pub fn uninitialized() -> Self {
66 Self {
67 handle: u32::MAX,
68 _phantom: PhantomData,
69 }
70 }
71
72 pub fn handle(&self) -> u32 {
73 self.handle
74 }
75}
76
77impl Signed<Opaque> {
78 pub fn assume<T: MintableType>(self) -> Signed<T> {
79 Signed {
80 handle: self.handle,
81 _phantom: PhantomData,
82 }
83 }
84}
85
86#[cfg(feature = "alloc")]
87pub trait AssumeTokens: Sized {
88 fn try_assume_tokens(
98 tokens: alloc::vec::Vec<Signed<Opaque>>,
99 ) -> Result<Self, crate::exn::CallingError>;
100
101 #[cfg_attr(not(target_arch = "riscv32"), track_caller)]
105 fn assume_tokens(tokens: alloc::vec::Vec<Signed<Opaque>>) -> Self {
106 use crate::re_throw::OrReThrow;
107 Self::try_assume_tokens(tokens).or_re_throw()
108 }
109}
110
111#[cfg(feature = "alloc")]
113fn check_token_count(
114 got: usize,
115 expected: usize,
116) -> Result<(), crate::exn::CallingError> {
117 if got == expected {
118 Ok(())
119 } else {
120 Err(crate::exn::CallingError::DecodingSuccess(
121 FaultString::from_display(format_args!(
122 "expected {expected} auth tokens, got {got}"
123 )),
124 ))
125 }
126}
127
128#[cfg(feature = "alloc")]
129macro_rules! impl_assume_tokens {
130 () => {
131 impl AssumeTokens for () {
132 fn try_assume_tokens(
133 tokens: alloc::vec::Vec<Signed<Opaque>>,
134 ) -> Result<Self, crate::exn::CallingError> {
135 check_token_count(tokens.len(), 0)
136 }
137 }
138 };
139 ($($idx:tt : $T:ident),+) => {
140 impl<$($T: MintableType),+> AssumeTokens for ($(Signed<$T>,)+) {
141 fn try_assume_tokens(
142 tokens: alloc::vec::Vec<Signed<Opaque>>,
143 ) -> Result<Self, crate::exn::CallingError> {
144 const N: usize = impl_assume_tokens!(@count $($T),+);
145 check_token_count(tokens.len(), N)?;
146 let mut iter = tokens.into_iter();
147 Ok(($(impl_assume_tokens!(@take iter, $T),)+))
148 }
149 }
150 };
151 (@take $iter:ident, $T:ident) => {
152 $iter.next().unwrap().assume::<$T>()
153 };
154 (@count $head:ident $(, $tail:ident)*) => {
155 1 + impl_assume_tokens!(@count $($tail),*)
156 };
157 (@count) => { 0 };
158}
159
160#[cfg(feature = "alloc")]
161impl_assume_tokens!();
162#[cfg(feature = "alloc")]
163impl_assume_tokens!(0: A);
164#[cfg(feature = "alloc")]
165impl_assume_tokens!(0: A, 1: B);
166#[cfg(feature = "alloc")]
167impl_assume_tokens!(0: A, 1: B, 2: C);
168#[cfg(feature = "alloc")]
169impl_assume_tokens!(0: A, 1: B, 2: C, 3: D);
170#[cfg(feature = "alloc")]
171impl_assume_tokens!(0: A, 1: B, 2: C, 3: D, 4: E);
172#[cfg(feature = "alloc")]
173impl_assume_tokens!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F);
174#[cfg(feature = "alloc")]
175impl_assume_tokens!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G);
176#[cfg(feature = "alloc")]
177impl_assume_tokens!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H);
178#[cfg(feature = "alloc")]
179impl_assume_tokens!(0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I);
180#[cfg(feature = "alloc")]
181impl_assume_tokens!(
182 0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J
183);
184#[cfg(feature = "alloc")]
185impl_assume_tokens!(
186 0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K
187);
188#[cfg(feature = "alloc")]
189impl_assume_tokens!(
190 0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K, 11: L
191);
192#[cfg(feature = "alloc")]
193impl_assume_tokens!(
194 0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K,
195 11: L, 12: M
196);
197#[cfg(feature = "alloc")]
198impl_assume_tokens!(
199 0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K,
200 11: L, 12: M, 13: N
201);
202#[cfg(feature = "alloc")]
203impl_assume_tokens!(
204 0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K,
205 11: L, 12: M, 13: N, 14: O
206);
207#[cfg(feature = "alloc")]
208impl_assume_tokens!(
209 0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K,
210 11: L, 12: M, 13: N, 14: O, 15: P
211);
212#[cfg(feature = "alloc")]
213impl_assume_tokens!(
214 0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K,
215 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q
216);
217#[cfg(feature = "alloc")]
218impl_assume_tokens!(
219 0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K,
220 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R
221);
222#[cfg(feature = "alloc")]
223impl_assume_tokens!(
224 0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K,
225 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S
226);
227#[cfg(feature = "alloc")]
228impl_assume_tokens!(
229 0: A, 1: B, 2: C, 3: D, 4: E, 5: F, 6: G, 7: H, 8: I, 9: J, 10: K,
230 11: L, 12: M, 13: N, 14: O, 15: P, 16: Q, 17: R, 18: S, 19: T
231);
232
233use crate::collect_auth_handles::{AuthHandleBuf, CollectAuthHandles};
234use crate::receive_auth_handles::ReceiveAuthHandles;
235
236impl<T> CollectAuthHandles for Signed<T> {
237 #[inline(always)]
238 fn collect_auth_handles(
239 &self,
240 out: &mut AuthHandleBuf,
241 ) -> Result<(), FaultString> {
242 debug_assert_ne!(
243 self.handle,
244 u32::MAX,
245 "Signed handle not initialized"
246 );
247 #[cfg(not(feature = "alloc"))]
248 match out.push(self.handle) {
249 Ok(()) => Ok(()),
250 Err(e) => Err(FaultString::from_display(e)),
251 }?;
252 #[cfg(feature = "alloc")]
253 out.push(self.handle);
254 Ok(())
255 }
256}
257
258impl<T> ReceiveAuthHandles for Signed<T> {
259 fn receive_auth_handles(
260 &mut self,
261 handles: &mut impl Iterator<Item = u32>,
262 ) -> Result<(), AuthTokenError> {
263 match handles.next() {
264 Some(handle) => {
265 self.handle = handle;
266 Ok(())
267 }
268 None => Err(AuthTokenError::ReceiveAuthHandlesFailure(
269 FaultString::from_display("missing auth handle for Signed"),
270 )),
271 }
272 }
273}
274
275impl thruput_contract_spec::ContractTypeRefSpec for Opaque {
281 const INFO: thruput_contract_spec::ContractTypeInfo<'static> =
282 thruput_contract_spec::ContractTypeInfo {
283 ty: thruput_contract_spec::view::ContractTypeView::Void,
284 };
285
286 const SIGNED_PREIMAGE: Option<
287 &'static thruput_contract_spec::view::ContractTypeView<'static>,
288 > = None;
289}
290
291impl<T> thruput_contract_spec::ContractTypeRefSpec for Signed<T>
296where
297 T: thruput_contract_spec::ContractTypeRefSpec,
298{
299 const INFO: thruput_contract_spec::ContractTypeInfo<'static> =
300 thruput_contract_spec::ContractTypeInfo {
301 ty: thruput_contract_spec::view::ContractTypeView::SignedToken(
302 T::SIGNED_PREIMAGE,
303 ),
304 };
305
306 #[cfg_attr(target_arch = "riscv32", inline(never))]
307 #[cfg_attr(
308 target_arch = "riscv32",
309 unsafe(link_section = "thruputspecanchortextv0")
310 )]
311 fn __thruput_spec_anchor() -> usize {
312 thruput_contract_spec::mix_anchor_fn(
313 0,
314 <T as thruput_contract_spec::ContractTypeRefSpec>::__THRUPUT_SPEC_ANCHOR_ROOT,
315 )
316 }
317}
318
319#[cfg(feature = "alloc")]
320use crate::mintable::{AuthTokenPreimage, Mintable};
321
322impl<T: MintableType> MintableType for Signed<T> {
323 type Preimage =
324 crate::mintable::AuthTokenPreimage<<T as MintableType>::Preimage>;
325}
326
327#[cfg(feature = "alloc")]
328impl<T: Mintable> Mintable for Signed<T>
329where
330 T::Preimage: crate::xdr::WriteXDR + for<'r> crate::xdr::ReadXDR<'r>,
331 AuthTokenPreimage<T::Preimage>:
332 crate::xdr::WriteXDR + for<'r> crate::xdr::ReadXDR<'r>,
333{
334 fn mint_into_parent(
335 preimage: AuthTokenPreimage<<T as MintableType>::Preimage>,
336 children: &mut crate::mintable::ChildNodeBuf,
337 ) -> Self {
338 let node = crate::mintable::build_auth_token_node::<T>(preimage);
339 children.push(node).expect("too many auth token children");
340 Signed::uninitialized()
342 }
343}
344
345#[cfg(feature = "alloc")]
358pub fn mint_auth_token<T: Mintable>(
359 expiry: thruput_xdr::types::BlockHeight,
360 preimage: crate::mintable::AuthTokenPreimage<
361 <T as MintableType>::Preimage,
362 >,
363) -> Signed<T>
364where
365 T::Preimage: crate::xdr::WriteXDR + for<'r> crate::xdr::ReadXDR<'r>,
366 AuthTokenPreimage<T::Preimage>:
367 crate::xdr::WriteXDR + for<'r> crate::xdr::ReadXDR<'r>,
368{
369 let root_node = crate::mintable::build_auth_token_node::<T>(preimage);
370 let buf = crate::mintable::encode_auth_token_node(&root_node);
371
372 mint_encoded_node(expiry, buf.as_ref())
373}
374
375#[cfg(feature = "alloc")]
387pub fn mint_opaque_auth_token(
388 expiry: thruput_xdr::types::BlockHeight,
389 encoded_node: &[u8],
390) -> Signed<Opaque> {
391 mint_encoded_node(expiry, encoded_node)
392}
393
394#[cfg(feature = "alloc")]
396fn mint_encoded_node<T>(
397 expiry: thruput_xdr::types::BlockHeight,
398 encoded_node: &[u8],
399) -> Signed<T> {
400 use crate::re_throw::OrSyscallThrow;
401 use crate::sys::SyscallNo;
402
403 let mut token_id: core::mem::MaybeUninit<u32> =
404 core::mem::MaybeUninit::uninit();
405
406 crate::sys::uninit_MintAuthToken(&expiry, encoded_node, &mut token_id)
407 .or_syscall_throw(SyscallNo::kMintAuthToken);
408
409 let handle = unsafe { token_id.assume_init() };
410 Signed::from_handle(handle)
411}
412
413impl<T> Signed<T>
414where
415 T: for<'a> ReadXDR<'a> + crate::ReceiveAuthHandles,
416{
417 pub fn consume(self) -> (T, ContractAddress) {
438 debug_assert_ne!(
439 self.handle,
440 u32::MAX,
441 "Signed handle not initialized"
442 );
443
444 use crate::internal::MUBuf;
445 use crate::re_throw::OrSyscallThrow;
446 use crate::sys::SyscallNo;
447 use core::mem::MaybeUninit;
448
449 let mut payload_buf: MUBuf<500> = MUBuf::new();
450 let mut payload_written: MaybeUninit<
451 crate::types::RuntimeMemoryLength,
452 > = MaybeUninit::uninit();
453 let mut child_start: MaybeUninit<u32> = MaybeUninit::uninit();
454 let mut child_count: MaybeUninit<u32> = MaybeUninit::uninit();
455
456 let mut minter_address: MaybeUninit<ContractAddress> =
457 MaybeUninit::uninit();
458
459 crate::sys::uninit_ConsumeAuthToken(
460 self.handle,
461 payload_buf.spare_capacity_mut(),
462 &mut minter_address,
463 &mut payload_written,
464 &mut child_start,
465 &mut child_count,
466 )
467 .or_syscall_throw(SyscallNo::kConsumeAuthToken);
468
469 let written = unsafe { payload_written.assume_init() } as usize;
470 unsafe { payload_buf.set_len(written) };
471
472 let start = unsafe { child_start.assume_init() };
473 let count = unsafe { child_count.assume_init() };
474 let minter_address = unsafe { minter_address.assume_init() };
475
476 let mut value: T =
477 cxs::decode(payload_buf.as_ref()).unwrap_or_else(|e| {
478 crate::exn::library_throw(
479 crate::AuthTokenError::ConsumeDecodeFailure(
480 crate::exn::FaultString::from_display(e),
481 ),
482 )
483 });
484
485 let end = start
486 .checked_add(count)
487 .expect("child handle range overflow");
488 let mut iter = start..end;
489 value
490 .receive_auth_handles(&mut iter)
491 .unwrap_or_else(|e| crate::exn::library_throw(e));
492
493 if iter.next().is_some() {
494 crate::exn::library_throw(
495 crate::AuthTokenError::ReceiveAuthHandlesFailure(
496 FaultString::from_display(
497 "unexpected auth handle remaining",
498 ),
499 ),
500 )
501 }
502
503 (value, minter_address)
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use super::*;
510 #[cfg(feature = "alloc")]
511 use alloc::vec;
512 use core::mem::MaybeUninit;
513
514 #[test]
515 fn signer_xdr_writes_zero_bytes() {
516 let signer: Signed<u64> = Signed::from_handle(42);
517 let mut buf: [MaybeUninit<u8>; 32] = [MaybeUninit::uninit(); 32];
518 let written = cxs::encode_into(&signer, &mut buf).unwrap();
519 assert_eq!(written, 0);
520 }
521
522 #[test]
523 fn signer_xdr_reads_sentinel() {
524 let decoded: Signed<u64> = cxs::decode(&[]).unwrap();
525 assert_eq!(decoded.handle(), u32::MAX);
526 }
527
528 #[test]
529 fn signer_xdr_in_tuple_excludes_handle() {
530 let args: (u64, Signed<u32>, i32) = (100, Signed::from_handle(7), -1);
531
532 let mut buf: [MaybeUninit<u8>; 64] = [MaybeUninit::uninit(); 64];
533 let written = cxs::encode_into(&args, &mut buf).unwrap();
534
535 let init: &[u8] = unsafe {
536 core::slice::from_raw_parts(buf.as_ptr() as *const u8, written)
537 };
538
539 let decoded: (u64, Signed<u32>, i32) = cxs::decode(init).unwrap();
541 assert_eq!(decoded.0, 100);
542 assert_eq!(decoded.1.handle(), u32::MAX); assert_eq!(decoded.2, -1);
544 }
545
546 #[test]
547 fn signer_receive_auth_handles() {
548 use crate::collect_auth_handles::CollectAuthHandles;
549
550 let mut args: (u64, Signed<u32>, i32, Signed<bool>) = (
552 100,
553 Signed::from_handle(u32::MAX),
554 -1,
555 Signed::from_handle(u32::MAX),
556 );
557
558 let mut iter = [10u32, 20u32].into_iter();
560 args.receive_auth_handles(&mut iter).unwrap();
561
562 assert_eq!(args.1.handle(), 10);
563 assert_eq!(args.3.handle(), 20);
564
565 let mut buf = crate::collect_auth_handles::AuthHandleBuf::new();
567 args.collect_auth_handles(&mut buf).unwrap();
568 assert_eq!(buf.as_slice(), &[10, 20]);
569 }
570
571 #[test]
572 fn signed_spec_is_signed_token_of_payload_type() {
573 use thruput_contract_spec::type_ref;
574 use thruput_contract_spec::view::ContractTypeView;
575
576 assert_eq!(
577 type_ref::<Signed<u64>>(),
578 ContractTypeView::SignedToken(Some(&ContractTypeView::U64))
579 );
580 assert_eq!(
582 type_ref::<Signed<Opaque>>(),
583 ContractTypeView::SignedToken(None)
584 );
585 assert_eq!(
587 type_ref::<Signed<Signed<u32>>>(),
588 ContractTypeView::SignedToken(Some(
589 &ContractTypeView::SignedToken(Some(&ContractTypeView::U32))
590 ))
591 );
592 }
593
594 #[test]
595 fn assume_preserves_handle() {
596 let opaque: Signed<Opaque> = Signed::from_handle(42);
597 let typed: Signed<u64> = opaque.assume();
598 assert_eq!(typed.handle(), 42);
599 }
600
601 #[cfg(feature = "alloc")]
602 #[test]
603 fn try_assume_tokens_unit() {
604 use crate::signer::AssumeTokens;
605 let tokens = vec![];
606 let () = AssumeTokens::try_assume_tokens(tokens).unwrap();
607 }
608
609 #[cfg(feature = "alloc")]
610 #[test]
611 fn try_assume_tokens_single() {
612 use crate::signer::AssumeTokens;
613 let tokens = vec![Signed::<Opaque>::from_handle(10)];
614 let (a,): (Signed<u64>,) =
615 AssumeTokens::try_assume_tokens(tokens).unwrap();
616 assert_eq!(a.handle(), 10);
617 }
618
619 #[cfg(feature = "alloc")]
620 #[test]
621 fn try_assume_tokens_triple() {
622 use crate::signer::AssumeTokens;
623 let tokens = vec![
624 Signed::<Opaque>::from_handle(10),
625 Signed::<Opaque>::from_handle(20),
626 Signed::<Opaque>::from_handle(30),
627 ];
628 let (a, b, c): (Signed<u64>, Signed<u32>, Signed<bool>) =
629 AssumeTokens::try_assume_tokens(tokens).unwrap();
630 assert_eq!(a.handle(), 10);
631 assert_eq!(b.handle(), 20);
632 assert_eq!(c.handle(), 30);
633 }
634
635 #[cfg(feature = "alloc")]
636 #[test]
637 fn try_assume_tokens_pair() {
638 use crate::signer::AssumeTokens;
639 let tokens = vec![
640 Signed::<Opaque>::from_handle(10),
641 Signed::<Opaque>::from_handle(20),
642 ];
643 let (a, b): (Signed<u64>, Signed<u32>) =
644 AssumeTokens::try_assume_tokens(tokens).unwrap();
645 assert_eq!(a.handle(), 10);
646 assert_eq!(b.handle(), 20);
647 }
648
649 #[cfg(feature = "alloc")]
650 #[test]
651 fn try_assume_tokens_too_many_is_decode_error() {
652 use crate::exn::CallingError;
653 use crate::signer::AssumeTokens;
654 let tokens = vec![
655 Signed::<Opaque>::from_handle(1),
656 Signed::<Opaque>::from_handle(2),
657 Signed::<Opaque>::from_handle(3),
658 ];
659 let r: Result<(Signed<u64>, Signed<u32>), CallingError> =
660 AssumeTokens::try_assume_tokens(tokens);
661 assert!(
662 matches!(r, Err(CallingError::DecodingSuccess(_))),
663 "expected DecodingSuccess, got {r:?}"
664 );
665 }
666
667 #[cfg(feature = "alloc")]
668 #[test]
669 fn try_assume_tokens_too_few_is_decode_error() {
670 use crate::exn::CallingError;
671 use crate::signer::AssumeTokens;
672 let tokens = vec![Signed::<Opaque>::from_handle(1)];
673 let r: Result<(Signed<u64>, Signed<u32>), CallingError> =
674 AssumeTokens::try_assume_tokens(tokens);
675 assert!(
676 matches!(r, Err(CallingError::DecodingSuccess(_))),
677 "expected DecodingSuccess, got {r:?}"
678 );
679 }
680
681 #[cfg(feature = "alloc")]
682 #[test]
683 fn try_assume_tokens_unit_rejects_nonempty() {
684 use crate::exn::CallingError;
685 use crate::signer::AssumeTokens;
686 let tokens = vec![Signed::<Opaque>::from_handle(1)];
687 let r: Result<(), CallingError> =
688 AssumeTokens::try_assume_tokens(tokens);
689 assert!(
690 matches!(r, Err(CallingError::DecodingSuccess(_))),
691 "expected DecodingSuccess, got {r:?}"
692 );
693 }
694}