ccnext_query_builder/abi/
utils.rs

1use crate::abi::models::FieldMetadata;
2use alloy::{
3    dyn_abi::{Decoder, DynSolType},
4    sol_types::Error,
5};
6
7pub(crate) const WORD_SIZE: usize = 32;
8
9#[derive(Debug)]
10pub enum ComputeAbiOffsetsError {
11    FailedToDecode(Error),
12}
13
14pub fn compute_abi_offsets(
15    types: Vec<DynSolType>,
16    abi: &[u8],
17) -> Result<Vec<FieldMetadata>, ComputeAbiOffsetsError> {
18    let mut reader = Decoder::new(abi, false);
19    match decode_offset_recursive(&mut reader, types, 0) {
20        Ok(result) => Ok(result),
21        Err(err) => Err(ComputeAbiOffsetsError::FailedToDecode(err)),
22    }
23}
24
25fn decode_offset_recursive(
26    reader: &mut Decoder,
27    types: Vec<DynSolType>,
28    base_offset: usize,
29) -> Result<Vec<FieldMetadata>, Error> {
30    let mut result = Vec::new();
31    for sol_type in types {
32        match sol_type.clone() {
33            DynSolType::Bool
34            | DynSolType::Int(_)
35            | DynSolType::Uint(_)
36            | DynSolType::FixedBytes(_)
37            | DynSolType::Address => {
38                let relative_offset = reader.offset();
39                let absolute_offset = base_offset + relative_offset;
40                let word = reader.take_word()?;
41                result.push(FieldMetadata {
42                    sol_type: sol_type.clone(),
43                    offset: absolute_offset,
44                    size: Some(WORD_SIZE),
45                    is_dynamic: false,
46                    value: Some(word.0.to_vec()),
47                    children: vec![],
48                });
49            }
50            DynSolType::Bytes | DynSolType::String => {
51                let offset = reader.take_offset()?;
52                let mut sub_reader = reader.child(offset)?;
53                let size = sub_reader.take_offset()?; // take_size() dosen't exist, its the same though.
54                let data = sub_reader.take_slice(size)?;
55                let dynamic_field_absolute_offset = base_offset + offset + WORD_SIZE;
56                result.push(FieldMetadata {
57                    sol_type: sol_type.clone(),
58                    offset: dynamic_field_absolute_offset,
59                    size: Some(size),
60                    is_dynamic: true,
61                    value: Some(data.to_vec()),
62                    children: vec![],
63                });
64            }
65            DynSolType::Array(array_element_sol_type_boxed) => {
66                // all array's not fixed length are always dynamic
67                let field_dynamic_offset = reader.take_offset()?; // first we get the offset of the array
68                let absolute_offset = base_offset + field_dynamic_offset;
69                let mut sub_reader = reader.child(field_dynamic_offset)?; // create a sub reader..
70                let number_of_elements = sub_reader.take_offset()?; // reader the number of elements..
71                                                                    // if the child of the array is dynamic, its treated quite differently
72                                                                    // as if it was a dynamic child.
73                let array_element_sol_type_unboxed = *array_element_sol_type_boxed;
74                // this can probably be a function on its own D:
75                let array_components: Vec<DynSolType> = match array_element_sol_type_unboxed.clone()
76                {
77                    // Likely a bug. We would want to add `dyn_sol_types` repeatedly for 0..number_of_elements.
78                    // It's fine for the number of array_components to end up larger than the number_of_elements.
79                    DynSolType::Tuple(dyn_sol_types) => dyn_sol_types,
80                    other => {
81                        let mut types = Vec::new();
82                        for _ in 0..number_of_elements {
83                            types.push(other.clone());
84                        }
85                        types
86                    }
87                };
88
89                let mut children = Vec::new();
90                if is_dynamic(array_element_sol_type_unboxed.clone()) {
91                    // if its a dynamic child we need to compute the offsets
92                    // for each element.
93                    let mut dynamic_array_relative_offsets = Vec::new();
94                    for _ in 0..number_of_elements {
95                        let child_element_offset = sub_reader.take_offset()?;
96                        dynamic_array_relative_offsets.push(child_element_offset);
97                    }
98
99                    // STRICT FIX: Only handle bytes/string arrays differently
100                    let is_bytes_or_string = matches!(
101                        array_element_sol_type_unboxed,
102                        DynSolType::Bytes | DynSolType::String
103                    );
104
105                    for array_element_offset in dynamic_array_relative_offsets {
106                        if is_bytes_or_string {
107                            // For bytes/string in arrays, arrayElementOffset is relative to the start of offsets array
108                            // Offsets array starts at field_dynamic_offset + WORD_SIZE (after array length prefix)
109                            // So absolute position is: base_offset + field_dynamic_offset + WORD_SIZE + array_element_offset
110                            let absolute_data_offset = base_offset
111                                + field_dynamic_offset
112                                + WORD_SIZE
113                                + array_element_offset;
114                            let mut data_sub_reader = reader.child(absolute_data_offset)?;
115                            let length = data_sub_reader.take_offset()?; // Read length
116                            let data = data_sub_reader.take_slice(length)?;
117
118                            children.push(FieldMetadata {
119                                sol_type: array_element_sol_type_unboxed.clone(),
120                                offset: absolute_data_offset + WORD_SIZE, // Absolute position of data start (after length prefix)
121                                size: Some(length),
122                                is_dynamic: true,
123                                value: Some(data.to_vec()),
124                                children: vec![],
125                            });
126                        } else {
127                            // Original code path for tuples and other types (unchanged)
128                            // the only problem is to calculate the absolute position
129                            // its a bit more complicated you need to consider the offset since the begining
130                            // so hmm at this point the sub reader has already read all the offset so its cursor is kind of already at the right place.
131                            // so the position is hmm
132                            let semi_absolute_positon =
133                                field_dynamic_offset + array_element_offset + WORD_SIZE;
134                            let mut child_element_sub_reader =
135                                reader.child(semi_absolute_positon)?;
136
137                            // absolute position since parents..
138                            let position = base_offset + semi_absolute_positon;
139                            // go decode the children :)
140                            let children_of_child = decode_offset_recursive(
141                                &mut child_element_sub_reader,
142                                array_components.clone(),
143                                position,
144                            )?;
145
146                            children.push(FieldMetadata {
147                                sol_type: array_element_sol_type_unboxed.clone(),
148                                offset: position,
149                                size: None,
150                                is_dynamic: true,
151                                value: None,
152                                children: children_of_child,
153                            });
154                        }
155                    }
156                } else {
157                    // in this case it means that the inner type of the dynamic array is not a dynamic type
158                    // and what you want to do is to decode them by passing the sub reader down to the
159                    // next iteration of the decoder, this way it moves forward properly.
160                    children = decode_offset_recursive(
161                        &mut sub_reader,
162                        array_components,
163                        absolute_offset,
164                    )?;
165                }
166
167                result.push(FieldMetadata {
168                    sol_type: sol_type.clone(),
169                    offset: absolute_offset,
170                    size: None,
171                    is_dynamic: true,
172                    value: None,
173                    children,
174                });
175            }
176            DynSolType::FixedArray(array_element_sol_type_boxed, number_of_elements) => {
177                // if the child of the array is dynamic, its treated quite differently
178                // as if it was a dynamic child.
179                let array_element_sol_type_unboxed = *array_element_sol_type_boxed;
180                // this can probably be a function on its own D:
181                let array_components: Vec<DynSolType> = match array_element_sol_type_unboxed.clone()
182                {
183                    // Maybe same issue here as noted in Array case
184                    DynSolType::Tuple(dyn_sol_types) => dyn_sol_types,
185                    other => {
186                        let mut types = Vec::new();
187                        for _ in 0..number_of_elements {
188                            types.push(other.clone());
189                        }
190                        types
191                    }
192                };
193
194                // if its dynamic don't know what to do :D
195                if is_dynamic(array_element_sol_type_unboxed.clone()) {
196                    // okay so this uses an offset..
197                    let offset = reader.take_offset()?;
198                    let absolute_offset = base_offset + offset;
199                    // we need a sub reader there..
200                    let mut sub_reader = reader.child(offset)?;
201                    let children = decode_offset_recursive(
202                        &mut sub_reader,
203                        array_components,
204                        absolute_offset,
205                    )?;
206                    result.push(FieldMetadata {
207                        sol_type: sol_type.clone(),
208                        offset: absolute_offset,
209                        size: None,
210                        is_dynamic: true,
211                        value: None,
212                        children,
213                    });
214                } else {
215                    // easy enough we just recursive.
216                    let absolute_offset = base_offset + reader.offset();
217                    let children =
218                        decode_offset_recursive(reader, array_components, absolute_offset)?;
219                    result.push(FieldMetadata {
220                        sol_type: sol_type.clone(),
221                        offset: absolute_offset,
222                        size: None,
223                        is_dynamic: false,
224                        value: None,
225                        children,
226                    });
227                }
228            }
229            DynSolType::Tuple(tuple_components) => {
230                if is_dynamic(sol_type.clone()) {
231                    let offset_of_tuple = base_offset + reader.offset();
232                    let offset_of_dynamic_data = reader.take_offset()?;
233                    let mut sub_reader = reader.child(offset_of_dynamic_data)?;
234                    let children = decode_offset_recursive(
235                        &mut sub_reader,
236                        tuple_components,
237                        offset_of_dynamic_data,
238                    )?;
239                    result.push(FieldMetadata {
240                        sol_type: sol_type.clone(),
241                        offset: offset_of_tuple,
242                        size: None,
243                        is_dynamic: true,
244                        value: None,
245                        children,
246                    });
247                } else {
248                    let offset_of_tuple = base_offset + reader.offset();
249                    let children = decode_offset_recursive(reader, tuple_components, base_offset)?;
250                    result.push(FieldMetadata {
251                        sol_type: sol_type.clone(),
252                        offset: offset_of_tuple,
253                        size: None,
254                        is_dynamic: false,
255                        value: None,
256                        children,
257                    });
258                }
259            }
260            DynSolType::Function => {
261                return Err(Error::custom(
262                    "Cannot decode functions, with this decoder..",
263                ));
264            }
265        }
266    }
267    Ok(result)
268}
269
270pub fn is_dynamic(sol_type: DynSolType) -> bool {
271    match sol_type {
272        DynSolType::Bool => false,
273        DynSolType::Int(_) => false,
274        DynSolType::Uint(_) => false,
275        DynSolType::FixedBytes(_) => false,
276        DynSolType::Address => false,
277        DynSolType::Function => todo!(),
278        DynSolType::Bytes => true,
279        DynSolType::String => true,
280        DynSolType::Array(_array_children) => true,
281        DynSolType::FixedArray(array_children, _array_size) => is_dynamic(*array_children),
282        DynSolType::Tuple(tuple_components) => {
283            for tuple_component in tuple_components {
284                if is_dynamic(tuple_component) {
285                    return true;
286                }
287            }
288            false
289        }
290    }
291}
292
293/// Recursively makes offsets absolute by adding baseOffset to field and all children
294pub(crate) fn make_offsets_absolute(field: &mut FieldMetadata, base_offset: usize) {
295    field.offset += base_offset;
296    for child in &mut field.children {
297        make_offsets_absolute(child, base_offset);
298    }
299}