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