pint_abi/
lib.rs

1//! Encoding, decoding and other helpers for working with the Pint Essential ABI.
2
3// The `pint_abi_gen` macros require that the `pint_abi` crate is available, so
4// we re-export the ABI item-generation macros here so that users don't need to
5// import both crates separately.
6#[cfg(feature = "pint-abi-gen")]
7#[doc(inline)]
8pub use pint_abi_gen::from_file as gen_from_file;
9#[cfg(feature = "pint-abi-gen")]
10#[doc(inline)]
11pub use pint_abi_gen::from_str as gen_from_str;
12
13#[doc(inline)]
14pub use decode::Decode;
15#[doc(inline)]
16pub use encode::Encode;
17#[doc(inline)]
18pub use read::Read;
19#[doc(inline)]
20pub use write::Write;
21
22use std::{collections::BTreeSet, path::Path};
23use thiserror::Error;
24use types::{
25    essential::{
26        contract::Contract,
27        predicate::{Predicate, Program},
28        Word,
29    },
30    ContractABI, PredicateABI,
31};
32
33pub mod decode;
34mod encode;
35pub mod key;
36mod read;
37mod write;
38
39/// Both Pint ABI and Essential protocol types.
40pub mod types {
41    // Re-export the contents of `pint_abi_types` here so that it is accesible
42    // via `pint_abi::types`.
43    #[doc(inline)]
44    pub use pint_abi_types::*;
45    // Re-export the Essential protocol types behind the `essential` namespace.
46    #[doc(inline)]
47    pub use essential_types as essential;
48}
49
50/// Errors that might occur when loading a [`ContractABI`] or [`Contract`] from a path.
51#[derive(Debug, Error)]
52pub enum FromPathError {
53    /// An I/O error occurred.
54    #[error("an I/O error occurred: {0}")]
55    Io(#[from] std::io::Error),
56    /// Failed to deserialize from JSON.
57    #[error("failed to deserialize the ABI from JSON: {0}")]
58    Json(#[from] serde_json::Error),
59}
60
61/// Attempted to `Decode` an instance of a type from a slice containing more
62/// `Word`s than necessary.
63#[derive(Debug, Error)]
64#[error("invalid slice length: `{remaining}` words too many")]
65pub struct TooManyWords {
66    /// The number of words left remaining in the slice.
67    pub remaining: usize,
68}
69
70/// Any error that might occur during a call to `decode`.
71#[derive(Debug, Error)]
72pub enum DecodeError<E> {
73    /// Failed to decode the type.
74    #[error("{0}")]
75    Decode(E),
76    /// Invalid slice length. Slice contained more words than necessary.
77    #[error("{0}")]
78    TooManyWords(#[from] TooManyWords),
79}
80
81/// Shorthand for encoding the given value into a `Vec` of Essential `Word`s.
82pub fn encode<T: Encode>(t: &T) -> Vec<Word> {
83    let mut v = vec![];
84    t.encode(&mut v)
85        .expect("encoding into `Vec<Word>` cannot error");
86    v
87}
88
89/// Shorthand for decoding an instance of `T` from a slice of Essential `Word`s.
90///
91/// Note that dynamically sized types (like `Vec<T>`) are not supported, as the
92/// length cannot be known.
93pub fn decode<T: Decode>(mut words: &[Word]) -> Result<T, DecodeError<T::Error>> {
94    let cursor = &mut words;
95    let t = T::decode(cursor).map_err(DecodeError::Decode)?;
96    if !cursor.is_empty() {
97        let remaining = cursor.len();
98        return Err(TooManyWords { remaining }.into());
99    }
100    Ok(t)
101}
102
103/// Shorthand for loading the [`ContractABI`] from a given ABI JSON file path.
104///
105/// By default, after building a pint package this will be located within the
106/// package's output directory at `out/<profile>/<name>-abi.json`.
107pub fn from_path(path: &Path) -> Result<ContractABI, FromPathError> {
108    let json_str = std::fs::read_to_string(path)?;
109    let abi = serde_json::from_str(&json_str)?;
110    Ok(abi)
111}
112
113/// Shorthand for loading a [`Contract`] from a given JSON file path.
114///
115/// By default, after building a pint package this will be located within the
116/// package's output directory at `out/<profile>/<name>.json`.
117pub fn contract_from_path(path: &Path) -> Result<(Contract, BTreeSet<Program>), FromPathError> {
118    let json_str = std::fs::read_to_string(path)?;
119    let abi = serde_json::from_str(&json_str)?;
120    Ok(abi)
121}
122
123/// Given a `Contract` and its associated `ContractABI`, find and return the
124/// predicate with the given name.
125///
126/// Returns the predicate ABI alongside the predicate itself.
127pub fn find_predicate<'a>(
128    contract: &'a Contract,
129    abi: &'a ContractABI,
130    pred_name: &str,
131) -> Option<(&'a Predicate, &'a PredicateABI)> {
132    // Currently, the ABI always includes the root predicate, even if the
133    // contract does not. Here, we determine whether or not the root predicate
134    // should be skipped. We skip if it is not the only predicate and it exists
135    // as the first predicate, otherwise we assume it is the only predicate and
136    // should be included in the search.
137    const ROOT_NAME: &str = "";
138    let skip_root_predicate = abi.predicates.len() > 1
139        && matches!(abi.predicates.first(), Some(p) if p.name == ROOT_NAME);
140
141    // Skip the root predicate from the ABI here.
142    let mut abi_predicates = abi.predicates.iter();
143    if skip_root_predicate {
144        abi_predicates.next();
145    }
146
147    // Find the matching predicate entry and return it alongside its ABI.
148    contract
149        .predicates
150        .iter()
151        .zip(abi_predicates)
152        .find(|(_, pred_abi)| predicate_name_matches(&pred_abi.name, pred_name))
153}
154
155/// Checks if the predicate name matches exactly, and if not checks if the
156/// predicate name matches with the `::` prefix.
157fn predicate_name_matches(abi_pred_name: &str, pred_name: &str) -> bool {
158    abi_pred_name == pred_name || abi_pred_name.split("::").nth(1) == Some(pred_name)
159}