ab_system_contract_simple_wallet_base/payload/
builder.rs1#[cfg(test)]
4mod tests;
5
6extern crate alloc;
7
8use crate::payload::{
9 FfiDataSizeCapacityRo, TransactionInput, TransactionMethodContext, TransactionSlot,
10};
11use ab_contracts_common::MAX_TOTAL_METHOD_ARGS;
12use ab_contracts_common::metadata::decode::{
13 ArgumentKind, MetadataDecodingError, MethodMetadataDecoder, MethodMetadataItem,
14 MethodsContainerKind,
15};
16use ab_contracts_common::method::{ExternalArgs, MethodFingerprint};
17use ab_core_primitives::address::Address;
18use ab_io_type::MAX_ALIGNMENT;
19use ab_io_type::metadata::IoTypeDetails;
20use ab_io_type::trivial_type::TrivialType;
21use alloc::vec::Vec;
22use core::ffi::c_void;
23use core::mem::MaybeUninit;
24use core::num::NonZeroU8;
25use core::ptr::NonNull;
26use core::{ptr, slice};
27
28const {
29 assert!(MAX_TOTAL_METHOD_ARGS as u32 == u8::BITS);
31}
32
33#[inline(always)]
39unsafe fn read_external_args<T>(external_args: &mut NonNull<c_void>) -> T {
40 unsafe {
42 let value = external_args.cast::<T>().read();
43 *external_args = external_args.byte_add(size_of::<T>());
44 value
45 }
46}
47
48#[derive(Debug, thiserror::Error)]
50pub enum TransactionPayloadBuilderError<'a> {
51 #[error("Metadata decoding error: {0}")]
53 MetadataDecodingError(MetadataDecodingError<'a>),
54 #[error("Too many arguments")]
56 TooManyArguments(u8),
57 #[error("Invalid alignment: {0}")]
59 InvalidAlignment(NonZeroU8),
60 #[error("Invalid output index: {0}")]
62 InvalidOutputIndex(u8),
63}
64
65#[derive(Debug, Clone)]
73pub struct TransactionPayloadBuilder {
74 payload: Vec<u8>,
75}
76
77impl Default for TransactionPayloadBuilder {
78 #[inline]
79 fn default() -> Self {
80 Self {
81 payload: Vec::with_capacity(1024),
82 }
83 }
84}
85
86impl TransactionPayloadBuilder {
87 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
95 pub fn with_method_call<Args>(
96 &mut self,
97 contract: &Address,
98 external_args: &Args,
99 method_context: TransactionMethodContext,
100 slot_output_index: &[Option<u8>],
101 input_output_index: &[Option<u8>],
102 ) -> Result<(), TransactionPayloadBuilderError<'static>>
103 where
104 Args: ExternalArgs,
105 {
106 let external_args = NonNull::from_ref(external_args).cast::<*const c_void>();
107
108 unsafe {
110 self.with_method_call_untyped(
111 contract,
112 external_args,
113 Args::METADATA,
114 &Args::FINGERPRINT,
115 method_context,
116 slot_output_index,
117 input_output_index,
118 )
119 }
120 }
121
122 #[expect(
129 clippy::too_many_arguments,
130 reason = "Only exceeds the limit due to being untyped, while above typed version is not"
131 )]
132 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
133 pub unsafe fn with_method_call_untyped<'a>(
134 &mut self,
135 contract: &Address,
136 external_args: NonNull<*const c_void>,
137 mut method_metadata: &'a [u8],
138 method_fingerprint: &MethodFingerprint,
139 method_context: TransactionMethodContext,
140 slot_output_index: &[Option<u8>],
141 input_output_index: &[Option<u8>],
142 ) -> Result<(), TransactionPayloadBuilderError<'a>> {
143 let external_args = &mut external_args.cast::<c_void>();
144
145 let (metadata_decoder, method_metadata_item) =
146 MethodMetadataDecoder::new(&mut method_metadata, MethodsContainerKind::Unknown)
147 .decode_next()
148 .map_err(TransactionPayloadBuilderError::MetadataDecodingError)?;
149 let mut metadata_decoder = metadata_decoder.without_auto_drain();
150
151 #[expect(
152 clippy::rest_pattern_accessible_field,
153 reason = "Do not need other fields"
154 )]
155 let MethodMetadataItem {
156 method_kind,
157 num_arguments,
158 ..
159 } = method_metadata_item;
160 let number_of_arguments = num_arguments.saturating_add(u8::from(method_kind.has_self()));
161
162 if number_of_arguments > MAX_TOTAL_METHOD_ARGS {
163 return Err(TransactionPayloadBuilderError::TooManyArguments(
164 number_of_arguments,
165 ));
166 }
167
168 self.extend_payload_with_alignment(contract.as_bytes(), align_of_val(contract));
169 self.extend_payload_with_alignment(
170 method_fingerprint.as_bytes(),
171 align_of_val(method_fingerprint),
172 );
173 self.push_payload_byte(method_context as u8);
174
175 let mut num_slot_arguments = 0u8;
176 let mut num_input_arguments = 0u8;
177 let mut num_output_arguments = 0u8;
178
179 let mut input_output_type_details =
180 [MaybeUninit::<IoTypeDetails>::uninit(); MAX_TOTAL_METHOD_ARGS as usize];
181 while let Some(item) = metadata_decoder
183 .decode_next()
184 .transpose()
185 .map_err(TransactionPayloadBuilderError::MetadataDecodingError)?
186 {
187 match item.argument_kind {
188 ArgumentKind::EnvRo
189 | ArgumentKind::EnvRw
190 | ArgumentKind::TmpRo
191 | ArgumentKind::TmpRw => {
192 }
194 ArgumentKind::SlotRo | ArgumentKind::SlotRw => {
195 num_slot_arguments += 1;
196 }
197 ArgumentKind::Input => {
198 input_output_type_details[usize::from(num_input_arguments)]
199 .write(item.type_details.unwrap_or(IoTypeDetails::bytes(0)));
200 num_input_arguments += 1;
201 }
202 ArgumentKind::Output | ArgumentKind::Return => {
203 input_output_type_details
204 [usize::from(num_input_arguments + num_output_arguments)]
205 .write(item.type_details.unwrap_or(IoTypeDetails::bytes(0)));
206 num_output_arguments += 1;
207 }
208 }
209 }
210 let (input_type_details, output_type_details) = unsafe {
212 let (input_type_details, output_type_details) =
213 input_output_type_details.split_at_unchecked(usize::from(num_input_arguments));
214 let output_type_details =
215 output_type_details.get_unchecked(..usize::from(num_output_arguments));
216
217 (
218 input_type_details.assume_init_ref(),
219 output_type_details.assume_init_ref(),
220 )
221 };
222
223 self.push_payload_byte(num_slot_arguments);
225 for slot_offset in 0..usize::from(num_slot_arguments) {
226 let slot_type = if let Some(&Some(output_index)) = slot_output_index.get(slot_offset) {
227 TransactionSlot::new_output_index(output_index).ok_or(
228 TransactionPayloadBuilderError::InvalidOutputIndex(output_index),
229 )?
230 } else {
231 TransactionSlot::new_address()
232 };
233 self.push_payload_byte(slot_type.into_u8());
234 }
235
236 self.push_payload_byte(num_input_arguments);
238 for (input_offset, type_details) in input_type_details.iter().enumerate() {
239 let input_type = if let Some(&Some(output_index)) = input_output_index.get(input_offset)
240 {
241 TransactionInput::new_output_index(output_index).ok_or(
242 TransactionPayloadBuilderError::InvalidOutputIndex(output_index),
243 )?
244 } else {
245 TransactionInput::new_value(type_details.alignment).ok_or(
246 TransactionPayloadBuilderError::InvalidAlignment(type_details.alignment),
247 )?
248 };
249 self.push_payload_byte(input_type.into_u8());
250 }
251
252 self.push_payload_byte(num_output_arguments);
254
255 for slot_offset in 0..usize::from(num_slot_arguments) {
256 let address = unsafe { read_external_args::<NonNull<Address>>(external_args).as_ref() };
258
259 if slot_output_index
260 .get(slot_offset)
261 .copied()
262 .flatten()
263 .is_none()
264 {
265 self.extend_payload_with_alignment(address.as_bytes(), align_of_val(address));
266 }
267 }
268
269 for (input_offset, type_details) in input_type_details.iter().enumerate() {
270 let (size, data) = unsafe {
272 let FfiDataSizeCapacityRo {
273 data_ptr,
274 size,
275 capacity: _,
276 } = read_external_args(external_args);
277
278 let data = slice::from_raw_parts(data_ptr.as_ptr().cast_const(), size as usize);
279
280 (size, data)
281 };
282
283 if input_output_index
284 .get(input_offset)
285 .copied()
286 .flatten()
287 .is_none()
288 {
289 self.extend_payload_with_alignment(&size.to_le_bytes(), align_of_val(&size));
290 self.extend_payload_with_alignment(data, type_details.alignment.get() as usize);
291 }
292 }
293
294 for type_details in output_type_details {
295 self.extend_payload_with_alignment(
296 &type_details.recommended_capacity.to_le_bytes(),
297 align_of_val(&type_details.recommended_capacity),
298 );
299 self.extend_payload_with_alignment(
300 &[type_details.alignment.ilog2() as u8],
301 align_of::<u8>(),
302 );
303 }
304
305 Ok(())
306 }
307
308 pub fn into_aligned_bytes(mut self) -> Vec<u128> {
337 self.ensure_alignment(usize::from(MAX_ALIGNMENT));
339
340 let output_len = self.payload.len() / size_of::<u128>();
341 let mut output = Vec::<u128>::with_capacity(output_len);
342
343 unsafe {
345 ptr::copy_nonoverlapping(
346 self.payload.as_ptr(),
347 output.as_mut_ptr().cast::<u8>(),
348 self.payload.len(),
349 );
350 output.set_len(output_len);
351 }
352
353 debug_assert_eq!(align_of_val(output.as_slice()), usize::from(MAX_ALIGNMENT));
354
355 output
356 }
357
358 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
359 fn extend_payload_with_alignment(&mut self, bytes: &[u8], alignment: usize) {
360 self.ensure_alignment(alignment);
361
362 self.payload.extend_from_slice(bytes);
363 }
364
365 fn ensure_alignment(&mut self, alignment: usize) {
367 debug_assert!(alignment <= usize::from(MAX_ALIGNMENT));
368
369 let unaligned_by = self.payload.len() & (alignment - 1);
372 if unaligned_by > 0 {
373 let padding_bytes = unsafe { alignment.unchecked_sub(unaligned_by) };
375 self.payload.resize(self.payload.len() + padding_bytes, 0);
376 }
377 }
378
379 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
380 fn push_payload_byte(&mut self, byte: u8) {
381 self.payload.push(byte);
382 }
383}