diff --git a/CHANGELOG.md b/CHANGELOG.md index a900b339c..7cb9e41ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ The minor version will be incremented upon a breaking change and the patch versi - cli: Sync program ids on the initial build ([#3023](https://github.com/coral-xyz/anchor/pull/3023)). - idl: Remove `anchor-syn` dependency ([#3030](https://github.com/coral-xyz/anchor/pull/3030)). - lang: Add `const` of program ID to `declare_id!` and `declare_program!` ([#3019](https://github.com/coral-xyz/anchor/pull/3019)). +- idl: Add separate spec crate ([#3036](https://github.com/coral-xyz/anchor/pull/3036)). ### Fixes diff --git a/Cargo.lock b/Cargo.lock index 267c0c4a1..ff6c0edef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -291,6 +291,7 @@ dependencies = [ name = "anchor-lang-idl" version = "0.1.0" dependencies = [ + "anchor-lang-idl-spec", "anyhow", "heck 0.3.3", "regex", @@ -299,6 +300,14 @@ dependencies = [ "sha2 0.10.8", ] +[[package]] +name = "anchor-lang-idl-spec" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", +] + [[package]] name = "anchor-spl" version = "0.30.0" diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 100da77de..9befe8454 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -8,6 +8,7 @@ use crate::config::{ use anchor_client::Cluster; use anchor_lang::idl::{IdlAccount, IdlInstruction, ERASED_AUTHORITY}; use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize}; +use anchor_lang_idl::convert::convert_idl; use anchor_lang_idl::types::{Idl, IdlArrayLen, IdlDefinedFields, IdlType, IdlTypeDefTy}; use anyhow::{anyhow, Context, Result}; use checks::{check_anchor_version, check_overflow}; @@ -2734,7 +2735,7 @@ fn idl_fetch(cfg_override: &ConfigOverride, address: Pubkey, out: Option fn idl_convert(path: String, out: Option) -> Result<()> { let idl = fs::read(path)?; - let idl = Idl::from_slice_with_conversion(&idl)?; + let idl = convert_idl(&idl)?; let out = match out { None => OutFile::Stdout, Some(out) => OutFile::File(PathBuf::from(out)), @@ -2744,7 +2745,7 @@ fn idl_convert(path: String, out: Option) -> Result<()> { fn idl_type(path: String, out: Option) -> Result<()> { let idl = fs::read(path)?; - let idl = Idl::from_slice_with_conversion(&idl)?; + let idl = convert_idl(&idl)?; let types = idl_ts(&idl)?; match out { Some(out) => fs::write(out, types)?, diff --git a/idl/Cargo.toml b/idl/Cargo.toml index 5d0421a4a..47581aef3 100644 --- a/idl/Cargo.toml +++ b/idl/Cargo.toml @@ -16,6 +16,7 @@ build = ["regex"] convert = ["heck", "sha2"] [dependencies] +anchor-lang-idl-spec = { path = "./spec", version = "0.1.0" } anyhow = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/idl/spec/Cargo.toml b/idl/spec/Cargo.toml new file mode 100644 index 000000000..db7966683 --- /dev/null +++ b/idl/spec/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "anchor-lang-idl-spec" +version = "0.1.0" +authors = ["Anchor Maintainers "] +repository = "https://github.com/coral-xyz/anchor" +edition = "2021" +license = "Apache-2.0" +description = "Anchor framework IDL spec" + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +anyhow = "1" +serde = { version = "1", features = ["derive"] } diff --git a/idl/src/types.rs b/idl/spec/src/lib.rs similarity index 99% rename from idl/src/types.rs rename to idl/spec/src/lib.rs index eb5c7dc9e..d1fb0b0e6 100644 --- a/idl/src/types.rs +++ b/idl/spec/src/lib.rs @@ -309,6 +309,7 @@ pub enum IdlType { Generic(String), } +// TODO: Move to utils crate impl FromStr for IdlType { type Err = anyhow::Error; diff --git a/idl/src/convert.rs b/idl/src/convert.rs index e01700c3d..898d89b67 100644 --- a/idl/src/convert.rs +++ b/idl/src/convert.rs @@ -2,30 +2,28 @@ 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)?, - } +/// 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 convert_idl(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)?, } } @@ -433,10 +431,11 @@ mod legacy { fn from(value: IdlTypeDefinitionTy) -> Self { match value { IdlTypeDefinitionTy::Struct { fields } => Self::Struct { - fields: fields - .is_empty() - .then(|| None) - .unwrap_or_else(|| Some(fields.into())), + fields: fields.is_empty().then(|| None).unwrap_or_else(|| { + Some(t::IdlDefinedFields::Named( + fields.into_iter().map(Into::into).collect(), + )) + }), }, IdlTypeDefinitionTy::Enum { variants } => Self::Enum { variants: variants @@ -444,7 +443,9 @@ mod legacy { .map(|variant| t::IdlEnumVariant { name: variant.name, fields: variant.fields.map(|fields| match fields { - EnumFields::Named(fields) => fields.into(), + EnumFields::Named(fields) => t::IdlDefinedFields::Named( + fields.into_iter().map(Into::into).collect(), + ), EnumFields::Tuple(tys) => t::IdlDefinedFields::Tuple( tys.into_iter().map(Into::into).collect(), ), @@ -469,12 +470,6 @@ mod legacy { } } - 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 { diff --git a/idl/src/lib.rs b/idl/src/lib.rs index d49038a2d..de9ac5608 100644 --- a/idl/src/lib.rs +++ b/idl/src/lib.rs @@ -1,12 +1,12 @@ //! Anchor IDL. -pub mod types; - #[cfg(feature = "build")] pub mod build; #[cfg(feature = "convert")] pub mod convert; +pub use anchor_lang_idl_spec as types; + #[cfg(feature = "build")] pub use serde_json; diff --git a/lang/attribute/program/src/declare_program/mod.rs b/lang/attribute/program/src/declare_program/mod.rs index 82c9002b2..2b96995ee 100644 --- a/lang/attribute/program/src/declare_program/mod.rs +++ b/lang/attribute/program/src/declare_program/mod.rs @@ -1,7 +1,7 @@ mod common; mod mods; -use anchor_lang_idl::types::Idl; +use anchor_lang_idl::{convert::convert_idl, types::Idl}; use anyhow::anyhow; use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream}; @@ -45,7 +45,7 @@ fn get_idl(name: &syn::Ident) -> anyhow::Result { .map(|idl_dir| idl_dir.join(name.to_string()).with_extension("json")) .map(std::fs::read)? .map_err(|e| anyhow!("Failed to read IDL `{name}`: {e}")) - .map(|buf| Idl::from_slice_with_conversion(&buf))? + .map(|buf| convert_idl(&buf))? } fn gen_program(idl: &Idl, name: &syn::Ident) -> proc_macro2::TokenStream {