1use std::str::FromStr;
2
3use anyhow::anyhow;
4use serde::{Deserialize, Serialize};
5
6pub 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
312impl 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 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
412fn 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}