use anyhow::{anyhow, Result}; use crate::types::Idl; impl Idl { /// Create an [`Idl`] value with additional support for older specs based on the /// `idl.metadata.spec` field. /// /// If `spec` field is not specified, the conversion will fallback to the legacy IDL spec /// (pre Anchor v0.30.0). /// /// **Note:** For legacy IDLs, `idl.metadata.address` field is required to be populated with /// program's address otherwise an error will be returned. pub fn from_slice_with_conversion(idl: &[u8]) -> Result { let value = serde_json::from_slice::(idl)?; let spec = value .get("metadata") .and_then(|m| m.get("spec")) .and_then(|spec| spec.as_str()); match spec { // New standard Some(spec) => match spec { "0.1.0" => serde_json::from_value(value).map_err(Into::into), _ => Err(anyhow!("IDL spec not supported: `{spec}`")), }, // Legacy None => serde_json::from_value::(value).map(TryInto::try_into)?, } } } /// Legacy IDL spec (pre Anchor v0.30.0) mod legacy { use crate::types as t; use anyhow::{anyhow, Result}; use heck::SnakeCase; use serde::{Deserialize, Serialize}; #[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 { /// - `idl-parse`: always the name of the type /// - `idl-build`: full path if there is a name conflict, otherwise the name of the type pub name: String, /// Documentation comments #[serde(skip_serializing_if = "Option::is_none")] pub docs: Option>, /// Generics, only supported with `idl-build` #[serde(skip_serializing_if = "Option::is_none")] pub generics: Option>, /// Type definition, `struct` or `enum` #[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 }, Alias { value: IdlType }, } #[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), GenericLenArray(Box, String), Generic(String), DefinedWithTypeArgs { name: String, args: Vec, }, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub enum IdlDefinedTypeArg { Generic(String), Value(String), Type(IdlType), } #[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, } impl TryFrom for t::Idl { type Error = anyhow::Error; fn try_from(idl: Idl) -> Result { Ok(Self { address: idl .metadata .as_ref() .and_then(|m| m.get("address")) .and_then(|a| a.as_str()) .ok_or_else(|| anyhow!("Program id missing in `idl.metadata.address` field"))? .into(), metadata: t::IdlMetadata { name: idl.name, version: idl.version, spec: t::IDL_SPEC.into(), description: Default::default(), repository: Default::default(), dependencies: Default::default(), contact: Default::default(), deployments: Default::default(), }, docs: idl.docs.unwrap_or_default(), instructions: idl.instructions.into_iter().map(Into::into).collect(), accounts: idl.accounts.clone().into_iter().map(Into::into).collect(), events: idl .events .clone() .unwrap_or_default() .into_iter() .map(Into::into) .collect(), errors: idl .errors .unwrap_or_default() .into_iter() .map(Into::into) .collect(), types: idl .types .into_iter() .map(Into::into) .chain(idl.accounts.into_iter().map(Into::into)) .chain(idl.events.unwrap_or_default().into_iter().map(Into::into)) .collect(), constants: idl.constants.into_iter().map(Into::into).collect(), }) } } fn get_disc(prefix: &str, name: &str) -> Vec { use sha2::{Digest, Sha256}; let mut hasher = Sha256::new(); hasher.update(prefix); hasher.update(b":"); hasher.update(name); hasher.finalize()[..8].into() } impl From for t::IdlInstruction { fn from(value: IdlInstruction) -> Self { let name = value.name.to_snake_case(); Self { discriminator: get_disc("global", &name), name, docs: value.docs.unwrap_or_default(), accounts: value.accounts.into_iter().map(Into::into).collect(), args: value.args.into_iter().map(Into::into).collect(), returns: value.returns.map(|r| r.into()), } } } impl From for t::IdlAccount { fn from(value: IdlTypeDefinition) -> Self { Self { discriminator: get_disc("account", &value.name), name: value.name, } } } impl From for t::IdlEvent { fn from(value: IdlEvent) -> Self { Self { discriminator: get_disc("event", &value.name), name: value.name, } } } impl From for t::IdlErrorCode { fn from(value: IdlErrorCode) -> Self { Self { name: value.name, code: value.code, msg: value.msg, } } } impl From for t::IdlConst { fn from(value: IdlConst) -> Self { Self { name: value.name, docs: Default::default(), ty: value.ty.into(), value: value.value, } } } impl From for t::IdlGenericArg { fn from(value: IdlDefinedTypeArg) -> Self { match value { IdlDefinedTypeArg::Type(ty) => Self::Type { ty: ty.into() }, IdlDefinedTypeArg::Value(value) => Self::Const { value }, IdlDefinedTypeArg::Generic(generic) => Self::Type { ty: t::IdlType::Generic(generic), }, } } } impl From for t::IdlTypeDef { fn from(value: IdlTypeDefinition) -> Self { Self { name: value.name, docs: value.docs.unwrap_or_default(), serialization: Default::default(), repr: Default::default(), generics: Default::default(), ty: value.ty.into(), } } } impl From for t::IdlTypeDef { fn from(value: IdlEvent) -> Self { Self { name: value.name, docs: Default::default(), serialization: Default::default(), repr: Default::default(), generics: Default::default(), ty: t::IdlTypeDefTy::Struct { fields: Some(t::IdlDefinedFields::Named( value .fields .into_iter() .map(|f| t::IdlField { name: f.name.to_snake_case(), docs: Default::default(), ty: f.ty.into(), }) .collect(), )), }, } } } impl From for t::IdlTypeDefTy { fn from(value: IdlTypeDefinitionTy) -> Self { match value { IdlTypeDefinitionTy::Struct { fields } => Self::Struct { fields: fields .is_empty() .then(|| None) .unwrap_or_else(|| Some(fields.into())), }, IdlTypeDefinitionTy::Enum { variants } => Self::Enum { variants: variants .into_iter() .map(|variant| t::IdlEnumVariant { name: variant.name, fields: variant.fields.map(|fields| match fields { EnumFields::Named(fields) => fields.into(), EnumFields::Tuple(tys) => t::IdlDefinedFields::Tuple( tys.into_iter().map(Into::into).collect(), ), }), }) .collect(), }, IdlTypeDefinitionTy::Alias { value } => Self::Type { alias: value.into(), }, } } } impl From for t::IdlField { fn from(value: IdlField) -> Self { Self { name: value.name.to_snake_case(), docs: value.docs.unwrap_or_default(), ty: value.ty.into(), } } } impl From> for t::IdlDefinedFields { fn from(value: Vec) -> Self { Self::Named(value.into_iter().map(Into::into).collect()) } } impl From for t::IdlType { fn from(value: IdlType) -> Self { match value { IdlType::PublicKey => t::IdlType::Pubkey, IdlType::Defined(name) => t::IdlType::Defined { name, generics: Default::default(), }, IdlType::DefinedWithTypeArgs { name, args } => t::IdlType::Defined { name, generics: args.into_iter().map(Into::into).collect(), }, IdlType::Option(ty) => t::IdlType::Option(ty.into()), IdlType::Vec(ty) => t::IdlType::Vec(ty.into()), IdlType::Array(ty, len) => t::IdlType::Array(ty.into(), t::IdlArrayLen::Value(len)), IdlType::GenericLenArray(ty, generic) => { t::IdlType::Array(ty.into(), t::IdlArrayLen::Generic(generic)) } _ => serde_json::to_value(value) .and_then(serde_json::from_value) .unwrap(), } } } impl From> for Box { fn from(value: Box) -> Self { Box::new((*value).into()) } } impl From for t::IdlInstructionAccountItem { fn from(value: IdlAccountItem) -> Self { match value { IdlAccountItem::IdlAccount(acc) => Self::Single(t::IdlInstructionAccount { name: acc.name.to_snake_case(), docs: acc.docs.unwrap_or_default(), writable: acc.is_mut, signer: acc.is_signer, optional: acc.is_optional.unwrap_or_default(), address: Default::default(), pda: acc .pda .map(|pda| -> Result { Ok(t::IdlPda { seeds: pda .seeds .into_iter() .map(TryInto::try_into) .collect::>()?, program: pda.program_id.map(TryInto::try_into).transpose()?, }) }) .transpose() .unwrap_or_default(), relations: acc.relations, }), IdlAccountItem::IdlAccounts(accs) => Self::Composite(t::IdlInstructionAccounts { name: accs.name.to_snake_case(), accounts: accs.accounts.into_iter().map(Into::into).collect(), }), } } } impl TryFrom for t::IdlSeed { type Error = anyhow::Error; fn try_from(value: IdlSeed) -> Result { let seed = match value { IdlSeed::Account(seed) => Self::Account(t::IdlSeedAccount { account: seed.account, path: seed.path, }), IdlSeed::Arg(seed) => Self::Arg(t::IdlSeedArg { path: seed.path }), IdlSeed::Const(seed) => Self::Const(t::IdlSeedConst { value: match seed.ty { IdlType::String => seed.value.to_string().as_bytes().into(), _ => return Err(anyhow!("Const seed conversion not supported")), }, }), }; Ok(seed) } } }