ab_contracts_common/metadata/
compact.rs

1use crate::metadata::ContractMetadataKind;
2use ab_contracts_io_type::metadata::{IoTypeMetadataKind, MAX_METADATA_CAPACITY};
3
4#[inline(always)]
5pub(super) const fn compact_metadata(
6    metadata: &[u8],
7    for_external_args: bool,
8) -> Option<([u8; MAX_METADATA_CAPACITY], usize)> {
9    let mut metadata_scratch = [0; MAX_METADATA_CAPACITY];
10
11    let Some((metadata, remainder)) =
12        compact_metadata_inner(metadata, &mut metadata_scratch, for_external_args)
13    else {
14        return None;
15    };
16
17    if !metadata.is_empty() {
18        return None;
19    }
20
21    let remainder_len = remainder.len();
22    let size = metadata_scratch.len() - remainder_len;
23    Some((metadata_scratch, size))
24}
25
26/// This macro is necessary to reduce boilerplate due to lack of `?` in const environment
27macro_rules! forward_option {
28    ($expr:expr) => {{
29        let Some(result) = $expr else {
30            return None;
31        };
32        result
33    }};
34}
35
36#[inline(always)]
37const fn compact_metadata_inner<'i, 'o>(
38    mut input: &'i [u8],
39    mut output: &'o mut [u8],
40    for_external_args: bool,
41) -> Option<(&'i [u8], &'o mut [u8])> {
42    if input.is_empty() || output.is_empty() {
43        return None;
44    }
45
46    let kind = forward_option!(ContractMetadataKind::try_from_u8(input[0]));
47
48    match kind {
49        ContractMetadataKind::Contract => {
50            (input, output) = forward_option!(copy_n_bytes(input, output, 1));
51
52            if input.is_empty() || output.is_empty() {
53                return None;
54            }
55
56            // Compact contract state type
57            (input, output) = forward_option!(IoTypeMetadataKind::compact(input, output));
58            // Compact contract `#[slot]` type
59            (input, output) = forward_option!(IoTypeMetadataKind::compact(input, output));
60            // Compact contract `#[tmp]` type
61            (input, output) = forward_option!(IoTypeMetadataKind::compact(input, output));
62
63            if input.is_empty() {
64                return None;
65            }
66
67            let mut num_methods = input[0];
68            (input, output) = forward_option!(copy_n_bytes(input, output, 1));
69
70            // Compact methods
71            while num_methods > 0 {
72                if input.is_empty() {
73                    return None;
74                }
75
76                (input, output) =
77                    forward_option!(compact_metadata_inner(input, output, for_external_args));
78
79                num_methods -= 1;
80            }
81        }
82        ContractMetadataKind::Trait => {
83            (input, output) = forward_option!(copy_n_bytes(input, output, 1));
84
85            if input.is_empty() || output.is_empty() {
86                return None;
87            }
88
89            // Remove trait name
90            let trait_name_length = input[0] as usize;
91            output[0] = 0;
92            (input, output) = forward_option!(skip_n_bytes_io(input, output, 1));
93            input = forward_option!(skip_n_bytes(input, trait_name_length));
94
95            if input.is_empty() {
96                return None;
97            }
98
99            let mut num_methods = input[0];
100            (input, output) = forward_option!(copy_n_bytes(input, output, 1));
101
102            // Compact methods
103            while num_methods > 0 {
104                if input.is_empty() {
105                    return None;
106                }
107
108                (input, output) =
109                    forward_option!(compact_metadata_inner(input, output, for_external_args));
110
111                num_methods -= 1;
112            }
113        }
114        ContractMetadataKind::Init
115        | ContractMetadataKind::UpdateStateless
116        | ContractMetadataKind::UpdateStatefulRo
117        | ContractMetadataKind::UpdateStatefulRw
118        | ContractMetadataKind::ViewStateless
119        | ContractMetadataKind::ViewStateful => {
120            match kind {
121                ContractMetadataKind::Init => {
122                    (input, output) = forward_option!(copy_n_bytes(input, output, 1));
123                }
124                ContractMetadataKind::UpdateStateless
125                | ContractMetadataKind::UpdateStatefulRo
126                | ContractMetadataKind::UpdateStatefulRw => {
127                    if for_external_args {
128                        // For `ExternalArgs` the kind of `#[update]` doesn't matter
129                        output[0] = ContractMetadataKind::UpdateStateless as u8;
130                        (input, output) = forward_option!(skip_n_bytes_io(input, output, 1));
131                    } else {
132                        (input, output) = forward_option!(copy_n_bytes(input, output, 1));
133                    }
134                }
135                ContractMetadataKind::ViewStateless | ContractMetadataKind::ViewStateful => {
136                    if for_external_args {
137                        // For `ExternalArgs` the kind of `#[view]` doesn't matter
138                        output[0] = ContractMetadataKind::ViewStateless as u8;
139                        (input, output) = forward_option!(skip_n_bytes_io(input, output, 1));
140                    } else {
141                        (input, output) = forward_option!(copy_n_bytes(input, output, 1));
142                    }
143                }
144                _ => {
145                    // Just matched above
146                    unreachable!();
147                }
148            }
149
150            if input.is_empty() || output.is_empty() {
151                return None;
152            }
153
154            // Copy method name
155            let method_name_length = input[0] as usize;
156            (input, output) = forward_option!(copy_n_bytes(input, output, 1 + method_name_length));
157
158            if input.is_empty() {
159                return None;
160            }
161
162            let mut num_arguments = input[0];
163            (input, output) = forward_option!(copy_n_bytes(input, output, 1));
164
165            // Compact arguments
166            while num_arguments > 0 {
167                if input.is_empty() {
168                    return None;
169                }
170
171                num_arguments -= 1;
172
173                (input, output) = forward_option!(compact_method_argument(
174                    input,
175                    output,
176                    kind,
177                    num_arguments == 0,
178                    for_external_args
179                ));
180            }
181        }
182        ContractMetadataKind::EnvRo
183        | ContractMetadataKind::EnvRw
184        | ContractMetadataKind::TmpRo
185        | ContractMetadataKind::TmpRw
186        | ContractMetadataKind::SlotRo
187        | ContractMetadataKind::SlotRw
188        | ContractMetadataKind::Input
189        | ContractMetadataKind::Output => {
190            // Can't start with argument
191            return None;
192        }
193    }
194
195    Some((input, output))
196}
197
198#[inline(always)]
199const fn compact_method_argument<'i, 'o>(
200    mut input: &'i [u8],
201    mut output: &'o mut [u8],
202    method_kind: ContractMetadataKind,
203    last_argument: bool,
204    for_external_args: bool,
205) -> Option<(&'i [u8], &'o mut [u8])> {
206    if input.is_empty() || output.is_empty() {
207        return None;
208    }
209
210    let kind = forward_option!(ContractMetadataKind::try_from_u8(input[0]));
211
212    match kind {
213        ContractMetadataKind::Contract
214        | ContractMetadataKind::Trait
215        | ContractMetadataKind::Init
216        | ContractMetadataKind::UpdateStateless
217        | ContractMetadataKind::UpdateStatefulRo
218        | ContractMetadataKind::UpdateStatefulRw
219        | ContractMetadataKind::ViewStateless
220        | ContractMetadataKind::ViewStateful => {
221            // Expected argument here
222            return None;
223        }
224        ContractMetadataKind::EnvRo | ContractMetadataKind::EnvRw => {
225            if for_external_args {
226                // For `ExternalArgs` `#[env]` doesn't matter
227                input = forward_option!(skip_n_bytes(input, 1));
228            } else {
229                (input, output) = forward_option!(copy_n_bytes(input, output, 1));
230            }
231            // Nothing else to do here, `#[env]` doesn't include metadata of its type
232        }
233        ContractMetadataKind::TmpRo
234        | ContractMetadataKind::TmpRw
235        | ContractMetadataKind::SlotRo
236        | ContractMetadataKind::SlotRw => {
237            match kind {
238                ContractMetadataKind::TmpRo | ContractMetadataKind::TmpRw => {
239                    if for_external_args {
240                        // For `ExternalArgs` `#[tmp]` doesn't matter
241                        input = forward_option!(skip_n_bytes(input, 1));
242                    } else {
243                        (input, output) = forward_option!(copy_n_bytes(input, output, 1));
244                    }
245                }
246                ContractMetadataKind::SlotRo | ContractMetadataKind::SlotRw => {
247                    if for_external_args {
248                        // For `ExternalArgs` the kind of `#[slot]` doesn't matter
249                        output[0] = ContractMetadataKind::SlotRo as u8;
250                        (input, output) = forward_option!(skip_n_bytes_io(input, output, 1));
251                    } else {
252                        (input, output) = forward_option!(copy_n_bytes(input, output, 1));
253                    }
254                }
255                _ => {
256                    // Just matched above
257                    unreachable!()
258                }
259            }
260
261            if input.is_empty() || output.is_empty() {
262                return None;
263            }
264
265            // Remove argument name
266            let argument_name_length = input[0] as usize;
267            output[0] = 0;
268            (input, output) = forward_option!(skip_n_bytes_io(input, output, 1));
269            input = forward_option!(skip_n_bytes(input, argument_name_length));
270        }
271        ContractMetadataKind::Input | ContractMetadataKind::Output => {
272            (input, output) = forward_option!(copy_n_bytes(input, output, 1));
273
274            if input.is_empty() || output.is_empty() {
275                return None;
276            }
277
278            // Remove argument name
279            let argument_name_length = input[0] as usize;
280            output[0] = 0;
281            (input, output) = forward_option!(skip_n_bytes_io(input, output, 1));
282            input = forward_option!(skip_n_bytes(input, argument_name_length));
283
284            // May be skipped for `#[init]`, see `ContractMetadataKind::Init` for details
285            let skip_argument_type = matches!(
286                (method_kind, kind, last_argument),
287                (
288                    ContractMetadataKind::Init,
289                    ContractMetadataKind::Output,
290                    true
291                )
292            );
293            if !skip_argument_type {
294                // Compact argument type
295                (input, output) = forward_option!(IoTypeMetadataKind::compact(input, output));
296            }
297        }
298    }
299
300    Some((input, output))
301}
302
303/// Copies `n` bytes from input to output and returns both input and output after `n` bytes offset
304#[inline(always)]
305const fn copy_n_bytes<'i, 'o>(
306    input: &'i [u8],
307    output: &'o mut [u8],
308    n: usize,
309) -> Option<(&'i [u8], &'o mut [u8])> {
310    if n > input.len() || n > output.len() {
311        return None;
312    }
313
314    let (source, input) = input.split_at(n);
315    let (target, output) = output.split_at_mut(n);
316    target.copy_from_slice(source);
317
318    Some((input, output))
319}
320
321/// Skips `n` bytes and return remainder
322#[inline(always)]
323const fn skip_n_bytes(input: &[u8], n: usize) -> Option<&[u8]> {
324    if n > input.len() {
325        return None;
326    }
327
328    // `&input[n..]` not supported in const yet
329    Some(input.split_at(n).1)
330}
331
332/// Skips `n` bytes in input and output
333#[inline(always)]
334const fn skip_n_bytes_io<'i, 'o>(
335    mut input: &'i [u8],
336    mut output: &'o mut [u8],
337    n: usize,
338) -> Option<(&'i [u8], &'o mut [u8])> {
339    if n > input.len() || n > output.len() {
340        return None;
341    }
342
343    // `&input[n..]` not supported in const yet
344    input = input.split_at(n).1;
345    // `&mut output[n..]` not supported in const yet
346    output = output.split_at_mut(n).1;
347
348    Some((input, output))
349}