ab_system_contract_simple_wallet_base/payload/
decoder.rs1#[cfg(test)]
4mod tests;
5
6use crate::MAX_OUTPUTS;
7use crate::payload::decoder_core::{MethodHeader, MethodSink, OutputAllocator, PayloadDecoderCore};
8use crate::payload::{
9 DeclaredOutput, TransactionMethodContext, TransactionOutputKind, TransactionPayloadDecoderError,
10};
11use ab_contracts_common::MAX_TOTAL_METHOD_ARGS;
12use ab_contracts_common::method::MethodFingerprint;
13use ab_core_primitives::address::Address;
14use ab_io_type::alignment::Alignment;
15use core::hint;
16use core::mem::MaybeUninit;
17
18#[derive(Debug, Copy, Clone, Eq, PartialEq)]
21#[expect(
22 clippy::partial_pub_fields,
23 reason = "Offset and size are details of calling methods, see `Self::return_value_size()`"
24)]
25pub struct OutputRef {
26 pub mut(crate) output_index: u8,
28 pub mut(crate) kind: TransactionOutputKind,
30 pub(crate) offset: u32,
32 pub(crate) size: u32,
34}
35
36impl OutputRef {
37 #[inline(always)]
41 pub const fn return_value_size(&self) -> Option<u32> {
42 match self.kind {
43 TransactionOutputKind::Output => None,
44 TransactionOutputKind::ReturnValue => Some(self.size),
45 }
46 }
47}
48
49#[derive(Debug, Copy, Clone, Eq, PartialEq)]
51pub enum DecodedSlot<'payload> {
52 Address(&'payload Address),
54 Output(OutputRef),
57}
58
59#[derive(Debug, Copy, Clone, Eq, PartialEq)]
61pub enum DecodedInput<'payload> {
62 Value {
64 bytes: &'payload [u8],
65 alignment: Alignment,
66 },
67 Output(OutputRef),
69}
70
71#[derive(Debug, Copy, Clone, Eq, PartialEq)]
73#[expect(
74 clippy::partial_pub_fields,
75 reason = "The size is only meaningful for a return value, see `Self::return_value_size()`"
76)]
77pub struct DecodedOutput {
78 pub mut(crate) output_index: u8,
81 pub mut(crate) kind: TransactionOutputKind,
83 pub mut(crate) capacity: u32,
85 pub(crate) size: u32,
88}
89
90impl DecodedOutput {
91 #[inline(always)]
96 pub const fn return_value_size(&self) -> Option<u32> {
97 match self.kind {
98 TransactionOutputKind::Output => None,
99 TransactionOutputKind::ReturnValue => Some(self.size),
100 }
101 }
102}
103
104const UNUSED_SLOT: DecodedSlot<'static> = DecodedSlot::Address(&Address::NULL);
106const UNUSED_INPUT: DecodedInput<'static> = DecodedInput::Value {
108 bytes: &[],
109 alignment: Alignment::of::<u8>(),
110};
111const UNUSED_OUTPUT: DecodedOutput = DecodedOutput {
113 output_index: 0,
114 kind: TransactionOutputKind::Output,
115 capacity: 0,
116 size: 0,
117};
118
119#[derive(Debug)]
121struct DecodedArguments<'payload> {
122 slots: [DecodedSlot<'payload>; MAX_TOTAL_METHOD_ARGS as usize],
124 num_slots: u8,
125 inputs: [DecodedInput<'payload>; MAX_TOTAL_METHOD_ARGS as usize],
127 num_inputs: u8,
128 outputs: [DecodedOutput; MAX_TOTAL_METHOD_ARGS as usize],
130 num_outputs: u8,
131}
132
133impl<'payload> MethodSink<'payload> for DecodedArguments<'payload> {
136 #[inline(always)]
137 #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
138 fn slot(&mut self, slot: DecodedSlot<'payload>) -> Result<(), TransactionPayloadDecoderError> {
139 if let Some(entry) = self.slots.get_mut(usize::from(self.num_slots)) {
140 *entry = slot;
141 self.num_slots += 1;
142 }
143
144 Ok(())
145 }
146
147 #[inline(always)]
148 #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
149 fn input(&mut self, input: DecodedInput<'payload>) {
150 if let Some(entry) = self.inputs.get_mut(usize::from(self.num_inputs)) {
151 *entry = input;
152 self.num_inputs += 1;
153 }
154 }
155
156 #[inline(always)]
157 #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
158 fn output(&mut self, output: DecodedOutput, _offset: u32) {
159 if let Some(entry) = self.outputs.get_mut(usize::from(self.num_outputs)) {
160 *entry = output;
161 self.num_outputs += 1;
162 }
163 }
164}
165
166struct NoOutputBuffer;
169
170impl OutputAllocator for NoOutputBuffer {
171 #[inline(always)]
172 fn alloc(&mut self, _capacity: u32) -> Result<u32, TransactionPayloadDecoderError> {
173 Ok(0)
174 }
175}
176
177impl<'payload> DecodedArguments<'payload> {
178 #[inline(always)]
179 fn new() -> Self {
180 Self {
181 slots: [UNUSED_SLOT; MAX_TOTAL_METHOD_ARGS as usize],
182 num_slots: 0,
183 inputs: [UNUSED_INPUT; MAX_TOTAL_METHOD_ARGS as usize],
184 num_inputs: 0,
185 outputs: [UNUSED_OUTPUT; MAX_TOTAL_METHOD_ARGS as usize],
186 num_outputs: 0,
187 }
188 }
189
190 #[inline(always)]
192 fn clear(&mut self) {
193 self.num_slots = 0;
194 self.num_inputs = 0;
195 self.num_outputs = 0;
196 }
197
198 #[inline(always)]
200 #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
201 fn slots(&self) -> &[DecodedSlot<'payload>] {
202 unsafe {
206 hint::assert_unchecked(usize::from(self.num_slots) <= self.slots.len());
207 }
208
209 self.slots
210 .get(..usize::from(self.num_slots))
211 .expect("Within the length of `slots`, as asserted above; qed")
212 }
213
214 #[inline(always)]
216 #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
217 fn inputs(&self) -> &[DecodedInput<'payload>] {
218 unsafe {
222 hint::assert_unchecked(usize::from(self.num_inputs) <= self.inputs.len());
223 }
224
225 self.inputs
226 .get(..usize::from(self.num_inputs))
227 .expect("Within the length of `inputs`, as asserted above; qed")
228 }
229
230 #[inline(always)]
232 #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
233 fn outputs(&self) -> &[DecodedOutput] {
234 unsafe {
238 hint::assert_unchecked(usize::from(self.num_outputs) <= self.outputs.len());
239 }
240
241 self.outputs
242 .get(..usize::from(self.num_outputs))
243 .expect("Within the length of `outputs`, as asserted above; qed")
244 }
245}
246
247#[derive(Debug, Copy, Clone)]
255pub struct DecodedMethod<'decoder, 'payload> {
256 header: MethodHeader<'payload>,
257 arguments: &'decoder DecodedArguments<'payload>,
258}
259
260impl<'decoder, 'payload> DecodedMethod<'decoder, 'payload> {
261 #[inline(always)]
263 pub fn contract(&self) -> &'payload Address {
264 self.header.contract
265 }
266
267 #[inline(always)]
269 pub fn fingerprint(&self) -> &'payload MethodFingerprint {
270 self.header.fingerprint
271 }
272
273 #[inline(always)]
275 pub fn context(&self) -> TransactionMethodContext {
276 self.header.context
277 }
278
279 #[inline(always)]
281 #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
282 pub fn slots(&self) -> &'decoder [DecodedSlot<'payload>] {
283 self.arguments.slots()
284 }
285
286 #[inline(always)]
288 #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
289 pub fn inputs(&self) -> &'decoder [DecodedInput<'payload>] {
290 self.arguments.inputs()
291 }
292
293 #[inline(always)]
295 #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
296 pub fn outputs(&self) -> &'decoder [DecodedOutput] {
297 self.arguments.outputs()
298 }
299
300 #[inline(always)]
302 #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
303 pub fn return_value(&self) -> Option<&'decoder DecodedOutput> {
304 self.outputs()
305 .last()
306 .filter(|output| output.kind == TransactionOutputKind::ReturnValue)
307 }
308}
309
310#[derive(Debug)]
339pub struct TransactionPayloadDecoder<'payload, 'outputs> {
340 core: PayloadDecoderCore<'payload, 'outputs>,
341 arguments: DecodedArguments<'payload>,
343 failed: bool,
345}
346
347impl<'payload, 'outputs> TransactionPayloadDecoder<'payload, 'outputs> {
348 #[inline]
354 #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
355 pub fn new(
356 payload: &'payload [u128],
357 declared_outputs: &'outputs mut [MaybeUninit<DeclaredOutput>; MAX_OUTPUTS],
358 ) -> Result<Self, TransactionPayloadDecoderError> {
359 Ok(Self {
360 core: PayloadDecoderCore::new(payload, declared_outputs, &mut NoOutputBuffer)?,
361 arguments: DecodedArguments::new(),
362 failed: false,
363 })
364 }
365
366 #[inline]
368 #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
369 pub fn declared_outputs(&self) -> &[DeclaredOutput] {
370 self.core.declared_outputs()
371 }
372
373 #[cfg_attr(feature = "no-panic", no_panic_const::no_panic)]
379 pub fn next_method(
380 &mut self,
381 ) -> Result<Option<DecodedMethod<'_, 'payload>>, TransactionPayloadDecoderError> {
382 if self.failed {
383 return Err(TransactionPayloadDecoderError::FailedEarlier);
384 }
385
386 self.arguments.clear();
387 match self.core.decode_next_method(&mut self.arguments) {
388 Ok(Some(header)) => Ok(Some(DecodedMethod {
389 header,
390 arguments: &self.arguments,
391 })),
392 Ok(None) => Ok(None),
393 Err(error) => {
394 self.failed = true;
395 Err(error)
396 }
397 }
398 }
399}