ab_contracts_common/metadata/
compact.rs1use 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
26macro_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 (input, output) = forward_option!(IoTypeMetadataKind::compact(input, output));
58 (input, output) = forward_option!(IoTypeMetadataKind::compact(input, output));
60 (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 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 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 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 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 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 unreachable!();
147 }
148 }
149
150 if input.is_empty() || output.is_empty() {
151 return None;
152 }
153
154 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 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 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 return None;
223 }
224 ContractMetadataKind::EnvRo | ContractMetadataKind::EnvRw => {
225 if for_external_args {
226 input = forward_option!(skip_n_bytes(input, 1));
228 } else {
229 (input, output) = forward_option!(copy_n_bytes(input, output, 1));
230 }
231 }
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 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 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 unreachable!()
258 }
259 }
260
261 if input.is_empty() || output.is_empty() {
262 return None;
263 }
264
265 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 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 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 (input, output) = forward_option!(IoTypeMetadataKind::compact(input, output));
296 }
297 }
298 }
299
300 Some((input, output))
301}
302
303#[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#[inline(always)]
323const fn skip_n_bytes(input: &[u8], n: usize) -> Option<&[u8]> {
324 if n > input.len() {
325 return None;
326 }
327
328 Some(input.split_at(n).1)
330}
331
332#[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 = input.split_at(n).1;
345 output = output.split_at_mut(n).1;
347
348 Some((input, output))
349}