anchor_lang_idl_spec/
lib.rs

1use std::str::FromStr;
2
3use anyhow::anyhow;
4use serde::{Deserialize, Serialize};
5
6/// IDL specification Semantic Version
7pub const IDL_SPEC: &str = env!("CARGO_PKG_VERSION");
8
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
10pub struct Idl {
11    pub address: String,
12    pub metadata: IdlMetadata,
13    #[serde(default, skip_serializing_if = "is_default")]
14    pub docs: Vec<String>,
15    pub instructions: Vec<IdlInstruction>,
16    #[serde(default, skip_serializing_if = "is_default")]
17    pub accounts: Vec<IdlAccount>,
18    #[serde(default, skip_serializing_if = "is_default")]
19    pub events: Vec<IdlEvent>,
20    #[serde(default, skip_serializing_if = "is_default")]
21    pub errors: Vec<IdlErrorCode>,
22    #[serde(default, skip_serializing_if = "is_default")]
23    pub types: Vec<IdlTypeDef>,
24    #[serde(default, skip_serializing_if = "is_default")]
25    pub constants: Vec<IdlConst>,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
29pub struct IdlMetadata {
30    pub name: String,
31    pub version: String,
32    pub spec: String,
33    #[serde(skip_serializing_if = "is_default")]
34    pub description: Option<String>,
35    #[serde(skip_serializing_if = "is_default")]
36    pub repository: Option<String>,
37    #[serde(default, skip_serializing_if = "is_default")]
38    pub dependencies: Vec<IdlDependency>,
39    #[serde(skip_serializing_if = "is_default")]
40    pub contact: Option<String>,
41    #[serde(skip_serializing_if = "is_default")]
42    pub deployments: Option<IdlDeployments>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
46pub struct IdlDependency {
47    pub name: String,
48    pub version: String,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
52pub struct IdlDeployments {
53    pub mainnet: Option<String>,
54    pub testnet: Option<String>,
55    pub devnet: Option<String>,
56    pub localnet: Option<String>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
60pub struct IdlInstruction {
61    pub name: String,
62    #[serde(default, skip_serializing_if = "is_default")]
63    pub docs: Vec<String>,
64    pub discriminator: IdlDiscriminator,
65    pub accounts: Vec<IdlInstructionAccountItem>,
66    pub args: Vec<IdlField>,
67    #[serde(skip_serializing_if = "is_default")]
68    pub returns: Option<IdlType>,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
72#[serde(untagged)]
73pub enum IdlInstructionAccountItem {
74    Composite(IdlInstructionAccounts),
75    Single(IdlInstructionAccount),
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
79pub struct IdlInstructionAccount {
80    pub name: String,
81    #[serde(default, skip_serializing_if = "is_default")]
82    pub docs: Vec<String>,
83    #[serde(default, skip_serializing_if = "is_default")]
84    pub writable: bool,
85    #[serde(default, skip_serializing_if = "is_default")]
86    pub signer: bool,
87    #[serde(default, skip_serializing_if = "is_default")]
88    pub optional: bool,
89    #[serde(skip_serializing_if = "is_default")]
90    pub address: Option<String>,
91    #[serde(skip_serializing_if = "is_default")]
92    pub pda: Option<IdlPda>,
93    #[serde(default, skip_serializing_if = "is_default")]
94    pub relations: Vec<String>,
95}
96
97#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
98pub struct IdlInstructionAccounts {
99    pub name: String,
100    pub accounts: Vec<IdlInstructionAccountItem>,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
104pub struct IdlPda {
105    pub seeds: Vec<IdlSeed>,
106    #[serde(skip_serializing_if = "is_default")]
107    pub program: Option<IdlSeed>,
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
111#[serde(tag = "kind", rename_all = "lowercase")]
112pub enum IdlSeed {
113    Const(IdlSeedConst),
114    Arg(IdlSeedArg),
115    Account(IdlSeedAccount),
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
119pub struct IdlSeedConst {
120    pub value: Vec<u8>,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
124pub struct IdlSeedArg {
125    pub path: String,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
129pub struct IdlSeedAccount {
130    pub path: String,
131    #[serde(skip_serializing_if = "is_default")]
132    pub account: Option<String>,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
136pub struct IdlAccount {
137    pub name: String,
138    pub discriminator: IdlDiscriminator,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
142pub struct IdlEvent {
143    pub name: String,
144    pub discriminator: IdlDiscriminator,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
148pub struct IdlConst {
149    pub name: String,
150    #[serde(default, skip_serializing_if = "is_default")]
151    pub docs: Vec<String>,
152    #[serde(rename = "type")]
153    pub ty: IdlType,
154    pub value: String,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
158pub struct IdlErrorCode {
159    pub code: u32,
160    pub name: String,
161    #[serde(skip_serializing_if = "is_default")]
162    pub msg: Option<String>,
163}
164
165#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
166pub struct IdlField {
167    pub name: String,
168    #[serde(default, skip_serializing_if = "is_default")]
169    pub docs: Vec<String>,
170    #[serde(rename = "type")]
171    pub ty: IdlType,
172}
173
174#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
175pub struct IdlTypeDef {
176    pub name: String,
177    #[serde(default, skip_serializing_if = "is_default")]
178    pub docs: Vec<String>,
179    #[serde(default, skip_serializing_if = "is_default")]
180    pub serialization: IdlSerialization,
181    #[serde(skip_serializing_if = "is_default")]
182    pub repr: Option<IdlRepr>,
183    #[serde(default, skip_serializing_if = "is_default")]
184    pub generics: Vec<IdlTypeDefGeneric>,
185    #[serde(rename = "type")]
186    pub ty: IdlTypeDefTy,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
190#[serde(rename_all = "lowercase")]
191#[non_exhaustive]
192pub enum IdlSerialization {
193    #[default]
194    Borsh,
195    Bytemuck,
196    BytemuckUnsafe,
197    Custom(String),
198}
199
200#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
201#[serde(tag = "kind", rename_all = "lowercase")]
202#[non_exhaustive]
203pub enum IdlRepr {
204    Rust(IdlReprModifier),
205    C(IdlReprModifier),
206    Transparent,
207}
208
209#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
210pub struct IdlReprModifier {
211    #[serde(default, skip_serializing_if = "is_default")]
212    pub packed: bool,
213    #[serde(skip_serializing_if = "is_default")]
214    pub align: Option<usize>,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
218#[serde(tag = "kind", rename_all = "lowercase")]
219pub enum IdlTypeDefGeneric {
220    Type {
221        name: String,
222    },
223    Const {
224        name: String,
225        #[serde(rename = "type")]
226        ty: String,
227    },
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
231#[serde(tag = "kind", rename_all = "lowercase")]
232pub enum IdlTypeDefTy {
233    Struct {
234        #[serde(skip_serializing_if = "is_default")]
235        fields: Option<IdlDefinedFields>,
236    },
237    Enum {
238        variants: Vec<IdlEnumVariant>,
239    },
240    Type {
241        alias: IdlType,
242    },
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
246pub struct IdlEnumVariant {
247    pub name: String,
248    #[serde(skip_serializing_if = "is_default")]
249    pub fields: Option<IdlDefinedFields>,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
253#[serde(untagged)]
254pub enum IdlDefinedFields {
255    Named(Vec<IdlField>),
256    Tuple(Vec<IdlType>),
257}
258
259#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
260#[serde(rename_all = "lowercase")]
261pub enum IdlArrayLen {
262    Generic(String),
263    #[serde(untagged)]
264    Value(usize),
265}
266
267#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
268#[serde(tag = "kind", rename_all = "lowercase")]
269pub enum IdlGenericArg {
270    Type {
271        #[serde(rename = "type")]
272        ty: IdlType,
273    },
274    Const {
275        value: String,
276    },
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
280#[serde(rename_all = "lowercase")]
281#[non_exhaustive]
282pub enum IdlType {
283    Bool,
284    U8,
285    I8,
286    U16,
287    I16,
288    U32,
289    I32,
290    F32,
291    U64,
292    I64,
293    F64,
294    U128,
295    I128,
296    U256,
297    I256,
298    Bytes,
299    String,
300    Pubkey,
301    Option(Box<IdlType>),
302    Vec(Box<IdlType>),
303    Array(Box<IdlType>, IdlArrayLen),
304    Defined {
305        name: String,
306        #[serde(default, skip_serializing_if = "is_default")]
307        generics: Vec<IdlGenericArg>,
308    },
309    Generic(String),
310}
311
312// TODO: Move to utils crate
313impl FromStr for IdlType {
314    type Err = anyhow::Error;
315
316    fn from_str(s: &str) -> Result<Self, Self::Err> {
317        let mut s = s.to_owned();
318        s.retain(|c| !c.is_whitespace());
319
320        let r = match s.as_str() {
321            "bool" => IdlType::Bool,
322            "u8" => IdlType::U8,
323            "i8" => IdlType::I8,
324            "u16" => IdlType::U16,
325            "i16" => IdlType::I16,
326            "u32" => IdlType::U32,
327            "i32" => IdlType::I32,
328            "f32" => IdlType::F32,
329            "u64" => IdlType::U64,
330            "i64" => IdlType::I64,
331            "f64" => IdlType::F64,
332            "u128" => IdlType::U128,
333            "i128" => IdlType::I128,
334            "u256" => IdlType::U256,
335            "i256" => IdlType::I256,
336            "Vec<u8>" => IdlType::Bytes,
337            "String" | "&str" | "&'staticstr" => IdlType::String,
338            "Pubkey" => IdlType::Pubkey,
339            _ => {
340                if let Some(inner) = s.strip_prefix("Option<") {
341                    let inner_ty = Self::from_str(
342                        inner
343                            .strip_suffix('>')
344                            .ok_or_else(|| anyhow!("Invalid Option"))?,
345                    )?;
346                    return Ok(IdlType::Option(Box::new(inner_ty)));
347                }
348
349                if let Some(inner) = s.strip_prefix("Vec<") {
350                    let inner_ty = Self::from_str(
351                        inner
352                            .strip_suffix('>')
353                            .ok_or_else(|| anyhow!("Invalid Vec"))?,
354                    )?;
355                    return Ok(IdlType::Vec(Box::new(inner_ty)));
356                }
357
358                if s.starts_with('[') {
359                    fn array_from_str(inner: &str) -> IdlType {
360                        match inner.strip_suffix(']') {
361                            Some(nested_inner) => array_from_str(&nested_inner[1..]),
362                            None => {
363                                let (raw_type, raw_length) = inner.rsplit_once(';').unwrap();
364                                let ty = IdlType::from_str(raw_type).unwrap();
365                                let len = match raw_length.replace('_', "").parse::<usize>() {
366                                    Ok(len) => IdlArrayLen::Value(len),
367                                    Err(_) => IdlArrayLen::Generic(raw_length.to_owned()),
368                                };
369                                IdlType::Array(Box::new(ty), len)
370                            }
371                        }
372                    }
373                    return Ok(array_from_str(&s));
374                }
375
376                // Defined
377                let (name, generics) = if let Some(i) = s.find('<') {
378                    (
379                        s.get(..i).unwrap().to_owned(),
380                        s.get(i + 1..)
381                            .unwrap()
382                            .strip_suffix('>')
383                            .unwrap()
384                            .split(',')
385                            .map(|g| g.trim().to_owned())
386                            .map(|g| {
387                                if g.parse::<bool>().is_ok()
388                                    || g.parse::<u128>().is_ok()
389                                    || g.parse::<i128>().is_ok()
390                                    || g.parse::<char>().is_ok()
391                                {
392                                    Ok(IdlGenericArg::Const { value: g })
393                                } else {
394                                    Self::from_str(&g).map(|ty| IdlGenericArg::Type { ty })
395                                }
396                            })
397                            .collect::<Result<Vec<_>, _>>()?,
398                    )
399                } else {
400                    (s.to_owned(), vec![])
401                };
402
403                IdlType::Defined { name, generics }
404            }
405        };
406        Ok(r)
407    }
408}
409
410pub type IdlDiscriminator = Vec<u8>;
411
412/// Get whether the given data is the default of its type.
413fn is_default<T: Default + PartialEq>(it: &T) -> bool {
414    *it == T::default()
415}
416
417#[cfg(test)]
418mod tests {
419    use super::*;
420
421    #[test]
422    fn option() {
423        assert_eq!(
424            IdlType::from_str("Option<bool>").unwrap(),
425            IdlType::Option(Box::new(IdlType::Bool))
426        )
427    }
428
429    #[test]
430    fn vector() {
431        assert_eq!(
432            IdlType::from_str("Vec<bool>").unwrap(),
433            IdlType::Vec(Box::new(IdlType::Bool))
434        )
435    }
436
437    #[test]
438    fn array() {
439        assert_eq!(
440            IdlType::from_str("[Pubkey; 16]").unwrap(),
441            IdlType::Array(Box::new(IdlType::Pubkey), IdlArrayLen::Value(16))
442        );
443    }
444
445    #[test]
446    fn array_with_underscored_length() {
447        assert_eq!(
448            IdlType::from_str("[u8; 50_000]").unwrap(),
449            IdlType::Array(Box::new(IdlType::U8), IdlArrayLen::Value(50000))
450        );
451    }
452
453    #[test]
454    fn multidimensional_array() {
455        assert_eq!(
456            IdlType::from_str("[[u8; 16]; 32]").unwrap(),
457            IdlType::Array(
458                Box::new(IdlType::Array(
459                    Box::new(IdlType::U8),
460                    IdlArrayLen::Value(16)
461                )),
462                IdlArrayLen::Value(32)
463            )
464        );
465    }
466
467    #[test]
468    fn generic_array() {
469        assert_eq!(
470            IdlType::from_str("[u64; T]").unwrap(),
471            IdlType::Array(Box::new(IdlType::U64), IdlArrayLen::Generic("T".into()))
472        );
473    }
474
475    #[test]
476    fn defined() {
477        assert_eq!(
478            IdlType::from_str("MyStruct").unwrap(),
479            IdlType::Defined {
480                name: "MyStruct".into(),
481                generics: vec![]
482            }
483        )
484    }
485
486    #[test]
487    fn defined_with_generics() {
488        assert_eq!(
489            IdlType::from_str("MyStruct<Pubkey, u64, 8>").unwrap(),
490            IdlType::Defined {
491                name: "MyStruct".into(),
492                generics: vec![
493                    IdlGenericArg::Type {
494                        ty: IdlType::Pubkey
495                    },
496                    IdlGenericArg::Type { ty: IdlType::U64 },
497                    IdlGenericArg::Const { value: "8".into() },
498                ],
499            }
500        )
501    }
502}