aztec_core/abi/
checkers.rs1use super::types::{AbiType, ContractArtifact, FunctionArtifact};
2
3pub fn is_address_struct(typ: &AbiType) -> bool {
5 is_aztec_address_struct(typ) || is_eth_address_struct(typ)
6}
7
8pub fn is_aztec_address_struct(typ: &AbiType) -> bool {
10 matches!(typ, AbiType::Struct { name, .. } if name.ends_with("address::AztecAddress"))
11}
12
13pub fn is_eth_address_struct(typ: &AbiType) -> bool {
15 matches!(typ, AbiType::Struct { name, .. } if name.ends_with("address::EthAddress"))
16}
17
18pub fn is_function_selector_struct(typ: &AbiType) -> bool {
20 matches!(typ, AbiType::Struct { name, .. }
21 if name.ends_with("function_selector::FunctionSelector"))
22}
23
24pub 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
32pub 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
43pub 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
52pub 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
61pub 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
72pub 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
81fn 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
140pub 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 ¶m.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}