use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; pub mod file; pub mod pda; pub mod relations; #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct Idl { pub version: String, pub name: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub docs: Option>, #[serde(skip_serializing_if = "Vec::is_empty", default)] pub constants: Vec, pub instructions: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] pub accounts: Vec, #[serde(skip_serializing_if = "Vec::is_empty", default)] pub types: Vec, #[serde(skip_serializing_if = "Option::is_none", default)] pub events: Option>, #[serde(skip_serializing_if = "Option::is_none", default)] pub errors: Option>, #[serde(skip_serializing_if = "Option::is_none", default)] pub metadata: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct IdlConst { pub name: String, #[serde(rename = "type")] pub ty: IdlType, pub value: String, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct IdlState { #[serde(rename = "struct")] pub strct: IdlTypeDefinition, pub methods: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct IdlInstruction { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub docs: Option>, pub accounts: Vec, pub args: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub returns: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct IdlAccounts { pub name: String, pub accounts: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(untagged)] pub enum IdlAccountItem { IdlAccount(IdlAccount), IdlAccounts(IdlAccounts), } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct IdlAccount { pub name: String, pub is_mut: bool, pub is_signer: bool, #[serde(skip_serializing_if = "Option::is_none")] pub is_optional: Option, #[serde(skip_serializing_if = "Option::is_none")] pub docs: Option>, #[serde(skip_serializing_if = "Option::is_none", default)] pub pda: Option, #[serde(skip_serializing_if = "Vec::is_empty", default)] pub relations: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct IdlPda { pub seeds: Vec, #[serde(skip_serializing_if = "Option::is_none", default)] pub program_id: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase", tag = "kind")] pub enum IdlSeed { Const(IdlSeedConst), Arg(IdlSeedArg), Account(IdlSeedAccount), } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct IdlSeedAccount { #[serde(rename = "type")] pub ty: IdlType, // account_ty points to the entry in the "accounts" section. // Some only if the `Account` type is used. #[serde(skip_serializing_if = "Option::is_none")] pub account: Option, pub path: String, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct IdlSeedArg { #[serde(rename = "type")] pub ty: IdlType, pub path: String, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct IdlSeedConst { #[serde(rename = "type")] pub ty: IdlType, pub value: serde_json::Value, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct IdlField { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub docs: Option>, #[serde(rename = "type")] pub ty: IdlType, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct IdlEvent { pub name: String, pub fields: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct IdlEventField { pub name: String, #[serde(rename = "type")] pub ty: IdlType, pub index: bool, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct IdlTypeDefinition { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub docs: Option>, #[serde(rename = "type")] pub ty: IdlTypeDefinitionTy, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "lowercase", tag = "kind")] pub enum IdlTypeDefinitionTy { Struct { fields: Vec }, Enum { variants: Vec }, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct IdlEnumVariant { pub name: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub fields: Option, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(untagged)] pub enum EnumFields { Named(Vec), Tuple(Vec), } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub enum IdlType { Bool, U8, I8, U16, I16, U32, I32, F32, U64, I64, F64, U128, I128, U256, I256, Bytes, String, PublicKey, Defined(String), Option(Box), Vec(Box), Array(Box, usize), } impl std::str::FromStr for IdlType { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let mut s = s.to_string(); fn array_from_str(inner: &str) -> IdlType { match inner.strip_suffix(']') { None => { let (raw_type, raw_length) = inner.rsplit_once(';').unwrap(); let ty = IdlType::from_str(raw_type).unwrap(); let len = raw_length.replace('_', "").parse::().unwrap(); IdlType::Array(Box::new(ty), len) } Some(nested_inner) => array_from_str(&nested_inner[1..]), } } s.retain(|c| !c.is_whitespace()); let r = match s.as_str() { "bool" => IdlType::Bool, "u8" => IdlType::U8, "i8" => IdlType::I8, "u16" => IdlType::U16, "i16" => IdlType::I16, "u32" => IdlType::U32, "i32" => IdlType::I32, "f32" => IdlType::F32, "u64" => IdlType::U64, "i64" => IdlType::I64, "f64" => IdlType::F64, "u128" => IdlType::U128, "i128" => IdlType::I128, "u256" => IdlType::U256, "i256" => IdlType::I256, "Vec" => IdlType::Bytes, "String" | "&str" | "&'staticstr" => IdlType::String, "Pubkey" => IdlType::PublicKey, _ => match s.to_string().strip_prefix("Option<") { None => match s.to_string().strip_prefix("Vec<") { None => { if s.to_string().starts_with('[') { array_from_str(&s) } else { IdlType::Defined(s.to_string()) } } Some(inner) => { let inner_ty = Self::from_str( inner .strip_suffix('>') .ok_or_else(|| anyhow::anyhow!("Invalid option"))?, )?; IdlType::Vec(Box::new(inner_ty)) } }, Some(inner) => { let inner_ty = Self::from_str( inner .strip_suffix('>') .ok_or_else(|| anyhow::anyhow!("Invalid option"))?, )?; IdlType::Option(Box::new(inner_ty)) } }, }; Ok(r) } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct IdlErrorCode { pub code: u32, pub name: String, #[serde(skip_serializing_if = "Option::is_none", default)] pub msg: Option, } #[cfg(test)] mod tests { use crate::idl::IdlType; use std::str::FromStr; #[test] fn multidimensional_array() { assert_eq!( IdlType::from_str("[[u8;16];32]").unwrap(), IdlType::Array(Box::new(IdlType::Array(Box::new(IdlType::U8), 16)), 32) ); } #[test] fn array() { assert_eq!( IdlType::from_str("[Pubkey;16]").unwrap(), IdlType::Array(Box::new(IdlType::PublicKey), 16) ); } #[test] fn array_with_underscored_length() { assert_eq!( IdlType::from_str("[u8;50_000]").unwrap(), IdlType::Array(Box::new(IdlType::U8), 50000) ); } #[test] fn option() { assert_eq!( IdlType::from_str("Option").unwrap(), IdlType::Option(Box::new(IdlType::Bool)) ) } #[test] fn vector() { assert_eq!( IdlType::from_str("Vec").unwrap(), IdlType::Vec(Box::new(IdlType::Bool)) ) } }