ab_system_contract_simple_wallet_base/payload/
builder.rs1#[cfg(test)]
4mod tests;
5
6extern crate alloc;
7
8use crate::payload::{TransactionInput, TransactionMethodContext, TransactionSlot};
9use ab_contracts_common::MAX_TOTAL_METHOD_ARGS;
10use ab_contracts_common::metadata::decode::{
11 ArgumentKind, MetadataDecodingError, MethodMetadataDecoder, MethodMetadataItem,
12 MethodsContainerKind,
13};
14use ab_contracts_common::method::{ExternalArgs, MethodFingerprint};
15use ab_core_primitives::address::Address;
16use ab_io_type::MAX_ALIGNMENT;
17use ab_io_type::metadata::IoTypeDetails;
18use ab_io_type::trivial_type::TrivialType;
19use alloc::vec::Vec;
20use core::ffi::c_void;
21use core::mem::MaybeUninit;
22use core::num::NonZeroU8;
23use core::ptr::NonNull;
24use core::{ptr, slice};
25
26const _: () = {
27 assert!(MAX_TOTAL_METHOD_ARGS as u32 == u8::BITS);
29};
30
31#[derive(Debug, thiserror::Error)]
33pub enum TransactionPayloadBuilderError<'a> {
34 #[error("Metadata decoding error: {0}")]
36 MetadataDecodingError(MetadataDecodingError<'a>),
37 #[error("Too many arguments")]
39 TooManyArguments(u8),
40 #[error("Invalid alignment: {0}")]
42 InvalidAlignment(NonZeroU8),
43 #[error("Invalid output index: {0}")]
45 InvalidOutputIndex(u8),
46}
47
48#[derive(Debug, Clone)]
56pub struct TransactionPayloadBuilder {
57 payload: Vec<u8>,
58}
59
60impl Default for TransactionPayloadBuilder {
61 #[inline]
62 fn default() -> Self {
63 Self {
64 payload: Vec::with_capacity(1024),
65 }
66 }
67}
68
69impl TransactionPayloadBuilder {
70 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
78 pub fn with_method_call<Args>(
79 &mut self,
80 contract: &Address,
81 external_args: &Args,
82 method_context: TransactionMethodContext,
83 slot_output_index: &[Option<u8>],
84 input_output_index: &[Option<u8>],
85 ) -> Result<(), TransactionPayloadBuilderError<'static>>
86 where
87 Args: ExternalArgs,
88 {
89 let external_args = NonNull::from_ref(external_args).cast::<*const c_void>();
90
91 unsafe {
93 self.with_method_call_untyped(
94 contract,
95 &external_args,
96 Args::METADATA,
97 &Args::FINGERPRINT,
98 method_context,
99 slot_output_index,
100 input_output_index,
101 )
102 }
103 }
104
105 #[expect(
112 clippy::too_many_arguments,
113 reason = "Only exceeds the limit due to being untyped, while above typed version is not"
114 )]
115 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
116 pub unsafe fn with_method_call_untyped<'a>(
117 &mut self,
118 contract: &Address,
119 external_args: &NonNull<*const c_void>,
120 mut method_metadata: &'a [u8],
121 method_fingerprint: &MethodFingerprint,
122 method_context: TransactionMethodContext,
123 slot_output_index: &[Option<u8>],
124 input_output_index: &[Option<u8>],
125 ) -> Result<(), TransactionPayloadBuilderError<'a>> {
126 let mut external_args = *external_args;
127
128 let (mut metadata_decoder, method_metadata_item) =
129 MethodMetadataDecoder::new(&mut method_metadata, MethodsContainerKind::Unknown)
130 .decode_next()
131 .map_err(TransactionPayloadBuilderError::MetadataDecodingError)?;
132
133 let MethodMetadataItem {
134 method_kind,
135 num_arguments,
136 ..
137 } = method_metadata_item;
138 let number_of_arguments =
139 num_arguments.saturating_add(if method_kind.has_self() { 1 } else { 0 });
140
141 if number_of_arguments > MAX_TOTAL_METHOD_ARGS {
142 return Err(TransactionPayloadBuilderError::TooManyArguments(
143 number_of_arguments,
144 ));
145 }
146
147 self.extend_payload_with_alignment(contract.as_bytes(), align_of_val(contract));
148 self.extend_payload_with_alignment(
149 method_fingerprint.as_bytes(),
150 align_of_val(method_fingerprint),
151 );
152 self.push_payload_byte(method_context as u8);
153
154 let mut num_slot_arguments = 0u8;
155 let mut num_input_arguments = 0u8;
156 let mut num_output_arguments = 0u8;
157
158 let mut input_output_type_details =
159 [MaybeUninit::<IoTypeDetails>::uninit(); MAX_TOTAL_METHOD_ARGS as usize];
160 while let Some(item) = metadata_decoder
162 .decode_next()
163 .transpose()
164 .map_err(TransactionPayloadBuilderError::MetadataDecodingError)?
165 {
166 match item.argument_kind {
167 ArgumentKind::EnvRo
168 | ArgumentKind::EnvRw
169 | ArgumentKind::TmpRo
170 | ArgumentKind::TmpRw => {
171 }
173 ArgumentKind::SlotRo | ArgumentKind::SlotRw => {
174 num_slot_arguments += 1;
175 }
176 ArgumentKind::Input => {
177 input_output_type_details[usize::from(num_input_arguments)]
178 .write(item.type_details.unwrap_or(IoTypeDetails::bytes(0)));
179 num_input_arguments += 1;
180 }
181 ArgumentKind::Output => {
182 input_output_type_details
183 [usize::from(num_input_arguments + num_output_arguments)]
184 .write(item.type_details.unwrap_or(IoTypeDetails::bytes(0)));
185 num_output_arguments += 1;
186 }
187 }
188 }
189 let (input_type_details, output_type_details) = unsafe {
191 let (input_type_details, output_type_details) =
192 input_output_type_details.split_at_unchecked(usize::from(num_input_arguments));
193 let (output_type_details, _) =
194 output_type_details.split_at_unchecked(usize::from(num_output_arguments));
195
196 (
197 input_type_details.assume_init_ref(),
198 output_type_details.assume_init_ref(),
199 )
200 };
201
202 self.push_payload_byte(num_slot_arguments);
204 for slot_offset in 0..usize::from(num_slot_arguments) {
205 let slot_type = if let Some(&Some(output_index)) = slot_output_index.get(slot_offset) {
206 TransactionSlot::new_output_index(output_index).ok_or(
207 TransactionPayloadBuilderError::InvalidOutputIndex(output_index),
208 )?
209 } else {
210 TransactionSlot::new_address()
211 };
212 self.push_payload_byte(slot_type.into_u8());
213 }
214
215 self.push_payload_byte(num_input_arguments);
217 for (input_offset, type_details) in input_type_details.iter().enumerate() {
218 let input_type = if let Some(&Some(output_index)) = input_output_index.get(input_offset)
219 {
220 TransactionInput::new_output_index(output_index).ok_or(
221 TransactionPayloadBuilderError::InvalidOutputIndex(output_index),
222 )?
223 } else {
224 TransactionInput::new_value(type_details.alignment).ok_or(
225 TransactionPayloadBuilderError::InvalidAlignment(type_details.alignment),
226 )?
227 };
228 self.push_payload_byte(input_type.into_u8());
229 }
230
231 self.push_payload_byte(num_output_arguments);
233
234 for slot_offset in 0..usize::from(num_slot_arguments) {
235 let address = unsafe {
237 let address = external_args.cast::<NonNull<Address>>().read().as_ref();
238 external_args = external_args.offset(1);
239 address
240 };
241
242 if slot_output_index
243 .get(slot_offset)
244 .copied()
245 .flatten()
246 .is_none()
247 {
248 self.extend_payload_with_alignment(address.as_bytes(), align_of_val(address));
249 }
250 }
251
252 for (input_offset, type_details) in input_type_details.iter().enumerate() {
253 let (size, data) = unsafe {
255 let data = external_args.cast::<NonNull<u8>>().read();
256 external_args = external_args.offset(1);
257 let size = external_args.cast::<NonNull<u32>>().read().read();
258 external_args = external_args.offset(1);
259
260 let data = slice::from_raw_parts(data.as_ptr().cast_const(), size as usize);
261
262 (size, data)
263 };
264
265 if input_output_index
266 .get(input_offset)
267 .copied()
268 .flatten()
269 .is_none()
270 {
271 self.extend_payload_with_alignment(&size.to_le_bytes(), align_of_val(&size));
272 self.extend_payload_with_alignment(data, type_details.alignment.get() as usize);
273 }
274 }
275
276 for type_details in output_type_details {
277 self.extend_payload_with_alignment(
278 &type_details.recommended_capacity.to_le_bytes(),
279 align_of_val(&type_details.recommended_capacity),
280 );
281 self.extend_payload_with_alignment(
282 &[type_details.alignment.ilog2() as u8],
283 align_of::<u8>(),
284 );
285 }
286
287 Ok(())
288 }
289
290 pub fn into_aligned_bytes(mut self) -> Vec<u128> {
319 self.ensure_alignment(usize::from(MAX_ALIGNMENT));
321
322 let output_len = self.payload.len() / size_of::<u128>();
323 let mut output = Vec::<u128>::with_capacity(output_len);
324
325 unsafe {
327 ptr::copy_nonoverlapping(
328 self.payload.as_ptr(),
329 output.as_mut_ptr().cast::<u8>(),
330 self.payload.len(),
331 );
332 output.set_len(output_len);
333 }
334
335 debug_assert_eq!(align_of_val(output.as_slice()), usize::from(MAX_ALIGNMENT));
336
337 output
338 }
339
340 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
341 fn extend_payload_with_alignment(&mut self, bytes: &[u8], alignment: usize) {
342 self.ensure_alignment(alignment);
343
344 self.payload.extend_from_slice(bytes);
345 }
346
347 fn ensure_alignment(&mut self, alignment: usize) {
349 debug_assert!(alignment <= usize::from(MAX_ALIGNMENT));
350
351 let unaligned_by = self.payload.len() & (alignment - 1);
354 if unaligned_by > 0 {
355 let padding_bytes = unsafe { alignment.unchecked_sub(unaligned_by) };
357 self.payload.resize(self.payload.len() + padding_bytes, 0);
358 }
359 }
360
361 #[cfg_attr(feature = "no-panic", no_panic::no_panic)]
362 fn push_payload_byte(&mut self, byte: u8) {
363 self.payload.push(byte);
364 }
365}