Skip to main content

aztec_core/
abi.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2use std::collections::BTreeMap;
3use std::fmt;
4
5use crate::types::Fr;
6use crate::Error;
7
8fn strip_0x(s: &str) -> &str {
9    s.strip_prefix("0x").unwrap_or(s)
10}
11
12fn decode_selector_hex(s: &str) -> Result<[u8; 4], Error> {
13    let raw = strip_0x(s);
14    if raw.len() > 8 {
15        return Err(Error::InvalidData(
16            "function selector must fit in 4 bytes".to_owned(),
17        ));
18    }
19    let padded = format!("{raw:0>8}");
20    let bytes = hex::decode(padded).map_err(|e| Error::InvalidData(e.to_string()))?;
21    let mut out = [0u8; 4];
22    out.copy_from_slice(&bytes);
23    Ok(out)
24}
25
26/// A 4-byte function selector used to identify contract functions.
27#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
28pub struct FunctionSelector(pub [u8; 4]);
29
30impl FunctionSelector {
31    /// Parse a function selector from a hex string (e.g. `"0xaabbccdd"`).
32    pub fn from_hex(value: &str) -> Result<Self, Error> {
33        Ok(Self(decode_selector_hex(value)?))
34    }
35
36    /// Compute a function selector from a Noir function signature string.
37    ///
38    /// The selector is the first 4 bytes of the Keccak-256 hash of the signature.
39    ///
40    /// # Example
41    /// ```
42    /// # use aztec_core::abi::FunctionSelector;
43    /// let selector = FunctionSelector::from_signature("sponsor_unconditionally()");
44    /// ```
45    pub fn from_signature(signature: &str) -> Self {
46        use sha3::{Digest, Keccak256};
47        let hash = Keccak256::digest(signature.as_bytes());
48        let mut bytes = [0u8; 4];
49        bytes.copy_from_slice(&hash[..4]);
50        Self(bytes)
51    }
52
53    /// Derive a function selector from a function name.
54    ///
55    /// Not yet implemented — returns an error.
56    pub fn from_name(_name: &str) -> Result<Self, Error> {
57        Err(Error::Abi(
58            "function selector derivation is not implemented yet".to_owned(),
59        ))
60    }
61}
62
63impl fmt::Display for FunctionSelector {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        write!(f, "0x{}", hex::encode(self.0))
66    }
67}
68
69impl Serialize for FunctionSelector {
70    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
71    where
72        S: Serializer,
73    {
74        serializer.serialize_str(&self.to_string())
75    }
76}
77
78impl<'de> Deserialize<'de> for FunctionSelector {
79    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
80    where
81        D: Deserializer<'de>,
82    {
83        let s = String::deserialize(deserializer)?;
84        Self::from_hex(&s).map_err(serde::de::Error::custom)
85    }
86}
87
88/// A field-element event selector used to identify contract events.
89#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
90pub struct EventSelector(pub Fr);
91
92/// The type of a contract function.
93#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
94#[serde(rename_all = "lowercase")]
95pub enum FunctionType {
96    /// A private function executed in the user's PXE.
97    Private,
98    /// A public function executed by the sequencer.
99    Public,
100    /// A utility (view/unconstrained) function for read-only queries.
101    Utility,
102}
103
104/// ABI type representation for function parameters and return values.
105#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
106#[serde(tag = "kind", rename_all = "snake_case")]
107pub enum AbiType {
108    /// A BN254 field element.
109    Field,
110    /// A boolean value.
111    Boolean,
112    /// A signed or unsigned integer with a specific bit width.
113    Integer {
114        /// `"signed"` or `"unsigned"`.
115        sign: String,
116        /// Bit width of the integer.
117        width: u16,
118    },
119    /// A fixed-length array of elements.
120    Array {
121        /// Element type.
122        element: Box<Self>,
123        /// Fixed array length.
124        length: usize,
125    },
126    /// A fixed-length string.
127    String {
128        /// Maximum string length.
129        length: usize,
130    },
131    /// A named struct with typed fields.
132    Struct {
133        /// Struct type name.
134        name: String,
135        /// Struct fields.
136        fields: Vec<AbiParameter>,
137    },
138    /// An anonymous tuple of types.
139    Tuple {
140        /// Element types.
141        elements: Vec<Self>,
142    },
143}
144
145/// A concrete ABI value used as a function argument or return value.
146#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
147#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
148pub enum AbiValue {
149    /// A BN254 field element value.
150    Field(Fr),
151    /// A boolean value.
152    Boolean(bool),
153    /// An integer value.
154    Integer(i128),
155    /// An array of values.
156    Array(Vec<Self>),
157    /// A string value.
158    String(String),
159    /// A struct value with named fields.
160    Struct(BTreeMap<String, Self>),
161    /// A tuple of values.
162    Tuple(Vec<Self>),
163}
164
165/// A named, typed parameter in a function ABI.
166#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
167pub struct AbiParameter {
168    /// Parameter name.
169    pub name: String,
170    /// Parameter type.
171    #[serde(rename = "type")]
172    pub typ: AbiType,
173    /// Visibility (e.g. `"private"`, `"public"`).
174    #[serde(default)]
175    pub visibility: Option<String>,
176}
177
178/// Metadata for a single function within a contract artifact.
179#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
180pub struct FunctionArtifact {
181    /// Function name.
182    pub name: String,
183    /// Whether this is a private, public, or utility function.
184    pub function_type: FunctionType,
185    /// Whether this function is a contract initializer (constructor).
186    #[serde(default)]
187    pub is_initializer: bool,
188    /// Whether this function is a static (read-only) call.
189    #[serde(default)]
190    pub is_static: bool,
191    /// Function parameters.
192    #[serde(default)]
193    pub parameters: Vec<AbiParameter>,
194    /// Return types.
195    #[serde(default)]
196    pub return_types: Vec<AbiType>,
197    /// Pre-computed function selector.
198    #[serde(default)]
199    pub selector: Option<FunctionSelector>,
200}
201
202/// A deserialized contract artifact containing function metadata.
203#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
204pub struct ContractArtifact {
205    /// Contract name.
206    pub name: String,
207    /// Functions defined in the contract.
208    #[serde(default)]
209    pub functions: Vec<FunctionArtifact>,
210}
211
212impl ContractArtifact {
213    /// Deserialize a contract artifact from a JSON string.
214    pub fn from_json(json: &str) -> Result<Self, Error> {
215        serde_json::from_str(json).map_err(Error::from)
216    }
217
218    /// Find a function by name, returning an error if not found.
219    pub fn find_function(&self, name: &str) -> Result<&FunctionArtifact, Error> {
220        self.functions
221            .iter()
222            .find(|f| f.name == name)
223            .ok_or_else(|| {
224                Error::Abi(format!(
225                    "function '{}' not found in artifact '{}'",
226                    name, self.name
227                ))
228            })
229    }
230
231    /// Find a function by name and type, returning an error if not found.
232    pub fn find_function_by_type(
233        &self,
234        name: &str,
235        function_type: &FunctionType,
236    ) -> Result<&FunctionArtifact, Error> {
237        self.functions
238            .iter()
239            .find(|f| f.name == name && &f.function_type == function_type)
240            .ok_or_else(|| {
241                Error::Abi(format!(
242                    "{:?} function '{}' not found in artifact '{}'",
243                    function_type, name, self.name
244                ))
245            })
246    }
247}
248
249#[cfg(test)]
250#[allow(clippy::expect_used, clippy::panic)]
251mod tests {
252    use super::*;
253
254    const MINIMAL_ARTIFACT: &str = r#"
255    {
256      "name": "TestContract",
257      "functions": [
258        {
259          "name": "increment",
260          "function_type": "public",
261          "is_initializer": false,
262          "is_static": false,
263          "parameters": [
264            { "name": "value", "type": { "kind": "field" } }
265          ],
266          "return_types": []
267        }
268      ]
269    }
270    "#;
271
272    const MULTI_FUNCTION_ARTIFACT: &str = r#"
273    {
274      "name": "TokenContract",
275      "functions": [
276        {
277          "name": "constructor",
278          "function_type": "private",
279          "is_initializer": true,
280          "is_static": false,
281          "parameters": [
282            { "name": "admin", "type": { "kind": "field" } },
283            { "name": "name", "type": { "kind": "string", "length": 31 } }
284          ],
285          "return_types": []
286        },
287        {
288          "name": "transfer",
289          "function_type": "private",
290          "is_initializer": false,
291          "is_static": false,
292          "parameters": [
293            { "name": "from", "type": { "kind": "field" } },
294            { "name": "to", "type": { "kind": "field" } },
295            { "name": "amount", "type": { "kind": "integer", "sign": "unsigned", "width": 64 } }
296          ],
297          "return_types": []
298        },
299        {
300          "name": "balance_of",
301          "function_type": "utility",
302          "is_initializer": false,
303          "is_static": true,
304          "parameters": [
305            { "name": "owner", "type": { "kind": "field" } }
306          ],
307          "return_types": [
308            { "kind": "integer", "sign": "unsigned", "width": 64 }
309          ]
310        },
311        {
312          "name": "total_supply",
313          "function_type": "public",
314          "is_initializer": false,
315          "is_static": true,
316          "parameters": [],
317          "return_types": [
318            { "kind": "integer", "sign": "unsigned", "width": 64 }
319          ]
320        }
321      ]
322    }
323    "#;
324
325    #[test]
326    fn function_type_roundtrip() {
327        for (ft, expected) in [
328            (FunctionType::Private, "\"private\""),
329            (FunctionType::Public, "\"public\""),
330            (FunctionType::Utility, "\"utility\""),
331        ] {
332            let json = serde_json::to_string(&ft).expect("serialize FunctionType");
333            assert_eq!(json, expected);
334            let decoded: FunctionType =
335                serde_json::from_str(&json).expect("deserialize FunctionType");
336            assert_eq!(decoded, ft);
337        }
338    }
339
340    #[test]
341    fn function_selector_hex_roundtrip() {
342        let selector = FunctionSelector::from_hex("0xaabbccdd").expect("valid hex");
343        assert_eq!(selector.0, [0xaa, 0xbb, 0xcc, 0xdd]);
344        assert_eq!(selector.to_string(), "0xaabbccdd");
345
346        let json = serde_json::to_string(&selector).expect("serialize selector");
347        let decoded: FunctionSelector = serde_json::from_str(&json).expect("deserialize selector");
348        assert_eq!(decoded, selector);
349    }
350
351    #[test]
352    fn function_selector_rejects_too_long() {
353        let result = FunctionSelector::from_hex("0xaabbccddee");
354        assert!(result.is_err());
355    }
356
357    #[test]
358    fn event_selector_roundtrip() {
359        let selector = EventSelector(Fr::from(42u64));
360        let json = serde_json::to_string(&selector).expect("serialize EventSelector");
361        let decoded: EventSelector =
362            serde_json::from_str(&json).expect("deserialize EventSelector");
363        assert_eq!(decoded, selector);
364    }
365
366    #[test]
367    fn load_minimal_artifact() {
368        let artifact = ContractArtifact::from_json(MINIMAL_ARTIFACT).expect("parse artifact");
369        assert_eq!(artifact.name, "TestContract");
370        assert_eq!(artifact.functions.len(), 1);
371        assert_eq!(artifact.functions[0].name, "increment");
372        assert_eq!(artifact.functions[0].function_type, FunctionType::Public);
373        assert!(!artifact.functions[0].is_initializer);
374        assert_eq!(artifact.functions[0].parameters.len(), 1);
375        assert_eq!(artifact.functions[0].parameters[0].name, "value");
376    }
377
378    #[test]
379    fn load_multi_function_artifact() {
380        let artifact =
381            ContractArtifact::from_json(MULTI_FUNCTION_ARTIFACT).expect("parse artifact");
382        assert_eq!(artifact.name, "TokenContract");
383        assert_eq!(artifact.functions.len(), 4);
384
385        let constructor = &artifact.functions[0];
386        assert_eq!(constructor.name, "constructor");
387        assert_eq!(constructor.function_type, FunctionType::Private);
388        assert!(constructor.is_initializer);
389        assert_eq!(constructor.parameters.len(), 2);
390
391        let transfer = &artifact.functions[1];
392        assert_eq!(transfer.name, "transfer");
393        assert_eq!(transfer.function_type, FunctionType::Private);
394        assert!(!transfer.is_static);
395
396        let balance = &artifact.functions[2];
397        assert_eq!(balance.name, "balance_of");
398        assert_eq!(balance.function_type, FunctionType::Utility);
399        assert!(balance.is_static);
400        assert_eq!(balance.return_types.len(), 1);
401
402        let supply = &artifact.functions[3];
403        assert_eq!(supply.name, "total_supply");
404        assert_eq!(supply.function_type, FunctionType::Public);
405        assert!(supply.is_static);
406    }
407
408    #[test]
409    fn find_function_by_name() {
410        let artifact =
411            ContractArtifact::from_json(MULTI_FUNCTION_ARTIFACT).expect("parse artifact");
412
413        let transfer = artifact.find_function("transfer").expect("find transfer");
414        assert_eq!(transfer.name, "transfer");
415        assert_eq!(transfer.function_type, FunctionType::Private);
416    }
417
418    #[test]
419    fn find_function_not_found() {
420        let artifact =
421            ContractArtifact::from_json(MULTI_FUNCTION_ARTIFACT).expect("parse artifact");
422
423        let result = artifact.find_function("nonexistent");
424        assert!(result.is_err());
425    }
426
427    #[test]
428    fn find_function_by_type() {
429        let artifact =
430            ContractArtifact::from_json(MULTI_FUNCTION_ARTIFACT).expect("parse artifact");
431
432        let balance = artifact
433            .find_function_by_type("balance_of", &FunctionType::Utility)
434            .expect("find balance_of as utility");
435        assert_eq!(balance.name, "balance_of");
436
437        let wrong_type = artifact.find_function_by_type("balance_of", &FunctionType::Public);
438        assert!(wrong_type.is_err());
439    }
440
441    #[test]
442    fn abi_value_field_roundtrip() {
443        let value = AbiValue::Field(Fr::from(1u64));
444        let json = serde_json::to_string(&value).expect("serialize AbiValue::Field");
445        assert!(json.contains("field"));
446        let decoded: AbiValue = serde_json::from_str(&json).expect("deserialize AbiValue");
447        assert_eq!(decoded, value);
448    }
449
450    #[test]
451    fn abi_value_boolean_roundtrip() {
452        let value = AbiValue::Boolean(true);
453        let json = serde_json::to_string(&value).expect("serialize");
454        let decoded: AbiValue = serde_json::from_str(&json).expect("deserialize");
455        assert_eq!(decoded, value);
456    }
457
458    #[test]
459    fn abi_value_integer_roundtrip() {
460        let value = AbiValue::Integer(42);
461        let json = serde_json::to_string(&value).expect("serialize");
462        let decoded: AbiValue = serde_json::from_str(&json).expect("deserialize");
463        assert_eq!(decoded, value);
464    }
465
466    #[test]
467    fn abi_value_array_roundtrip() {
468        let value = AbiValue::Array(vec![
469            AbiValue::Field(Fr::from(1u64)),
470            AbiValue::Field(Fr::from(2u64)),
471        ]);
472        let json = serde_json::to_string(&value).expect("serialize");
473        let decoded: AbiValue = serde_json::from_str(&json).expect("deserialize");
474        assert_eq!(decoded, value);
475    }
476
477    #[test]
478    fn abi_value_struct_roundtrip() {
479        let mut fields = BTreeMap::new();
480        fields.insert("x".to_owned(), AbiValue::Field(Fr::from(1u64)));
481        fields.insert("y".to_owned(), AbiValue::Integer(2));
482        let value = AbiValue::Struct(fields);
483        let json = serde_json::to_string(&value).expect("serialize");
484        let decoded: AbiValue = serde_json::from_str(&json).expect("deserialize");
485        assert_eq!(decoded, value);
486    }
487
488    #[test]
489    fn abi_type_struct_roundtrip() {
490        let typ = AbiType::Struct {
491            name: "Point".to_owned(),
492            fields: vec![
493                AbiParameter {
494                    name: "x".to_owned(),
495                    typ: AbiType::Field,
496                    visibility: None,
497                },
498                AbiParameter {
499                    name: "y".to_owned(),
500                    typ: AbiType::Field,
501                    visibility: None,
502                },
503            ],
504        };
505        let json = serde_json::to_string(&typ).expect("serialize AbiType::Struct");
506        let decoded: AbiType = serde_json::from_str(&json).expect("deserialize AbiType::Struct");
507        assert_eq!(decoded, typ);
508    }
509
510    #[test]
511    fn abi_type_array_roundtrip() {
512        let typ = AbiType::Array {
513            element: Box::new(AbiType::Field),
514            length: 10,
515        };
516        let json = serde_json::to_string(&typ).expect("serialize");
517        let decoded: AbiType = serde_json::from_str(&json).expect("deserialize");
518        assert_eq!(decoded, typ);
519    }
520
521    #[test]
522    fn artifact_from_invalid_json_fails() {
523        let result = ContractArtifact::from_json("not json");
524        assert!(result.is_err());
525    }
526
527    #[test]
528    fn from_signature_is_deterministic() {
529        let a = FunctionSelector::from_signature("sponsor_unconditionally()");
530        let b = FunctionSelector::from_signature("sponsor_unconditionally()");
531        assert_eq!(a, b);
532    }
533
534    #[test]
535    fn from_signature_different_inputs_differ() {
536        let a = FunctionSelector::from_signature("sponsor_unconditionally()");
537        let b = FunctionSelector::from_signature("claim_and_end_setup((Field),u128,Field,Field)");
538        assert_ne!(a, b);
539    }
540
541    #[test]
542    fn from_signature_empty_string() {
543        // Should not panic and should produce a deterministic result
544        let a = FunctionSelector::from_signature("");
545        let b = FunctionSelector::from_signature("");
546        assert_eq!(a, b);
547    }
548
549    #[test]
550    fn from_signature_produces_4_bytes() {
551        let selector = FunctionSelector::from_signature("transfer(Field,Field,u64)");
552        assert_eq!(selector.0.len(), 4);
553    }
554}