ab_system_contract_simple_wallet_base/
payload.rs1#[cfg(feature = "payload-builder")]
11pub mod builder;
12
13use ab_contracts_common::Address;
14use ab_contracts_common::env::{MethodContext, PreparedMethod};
15use ab_contracts_common::method::MethodFingerprint;
16use ab_contracts_io_type::MAX_ALIGNMENT;
17use ab_contracts_io_type::trivial_type::TrivialType;
18use core::ffi::c_void;
19use core::marker::PhantomData;
20use core::mem::MaybeUninit;
21use core::num::{NonZeroU8, NonZeroUsize};
22use core::ops::{Deref, DerefMut};
23use core::ptr::NonNull;
24use core::slice;
25
26#[derive(Debug, Copy, Clone, Eq, PartialEq, TrivialType)]
27#[repr(u8)]
28pub enum TransactionMethodContext {
29 Null,
31 Wallet,
33}
34
35impl TransactionMethodContext {
36 pub const fn try_from_u8(n: u8) -> Option<Self> {
39 Some(match n {
40 0 => Self::Null,
41 1 => Self::Wallet,
42 _ => {
43 return None;
44 }
45 })
46 }
47}
48
49#[derive(Debug, Copy, Eq, PartialEq, Clone)]
50pub enum TransactionInputType {
51 Value { alignment_power: u8 },
52 OutputIndex { output_index: u8 },
53}
54
55#[derive(Debug, Copy, Clone)]
64pub struct TransactionInput(TransactionInputType);
65
66impl TransactionInput {
67 pub const fn new_value(alignment: NonZeroU8) -> Option<Self> {
71 match alignment.get() {
72 1 | 2 | 4 | 8 | 16 => Some(Self(TransactionInputType::Value {
73 alignment_power: alignment.ilog2() as u8,
74 })),
75 _ => None,
76 }
77 }
78
79 pub const fn new_output_index(output_index: u8) -> Option<Self> {
83 if output_index > 0b0111_1111 {
84 return None;
85 }
86
87 Some(Self(TransactionInputType::OutputIndex { output_index }))
88 }
89
90 pub const fn from_u8(n: u8) -> Self {
92 if n & 0b1000_0000 == 0 {
94 Self(TransactionInputType::OutputIndex { output_index: n })
95 } else {
96 Self(TransactionInputType::Value {
97 alignment_power: n & 0b0111_1111,
98 })
99 }
100 }
101
102 pub const fn into_u8(self) -> u8 {
104 match self.0 {
106 TransactionInputType::Value { alignment_power } => 0b1000_0000 | alignment_power,
107 TransactionInputType::OutputIndex { output_index } => output_index,
108 }
109 }
110
111 pub const fn input_type(self) -> TransactionInputType {
113 self.0
114 }
115}
116
117#[derive(Debug, thiserror::Error)]
119pub enum TransactionPayloadDecoderError {
120 #[error("Payload too small")]
122 PayloadTooSmall,
123 #[error("`ExternalArgs` buffer too small")]
125 ExternalArgsBufferTooSmall,
126 #[error("Output index not found: {0}")]
128 OutputIndexNotFound(u8),
129 #[error("Alignment power is too large: {0}")]
131 AlignmentPowerTooLarge(u8),
132 #[error("Output buffer too small")]
134 OutputBufferTooSmall,
135 #[error("Output buffer offsets too small")]
137 OutputBufferOffsetsTooSmall,
138}
139
140#[derive(Debug)]
142pub struct TransactionPayloadDecoder<'a> {
143 payload: &'a [u8],
144 external_args_buffer: &'a mut [*mut c_void],
145 output_buffer: &'a mut [MaybeUninit<u128>],
146 output_buffer_cursor: usize,
147 output_buffer_offsets: &'a mut [MaybeUninit<(u32, u32)>],
148 output_buffer_offsets_cursor: usize,
149 map_context: fn(TransactionMethodContext) -> MethodContext,
150}
151
152impl<'a> TransactionPayloadDecoder<'a> {
153 #[inline]
166 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
167 pub fn new(
168 payload: &'a [u128],
169 external_args_buffer: &'a mut [*mut c_void],
170 output_buffer: &'a mut [MaybeUninit<u128>],
171 output_buffer_offsets: &'a mut [MaybeUninit<(u32, u32)>],
172 map_context: fn(TransactionMethodContext) -> MethodContext,
173 ) -> Self {
174 debug_assert_eq!(align_of_val(payload), usize::from(MAX_ALIGNMENT));
175 debug_assert_eq!(align_of_val(output_buffer), usize::from(MAX_ALIGNMENT));
176
177 let payload =
179 unsafe { slice::from_raw_parts(payload.as_ptr().cast::<u8>(), size_of_val(payload)) };
180
181 Self {
182 payload,
183 external_args_buffer,
184 output_buffer,
185 output_buffer_cursor: 0,
186 output_buffer_offsets,
187 output_buffer_offsets_cursor: 0,
188 map_context,
189 }
190 }
191}
192
193impl<'a> TransactionPayloadDecoder<'a> {
194 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
196 pub fn decode_next_method(
197 &mut self,
198 ) -> Result<Option<PreparedMethod<'_>>, TransactionPayloadDecoderError> {
199 TransactionPayloadDecoderInternal::<true>(self).decode_next_method()
200 }
201
202 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
208 pub unsafe fn decode_next_method_unchecked(&mut self) -> Option<PreparedMethod<'_>> {
209 TransactionPayloadDecoderInternal::<false>(self)
210 .decode_next_method()
211 .expect("No decoding errors are possible with trusted input; qed")
212 }
213}
214
215struct TransactionPayloadDecoderInternal<'tmp, 'decoder, const VERIFY: bool>(
219 &'tmp mut TransactionPayloadDecoder<'decoder>,
220);
221
222impl<'tmp, 'decoder, const VERIFY: bool> Deref
223 for TransactionPayloadDecoderInternal<'tmp, 'decoder, VERIFY>
224{
225 type Target = TransactionPayloadDecoder<'decoder>;
226
227 #[inline(always)]
228 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
229 fn deref(&self) -> &Self::Target {
230 self.0
231 }
232}
233
234impl<'tmp, 'decoder, const VERIFY: bool> DerefMut
235 for TransactionPayloadDecoderInternal<'tmp, 'decoder, VERIFY>
236{
237 #[inline(always)]
238 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
239 fn deref_mut(&mut self) -> &mut Self::Target {
240 self.0
241 }
242}
243
244impl<'tmp, 'decoder, const VERIFY: bool> TransactionPayloadDecoderInternal<'tmp, 'decoder, VERIFY> {
245 #[inline(always)]
246 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
247 fn decode_next_method(
248 mut self,
249 ) -> Result<Option<PreparedMethod<'decoder>>, TransactionPayloadDecoderError> {
250 if self.payload.len() <= usize::from(MAX_ALIGNMENT) {
251 return Ok(None);
252 }
253
254 let contract = self.get_trivial_type::<Address>()?;
255 let method_fingerprint = self.get_trivial_type::<MethodFingerprint>()?;
256 let method_context =
257 (self.map_context)(*self.get_trivial_type::<TransactionMethodContext>()?);
258 let num_slot_arguments = self.read_u8()?;
259 let num_input_arguments = self.read_u8()?;
260 let num_output_arguments = self.read_u8()?;
261
262 let expected_external_args_buffer_size = usize::from(num_slot_arguments)
265 + usize::from(num_input_arguments) * 2
266 + usize::from(num_output_arguments) * 3;
267 if expected_external_args_buffer_size > self.external_args_buffer.len() {
268 return Err(TransactionPayloadDecoderError::ExternalArgsBufferTooSmall);
269 }
270
271 let external_args =
272 NonNull::new(self.external_args_buffer.as_mut_ptr()).expect("Not null; qed");
273 {
274 let mut external_args_cursor = external_args;
275
276 for _ in 0..num_slot_arguments {
277 let slot = self.get_trivial_type::<Address>()?;
278 unsafe {
281 external_args_cursor.cast::<*const Address>().write(slot);
282 external_args_cursor = external_args_cursor.offset(1);
283 }
284 }
285
286 for _ in 0..num_input_arguments {
287 let (bytes, size) = match TransactionInput::from_u8(self.read_u8()?).input_type() {
288 TransactionInputType::Value { alignment_power } => {
289 let alignment = if VERIFY {
292 1_usize.checked_shl(u32::from(alignment_power)).ok_or(
293 TransactionPayloadDecoderError::AlignmentPowerTooLarge(
294 alignment_power,
295 ),
296 )?
297 } else {
298 unsafe { 1_usize.unchecked_shl(u32::from(alignment_power)) }
300 };
301
302 let size = self.get_trivial_type::<u32>()?;
303 let bytes = self.get_bytes(
304 *size,
305 NonZeroUsize::new(alignment).expect("Not zero; qed"),
306 )?;
307
308 (bytes, size)
309 }
310 TransactionInputType::OutputIndex { output_index } => {
311 let (size_offset, output_offset) = if VERIFY {
312 if usize::from(output_index) < self.output_buffer_offsets_cursor {
313 unsafe {
315 self.output_buffer_offsets
316 .get_unchecked(usize::from(output_index))
317 .assume_init()
318 }
319 } else {
320 return Err(TransactionPayloadDecoderError::OutputIndexNotFound(
321 output_index,
322 ));
323 }
324 } else {
325 unsafe {
327 self.output_buffer_offsets
328 .get_unchecked(usize::from(output_index))
329 .assume_init()
330 }
331 };
332
333 let size = unsafe {
336 self.output_buffer
337 .as_ptr()
338 .byte_add(size_offset as usize)
339 .cast::<u32>()
340 .as_ref_unchecked()
341 };
342 let bytes = unsafe {
345 let bytes_ptr = self
346 .output_buffer
347 .as_ptr()
348 .cast::<u8>()
349 .add(output_offset as usize);
350
351 slice::from_raw_parts(bytes_ptr, *size as usize)
352 };
353
354 (bytes, size)
355 }
356 };
357
358 unsafe {
361 external_args_cursor
362 .cast::<*const u8>()
363 .write(bytes.as_ptr());
364 external_args_cursor = external_args_cursor.offset(1);
365
366 external_args_cursor.cast::<*const u32>().write(size);
367 external_args_cursor = external_args_cursor.offset(1);
368 }
369 }
370
371 for _ in 0..num_output_arguments {
372 let recommended_capacity = self.get_trivial_type::<u32>()?;
373 let alignment_power = *self.get_trivial_type::<u8>()?;
374 let alignment = if VERIFY {
377 1_usize.checked_shl(u32::from(alignment_power)).ok_or(
378 TransactionPayloadDecoderError::AlignmentPowerTooLarge(alignment_power),
379 )?
380 } else {
381 unsafe { 1_usize.unchecked_shl(u32::from(alignment_power)) }
383 };
384
385 let (size, data) = self.allocate_output_buffer(
386 *recommended_capacity,
387 NonZeroUsize::new(alignment).expect("Not zero; qed"),
388 )?;
389
390 unsafe {
393 external_args_cursor.cast::<*mut u8>().write(data.as_ptr());
394 external_args_cursor = external_args_cursor.offset(1);
395
396 external_args_cursor.cast::<*mut u32>().write(size.as_ptr());
397 external_args_cursor = external_args_cursor.offset(1);
398
399 external_args_cursor
400 .cast::<*const u32>()
401 .write(recommended_capacity);
402 external_args_cursor = external_args_cursor.offset(1);
403 }
404 }
405 }
406
407 Ok(Some(PreparedMethod {
408 contract: *contract,
409 fingerprint: *method_fingerprint,
410 external_args: external_args.cast::<NonNull<c_void>>(),
411 method_context,
412 phantom: PhantomData,
413 }))
414 }
415
416 #[inline(always)]
417 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
418 fn get_trivial_type<T>(&mut self) -> Result<&'decoder T, TransactionPayloadDecoderError>
419 where
420 T: TrivialType,
421 {
422 self.ensure_alignment(NonZeroUsize::new(align_of::<T>()).expect("Not zero; qed"));
423
424 let bytes;
425 if VERIFY {
426 (bytes, self.payload) = self
427 .payload
428 .split_at_checked(size_of::<T>())
429 .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
430 } else {
431 (bytes, self.payload) = unsafe { self.payload.split_at_unchecked(size_of::<T>()) };
433 }
434
435 let value_ref = unsafe { bytes.as_ptr().cast::<T>().as_ref().expect("Not null; qed") };
437
438 Ok(value_ref)
439 }
440
441 #[inline(always)]
442 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
443 fn get_bytes(
444 &mut self,
445 size: u32,
446 alignment: NonZeroUsize,
447 ) -> Result<&'decoder [u8], TransactionPayloadDecoderError> {
448 self.ensure_alignment(alignment);
449
450 let bytes;
451 if VERIFY {
452 (bytes, self.payload) = self
453 .payload
454 .split_at_checked(size as usize)
455 .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
456 } else {
457 (bytes, self.payload) = unsafe { self.payload.split_at_unchecked(size as usize) };
459 }
460
461 Ok(bytes)
462 }
463
464 #[inline(always)]
465 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
466 fn read_u8(&mut self) -> Result<u8, TransactionPayloadDecoderError> {
467 let value;
468 if VERIFY {
469 (value, self.payload) = self
470 .payload
471 .split_at_checked(1)
472 .ok_or(TransactionPayloadDecoderError::PayloadTooSmall)?;
473 } else {
474 (value, self.payload) = unsafe { self.payload.split_at_unchecked(1) };
476 }
477
478 Ok(value[0])
479 }
480
481 #[inline(always)]
482 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
483 fn ensure_alignment(&mut self, alignment: NonZeroUsize) {
484 debug_assert!(alignment.get() <= usize::from(MAX_ALIGNMENT));
485
486 let unaligned_by = {
489 let mask = alignment
490 .get()
491 .checked_sub(1)
492 .expect("Left side is not zero; qed");
493 self.payload.len() & mask
494 };
495 self.payload = &self.payload[unaligned_by..];
496 }
497
498 #[inline(always)]
499 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
500 fn allocate_output_buffer(
501 &mut self,
502 capacity: u32,
503 output_alignment: NonZeroUsize,
504 ) -> Result<(NonNull<u32>, NonNull<u8>), TransactionPayloadDecoderError> {
505 if VERIFY && self.output_buffer_offsets.len() == self.output_buffer_offsets_cursor {
506 return Err(TransactionPayloadDecoderError::OutputBufferOffsetsTooSmall);
507 }
508
509 let (size_offset, size_ptr) = self
510 .allocate_output_buffer_ptr(
511 NonZeroUsize::new(align_of::<u32>()).expect("Not zero; qed"),
512 size_of::<u32>(),
513 )
514 .ok_or(TransactionPayloadDecoderError::OutputBufferTooSmall)?;
515 let (output_offset, output_ptr) = self
516 .allocate_output_buffer_ptr(output_alignment, capacity as usize)
517 .ok_or(TransactionPayloadDecoderError::OutputBufferTooSmall)?;
518
519 let output_buffer_offsets = unsafe {
521 let output_buffer_offsets_cursor = self.output_buffer_offsets_cursor;
523 self.output_buffer_offsets
524 .get_unchecked_mut(output_buffer_offsets_cursor)
525 };
526 output_buffer_offsets.write((size_offset as u32, output_offset as u32));
527 self.output_buffer_offsets_cursor += 1;
528
529 Ok((size_ptr, output_ptr))
530 }
531
532 #[inline(always)]
534 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
535 fn allocate_output_buffer_ptr<T>(
536 &mut self,
537 alignment: NonZeroUsize,
538 size: usize,
539 ) -> Option<(usize, NonNull<T>)> {
540 debug_assert!(alignment.get() <= usize::from(MAX_ALIGNMENT));
541
542 let unaligned_by = {
545 let mask = alignment
546 .get()
547 .checked_sub(1)
548 .expect("Left side is not zero; qed");
549 self.output_buffer_cursor & mask
550 };
551
552 let new_output_buffer_cursor = if VERIFY {
553 let new_output_buffer_cursor = self
554 .output_buffer_cursor
555 .checked_add(unaligned_by)?
556 .checked_add(size)?;
557
558 if new_output_buffer_cursor > size_of_val(self.output_buffer) {
559 return None;
560 }
561
562 new_output_buffer_cursor
563 } else {
564 unsafe {
566 self.output_buffer_cursor
567 .unchecked_add(unaligned_by)
568 .unchecked_add(size)
569 }
570 };
571
572 let (offset, buffer_ptr) = unsafe {
574 let offset = self.output_buffer_cursor.unchecked_add(unaligned_by);
575 let buffer_ptr = NonNull::new_unchecked(
576 self.output_buffer.as_mut_ptr().byte_add(offset).cast::<T>(),
577 );
578
579 (offset, buffer_ptr)
580 };
581 self.output_buffer_cursor = new_output_buffer_cursor;
582
583 Some((offset, buffer_ptr))
584 }
585}