Skip to main content

aztec_core/abi/
checkers.rs

1use super::types::{AbiType, ContractArtifact, FunctionArtifact};
2
3/// Returns true if the ABI type represents an AztecAddress or EthAddress struct.
4pub fn is_address_struct(typ: &AbiType) -> bool {
5    is_aztec_address_struct(typ) || is_eth_address_struct(typ)
6}
7
8/// Returns true if the ABI type is an `AztecAddress` struct.
9pub fn is_aztec_address_struct(typ: &AbiType) -> bool {
10    matches!(typ, AbiType::Struct { name, .. } if name.ends_with("address::AztecAddress"))
11}
12
13/// Returns true if the ABI type is an `EthAddress` struct.
14pub fn is_eth_address_struct(typ: &AbiType) -> bool {
15    matches!(typ, AbiType::Struct { name, .. } if name.ends_with("address::EthAddress"))
16}
17
18/// Returns true if the ABI type is a `FunctionSelector` struct.
19pub fn is_function_selector_struct(typ: &AbiType) -> bool {
20    matches!(typ, AbiType::Struct { name, .. }
21        if name.ends_with("function_selector::FunctionSelector"))
22}
23
24/// Returns true if the ABI type is a struct wrapping a single `inner: Field`.
25pub fn is_wrapped_field_struct(typ: &AbiType) -> bool {
26    matches!(typ, AbiType::Struct { fields, .. }
27        if fields.len() == 1
28           && fields[0].name == "inner"
29           && fields[0].typ == AbiType::Field)
30}
31
32/// Returns true if the ABI type is a `PublicKeys` struct (Noir ABI representation).
33pub fn is_public_keys_struct(typ: &AbiType) -> bool {
34    matches!(typ, AbiType::Struct { name, fields, .. }
35        if name.ends_with("public_keys::PublicKeys")
36           && fields.len() == 4
37           && fields[0].name == "npk_m"
38           && fields[1].name == "ivpk_m"
39           && fields[2].name == "ovpk_m"
40           && fields[3].name == "tpk_m")
41}
42
43/// Returns true if the ABI type is a `BoundedVec` struct.
44pub fn is_bounded_vec_struct(typ: &AbiType) -> bool {
45    matches!(typ, AbiType::Struct { name, fields, .. }
46        if name.ends_with("bounded_vec::BoundedVec")
47           && fields.len() == 2
48           && fields[0].name == "storage"
49           && fields[1].name == "len")
50}
51
52/// Returns true if the ABI type is an `Option` struct.
53pub fn is_option_struct(typ: &AbiType) -> bool {
54    matches!(typ, AbiType::Struct { name, fields, .. }
55        if name.ends_with("option::Option")
56           && fields.len() == 2
57           && fields[0].name == "_is_some"
58           && fields[1].name == "_value")
59}
60
61/// Compute the number of field elements an ABI type occupies when flattened.
62pub fn abi_type_size(typ: &AbiType) -> usize {
63    match typ {
64        AbiType::Field | AbiType::Boolean | AbiType::Integer { .. } => 1,
65        AbiType::String { length } => *length,
66        AbiType::Array { element, length } => length * abi_type_size(element),
67        AbiType::Struct { fields, .. } => fields.iter().map(|f| abi_type_size(&f.typ)).sum(),
68        AbiType::Tuple { elements } => elements.iter().map(abi_type_size).sum(),
69    }
70}
71
72/// Compute the total flattened field-element count for a function's parameters.
73pub fn count_arguments_size(function: &FunctionArtifact) -> usize {
74    function
75        .parameters
76        .iter()
77        .map(|p| abi_type_size(&p.typ))
78        .sum()
79}
80
81/// Validate a single ABI type recursively.
82///
83/// Returns a list of validation errors found at or below `path`.
84fn validate_abi_type(typ: &AbiType, path: &str) -> Vec<String> {
85    match typ {
86        AbiType::Field | AbiType::Boolean => vec![],
87        AbiType::Integer { width, .. } => {
88            if *width == 0 {
89                vec![format!("{path}: integer width must be > 0")]
90            } else {
91                vec![]
92            }
93        }
94        AbiType::String { length } => {
95            if *length == 0 {
96                vec![format!("{path}: string length must be > 0")]
97            } else {
98                vec![]
99            }
100        }
101        AbiType::Array { element, length } => {
102            let mut errors = Vec::new();
103            if *length == 0 {
104                errors.push(format!("{path}: array length must be > 0"));
105            }
106            errors.extend(validate_abi_type(element, &format!("{path}[]")));
107            errors
108        }
109        AbiType::Struct { name, fields } => {
110            let mut errors = Vec::new();
111            if name.is_empty() {
112                errors.push(format!("{path}: struct name must not be empty"));
113            }
114            if fields.is_empty() {
115                errors.push(format!(
116                    "{path}: struct '{name}' must have at least one field"
117                ));
118            }
119            for field in fields {
120                errors.extend(validate_abi_type(
121                    &field.typ,
122                    &format!("{path}.{}", field.name),
123                ));
124            }
125            errors
126        }
127        AbiType::Tuple { elements } => {
128            let mut errors = Vec::new();
129            if elements.is_empty() {
130                errors.push(format!("{path}: tuple must have at least one element"));
131            }
132            for (i, elem) in elements.iter().enumerate() {
133                errors.extend(validate_abi_type(elem, &format!("{path}.{i}")));
134            }
135            errors
136        }
137    }
138}
139
140/// Validate a contract artifact's ABI for correctness.
141///
142/// Checks:
143/// - At least one function exists
144/// - All functions have names and valid selectors
145/// - All parameter types are well-formed
146/// - A constructor (initializer) exists
147///
148/// Returns a list of validation errors (empty = valid).
149pub fn abi_checker(artifact: &ContractArtifact) -> Vec<String> {
150    let mut errors = Vec::new();
151
152    if artifact.functions.is_empty() {
153        errors.push("artifact has no functions".to_owned());
154        return errors;
155    }
156
157    let has_constructor = artifact.functions.iter().any(|f| f.is_initializer);
158    if !has_constructor {
159        errors.push("artifact has no constructor (initializer)".to_owned());
160    }
161
162    for func in &artifact.functions {
163        let fn_path = format!("function '{}'", func.name);
164
165        if func.name.is_empty() {
166            errors.push(format!("{fn_path}: function name must not be empty"));
167        }
168
169        if func.selector.is_none() {
170            errors.push(format!("{fn_path}: missing selector"));
171        }
172
173        for param in &func.parameters {
174            errors.extend(validate_abi_type(
175                &param.typ,
176                &format!("{fn_path}.{}", param.name),
177            ));
178        }
179
180        for (i, ret) in func.return_types.iter().enumerate() {
181            errors.extend(validate_abi_type(ret, &format!("{fn_path}->return[{i}]")));
182        }
183    }
184
185    errors
186}
187
188#[cfg(test)]
189#[allow(clippy::unwrap_used)]
190mod tests {
191    use super::*;
192    use crate::abi::types::AbiParameter;
193
194    fn valid_artifact() -> ContractArtifact {
195        ContractArtifact::from_json(
196            r#"{
197                "name": "TestContract",
198                "functions": [
199                    {
200                        "name": "constructor",
201                        "function_type": "private",
202                        "is_initializer": true,
203                        "is_static": false,
204                        "parameters": [
205                            { "name": "admin", "type": { "kind": "field" } }
206                        ],
207                        "return_types": [],
208                        "selector": "0xe5fb6c81"
209                    },
210                    {
211                        "name": "transfer",
212                        "function_type": "private",
213                        "is_initializer": false,
214                        "is_static": false,
215                        "parameters": [
216                            { "name": "from", "type": { "kind": "field" } },
217                            { "name": "to", "type": { "kind": "field" } }
218                        ],
219                        "return_types": [],
220                        "selector": "0xd6f42325"
221                    }
222                ]
223            }"#,
224        )
225        .unwrap()
226    }
227
228    #[test]
229    fn abi_checker_valid_artifact() {
230        let errors = abi_checker(&valid_artifact());
231        assert!(errors.is_empty(), "unexpected errors: {errors:?}");
232    }
233
234    #[test]
235    fn abi_checker_empty_functions() {
236        let artifact = ContractArtifact {
237            name: "Empty".to_owned(),
238            functions: vec![],
239            outputs: None,
240            file_map: None,
241            context_inputs_sizes: None,
242        };
243        let errors = abi_checker(&artifact);
244        assert!(!errors.is_empty());
245        assert!(errors[0].contains("no functions"));
246    }
247
248    #[test]
249    fn abi_checker_invalid_parameter_type() {
250        let mut artifact = valid_artifact();
251        artifact.functions[1].parameters.push(AbiParameter {
252            name: "bad".to_owned(),
253            typ: AbiType::Integer {
254                sign: "unsigned".to_owned(),
255                width: 0,
256            },
257            visibility: None,
258        });
259        let errors = abi_checker(&artifact);
260        assert!(!errors.is_empty());
261        assert!(
262            errors.iter().any(|e| e.contains("width must be > 0")),
263            "errors: {errors:?}"
264        );
265    }
266
267    #[test]
268    fn abi_checker_missing_constructor() {
269        let mut artifact = valid_artifact();
270        for func in &mut artifact.functions {
271            func.is_initializer = false;
272        }
273        let errors = abi_checker(&artifact);
274        assert!(
275            errors.iter().any(|e| e.contains("no constructor")),
276            "errors: {errors:?}"
277        );
278    }
279}