diff --git a/.github/workflows/reusable-tests.yaml b/.github/workflows/reusable-tests.yaml index 8861fcf305..b88c26386f 100644 --- a/.github/workflows/reusable-tests.yaml +++ b/.github/workflows/reusable-tests.yaml @@ -443,6 +443,8 @@ jobs: path: tests/anchor-cli-account - cmd: cd tests/bench && anchor test --skip-lint path: tests/bench + - cmd: cd tests/idl-build && ./test.sh + path: tests/idl-build steps: - uses: actions/checkout@v3 - uses: ./.github/actions/setup/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 60133dce50..f88abe5212 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The minor version will be incremented upon a breaking change and the patch versi - lang: Add `get_lamports`, `add_lamports` and `sub_lamports` methods for all account types ([#2552](https://github.com/coral-xyz/anchor/pull/2552)). - client: Add a helper struct `DynSigner` to simplify use of `Client where >` with Solana clap CLI utils that loads `Signer` as `Box` ([#2550](https://github.com/coral-xyz/anchor/pull/2550)). - lang: Allow CPI calls matching an interface without pinning program ID ([#2559](https://github.com/coral-xyz/anchor/pull/2559)). +- cli, lang: Add IDL generation through compilation. `anchor build` still uses parsing method to generate IDLs, use `anchor idl build` to generate IDLs with the build method ([#2011](https://github.com/coral-xyz/anchor/pull/2011)). ### Fixes @@ -23,6 +24,8 @@ The minor version will be incremented upon a breaking change and the patch versi ### Breaking +- syn: `idl` feature has been replaced with `idl-build`, `idl-parse` and `idl-types` features ([#2011](https://github.com/coral-xyz/anchor/pull/2011)). + ## [0.28.0] - 2023-06-09 ### Features diff --git a/Cargo.lock b/Cargo.lock index 88b5768d6e..fb21d444a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -138,6 +138,7 @@ version = "0.28.0" dependencies = [ "anchor-syn", "proc-macro2 1.0.60", + "quote 1.0.28", "syn 1.0.109", ] @@ -238,6 +239,17 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "anchor-derive-serde" +version = "0.28.0" +dependencies = [ + "anchor-syn", + "borsh-derive-internal 0.10.3", + "proc-macro2 1.0.60", + "quote 1.0.28", + "syn 1.0.109", +] + [[package]] name = "anchor-derive-space" version = "0.28.0" @@ -258,7 +270,9 @@ dependencies = [ "anchor-attribute-event", "anchor-attribute-program", "anchor-derive-accounts", + "anchor-derive-serde", "anchor-derive-space", + "anchor-syn", "arrayref", "base64 0.13.1", "bincode", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 76b26fb5e3..57de316329 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -19,7 +19,7 @@ default = [] [dependencies] anchor-client = { path = "../client", version = "0.28.0" } anchor-lang = { path = "../lang", version = "0.28.0" } -anchor-syn = { path = "../lang/syn", features = ["event-cpi", "idl", "init-if-needed"], version = "0.28.0" } +anchor-syn = { path = "../lang/syn", features = ["event-cpi", "idl-parse", "init-if-needed"], version = "0.28.0" } anyhow = "1.0.32" base64 = "0.13.1" bincode = "1.3.3" diff --git a/cli/src/config.rs b/cli/src/config.rs index 6328416106..fcbdae23f1 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -1,6 +1,6 @@ use crate::is_hidden; use anchor_client::Cluster; -use anchor_syn::idl::Idl; +use anchor_syn::idl::types::Idl; use anyhow::{anyhow, bail, Context, Error, Result}; use clap::{Parser, ValueEnum}; use heck::ToSnakeCase; diff --git a/cli/src/lib.rs b/cli/src/lib.rs index b03a57733f..0993463729 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -6,7 +6,10 @@ use crate::config::{ use anchor_client::Cluster; use anchor_lang::idl::{IdlAccount, IdlInstruction, ERASED_AUTHORITY}; use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize}; -use anchor_syn::idl::{EnumFields, Idl, IdlType, IdlTypeDefinitionTy}; +use anchor_syn::idl::types::{ + EnumFields, Idl, IdlConst, IdlErrorCode, IdlEvent, IdlType, IdlTypeDefinition, + IdlTypeDefinitionTy, +}; use anyhow::{anyhow, Context, Result}; use clap::Parser; use flate2::read::GzDecoder; @@ -417,6 +420,11 @@ pub enum IdlCommand { #[clap(long)] no_docs: bool, }, + /// Generates the IDL for the program using the compilation method. + Build { + #[clap(long)] + no_docs: bool, + }, /// Fetches an IDL for the given address from a cluster. /// The address can be a program, IDL account, or IDL buffer. Fetch { @@ -1834,7 +1842,7 @@ fn extract_idl( let manifest_from_path = std::env::current_dir()?.join(PathBuf::from(&*file).parent().unwrap()); let cargo = Manifest::discover_from_path(manifest_from_path)? .ok_or_else(|| anyhow!("Cargo.toml not found"))?; - anchor_syn::idl::file::parse( + anchor_syn::idl::parse::file::parse( &*file, cargo.version(), cfg.features.seeds, @@ -1880,6 +1888,7 @@ fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> { out_ts, no_docs, } => idl_parse(cfg_override, file, out, out_ts, no_docs), + IdlCommand::Build { no_docs } => idl_build(no_docs), IdlCommand::Fetch { address, out } => idl_fetch(cfg_override, address, out), } } @@ -2225,6 +2234,165 @@ fn idl_parse( Ok(()) } +fn idl_build(no_docs: bool) -> Result<()> { + let no_docs = if no_docs { "TRUE" } else { "FALSE" }; + + let cfg = Config::discover(&ConfigOverride::default())?.expect("Not in workspace."); + let seeds_feature = if cfg.features.seeds { "TRUE" } else { "FALSE" }; + + let exit = std::process::Command::new("cargo") + .args([ + "test", + "__anchor_private_print_idl", + "--features", + "idl-build", + "--", + "--show-output", + "--quiet", + ]) + .env("ANCHOR_IDL_GEN_NO_DOCS", no_docs) + .env("ANCHOR_IDL_GEN_SEEDS_FEATURE", seeds_feature) + .stderr(Stdio::inherit()) + .output() + .map_err(|e| anyhow::format_err!("{}", e.to_string()))?; + if !exit.status.success() { + std::process::exit(exit.status.code().unwrap_or(1)); + } + + enum State { + Pass, + ConstLines(Vec), + EventLines(Vec), + ErrorsLines(Vec), + ProgramLines(Vec), + } + + #[derive(Serialize, Deserialize)] + struct IdlGenEventPrint { + event: IdlEvent, + defined_types: Vec, + } + + let mut state = State::Pass; + + let mut events: Vec = vec![]; + let mut error_codes: Option> = None; + let mut constants: Vec = vec![]; + let mut defined_types: BTreeMap = BTreeMap::new(); + let mut curr_idl: Option = None; + + let mut idls: Vec = vec![]; + + let out = String::from_utf8_lossy(&exit.stdout); + for line in out.lines() { + match &mut state { + State::Pass => { + if line == "---- IDL begin const ----" { + state = State::ConstLines(vec![]); + continue; + } else if line == "---- IDL begin event ----" { + state = State::EventLines(vec![]); + continue; + } else if line == "---- IDL begin errors ----" { + state = State::ErrorsLines(vec![]); + continue; + } else if line == "---- IDL begin program ----" { + state = State::ProgramLines(vec![]); + continue; + } else if line.starts_with("test result: ok") { + let events = std::mem::take(&mut events); + let error_codes = error_codes.take(); + let constants = std::mem::take(&mut constants); + let mut defined_types = std::mem::take(&mut defined_types); + let curr_idl = curr_idl.take(); + + let events = if !events.is_empty() { + Some(events) + } else { + None + }; + + let mut idl = match curr_idl { + Some(idl) => idl, + None => continue, + }; + + idl.events = events; + idl.errors = error_codes; + idl.constants = constants; + + idl.constants.sort_by(|a, b| a.name.cmp(&b.name)); + idl.accounts.sort_by(|a, b| a.name.cmp(&b.name)); + if let Some(e) = idl.events.as_mut() { + e.sort_by(|a, b| a.name.cmp(&b.name)) + } + + let prog_ty = std::mem::take(&mut idl.types); + defined_types + .extend(prog_ty.into_iter().map(|ty| (ty.path.clone().unwrap(), ty))); + idl.types = defined_types.into_values().collect::>(); + + idls.push(idl); + continue; + } + } + State::ConstLines(lines) => { + if line == "---- IDL end const ----" { + let constant: IdlConst = serde_json::from_str(&lines.join("\n"))?; + constants.push(constant); + state = State::Pass; + continue; + } + lines.push(line.to_string()); + } + State::EventLines(lines) => { + if line == "---- IDL end event ----" { + let event: IdlGenEventPrint = serde_json::from_str(&lines.join("\n"))?; + events.push(event.event); + defined_types.extend( + event + .defined_types + .into_iter() + .map(|ty| (ty.path.clone().unwrap(), ty)), + ); + state = State::Pass; + continue; + } + lines.push(line.to_string()); + } + State::ErrorsLines(lines) => { + if line == "---- IDL end errors ----" { + let errs: Vec = serde_json::from_str(&lines.join("\n"))?; + error_codes = Some(errs); + state = State::Pass; + continue; + } + lines.push(line.to_string()); + } + State::ProgramLines(lines) => { + if line == "---- IDL end program ----" { + let idl: Idl = serde_json::from_str(&lines.join("\n"))?; + curr_idl = Some(idl); + state = State::Pass; + continue; + } + lines.push(line.to_string()); + } + } + } + + if idls.len() == 1 { + println!( + "{}", + serde_json::to_string_pretty(&idls.first().unwrap()).unwrap() + ); + } else if idls.len() >= 2 { + println!("{}", serde_json::to_string_pretty(&idls).unwrap()); + }; + + Ok(()) +} + fn idl_fetch(cfg_override: &ConfigOverride, address: Pubkey, out: Option) -> Result<()> { let idl = fetch_idl(cfg_override, address)?; let out = match out { @@ -2500,6 +2668,11 @@ fn deserialize_idl_type_to_json( JsonValue::Array(array_data) } + IdlType::GenericLenArray(_, _) => todo!("Generic length arrays are not yet supported"), + IdlType::Generic(_) => todo!("Generic types are not yet supported"), + IdlType::DefinedWithTypeArgs { path: _, args: _ } => { + todo!("Defined types with type args are not yet supported") + } }) } diff --git a/cli/src/rust_template.rs b/cli/src/rust_template.rs index f05d67f7da..ed4a840dcd 100644 --- a/cli/src/rust_template.rs +++ b/cli/src/rust_template.rs @@ -1,6 +1,6 @@ use crate::config::ProgramWorkspace; use crate::VERSION; -use anchor_syn::idl::Idl; +use anchor_syn::idl::types::Idl; use anyhow::Result; use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase}; use solana_sdk::{ diff --git a/cli/src/solidity_template.rs b/cli/src/solidity_template.rs index 238b2ab62a..48d81de9c7 100644 --- a/cli/src/solidity_template.rs +++ b/cli/src/solidity_template.rs @@ -1,6 +1,6 @@ use crate::config::ProgramWorkspace; use crate::VERSION; -use anchor_syn::idl::Idl; +use anchor_syn::idl::types::Idl; use anyhow::Result; use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase}; use solana_sdk::pubkey::Pubkey; diff --git a/lang/Cargo.toml b/lang/Cargo.toml index 7fa25bd652..3b6030c2c0 100644 --- a/lang/Cargo.toml +++ b/lang/Cargo.toml @@ -14,6 +14,16 @@ init-if-needed = ["anchor-derive-accounts/init-if-needed"] derive = [] default = [] event-cpi = ["anchor-attribute-event/event-cpi"] +idl-build = [ + "anchor-syn/idl-build", + "anchor-derive-accounts/idl-build", + "anchor-derive-serde/idl-build", + "anchor-attribute-account/idl-build", + "anchor-attribute-constant/idl-build", + "anchor-attribute-event/idl-build", + "anchor-attribute-error/idl-build", + "anchor-attribute-program/idl-build", +] anchor-debug = [ "anchor-attribute-access-control/anchor-debug", "anchor-attribute-account/anchor-debug", @@ -33,7 +43,11 @@ anchor-attribute-error = { path = "./attribute/error", version = "0.28.0" } anchor-attribute-event = { path = "./attribute/event", version = "0.28.0" } anchor-attribute-program = { path = "./attribute/program", version = "0.28.0" } anchor-derive-accounts = { path = "./derive/accounts", version = "0.28.0" } +anchor-derive-serde = { path = "./derive/serde", version = "0.28.0" } anchor-derive-space = { path = "./derive/space", version = "0.28.0" } +# anchor-syn can and should only be included only for idl-build. It won't compile +# for bpf due to proc-macro2 crate. +anchor-syn = { path = "./syn", version = "0.28.0", optional = true } arrayref = "0.3" base64 = "0.13" bincode = "1" diff --git a/lang/attribute/account/Cargo.toml b/lang/attribute/account/Cargo.toml index f64f1172e4..aed90e7224 100644 --- a/lang/attribute/account/Cargo.toml +++ b/lang/attribute/account/Cargo.toml @@ -12,6 +12,7 @@ edition = "2021" proc-macro = true [features] +idl-build = ["anchor-syn/idl-build"] anchor-debug = ["anchor-syn/anchor-debug"] [dependencies] diff --git a/lang/attribute/account/src/lib.rs b/lang/attribute/account/src/lib.rs index 52e975cf48..8e9a732cb8 100644 --- a/lang/attribute/account/src/lib.rs +++ b/lang/attribute/account/src/lib.rs @@ -1,5 +1,7 @@ extern crate proc_macro; +#[cfg(feature = "idl-build")] +use anchor_syn::idl::build::*; use quote::quote; use syn::parse_macro_input; @@ -392,13 +394,26 @@ pub fn zero_copy( quote! {#[derive(::bytemuck::Zeroable)]} }; - proc_macro::TokenStream::from(quote! { + let ret = quote! { #[derive(anchor_lang::__private::ZeroCopyAccessor, Copy, Clone)] #repr #pod #zeroable #account_strct - }) + }; + + #[cfg(feature = "idl-build")] + { + let no_docs = get_no_docs(); + let idl_gen_impl = gen_idl_gen_impl_for_struct(&account_strct, no_docs); + return proc_macro::TokenStream::from(quote! { + #ret + #idl_gen_impl + }); + } + + #[allow(unreachable_code)] + proc_macro::TokenStream::from(ret) } /// Defines the program's ID. This should be used at the root of all Anchor diff --git a/lang/attribute/constant/Cargo.toml b/lang/attribute/constant/Cargo.toml index df95b1e9d1..af7934cf6a 100644 --- a/lang/attribute/constant/Cargo.toml +++ b/lang/attribute/constant/Cargo.toml @@ -12,9 +12,11 @@ edition = "2021" proc-macro = true [features] +idl-build = ["anchor-syn/idl-build"] anchor-debug = ["anchor-syn/anchor-debug"] [dependencies] anchor-syn = { path = "../../syn", version = "0.28.0" } proc-macro2 = "1" +quote = "1" syn = { version = "1", features = ["full"] } diff --git a/lang/attribute/constant/src/lib.rs b/lang/attribute/constant/src/lib.rs index 98092f4b0f..fd1f4ad680 100644 --- a/lang/attribute/constant/src/lib.rs +++ b/lang/attribute/constant/src/lib.rs @@ -1,5 +1,8 @@ extern crate proc_macro; +#[cfg(feature = "idl-build")] +use {anchor_syn::idl::build::gen_idl_print_function_for_constant, quote::quote, syn}; + /// A marker attribute used to mark const values that should be included in the /// generated IDL but functionally does nothing. #[proc_macro_attribute] @@ -7,5 +10,24 @@ pub fn constant( _attr: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { + #[cfg(feature = "idl-build")] + { + let ts = match syn::parse(input).unwrap() { + syn::Item::Const(item) => { + let idl_print = gen_idl_print_function_for_constant(&item); + quote! { + #item + #idl_print + } + } + item => quote! {#item}, + }; + + return proc_macro::TokenStream::from(quote! { + #ts + }); + }; + + #[allow(unreachable_code)] input } diff --git a/lang/attribute/error/Cargo.toml b/lang/attribute/error/Cargo.toml index 2b2eae0e54..34126937c7 100644 --- a/lang/attribute/error/Cargo.toml +++ b/lang/attribute/error/Cargo.toml @@ -12,6 +12,7 @@ edition = "2021" proc-macro = true [features] +idl-build = ["anchor-syn/idl-build"] anchor-debug = ["anchor-syn/anchor-debug"] [dependencies] diff --git a/lang/attribute/event/Cargo.toml b/lang/attribute/event/Cargo.toml index 2a117c8569..228f58a340 100644 --- a/lang/attribute/event/Cargo.toml +++ b/lang/attribute/event/Cargo.toml @@ -12,6 +12,7 @@ edition = "2021" proc-macro = true [features] +idl-build = ["anchor-syn/idl-build"] anchor-debug = ["anchor-syn/anchor-debug"] event-cpi = ["anchor-syn/event-cpi"] diff --git a/lang/attribute/event/src/lib.rs b/lang/attribute/event/src/lib.rs index 7301162d3e..2ab207bb36 100644 --- a/lang/attribute/event/src/lib.rs +++ b/lang/attribute/event/src/lib.rs @@ -29,7 +29,7 @@ pub fn event( format!("{discriminator:?}").parse().unwrap() }; - proc_macro::TokenStream::from(quote! { + let ret = quote! { #[derive(anchor_lang::__private::EventIndex, AnchorSerialize, AnchorDeserialize)] #event_strct @@ -44,7 +44,19 @@ pub fn event( impl anchor_lang::Discriminator for #event_name { const DISCRIMINATOR: [u8; 8] = #discriminator; } - }) + }; + + #[cfg(feature = "idl-build")] + { + let idl_gen = anchor_syn::idl::build::gen_idl_print_function_for_event(&event_strct); + return proc_macro::TokenStream::from(quote! { + #ret + #idl_gen + }); + } + + #[allow(unreachable_code)] + proc_macro::TokenStream::from(ret) } // EventIndex is a marker macro. It functionally does nothing other than diff --git a/lang/attribute/program/Cargo.toml b/lang/attribute/program/Cargo.toml index 3090b3ab7c..40c00a8ef0 100644 --- a/lang/attribute/program/Cargo.toml +++ b/lang/attribute/program/Cargo.toml @@ -12,6 +12,7 @@ edition = "2021" proc-macro = true [features] +idl-build = ["anchor-syn/idl-build"] anchor-debug = ["anchor-syn/anchor-debug"] [dependencies] diff --git a/lang/derive/accounts/Cargo.toml b/lang/derive/accounts/Cargo.toml index 65a990ec2b..cd2f8c78de 100644 --- a/lang/derive/accounts/Cargo.toml +++ b/lang/derive/accounts/Cargo.toml @@ -15,6 +15,7 @@ proc-macro = true allow-missing-optionals = ["anchor-syn/allow-missing-optionals"] init-if-needed = ["anchor-syn/init-if-needed"] default = [] +idl-build = ["anchor-syn/idl-build"] anchor-debug = ["anchor-syn/anchor-debug"] [dependencies] diff --git a/lang/derive/serde/Cargo.toml b/lang/derive/serde/Cargo.toml new file mode 100644 index 0000000000..5f00f042f9 --- /dev/null +++ b/lang/derive/serde/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "anchor-derive-serde" +version = "0.28.0" +authors = ["Anchor Maintainers "] +repository = "https://github.com/coral-xyz/anchor" +license = "Apache-2.0" +description = "Anchor Derive macro for serialization and deserialization" +rust-version = "1.60" +edition = "2021" + +[lib] +proc-macro = true + +[features] +idl-build = [ + "anchor-syn/idl-build", +] + +[dependencies] +anchor-syn = { path = "../../syn", version = "0.28.0" } +borsh-derive-internal = ">=0.9, <0.11" +proc-macro2 = "1" +syn = { version = "1", features = ["full"] } +quote = "1" diff --git a/lang/derive/serde/src/lib.rs b/lang/derive/serde/src/lib.rs new file mode 100644 index 0000000000..4fbbdb3517 --- /dev/null +++ b/lang/derive/serde/src/lib.rs @@ -0,0 +1,82 @@ +extern crate proc_macro; + +use borsh_derive_internal::*; +use proc_macro::TokenStream; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use syn::{Ident, Item}; + +#[cfg(feature = "idl-build")] +use {anchor_syn::idl::build::*, quote::quote}; + +fn gen_borsh_serialize(input: TokenStream) -> TokenStream2 { + let cratename = Ident::new("borsh", Span::call_site()); + + let item: Item = syn::parse(input).unwrap(); + let res = match item { + Item::Struct(item) => struct_ser(&item, cratename), + Item::Enum(item) => enum_ser(&item, cratename), + Item::Union(item) => union_ser(&item, cratename), + // Derive macros can only be defined on structs, enums, and unions. + _ => unreachable!(), + }; + + match res { + Ok(res) => res, + Err(err) => err.to_compile_error(), + } +} + +#[proc_macro_derive(AnchorSerialize, attributes(borsh_skip))] +pub fn anchor_serialize(input: TokenStream) -> TokenStream { + #[cfg(not(feature = "idl-build"))] + let ret = gen_borsh_serialize(input); + #[cfg(feature = "idl-build")] + let ret = gen_borsh_serialize(input.clone()); + + #[cfg(feature = "idl-build")] + { + let no_docs = get_no_docs(); + + let idl_gen_impl = match syn::parse(input).unwrap() { + Item::Struct(item) => gen_idl_gen_impl_for_struct(&item, no_docs), + Item::Enum(item) => gen_idl_gen_impl_for_enum(item, no_docs), + Item::Union(item) => { + // unions are not included in the IDL - TODO print a warning + idl_gen_impl_skeleton(quote! {None}, quote! {}, &item.ident, &item.generics) + } + // Derive macros can only be defined on structs, enums, and unions. + _ => unreachable!(), + }; + + return TokenStream::from(quote! { + #ret + #idl_gen_impl + }); + }; + + #[allow(unreachable_code)] + TokenStream::from(ret) +} + +fn gen_borsh_deserialize(input: TokenStream) -> TokenStream2 { + let cratename = Ident::new("borsh", Span::call_site()); + + let item: Item = syn::parse(input).unwrap(); + let res = match item { + Item::Struct(item) => struct_de(&item, cratename), + Item::Enum(item) => enum_de(&item, cratename), + Item::Union(item) => union_de(&item, cratename), + // Derive macros can only be defined on structs, enums, and unions. + _ => unreachable!(), + }; + + match res { + Ok(res) => res, + Err(err) => err.to_compile_error(), + } +} + +#[proc_macro_derive(AnchorDeserialize, attributes(borsh_skip, borsh_init))] +pub fn borsh_deserialize(input: TokenStream) -> TokenStream { + TokenStream::from(gen_borsh_deserialize(input)) +} diff --git a/lang/src/lib.rs b/lang/src/lib.rs index afab0007bf..1ef35046de 100644 --- a/lang/src/lib.rs +++ b/lang/src/lib.rs @@ -54,11 +54,16 @@ pub use anchor_attribute_event::{emit, event}; pub use anchor_attribute_event::{emit_cpi, event_cpi}; pub use anchor_attribute_program::program; pub use anchor_derive_accounts::Accounts; +pub use anchor_derive_serde::{AnchorDeserialize, AnchorSerialize}; pub use anchor_derive_space::InitSpace; /// Borsh is the default serialization format for instructions and accounts. -pub use borsh::{BorshDeserialize as AnchorDeserialize, BorshSerialize as AnchorSerialize}; +pub use borsh::de::BorshDeserialize as AnchorDeserialize; +pub use borsh::ser::BorshSerialize as AnchorSerialize; pub use solana_program; +#[cfg(feature = "idl-build")] +pub use anchor_syn; + pub type Result = std::result::Result; /// A data structure of validated accounts that can be deserialized from the diff --git a/lang/syn/Cargo.toml b/lang/syn/Cargo.toml index e1a8b7b0fd..7c281d67f7 100644 --- a/lang/syn/Cargo.toml +++ b/lang/syn/Cargo.toml @@ -11,7 +11,9 @@ edition = "2021" [features] allow-missing-optionals = [] init-if-needed = [] -idl = [] +idl-build = [] +idl-parse = [] +idl-types = [] hash = [] default = [] anchor-debug = [] diff --git a/lang/syn/src/codegen/accounts/__client_accounts.rs b/lang/syn/src/codegen/accounts/__client_accounts.rs index b188fdf53d..5433beaf4f 100644 --- a/lang/syn/src/codegen/accounts/__client_accounts.rs +++ b/lang/syn/src/codegen/accounts/__client_accounts.rs @@ -62,12 +62,12 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { if f.is_optional { quote! { #docs - pub #name: Option + pub #name: Option } } else { quote! { #docs - pub #name: anchor_lang::solana_program::pubkey::Pubkey + pub #name: Pubkey } } } diff --git a/lang/syn/src/codegen/accounts/mod.rs b/lang/syn/src/codegen/accounts/mod.rs index 3a239cbe33..cc684b3f2e 100644 --- a/lang/syn/src/codegen/accounts/mod.rs +++ b/lang/syn/src/codegen/accounts/mod.rs @@ -22,7 +22,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { let __client_accounts_mod = __client_accounts::generate(accs); let __cpi_client_accounts_mod = __cpi_client_accounts::generate(accs); - quote! { + let ret = quote! { #impl_try_accounts #impl_to_account_infos #impl_to_account_metas @@ -30,7 +30,21 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream { #__client_accounts_mod #__cpi_client_accounts_mod + }; + + #[cfg(feature = "idl-build")] + { + #![allow(warnings)] + let no_docs = crate::idl::build::get_no_docs(); + let idl_gen_impl = crate::idl::build::gen_idl_gen_impl_for_accounts_strct(&accs, no_docs); + return quote! { + #ret + #idl_gen_impl + }; } + + #[allow(unreachable_code)] + ret } fn generics(accs: &AccountsStruct) -> ParsedGenerics { diff --git a/lang/syn/src/codegen/error.rs b/lang/syn/src/codegen/error.rs index 4dea4ea2b7..62c5e511be 100644 --- a/lang/syn/src/codegen/error.rs +++ b/lang/syn/src/codegen/error.rs @@ -1,6 +1,9 @@ use crate::Error; use quote::quote; +#[cfg(feature = "idl-build")] +use crate::idl::build::gen_idl_print_function_for_error; + pub fn generate(error: Error) -> proc_macro2::TokenStream { let error_enum = &error.raw_enum; let enum_name = &error.ident; @@ -47,7 +50,7 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream { }) .collect(); - let offset = match error.args { + let offset = match &error.args { None => quote! { anchor_lang::error::ERROR_CODE_OFFSET}, Some(args) => { let offset = &args.offset; @@ -55,7 +58,7 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream { } }; - quote! { + let ret = quote! { #[derive(std::fmt::Debug, Clone, Copy)] #[repr(u32)] #error_enum @@ -96,5 +99,17 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream { } } } - } + }; + + #[cfg(feature = "idl-build")] + { + let idl_gen = gen_idl_print_function_for_error(&error); + return quote! { + #ret + #idl_gen + }; + }; + + #[allow(unreachable_code)] + ret } diff --git a/lang/syn/src/codegen/program/mod.rs b/lang/syn/src/codegen/program/mod.rs index 22bec8adcf..a519d1eabf 100644 --- a/lang/syn/src/codegen/program/mod.rs +++ b/lang/syn/src/codegen/program/mod.rs @@ -21,16 +21,33 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { let cpi = cpi::generate(program); let accounts = accounts::generate(program); - quote! { - // TODO: remove once we allow segmented paths in `Accounts` structs. - use self::#mod_name::*; - - #entry - #dispatch - #handlers - #user_defined_program - #instruction - #cpi - #accounts - } + #[allow(clippy::let_and_return)] + let ret = { + quote! { + // TODO: remove once we allow segmented paths in `Accounts` structs. + use self::#mod_name::*; + + #entry + #dispatch + #handlers + #user_defined_program + #instruction + #cpi + #accounts + } + }; + + #[cfg(feature = "idl-build")] + { + let no_docs = crate::idl::build::get_no_docs(); + let idl_gen = crate::idl::build::gen_idl_print_function_for_program(program, no_docs); + + return quote! { + #ret + #idl_gen + }; + }; + + #[allow(unreachable_code)] + ret } diff --git a/lang/syn/src/idl/build.rs b/lang/syn/src/idl/build.rs new file mode 100644 index 0000000000..08c3ff3376 --- /dev/null +++ b/lang/syn/src/idl/build.rs @@ -0,0 +1,915 @@ +use crate::{parser::docs, AccountField, AccountsStruct, Error, Program}; +use heck::MixedCase; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +pub use serde_json; +use syn::{Ident, ItemEnum, ItemStruct}; + +#[inline(always)] +fn get_module_paths() -> (TokenStream, TokenStream) { + ( + quote!(anchor_lang::anchor_syn::idl::types), + quote!(anchor_lang::anchor_syn::idl::build::serde_json), + ) +} + +#[inline(always)] +pub fn get_no_docs() -> bool { + std::option_env!("ANCHOR_IDL_GEN_NO_DOCS") + .map(|val| val == "TRUE") + .unwrap_or(false) +} + +#[inline(always)] +pub fn get_seeds_feature() -> bool { + std::option_env!("ANCHOR_IDL_GEN_SEEDS_FEATURE") + .map(|val| val == "TRUE") + .unwrap_or(false) +} + +// Returns TokenStream for IdlType enum and the syn::TypePath for the defined +// type if any. +// Returns Err when the type wasn't parsed successfully. +#[allow(clippy::result_unit_err)] +pub fn idl_type_ts_from_syn_type( + ty: &syn::Type, + type_params: &Vec, +) -> Result<(TokenStream, Vec), ()> { + let (idl, _) = get_module_paths(); + + fn the_only_segment_is(path: &syn::TypePath, cmp: &str) -> bool { + if path.path.segments.len() != 1 { + return false; + }; + return path.path.segments.first().unwrap().ident == cmp; + } + + // Foo -> first::path + fn get_first_angle_bracketed_path_arg(segment: &syn::PathSegment) -> Option<&syn::Type> { + match &segment.arguments { + syn::PathArguments::AngleBracketed(arguments) => match arguments.args.first() { + Some(syn::GenericArgument::Type(ty)) => Some(ty), + _ => None, + }, + _ => None, + } + } + + match ty { + syn::Type::Path(path) if the_only_segment_is(path, "bool") => { + Ok((quote! { #idl::IdlType::Bool }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "u8") => { + Ok((quote! { #idl::IdlType::U8 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "i8") => { + Ok((quote! { #idl::IdlType::I8 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "u16") => { + Ok((quote! { #idl::IdlType::U16 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "i16") => { + Ok((quote! { #idl::IdlType::I16 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "u32") => { + Ok((quote! { #idl::IdlType::U32 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "i32") => { + Ok((quote! { #idl::IdlType::I32 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "f32") => { + Ok((quote! { #idl::IdlType::F32 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "u64") => { + Ok((quote! { #idl::IdlType::U64 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "i64") => { + Ok((quote! { #idl::IdlType::I64 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "f64") => { + Ok((quote! { #idl::IdlType::F64 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "u128") => { + Ok((quote! { #idl::IdlType::U128 }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "i128") => { + Ok((quote! { #idl::IdlType::I128 }, vec![])) + } + syn::Type::Path(path) + if the_only_segment_is(path, "String") || the_only_segment_is(path, "&str") => + { + Ok((quote! { #idl::IdlType::String }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "Pubkey") => { + Ok((quote! { #idl::IdlType::PublicKey }, vec![])) + } + syn::Type::Path(path) if the_only_segment_is(path, "Vec") => { + let segment = path.path.segments.first().unwrap(); + let arg = match get_first_angle_bracketed_path_arg(segment) { + Some(arg) => arg, + None => unreachable!("Vec arguments can only be of AngleBracketed variant"), + }; + match arg { + syn::Type::Path(path) if the_only_segment_is(path, "u8") => { + return Ok((quote! {#idl::IdlType::Bytes}, vec![])); + } + _ => (), + }; + let (inner, defined) = idl_type_ts_from_syn_type(arg, type_params)?; + Ok((quote! { #idl::IdlType::Vec(Box::new(#inner)) }, defined)) + } + syn::Type::Path(path) if the_only_segment_is(path, "Option") => { + let segment = path.path.segments.first().unwrap(); + let arg = match get_first_angle_bracketed_path_arg(segment) { + Some(arg) => arg, + None => unreachable!("Option arguments can only be of AngleBracketed variant"), + }; + let (inner, defined) = idl_type_ts_from_syn_type(arg, type_params)?; + Ok((quote! { #idl::IdlType::Option(Box::new(#inner)) }, defined)) + } + syn::Type::Path(path) if the_only_segment_is(path, "Box") => { + let segment = path.path.segments.first().unwrap(); + let arg = match get_first_angle_bracketed_path_arg(segment) { + Some(arg) => arg, + None => unreachable!("Box arguments can only be of AngleBracketed variant"), + }; + let (ts, defined) = idl_type_ts_from_syn_type(arg, type_params)?; + Ok((quote! { #ts }, defined)) + } + syn::Type::Array(arr) => { + let len = arr.len.clone(); + let len_is_generic = type_params.iter().any(|param| match len { + syn::Expr::Path(ref path) => path.path.is_ident(param), + _ => false, + }); + + let (inner, defined) = idl_type_ts_from_syn_type(&arr.elem, type_params)?; + + if len_is_generic { + match len { + syn::Expr::Path(ref len) => { + let len = len.path.get_ident().unwrap().to_string(); + Ok(( + quote! { #idl::IdlType::GenericLenArray(Box::new(#inner), #len.into()) }, + defined, + )) + } + _ => unreachable!("Array length can only be a generic parameter"), + } + } else { + Ok(( + quote! { #idl::IdlType::Array(Box::new(#inner), #len) }, + defined, + )) + } + } + syn::Type::Path(path) => { + let is_generic_param = type_params.iter().any(|param| path.path.is_ident(param)); + + if is_generic_param { + let generic = format!("{}", path.path.get_ident().unwrap()); + Ok((quote! { #idl::IdlType::Generic(#generic.into()) }, vec![])) + } else { + let mut params = vec![]; + let mut defined = vec![path.clone()]; + + if let Some(segment) = &path.path.segments.last() { + if let syn::PathArguments::AngleBracketed(ref args) = segment.arguments { + for arg in &args.args { + match arg { + syn::GenericArgument::Type(ty) => { + let (ts, def) = idl_type_ts_from_syn_type(ty, type_params)?; + params.push(quote! { #idl::IdlDefinedTypeArg::Type(#ts) }); + defined.extend(def); + } + syn::GenericArgument::Const(c) => params.push( + quote! { #idl::IdlDefinedTypeArg::Value(format!("{}", #c))}, + ), + _ => (), + } + } + } + } + + if !params.is_empty() { + let params = quote! { vec![#(#params),*] }; + Ok(( + quote! { #idl::IdlType::DefinedWithTypeArgs { + path: <#path>::__anchor_private_full_path(), + args: #params + } }, + defined, + )) + } else { + Ok(( + quote! { #idl::IdlType::Defined(<#path>::__anchor_private_full_path()) }, + vec![path.clone()], + )) + } + } + } + _ => Err(()), + } +} + +// Returns TokenStream for IdlField struct and the syn::TypePath for the defined +// type if any. +// Returns Err when the type wasn't parsed successfully +#[allow(clippy::result_unit_err)] +pub fn idl_field_ts_from_syn_field( + field: &syn::Field, + no_docs: bool, + type_params: &Vec, +) -> Result<(TokenStream, Vec), ()> { + let (idl, _) = get_module_paths(); + + let name = field.ident.as_ref().unwrap().to_string().to_mixed_case(); + let docs = match docs::parse(&field.attrs) { + Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])}, + _ => quote! {None}, + }; + let (ty, defined) = idl_type_ts_from_syn_type(&field.ty, type_params)?; + + Ok(( + quote! { + #idl::IdlField { + name: #name.into(), + docs: #docs, + ty: #ty, + } + }, + defined, + )) +} + +// Returns TokenStream for IdlEventField struct and the syn::TypePath for the defined +// type if any. +// Returns Err when the type wasn't parsed successfully +#[allow(clippy::result_unit_err)] +pub fn idl_event_field_ts_from_syn_field( + field: &syn::Field, +) -> Result<(TokenStream, Vec), ()> { + let (idl, _) = get_module_paths(); + + let name = field.ident.as_ref().unwrap().to_string().to_mixed_case(); + let (ty, defined) = idl_type_ts_from_syn_type(&field.ty, &vec![])?; + + let index: bool = field + .attrs + .get(0) + .and_then(|attr| attr.path.segments.first()) + .map(|segment| segment.ident == "index") + .unwrap_or(false); + + Ok(( + quote! { + #idl::IdlEventField { + name: #name.into(), + ty: #ty, + index: #index, + } + }, + defined, + )) +} + +// Returns TokenStream for IdlTypeDefinitionTy::Struct and Vec<&syn::TypePath> +// for the defined types if any. +// Returns Err if any of the fields weren't parsed successfully. +#[allow(clippy::result_unit_err)] +pub fn idl_type_definition_ts_from_syn_struct( + item_strct: &syn::ItemStruct, + no_docs: bool, +) -> Result<(TokenStream, Vec), ()> { + let (idl, _) = get_module_paths(); + + let name = item_strct.ident.to_string(); + let docs = match docs::parse(&item_strct.attrs) { + Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])}, + _ => quote! {None}, + }; + + let type_params = item_strct + .generics + .params + .iter() + .filter_map(|p| match p { + syn::GenericParam::Type(ty) => Some(ty.ident.clone()), + syn::GenericParam::Const(c) => Some(c.ident.clone()), + _ => None, + }) + .collect::>(); + let (fields, defined): (Vec, Vec>) = match &item_strct.fields { + syn::Fields::Named(fields) => fields + .named + .iter() + .map(|f: &syn::Field| idl_field_ts_from_syn_field(f, no_docs, &type_params)) + .collect::, _>>()? + .into_iter() + .unzip::<_, _, Vec<_>, Vec<_>>(), + _ => return Err(()), + }; + let defined = defined + .into_iter() + .flatten() + .collect::>(); + + let generics = if !type_params.is_empty() { + let g: Vec = type_params.iter().map(|id| id.to_string()).collect(); + quote! { Some(vec![#(#g.into()),*]) } + } else { + quote! { None } + }; + + Ok(( + quote! { + #idl::IdlTypeDefinition { + name: #name.into(), + path: Some(Self::__anchor_private_full_path()), + generics: #generics, + docs: #docs, + ty: #idl::IdlTypeDefinitionTy::Struct{ + fields: vec![ + #(#fields),* + ] + } + }, + }, + defined, + )) +} + +// Returns TokenStream for IdlTypeDefinitionTy::Enum and the Vec<&syn::TypePath> +// for the defined types if any. +// Returns Err if any of the fields didn't parse successfully. +#[allow(clippy::result_unit_err)] +pub fn idl_type_definition_ts_from_syn_enum( + enum_item: &syn::ItemEnum, + no_docs: bool, +) -> Result<(TokenStream, Vec), ()> { + let (idl, _) = get_module_paths(); + + let name = enum_item.ident.to_string(); + let docs = match docs::parse(&enum_item.attrs) { + Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])}, + _ => quote! {None}, + }; + + let type_params = enum_item + .generics + .params + .iter() + .filter_map(|p| match p { + syn::GenericParam::Type(ty) => Some(ty.ident.clone()), + syn::GenericParam::Const(c) => Some(c.ident.clone()), + _ => None, + }) + .collect::>(); + + let (variants, defined): (Vec, Vec>) = enum_item.variants.iter().map(|variant: &syn::Variant| { + let name = variant.ident.to_string(); + let (fields, defined): (TokenStream, Vec) = match &variant.fields { + syn::Fields::Unit => (quote!{None}, vec![]), + syn::Fields::Unnamed(fields) => { + let (types, defined) = fields.unnamed + .iter() + .map(|f| idl_type_ts_from_syn_type(&f.ty, &type_params)) + .collect::, _>>()? + .into_iter() + .unzip::, Vec, Vec>>(); + let defined = defined + .into_iter() + .flatten() + .collect::>(); + + (quote!{ Some(#idl::EnumFields::Tuple(vec![#(#types),*]))}, defined) + } + syn::Fields::Named(fields) => { + let (fields, defined) = fields.named + .iter() + .map(|f| idl_field_ts_from_syn_field(f, no_docs, &type_params)) + .collect::, _>>()? + .into_iter() + .unzip::, Vec, Vec>>(); + let defined = defined + .into_iter() + .flatten() + .collect::>(); + + (quote!{ Some(#idl::EnumFields::Named(vec![#(#fields),*]))}, defined) + } + }; + + Ok((quote!{ #idl::IdlEnumVariant{ name: #name.into(), fields: #fields }}, defined)) + }) + .collect::, _>>()? + .into_iter() + .unzip::, Vec, Vec>>(); + + let defined = defined + .into_iter() + .flatten() + .collect::>(); + + let generics = if !type_params.is_empty() { + let g: Vec = type_params.iter().map(|id| id.to_string()).collect(); + quote! { Some(vec![#(#g.into()),*]) } + } else { + quote! { None } + }; + + Ok(( + quote! { + #idl::IdlTypeDefinition { + name: #name.into(), + path: Some(Self::__anchor_private_full_path()), + generics: #generics, + docs: #docs, + ty: #idl::IdlTypeDefinitionTy::Enum{ + variants: vec![ + #(#variants),* + ] + } + } + }, + defined, + )) +} + +pub fn idl_gen_impl_skeleton( + idl_type_definition_ts: TokenStream, + insert_defined_ts: TokenStream, + ident: &Ident, + input_generics: &syn::Generics, +) -> TokenStream { + let (idl, _) = get_module_paths(); + let name = ident.to_string(); + let (impl_generics, ty_generics, where_clause) = input_generics.split_for_impl(); + + quote! { + impl #impl_generics #ident #ty_generics #where_clause { + pub fn __anchor_private_full_path() -> String { + format!("{}::{}", std::module_path!(), #name) + } + + pub fn __anchor_private_gen_idl_type() -> Option<#idl::IdlTypeDefinition> { + #idl_type_definition_ts + } + + pub fn __anchor_private_insert_idl_defined( + defined_types: &mut std::collections::HashMap + ) { + #insert_defined_ts + } + } + } +} + +// generates the IDL generation impl for for a struct +pub fn gen_idl_gen_impl_for_struct(strct: &ItemStruct, no_docs: bool) -> TokenStream { + let idl_type_definition_ts: TokenStream; + let insert_defined_ts: TokenStream; + + if let Ok((ts, defined)) = idl_type_definition_ts_from_syn_struct(strct, no_docs) { + idl_type_definition_ts = quote! {Some(#ts)}; + insert_defined_ts = quote! { + #({ + <#defined>::__anchor_private_insert_idl_defined(defined_types); + + let path = <#defined>::__anchor_private_full_path(); + <#defined>::__anchor_private_gen_idl_type() + .and_then(|ty| defined_types.insert(path, ty)); + });* + }; + } else { + idl_type_definition_ts = quote! {None}; + insert_defined_ts = quote! {}; + } + + let ident = &strct.ident; + let input_generics = &strct.generics; + + idl_gen_impl_skeleton( + idl_type_definition_ts, + insert_defined_ts, + ident, + input_generics, + ) +} + +// generates the IDL generation impl for for an enum +pub fn gen_idl_gen_impl_for_enum(enm: ItemEnum, no_docs: bool) -> TokenStream { + let idl_type_definition_ts: TokenStream; + let insert_defined_ts: TokenStream; + + if let Ok((ts, defined)) = idl_type_definition_ts_from_syn_enum(&enm, no_docs) { + idl_type_definition_ts = quote! {Some(#ts)}; + insert_defined_ts = quote! { + #({ + <#defined>::__anchor_private_insert_idl_defined(defined_types); + + let path = <#defined>::__anchor_private_full_path(); + <#defined>::__anchor_private_gen_idl_type() + .and_then(|ty| defined_types.insert(path, ty)); + });* + }; + } else { + idl_type_definition_ts = quote! {None}; + insert_defined_ts = quote! {}; + } + + let ident = &enm.ident; + let input_generics = &enm.generics; + + idl_gen_impl_skeleton( + idl_type_definition_ts, + insert_defined_ts, + ident, + input_generics, + ) +} + +// generates the IDL generation impl for for an event +pub fn gen_idl_gen_impl_for_event(event_strct: &ItemStruct) -> TokenStream { + fn parse_fields( + fields: &syn::FieldsNamed, + ) -> Result<(Vec, Vec), ()> { + let (fields, defined) = fields + .named + .iter() + .map(idl_event_field_ts_from_syn_field) + .collect::, _>>()? + .into_iter() + .unzip::<_, _, Vec<_>, Vec<_>>(); + let defined = defined + .into_iter() + .flatten() + .collect::>(); + + Ok((fields, defined)) + } + + let res = match &event_strct.fields { + syn::Fields::Named(fields) => parse_fields(fields), + _ => Err(()), + }; + + let (idl, _) = get_module_paths(); + let name = event_strct.ident.to_string(); + + let (ret_ts, types_ts) = match res { + Ok((fields, defined)) => { + let ret_ts = quote! { + Some( + #idl::IdlEvent { + name: #name.into(), + fields: vec![#(#fields),*], + } + ) + }; + let types_ts = quote! { + #({ + <#defined>::__anchor_private_insert_idl_defined(defined_types); + + let path = <#defined>::__anchor_private_full_path(); + <#defined>::__anchor_private_gen_idl_type() + .and_then(|ty| defined_types.insert(path, ty)); + });* + }; + (ret_ts, types_ts) + } + Err(()) => (quote! { None }, quote! {}), + }; + + let ident = &event_strct.ident; + let input_generics = &event_strct.generics; + let (impl_generics, ty_generics, where_clause) = input_generics.split_for_impl(); + + quote! { + impl #impl_generics #ident #ty_generics #where_clause { + pub fn __anchor_private_gen_idl_event( + defined_types: &mut std::collections::HashMap, + ) -> Option<#idl::IdlEvent> { + #types_ts + #ret_ts + } + } + } +} + +// generates the IDL generation impl for the Accounts struct +pub fn gen_idl_gen_impl_for_accounts_strct( + accs_strct: &AccountsStruct, + no_docs: bool, +) -> TokenStream { + let (idl, _) = get_module_paths(); + + let ident = &accs_strct.ident; + let (impl_generics, ty_generics, where_clause) = accs_strct.generics.split_for_impl(); + + let (accounts, acc_types): (Vec, Vec>) = accs_strct + .fields + .iter() + .map(|acc: &AccountField| match acc { + AccountField::CompositeField(comp_f) => { + let ty = if let syn::Type::Path(path) = &comp_f.raw_field.ty { + // some::path::Foo<'info> -> some::path::Foo + let mut res = syn::Path { + leading_colon: path.path.leading_colon, + segments: syn::punctuated::Punctuated::new(), + }; + for segment in &path.path.segments { + let s = syn::PathSegment { + ident: segment.ident.clone(), + arguments: syn::PathArguments::None, + }; + res.segments.push(s); + }; + res + } else { + panic!("expecting path") + }; + let name = comp_f.ident.to_string().to_mixed_case(); + (quote!{ + #idl::IdlAccountItem::IdlAccounts(#idl::IdlAccounts { + name: #name.into(), + accounts: <#ty>::__anchor_private_gen_idl_accounts(accounts, defined_types), + }) + }, None) + } + AccountField::Field(acc) => { + let name = acc.ident.to_string().to_mixed_case(); + let is_mut = acc.constraints.is_mutable(); + let is_signer = match acc.ty { + crate::Ty::Signer => true, + _ => acc.constraints.is_signer() + }; + let is_optional = if acc.is_optional { quote!{Some(true)} } else { quote!{None} }; + let docs = match &acc.docs { + Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])}, + _ => quote! {None}, + }; + let pda = quote!{None}; // TODO + let relations = super::parse::relations::parse(acc, get_seeds_feature()); + + let acc_type_path = match &acc.ty { + crate::Ty::Account(ty) => Some(&ty.account_type_path), + crate::Ty::AccountLoader(ty) => Some(&ty.account_type_path), + crate::Ty::InterfaceAccount(ty) => Some(&ty.account_type_path), + _ => None, + }; + + (quote!{ + #idl::IdlAccountItem::IdlAccount(#idl::IdlAccount{ + name: #name.into(), + is_mut: #is_mut, + is_signer: #is_signer, + is_optional: #is_optional, + docs: #docs, + pda: #pda, + relations: vec![#(#relations.into()),*], + }) + }, acc_type_path) + } + }) + .unzip::, Vec, Vec>>(); + let acc_types = acc_types + .into_iter() + .flatten() + .collect::>(); + + quote! { + impl #impl_generics #ident #ty_generics #where_clause { + pub fn __anchor_private_gen_idl_accounts( + accounts: &mut std::collections::HashMap, + defined_types: &mut std::collections::HashMap, + ) -> Vec<#idl::IdlAccountItem> { + #({ + <#acc_types>::__anchor_private_insert_idl_defined(defined_types); + + let path = <#acc_types>::__anchor_private_full_path(); + <#acc_types>::__anchor_private_gen_idl_type() + .and_then(|ty| accounts.insert(path, ty)); + + });* + + vec![#(#accounts),*] + } + } + } +} + +// generates the IDL generation print function for the program module +pub fn gen_idl_print_function_for_program(program: &Program, no_docs: bool) -> TokenStream { + let (idl, serde_json) = get_module_paths(); + + let (instructions, defined) = program + .ixs + .iter() + .flat_map(|ix| -> Result<_, ()> { + let name = ix.ident.to_string().to_mixed_case(); + let docs = match &ix.docs { + Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])}, + _ => quote! {None}, + }; + let ctx_ident = &ix.anchor_ident; + + let (args, mut defined) = ix + .args + .iter() + .map(|arg| { + let arg_name = arg.name.to_string().to_mixed_case(); + let docs = match docs::parse(&arg.raw_arg.attrs) { + Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])}, + _ => quote! {None}, + }; + let (ty, defined) = idl_type_ts_from_syn_type(&arg.raw_arg.ty, &vec![])?; + + Ok((quote! { + #idl::IdlField { + name: #arg_name.into(), + docs: #docs, + ty: #ty, + } + }, defined)) + }) + .collect::, ()>>()? + .into_iter() + .unzip::, Vec, Vec>>(); + + let returns = match idl_type_ts_from_syn_type(&ix.returns.ty, &vec![]) { + Ok((ty, def)) => { + defined.push(def); + quote!{ Some(#ty) } + }, + Err(()) => quote!{ None } + }; + + Ok((quote! { + #idl::IdlInstruction { + name: #name.into(), + docs: #docs, + accounts: #ctx_ident::__anchor_private_gen_idl_accounts( + &mut accounts, + &mut defined_types, + ), + args: vec![#(#args),*], + returns: #returns, + } + }, defined)) + }) + .unzip::>, Vec, Vec>>>(); + let defined = defined + .into_iter() + .flatten() + .flatten() + .collect::>(); + + let name = program.name.to_string(); + let docs = match &program.docs { + Some(docs) if !no_docs => quote! {Some(vec![#(#docs.into()),*])}, + _ => quote! {None}, + }; + + quote! { + #[test] + pub fn __anchor_private_print_idl_program() { + let mut accounts: std::collections::HashMap = + std::collections::HashMap::new(); + let mut defined_types: std::collections::HashMap = + std::collections::HashMap::new(); + + #({ + <#defined>::__anchor_private_insert_idl_defined(&mut defined_types); + + let path = <#defined>::__anchor_private_full_path(); + <#defined>::__anchor_private_gen_idl_type() + .and_then(|ty| defined_types.insert(path, ty)); + });* + + let instructions = vec![#(#instructions),*]; + + let idl = #idl::Idl { + version: env!("CARGO_PKG_VERSION").into(), + name: #name.into(), + docs: #docs, + constants: vec![], + instructions, + accounts: accounts.into_values().collect(), + types: defined_types.into_values().collect(), + events: None, + errors: None, + metadata: None, + }; + + println!("---- IDL begin program ----"); + println!("{}", #serde_json::to_string_pretty(&idl).unwrap()); + println!("---- IDL end program ----"); + } + } +} + +pub fn gen_idl_print_function_for_event(event: &ItemStruct) -> TokenStream { + let (idl, serde_json) = get_module_paths(); + + let ident = &event.ident; + let fn_name = format_ident!("__anchor_private_print_idl_event_{}", ident.to_string()); + let impl_gen = gen_idl_gen_impl_for_event(event); + + quote! { + #impl_gen + + #[test] + pub fn #fn_name() { + let mut defined_types: std::collections::HashMap = std::collections::HashMap::new(); + let event = #ident::__anchor_private_gen_idl_event(&mut defined_types); + + if let Some(event) = event { + let json = #serde_json::json!({ + "event": event, + "defined_types": defined_types.into_values().collect::>() + }); + + println!("---- IDL begin event ----"); + println!("{}", #serde_json::to_string_pretty(&json).unwrap()); + println!("---- IDL end event ----"); + } + } + } +} + +pub fn gen_idl_print_function_for_constant(item: &syn::ItemConst) -> TokenStream { + let fn_name = format_ident!( + "__anchor_private_print_idl_const_{}", + item.ident.to_string() + ); + let (idl, serde_json) = get_module_paths(); + + let name = item.ident.to_string(); + let expr = &item.expr; + + let impl_ts = match idl_type_ts_from_syn_type(&item.ty, &vec![]) { + Ok((ty, _)) => quote! { + let value = format!("{}", #expr); + + let idl = #idl::IdlConst { + name: #name.into(), + ty: #ty, + value, + }; + + println!("---- IDL begin const ----"); + println!("{}", #serde_json::to_string_pretty(&idl).unwrap()); + println!("---- IDL end const ----"); + }, + Err(()) => quote! {}, + }; + + quote! { + #[test] + pub fn #fn_name() { + #impl_ts + } + } +} + +pub fn gen_idl_print_function_for_error(error: &Error) -> TokenStream { + let fn_name = format_ident!( + "__anchor_private_print_idl_error_{}", + error.ident.to_string() + ); + let (idl, serde_json) = get_module_paths(); + + let error_codes = error + .codes + .iter() + .map(|code| { + let id = code.id; + let name = code.ident.to_string(); + + let msg = match code.msg.clone() { + Some(msg) => quote! { Some(#msg.to_string()) }, + None => quote! { None }, + }; + + quote! { + #idl::IdlErrorCode { + code: anchor_lang::error::ERROR_CODE_OFFSET + #id, + name: #name.into(), + msg: #msg, + } + } + }) + .collect::>(); + + quote! { + #[test] + pub fn #fn_name() { + let errors = vec![#(#error_codes),*]; + + println!("---- IDL begin errors ----"); + println!("{}", #serde_json::to_string_pretty(&errors).unwrap()); + println!("---- IDL end errors ----"); + } + } +} diff --git a/lang/syn/src/idl/mod.rs b/lang/syn/src/idl/mod.rs index 6729ca4a55..dce34bb5b0 100644 --- a/lang/syn/src/idl/mod.rs +++ b/lang/syn/src/idl/mod.rs @@ -1,330 +1,6 @@ -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)) - ) - } -} +#[cfg(feature = "idl-build")] +pub mod build; +#[cfg(any(feature = "idl-parse", feature = "idl-build"))] +pub mod parse; +#[cfg(any(feature = "idl-types", feature = "idl-build", feature = "idl-parse"))] +pub mod types; diff --git a/lang/syn/src/idl/file.rs b/lang/syn/src/idl/parse/file.rs similarity index 98% rename from lang/syn/src/idl/file.rs rename to lang/syn/src/idl/parse/file.rs index 908a1285e1..edd1ba40d9 100644 --- a/lang/syn/src/idl/file.rs +++ b/lang/syn/src/idl/parse/file.rs @@ -1,4 +1,4 @@ -use crate::idl::*; +use crate::idl::types::*; use crate::parser::context::CrateContext; use crate::parser::{self, accounts, docs, error, program}; use crate::Ty; @@ -13,6 +13,8 @@ use syn::{ Lit::{Byte, ByteStr}, }; +use super::relations; + const DERIVE_NAME: &str = "Accounts"; // TODO: share this with `anchor_lang` crate. const ERROR_CODE_OFFSET: u32 = 6000; @@ -346,6 +348,8 @@ fn parse_ty_defs(ctx: &CrateContext, no_docs: bool) -> Result Result>(); Some(Ok(IdlTypeDefinition { name, + path: None, + generics: None, docs: doc, ty: IdlTypeDefinitionTy::Enum { variants }, })) @@ -547,7 +553,7 @@ fn idl_accounts( }, is_optional: if acc.is_optional { Some(true) } else { None }, docs: if !no_docs { acc.docs.clone() } else { None }, - pda: pda::parse(ctx, accounts, acc, seeds_feature), + pda: super::pda::parse(ctx, accounts, acc, seeds_feature), relations: relations::parse(acc, seeds_feature), }), }) diff --git a/lang/syn/src/idl/parse/mod.rs b/lang/syn/src/idl/parse/mod.rs new file mode 100644 index 0000000000..88dc4e7ba7 --- /dev/null +++ b/lang/syn/src/idl/parse/mod.rs @@ -0,0 +1,120 @@ +use crate::idl::types::*; + +pub mod file; +pub mod pda; +pub mod relations; + +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) + } +} + +#[cfg(test)] +mod tests { + use crate::idl::types::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)) + ) + } +} diff --git a/lang/syn/src/idl/pda.rs b/lang/syn/src/idl/parse/pda.rs similarity index 99% rename from lang/syn/src/idl/pda.rs rename to lang/syn/src/idl/parse/pda.rs index 630243e873..d475dab431 100644 --- a/lang/syn/src/idl/pda.rs +++ b/lang/syn/src/idl/parse/pda.rs @@ -1,4 +1,4 @@ -use crate::idl::*; +use crate::idl::types::*; use crate::parser; use crate::parser::context::CrateContext; use crate::ConstraintSeedsGroup; diff --git a/lang/syn/src/idl/relations.rs b/lang/syn/src/idl/parse/relations.rs similarity index 100% rename from lang/syn/src/idl/relations.rs rename to lang/syn/src/idl/parse/relations.rs diff --git a/lang/syn/src/idl/types.rs b/lang/syn/src/idl/types.rs new file mode 100644 index 0000000000..22ef7d1377 --- /dev/null +++ b/lang/syn/src/idl/types.rs @@ -0,0 +1,229 @@ +use serde::{Deserialize, Serialize}; +use serde_json::Value as JsonValue; + +#[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 path: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub generics: Option>, + #[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), + GenericLenArray(Box, String), + Generic(String), + DefinedWithTypeArgs { + path: 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, +} diff --git a/lang/syn/src/lib.rs b/lang/syn/src/lib.rs index 7bad199db2..650f1decab 100644 --- a/lang/syn/src/lib.rs +++ b/lang/syn/src/lib.rs @@ -23,7 +23,6 @@ pub mod codegen; pub mod hash; #[cfg(not(feature = "hash"))] pub(crate) mod hash; -#[cfg(feature = "idl")] pub mod idl; pub mod parser; diff --git a/tests/idl-build/Anchor.toml b/tests/idl-build/Anchor.toml new file mode 100644 index 0000000000..d31cb4b5ae --- /dev/null +++ b/tests/idl-build/Anchor.toml @@ -0,0 +1,13 @@ +[features] +seeds = true + +[programs.localnet] +idl = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" +idl_2 = "Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS" + +[provider] +cluster = "localnet" +wallet = "~/.config/solana/id.json" + +[scripts] +test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" diff --git a/tests/idl-build/Cargo.toml b/tests/idl-build/Cargo.toml new file mode 100644 index 0000000000..ef17a63c0a --- /dev/null +++ b/tests/idl-build/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] +members = [ + "programs/*" +] + +[profile.release] +overflow-checks = true +lto = "fat" +codegen-units = 1 +[profile.release.build-override] +opt-level = 3 +incremental = false +codegen-units = 1 diff --git a/tests/idl-build/gen_testdata.sh b/tests/idl-build/gen_testdata.sh new file mode 100755 index 0000000000..f30c04b7d2 --- /dev/null +++ b/tests/idl-build/gen_testdata.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +cd programs/idl +anchor idl parse --file src/lib.rs > ../../tests/testdata/idl_parse_exp.json +anchor idl build > ../../tests/testdata/idl_build_exp.json + +cd ../generics +anchor idl build > ../../tests/testdata/generics_build_exp.json + +cd ../relations-derivation +anchor idl build > ../../tests/testdata/relations_build_exp.json \ No newline at end of file diff --git a/tests/idl-build/package.json b/tests/idl-build/package.json new file mode 100644 index 0000000000..3807440ae8 --- /dev/null +++ b/tests/idl-build/package.json @@ -0,0 +1,16 @@ +{ + "name": "idl-build", + "version": "0.28.0", + "license": "(MIT OR Apache-2.0)", + "homepage": "https://github.com/coral-xyz/anchor#readme", + "bugs": { + "url": "https://github.com/coral-xyz/anchor/issues" + }, + "repository": { + "type": "git", + "url": "https://github.com/coral-xyz/anchor.git" + }, + "engines": { + "node": ">=17" + } +} diff --git a/tests/idl-build/programs/generics/Cargo.toml b/tests/idl-build/programs/generics/Cargo.toml new file mode 100644 index 0000000000..224d57f462 --- /dev/null +++ b/tests/idl-build/programs/generics/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "generics" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "generics" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +idl-build = [ + "anchor-lang/idl-build", + "some-external-program/idl-build", +] +default = [] + +[dependencies] +anchor-lang = { path = "../../../../lang" } +anchor-spl = { path = "../../../../spl" } +some-external-program = { path = "../some_external_program", features = ["no-entrypoint"] } diff --git a/tests/idl-build/programs/generics/Xargo.toml b/tests/idl-build/programs/generics/Xargo.toml new file mode 100644 index 0000000000..475fb71ed1 --- /dev/null +++ b/tests/idl-build/programs/generics/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/tests/idl-build/programs/generics/src/lib.rs b/tests/idl-build/programs/generics/src/lib.rs new file mode 100644 index 0000000000..23ca6af32f --- /dev/null +++ b/tests/idl-build/programs/generics/src/lib.rs @@ -0,0 +1,80 @@ +use anchor_lang::prelude::*; +use some_external_program; + +declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); + +/// This is an example program used for testing +#[program] +pub mod example_program { + use super::*; + + pub fn generic( + ctx: Context, + generic_field: GenericType:: + ) -> Result<()>{ + ctx.accounts.generic_acc.data = generic_field; + Ok(()) + } +} + +#[derive(Accounts)] +pub struct GenericCtx<'info> { + generic_acc: Account<'info, GenericAccount>, + + #[account(mut)] + payer: Signer<'info>, + system_program: Program<'info, System>, +} + +#[account] +pub struct GenericAccount { + pub data: GenericType +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct GenericType +where + T: AnchorSerialize + AnchorDeserialize, + U: AnchorSerialize + AnchorDeserialize, +{ + pub gen1: T, + pub gen2: U, + pub gen3: GenericNested, + pub gen4: GenericNested, + pub gen5: GenericNested, + pub gen6: GenericNested, + pub gen7: GenericNested>, + pub arr: [u8; N], + pub warr: WrappedU8Array, + pub warrval: WrappedU8Array<10>, + pub enm1: GenericEnum, + pub enm2: GenericEnum, u32, 30>, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Default)] +pub struct GenericNested +where + V: AnchorSerialize + AnchorDeserialize, + Z: AnchorSerialize + AnchorDeserialize, +{ + pub gen1: V, + pub gen2: Z, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct WrappedU8Array(u8); + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub enum GenericEnum +where + T: AnchorSerialize + AnchorDeserialize, + U: AnchorSerialize + AnchorDeserialize, +{ + Unnamed(T, U), + Named { + gen1: T, + gen2: U, + }, + Struct(GenericNested), + Arr([T; N]), +} diff --git a/tests/idl-build/programs/idl/Cargo.toml b/tests/idl-build/programs/idl/Cargo.toml new file mode 100644 index 0000000000..4672e815b9 --- /dev/null +++ b/tests/idl-build/programs/idl/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "idl" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "idl" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +cpi = ["no-entrypoint"] +idl-build = [ + "anchor-lang/idl-build", + "some-external-program/idl-build", +] +default = [] + +[dependencies] +anchor-lang = { path = "../../../../lang" } +anchor-spl = { path = "../../../../spl" } +bytemuck = {version = "1.4.0", features = ["derive", "min_const_generics"]} +some-external-program = { path = "../some_external_program", features = ["no-entrypoint"] } diff --git a/tests/idl-build/programs/idl/Xargo.toml b/tests/idl-build/programs/idl/Xargo.toml new file mode 100644 index 0000000000..475fb71ed1 --- /dev/null +++ b/tests/idl-build/programs/idl/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/tests/idl-build/programs/idl/src/lib.rs b/tests/idl-build/programs/idl/src/lib.rs new file mode 100644 index 0000000000..7341eb044f --- /dev/null +++ b/tests/idl-build/programs/idl/src/lib.rs @@ -0,0 +1,326 @@ +use anchor_lang::prelude::*; +use some_external_program; +use std::str::FromStr; + +declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); + +#[constant] +pub const FOO_CONST: u128 = 1_000_000; +#[constant] +pub const BAR_CONST: u8 = 6; + +/// This is an example program used for testing +#[program] +pub mod example_program { + use super::*; + + pub fn initialize(ctx: Context) -> Result<()> { + ctx.accounts.state.set_inner(State::default()); + Ok(()) + } + + /// Initializes an account with specified values + pub fn initialize_with_values( + ctx: Context, + bool_field: bool, + u8_field: u8, + i8_field: i8, + u16_field: u16, + i16_field: i16, + u32_field: u32, + i32_field: i32, + f32_field: f32, + u64_field: u64, + i64_field: i64, + f64_field: f64, + u128_field: u128, + i128_field: i128, + bytes_field: Vec, + string_field: String, + pubkey_field: Pubkey, + vec_field: Vec, + vec_struct_field: Vec, + option_field: Option, + option_struct_field: Option, + struct_field: FooStruct, + array_field: [bool; 3], + enum_field_1: FooEnum, + enum_field_2: FooEnum, + enum_field_3: FooEnum, + enum_field_4: FooEnum, + ) -> Result<()> { + ctx.accounts.state.set_inner(State { + bool_field, + u8_field, + i8_field, + u16_field, + i16_field, + u32_field, + i32_field, + f32_field, + u64_field, + i64_field, + f64_field, + u128_field, + i128_field, + bytes_field, + string_field, + pubkey_field, + vec_field, + vec_struct_field, + option_field, + option_struct_field, + struct_field, + array_field, + enum_field_1, + enum_field_2, + enum_field_3, + enum_field_4, + }); + + Ok(()) + } + + /// a separate instruction due to initialize_with_values having too many arguments + /// https://github.com/solana-labs/solana/issues/23978 + pub fn initialize_with_values2( + ctx: Context, + vec_of_option: Vec>, + box_field: Box, + ) -> Result { + ctx.accounts.state.set_inner(State2 { vec_of_option, box_field }); + Ok(SomeRetStruct { some_field: 3}) + } + + pub fn cause_error(_ctx: Context) -> Result<()> { + return Err(error!(ErrorCode::SomeError)); + } +} + +/// Enum type +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub enum FooEnum { + /// Tuple kind + Unnamed(bool, u8, BarStruct), + UnnamedSingle(BarStruct), + Named { + /// A bool field inside a struct tuple kind + bool_field: bool, + u8_field: u8, + nested: BarStruct, + }, + Struct(BarStruct), + OptionStruct(Option), + VecStruct(Vec), + NoFields, +} + +/// Bar struct type +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct BarStruct { + /// Some field + some_field: bool, + other_field: u8, +} + +impl Default for BarStruct { + fn default() -> Self { + return BarStruct { + some_field: true, + other_field: 10, + }; + } +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct FooStruct { + field1: u8, + field2: u16, + nested: BarStruct, + vec_nested: Vec, + option_nested: Option, + enum_field: FooEnum, +} + +impl Default for FooStruct { + fn default() -> Self { + return FooStruct { + field1: 123, + field2: 999, + nested: BarStruct::default(), + vec_nested: vec![BarStruct::default()], + option_nested: Some(BarStruct::default()), + enum_field: FooEnum::Named { + bool_field: true, + u8_field: 15, + nested: BarStruct::default(), + }, + }; + } +} + +/// An account containing various fields +#[account] +pub struct State { + /// A boolean field + bool_field: bool, + u8_field: u8, + i8_field: i8, + u16_field: u16, + i16_field: i16, + u32_field: u32, + i32_field: i32, + f32_field: f32, + u64_field: u64, + i64_field: i64, + f64_field: f64, + u128_field: u128, + i128_field: i128, + bytes_field: Vec, + string_field: String, + pubkey_field: Pubkey, + vec_field: Vec, + vec_struct_field: Vec, + option_field: Option, + option_struct_field: Option, + struct_field: FooStruct, + array_field: [bool; 3], + enum_field_1: FooEnum, + enum_field_2: FooEnum, + enum_field_3: FooEnum, + enum_field_4: FooEnum, +} + +impl Default for State { + fn default() -> Self { + // some arbitrary default values + return State { + bool_field: true, + u8_field: 234, + i8_field: -123, + u16_field: 62345, + i16_field: -31234, + u32_field: 1234567891, + i32_field: -1234567891, + f32_field: 123456.5, + u64_field: u64::MAX / 2 + 10, + i64_field: i64::MIN / 2 - 10, + f64_field: 1234567891.345, + u128_field: u128::MAX / 2 + 10, + i128_field: i128::MIN / 2 - 10, + bytes_field: vec![1, 2, 255, 254], + string_field: String::from("hello"), + pubkey_field: Pubkey::from_str("EPZP2wrcRtMxrAPJCXVEQaYD9eH7fH7h12YqKDcd4aS7").unwrap(), + vec_field: vec![1, 2, 100, 1000, u64::MAX], + vec_struct_field: vec![FooStruct::default()], + option_field: None, + option_struct_field: Some(FooStruct::default()), + struct_field: FooStruct::default(), + array_field: [true, false, true], + enum_field_1: FooEnum::Unnamed(false, 10, BarStruct::default()), + enum_field_2: FooEnum::Named { + bool_field: true, + u8_field: 20, + nested: BarStruct::default(), + }, + enum_field_3: FooEnum::Struct(BarStruct::default()), + enum_field_4: FooEnum::NoFields, + }; + } +} + +#[account] +pub struct State2 { + vec_of_option: Vec>, + box_field: Box, +} +impl Default for State2 { + fn default() -> Self { + return State2 { + vec_of_option: vec![None, Some(10)], + box_field: Box::new(true), + }; + } +} + +#[derive(Accounts)] +pub struct NestedAccounts<'info> { + /// Sysvar clock + clock: Sysvar<'info, Clock>, + rent: Sysvar<'info, Rent>, +} + +#[derive(Accounts)] +pub struct Initialize<'info> { + /// State account + #[account( + init, + space = 8 + 1000, // TODO: use exact space required + payer = payer, + )] + state: Account<'info, State>, + + nested: NestedAccounts<'info>, + zc_account: AccountLoader<'info, SomeZcAccount>, + + #[account(mut)] + payer: Signer<'info>, + system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct Initialize2<'info> { + #[account( + init, + space = 8 + 1000, // TODO: use exact space required + payer = payer, + )] + state: Account<'info, State2>, + + #[account(mut)] + payer: Signer<'info>, + system_program: Program<'info, System>, +} + +#[derive(Accounts)] +pub struct CauseError {} + +#[error_code] +pub enum ErrorCode { + #[msg("Example error.")] + SomeError, + #[msg("Another error.")] + OtherError, + ErrorWithoutMsg, +} + +mod some_other_module { + use super::*; + + #[derive(AnchorSerialize, AnchorDeserialize, Clone)] + pub struct Baz { + some_u8: u8, + } +} + +#[event] +pub struct SomeEvent { + bool_field: bool, + external_baz: some_external_program::Baz, + other_module_baz: some_other_module::Baz, +} + +#[zero_copy] +pub struct ZcStruct { + pub some_field: u16, +} + +#[account(zero_copy)] +pub struct SomeZcAccount { + field: ZcStruct, +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct SomeRetStruct { + pub some_field: u8, +} \ No newline at end of file diff --git a/tests/idl-build/programs/relations-derivation/Cargo.toml b/tests/idl-build/programs/relations-derivation/Cargo.toml new file mode 100644 index 0000000000..5a8575757f --- /dev/null +++ b/tests/idl-build/programs/relations-derivation/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "relations-derivation" +version = "0.1.0" +description = "Created with Anchor" +rust-version = "1.60" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "relations_derivation" + +[features] +no-entrypoint = [] +no-idl = [] +cpi = ["no-entrypoint"] +idl-build = [ + "anchor-lang/idl-build", +] +default = [] + +[dependencies] +anchor-lang = { path = "../../../../lang" } diff --git a/tests/idl-build/programs/relations-derivation/Xargo.toml b/tests/idl-build/programs/relations-derivation/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/tests/idl-build/programs/relations-derivation/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/tests/idl-build/programs/relations-derivation/src/lib.rs b/tests/idl-build/programs/relations-derivation/src/lib.rs new file mode 100644 index 0000000000..d463044a38 --- /dev/null +++ b/tests/idl-build/programs/relations-derivation/src/lib.rs @@ -0,0 +1,68 @@ +//! The typescript example serves to show how one would setup an Anchor +//! workspace with TypeScript tests and migrations. + +use anchor_lang::prelude::*; + +declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); + +#[program] +pub mod relations_derivation { + use super::*; + + pub fn init_base(ctx: Context) -> Result<()> { + ctx.accounts.account.my_account = ctx.accounts.my_account.key(); + ctx.accounts.account.bump = ctx.bumps["account"]; + Ok(()) + } + pub fn test_relation(_ctx: Context) -> Result<()> { + Ok(()) + } +} + +#[derive(Accounts)] +pub struct InitBase<'info> { + /// CHECK: yeah I know + #[account(mut)] + my_account: Signer<'info>, + #[account( + init, + payer = my_account, + seeds = [b"seed"], + space = 100, + bump, + )] + account: Account<'info, MyAccount>, + system_program: Program<'info, System> +} + +#[derive(Accounts)] +pub struct Nested<'info> { + /// CHECK: yeah I know + my_account: UncheckedAccount<'info>, + #[account( + has_one = my_account, + seeds = [b"seed"], + bump = account.bump + )] + account: Account<'info, MyAccount>, +} + +#[derive(Accounts)] +pub struct TestRelation<'info> { + /// CHECK: yeah I know + my_account: UncheckedAccount<'info>, + #[account( + has_one = my_account, + seeds = [b"seed"], + bump = account.bump + )] + account: Account<'info, MyAccount>, + nested: Nested<'info>, +} + + +#[account] +pub struct MyAccount { + pub my_account: Pubkey, + pub bump: u8 +} diff --git a/tests/idl-build/programs/some_external_program/Cargo.toml b/tests/idl-build/programs/some_external_program/Cargo.toml new file mode 100644 index 0000000000..bde16f4c09 --- /dev/null +++ b/tests/idl-build/programs/some_external_program/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "some-external-program" +version = "0.1.0" +description = "Created with Anchor" +edition = "2021" + +[lib] +crate-type = ["cdylib", "lib"] +name = "some_external_program" + +[features] +no-entrypoint = [] +no-idl = [] +no-log-ix-name = [] +idl-build = ["anchor-lang/idl-build"] +cpi = ["no-entrypoint"] +default = [] + +[dependencies] +anchor-lang = { path = "../../../../lang" } +anchor-spl = { path = "../../../../spl" } + diff --git a/tests/idl-build/programs/some_external_program/Xargo.toml b/tests/idl-build/programs/some_external_program/Xargo.toml new file mode 100644 index 0000000000..475fb71ed1 --- /dev/null +++ b/tests/idl-build/programs/some_external_program/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] diff --git a/tests/idl-build/programs/some_external_program/src/lib.rs b/tests/idl-build/programs/some_external_program/src/lib.rs new file mode 100644 index 0000000000..0e620f8b5f --- /dev/null +++ b/tests/idl-build/programs/some_external_program/src/lib.rs @@ -0,0 +1,20 @@ +use anchor_lang::prelude::*; + +declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS"); + +#[program] +pub mod idl_2 { + use super::*; + + pub fn initialize(_ctx: Context, _baz: Baz) -> Result<()> { + Ok(()) + } +} + +#[derive(AnchorSerialize, AnchorDeserialize, Clone)] +pub struct Baz { + some_field: u8, +} + +#[derive(Accounts)] +pub struct Initialize {} diff --git a/tests/idl-build/test.sh b/tests/idl-build/test.sh new file mode 100755 index 0000000000..45d7b133c3 --- /dev/null +++ b/tests/idl-build/test.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -x +set -e + +TMPDIR=$(mktemp -d) + +cd programs/idl +anchor idl parse --file src/lib.rs > $TMPDIR/idl_parse_act.json +anchor idl build > $TMPDIR/idl_build_act.json + +cd ../generics +anchor idl build > $TMPDIR/generics_build_act.json + +cd ../relations-derivation +anchor idl build > $TMPDIR/relations_build_act.json + +cd ../.. +echo "----------------------------------------------------" +echo "idl parse before > after" +echo "----------------------------------------------------" +echo "" +diff -y --color tests/testdata/idl_parse_exp.json $TMPDIR/idl_parse_act.json +PARSE_RETCODE=$? + +echo "" +echo "" +echo "----------------------------------------------------" +echo "idl build before > after" +echo "----------------------------------------------------" +echo "" +diff -y --color tests/testdata/idl_build_exp.json $TMPDIR/idl_build_act.json +GEN_RETCODE=$? + +echo "" +echo "" +echo "----------------------------------------------------" +echo "idl generics build before > after" +echo "----------------------------------------------------" +echo "" +diff -y --color tests/testdata/generics_build_exp.json $TMPDIR/generics_build_act.json +GEN_GENERICS_RETCODE=$? + +echo "" +echo "" +echo "----------------------------------------------------" +echo "idl relations build before > after" +echo "----------------------------------------------------" +echo "" +diff -y --color tests/testdata/relations_build_exp.json $TMPDIR/relations_build_act.json +GEN_RELATIONS_RETCODE=$? + +# returns 0 when ok, or a positive integer when there are differences +exit $((PARSE_RETCODE+GEN_RETCODE+GEN_GENERICS_RETCODE+GEN_RELATIONS_RETCODE)) diff --git a/tests/idl-build/tests/testdata/generics_build_exp.json b/tests/idl-build/tests/testdata/generics_build_exp.json new file mode 100644 index 0000000000..1998a829bd --- /dev/null +++ b/tests/idl-build/tests/testdata/generics_build_exp.json @@ -0,0 +1,426 @@ +{ + "version": "0.1.0", + "name": "example_program", + "docs": [ + "This is an example program used for testing" + ], + "instructions": [ + { + "name": "generic", + "accounts": [ + { + "name": "genericAcc", + "isMut": false, + "isSigner": false + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "genericField", + "type": { + "definedWithTypeArgs": { + "path": "generics::GenericType", + "args": [ + { + "type": "u32" + }, + { + "type": "u64" + }, + { + "value": "10" + } + ] + } + } + } + ] + } + ], + "accounts": [ + { + "name": "GenericAccount", + "path": "generics::GenericAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "data", + "type": { + "definedWithTypeArgs": { + "path": "generics::GenericType", + "args": [ + { + "type": "u32" + }, + { + "type": "u64" + }, + { + "value": "10" + } + ] + } + } + } + ] + } + } + ], + "types": [ + { + "name": "GenericEnum", + "path": "generics::GenericEnum", + "generics": [ + "T", + "U", + "N" + ], + "type": { + "kind": "enum", + "variants": [ + { + "name": "Unnamed", + "fields": [ + { + "generic": "T" + }, + { + "generic": "U" + } + ] + }, + { + "name": "Named", + "fields": [ + { + "name": "gen1", + "type": { + "generic": "T" + } + }, + { + "name": "gen2", + "type": { + "generic": "U" + } + } + ] + }, + { + "name": "Struct", + "fields": [ + { + "definedWithTypeArgs": { + "path": "generics::GenericNested", + "args": [ + { + "type": { + "generic": "T" + } + }, + { + "type": { + "generic": "U" + } + } + ] + } + } + ] + }, + { + "name": "Arr", + "fields": [ + { + "genericLenArray": [ + { + "generic": "T" + }, + "N" + ] + } + ] + } + ] + } + }, + { + "name": "GenericNested", + "path": "generics::GenericNested", + "generics": [ + "V", + "Z" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "gen1", + "type": { + "generic": "V" + } + }, + { + "name": "gen2", + "type": { + "generic": "Z" + } + } + ] + } + }, + { + "name": "GenericType", + "path": "generics::GenericType", + "generics": [ + "T", + "U", + "N" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "gen1", + "type": { + "generic": "T" + } + }, + { + "name": "gen2", + "type": { + "generic": "U" + } + }, + { + "name": "gen3", + "type": { + "definedWithTypeArgs": { + "path": "generics::GenericNested", + "args": [ + { + "type": "u32" + }, + { + "type": { + "generic": "U" + } + } + ] + } + } + }, + { + "name": "gen4", + "type": { + "definedWithTypeArgs": { + "path": "generics::GenericNested", + "args": [ + { + "type": { + "generic": "T" + } + }, + { + "type": { + "defined": "some_external_program::Baz" + } + } + ] + } + } + }, + { + "name": "gen5", + "type": { + "definedWithTypeArgs": { + "path": "generics::GenericNested", + "args": [ + { + "type": { + "generic": "T" + } + }, + { + "type": { + "generic": "U" + } + } + ] + } + } + }, + { + "name": "gen6", + "type": { + "definedWithTypeArgs": { + "path": "generics::GenericNested", + "args": [ + { + "type": "u32" + }, + { + "type": "u64" + } + ] + } + } + }, + { + "name": "gen7", + "type": { + "definedWithTypeArgs": { + "path": "generics::GenericNested", + "args": [ + { + "type": { + "generic": "T" + } + }, + { + "type": { + "definedWithTypeArgs": { + "path": "generics::GenericNested", + "args": [ + { + "type": { + "generic": "T" + } + }, + { + "type": { + "generic": "U" + } + } + ] + } + } + } + ] + } + } + }, + { + "name": "arr", + "type": { + "genericLenArray": [ + "u8", + "N" + ] + } + }, + { + "name": "warr", + "type": { + "definedWithTypeArgs": { + "path": "generics::WrappedU8Array", + "args": [ + { + "type": { + "generic": "N" + } + } + ] + } + } + }, + { + "name": "warrval", + "type": { + "definedWithTypeArgs": { + "path": "generics::WrappedU8Array", + "args": [ + { + "value": "10" + } + ] + } + } + }, + { + "name": "enm1", + "type": { + "definedWithTypeArgs": { + "path": "generics::GenericEnum", + "args": [ + { + "type": { + "generic": "T" + } + }, + { + "type": { + "generic": "U" + } + }, + { + "type": { + "generic": "N" + } + } + ] + } + } + }, + { + "name": "enm2", + "type": { + "definedWithTypeArgs": { + "path": "generics::GenericEnum", + "args": [ + { + "type": { + "definedWithTypeArgs": { + "path": "generics::GenericNested", + "args": [ + { + "type": { + "generic": "T" + } + }, + { + "type": "u64" + } + ] + } + } + }, + { + "type": "u32" + }, + { + "value": "30" + } + ] + } + } + } + ] + } + }, + { + "name": "Baz", + "path": "some_external_program::Baz", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "type": "u8" + } + ] + } + } + ] +} diff --git a/tests/idl-build/tests/testdata/idl_build_exp.json b/tests/idl-build/tests/testdata/idl_build_exp.json new file mode 100644 index 0000000000..4f81d20400 --- /dev/null +++ b/tests/idl-build/tests/testdata/idl_build_exp.json @@ -0,0 +1,727 @@ +{ + "version": "0.1.0", + "name": "example_program", + "docs": [ + "This is an example program used for testing" + ], + "constants": [ + { + "name": "BAR_CONST", + "type": "u8", + "value": "6" + }, + { + "name": "FOO_CONST", + "type": "u128", + "value": "1000000" + } + ], + "instructions": [ + { + "name": "initialize", + "accounts": [ + { + "name": "state", + "isMut": true, + "isSigner": true, + "docs": [ + "State account" + ] + }, + { + "name": "nested", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Sysvar clock" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "zcAccount", + "isMut": false, + "isSigner": false + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, + { + "name": "initializeWithValues", + "docs": [ + "Initializes an account with specified values" + ], + "accounts": [ + { + "name": "state", + "isMut": true, + "isSigner": true, + "docs": [ + "State account" + ] + }, + { + "name": "nested", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Sysvar clock" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "zcAccount", + "isMut": false, + "isSigner": false + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "boolField", + "type": "bool" + }, + { + "name": "u8Field", + "type": "u8" + }, + { + "name": "i8Field", + "type": "i8" + }, + { + "name": "u16Field", + "type": "u16" + }, + { + "name": "i16Field", + "type": "i16" + }, + { + "name": "u32Field", + "type": "u32" + }, + { + "name": "i32Field", + "type": "i32" + }, + { + "name": "f32Field", + "type": "f32" + }, + { + "name": "u64Field", + "type": "u64" + }, + { + "name": "i64Field", + "type": "i64" + }, + { + "name": "f64Field", + "type": "f64" + }, + { + "name": "u128Field", + "type": "u128" + }, + { + "name": "i128Field", + "type": "i128" + }, + { + "name": "bytesField", + "type": "bytes" + }, + { + "name": "stringField", + "type": "string" + }, + { + "name": "pubkeyField", + "type": "publicKey" + }, + { + "name": "vecField", + "type": { + "vec": "u64" + } + }, + { + "name": "vecStructField", + "type": { + "vec": { + "defined": "idl::FooStruct" + } + } + }, + { + "name": "optionField", + "type": { + "option": "bool" + } + }, + { + "name": "optionStructField", + "type": { + "option": { + "defined": "idl::FooStruct" + } + } + }, + { + "name": "structField", + "type": { + "defined": "idl::FooStruct" + } + }, + { + "name": "arrayField", + "type": { + "array": [ + "bool", + 3 + ] + } + }, + { + "name": "enumField1", + "type": { + "defined": "idl::FooEnum" + } + }, + { + "name": "enumField2", + "type": { + "defined": "idl::FooEnum" + } + }, + { + "name": "enumField3", + "type": { + "defined": "idl::FooEnum" + } + }, + { + "name": "enumField4", + "type": { + "defined": "idl::FooEnum" + } + } + ] + }, + { + "name": "initializeWithValues2", + "docs": [ + "a separate instruction due to initialize_with_values having too many arguments", + "https://github.com/solana-labs/solana/issues/23978" + ], + "accounts": [ + { + "name": "state", + "isMut": true, + "isSigner": true + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "vecOfOption", + "type": { + "vec": { + "option": "u64" + } + } + }, + { + "name": "boxField", + "type": "bool" + } + ], + "returns": { + "defined": "idl::SomeRetStruct" + } + }, + { + "name": "causeError", + "accounts": [], + "args": [] + } + ], + "accounts": [ + { + "name": "SomeZcAccount", + "path": "idl::SomeZcAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "field", + "type": { + "defined": "idl::ZcStruct" + } + } + ] + } + }, + { + "name": "State", + "path": "idl::State", + "docs": [ + "An account containing various fields" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "boolField", + "docs": [ + "A boolean field" + ], + "type": "bool" + }, + { + "name": "u8Field", + "type": "u8" + }, + { + "name": "i8Field", + "type": "i8" + }, + { + "name": "u16Field", + "type": "u16" + }, + { + "name": "i16Field", + "type": "i16" + }, + { + "name": "u32Field", + "type": "u32" + }, + { + "name": "i32Field", + "type": "i32" + }, + { + "name": "f32Field", + "type": "f32" + }, + { + "name": "u64Field", + "type": "u64" + }, + { + "name": "i64Field", + "type": "i64" + }, + { + "name": "f64Field", + "type": "f64" + }, + { + "name": "u128Field", + "type": "u128" + }, + { + "name": "i128Field", + "type": "i128" + }, + { + "name": "bytesField", + "type": "bytes" + }, + { + "name": "stringField", + "type": "string" + }, + { + "name": "pubkeyField", + "type": "publicKey" + }, + { + "name": "vecField", + "type": { + "vec": "u64" + } + }, + { + "name": "vecStructField", + "type": { + "vec": { + "defined": "idl::FooStruct" + } + } + }, + { + "name": "optionField", + "type": { + "option": "bool" + } + }, + { + "name": "optionStructField", + "type": { + "option": { + "defined": "idl::FooStruct" + } + } + }, + { + "name": "structField", + "type": { + "defined": "idl::FooStruct" + } + }, + { + "name": "arrayField", + "type": { + "array": [ + "bool", + 3 + ] + } + }, + { + "name": "enumField1", + "type": { + "defined": "idl::FooEnum" + } + }, + { + "name": "enumField2", + "type": { + "defined": "idl::FooEnum" + } + }, + { + "name": "enumField3", + "type": { + "defined": "idl::FooEnum" + } + }, + { + "name": "enumField4", + "type": { + "defined": "idl::FooEnum" + } + } + ] + } + }, + { + "name": "State2", + "path": "idl::State2", + "type": { + "kind": "struct", + "fields": [ + { + "name": "vecOfOption", + "type": { + "vec": { + "option": "u64" + } + } + }, + { + "name": "boxField", + "type": "bool" + } + ] + } + } + ], + "types": [ + { + "name": "BarStruct", + "path": "idl::BarStruct", + "docs": [ + "Bar struct type" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "docs": [ + "Some field" + ], + "type": "bool" + }, + { + "name": "otherField", + "type": "u8" + } + ] + } + }, + { + "name": "FooEnum", + "path": "idl::FooEnum", + "docs": [ + "Enum type" + ], + "type": { + "kind": "enum", + "variants": [ + { + "name": "Unnamed", + "fields": [ + "bool", + "u8", + { + "defined": "idl::BarStruct" + } + ] + }, + { + "name": "UnnamedSingle", + "fields": [ + { + "defined": "idl::BarStruct" + } + ] + }, + { + "name": "Named", + "fields": [ + { + "name": "boolField", + "docs": [ + "A bool field inside a struct tuple kind" + ], + "type": "bool" + }, + { + "name": "u8Field", + "type": "u8" + }, + { + "name": "nested", + "type": { + "defined": "idl::BarStruct" + } + } + ] + }, + { + "name": "Struct", + "fields": [ + { + "defined": "idl::BarStruct" + } + ] + }, + { + "name": "OptionStruct", + "fields": [ + { + "option": { + "defined": "idl::BarStruct" + } + } + ] + }, + { + "name": "VecStruct", + "fields": [ + { + "vec": { + "defined": "idl::BarStruct" + } + } + ] + }, + { + "name": "NoFields" + } + ] + } + }, + { + "name": "FooStruct", + "path": "idl::FooStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "field1", + "type": "u8" + }, + { + "name": "field2", + "type": "u16" + }, + { + "name": "nested", + "type": { + "defined": "idl::BarStruct" + } + }, + { + "name": "vecNested", + "type": { + "vec": { + "defined": "idl::BarStruct" + } + } + }, + { + "name": "optionNested", + "type": { + "option": { + "defined": "idl::BarStruct" + } + } + }, + { + "name": "enumField", + "type": { + "defined": "idl::FooEnum" + } + } + ] + } + }, + { + "name": "SomeRetStruct", + "path": "idl::SomeRetStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "type": "u8" + } + ] + } + }, + { + "name": "ZcStruct", + "path": "idl::ZcStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "type": "u16" + } + ] + } + }, + { + "name": "Baz", + "path": "idl::some_other_module::Baz", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someU8", + "type": "u8" + } + ] + } + }, + { + "name": "Baz", + "path": "some_external_program::Baz", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "type": "u8" + } + ] + } + } + ], + "events": [ + { + "name": "SomeEvent", + "fields": [ + { + "name": "boolField", + "type": "bool", + "index": false + }, + { + "name": "externalBaz", + "type": { + "defined": "some_external_program::Baz" + }, + "index": false + }, + { + "name": "otherModuleBaz", + "type": { + "defined": "idl::some_other_module::Baz" + }, + "index": false + } + ] + } + ], + "errors": [ + { + "code": 6000, + "name": "SomeError", + "msg": "Example error." + }, + { + "code": 6001, + "name": "OtherError", + "msg": "Another error." + }, + { + "code": 6002, + "name": "ErrorWithoutMsg" + } + ] +} diff --git a/tests/idl-build/tests/testdata/idl_parse_exp.json b/tests/idl-build/tests/testdata/idl_parse_exp.json new file mode 100644 index 0000000000..899969853d --- /dev/null +++ b/tests/idl-build/tests/testdata/idl_parse_exp.json @@ -0,0 +1,705 @@ +{ + "version": "0.1.0", + "name": "example_program", + "docs": [ + "This is an example program used for testing" + ], + "constants": [ + { + "name": "FOO_CONST", + "type": "u128", + "value": "1_000_000" + }, + { + "name": "BAR_CONST", + "type": "u8", + "value": "6" + } + ], + "instructions": [ + { + "name": "initialize", + "accounts": [ + { + "name": "state", + "isMut": true, + "isSigner": true, + "docs": [ + "State account" + ] + }, + { + "name": "nested", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Sysvar clock" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "zcAccount", + "isMut": false, + "isSigner": false + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, + { + "name": "initializeWithValues", + "docs": [ + "Initializes an account with specified values" + ], + "accounts": [ + { + "name": "state", + "isMut": true, + "isSigner": true, + "docs": [ + "State account" + ] + }, + { + "name": "nested", + "accounts": [ + { + "name": "clock", + "isMut": false, + "isSigner": false, + "docs": [ + "Sysvar clock" + ] + }, + { + "name": "rent", + "isMut": false, + "isSigner": false + } + ] + }, + { + "name": "zcAccount", + "isMut": false, + "isSigner": false + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "boolField", + "type": "bool" + }, + { + "name": "u8Field", + "type": "u8" + }, + { + "name": "i8Field", + "type": "i8" + }, + { + "name": "u16Field", + "type": "u16" + }, + { + "name": "i16Field", + "type": "i16" + }, + { + "name": "u32Field", + "type": "u32" + }, + { + "name": "i32Field", + "type": "i32" + }, + { + "name": "f32Field", + "type": "f32" + }, + { + "name": "u64Field", + "type": "u64" + }, + { + "name": "i64Field", + "type": "i64" + }, + { + "name": "f64Field", + "type": "f64" + }, + { + "name": "u128Field", + "type": "u128" + }, + { + "name": "i128Field", + "type": "i128" + }, + { + "name": "bytesField", + "type": "bytes" + }, + { + "name": "stringField", + "type": "string" + }, + { + "name": "pubkeyField", + "type": "publicKey" + }, + { + "name": "vecField", + "type": { + "vec": "u64" + } + }, + { + "name": "vecStructField", + "type": { + "vec": { + "defined": "FooStruct" + } + } + }, + { + "name": "optionField", + "type": { + "option": "bool" + } + }, + { + "name": "optionStructField", + "type": { + "option": { + "defined": "FooStruct" + } + } + }, + { + "name": "structField", + "type": { + "defined": "FooStruct" + } + }, + { + "name": "arrayField", + "type": { + "array": [ + "bool", + 3 + ] + } + }, + { + "name": "enumField1", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField2", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField3", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField4", + "type": { + "defined": "FooEnum" + } + } + ] + }, + { + "name": "initializeWithValues2", + "docs": [ + "a separate instruction due to initialize_with_values having too many arguments", + "https://github.com/solana-labs/solana/issues/23978" + ], + "accounts": [ + { + "name": "state", + "isMut": true, + "isSigner": true + }, + { + "name": "payer", + "isMut": true, + "isSigner": true + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [ + { + "name": "vecOfOption", + "type": { + "vec": { + "option": "u64" + } + } + }, + { + "name": "boxField", + "type": "bool" + } + ], + "returns": { + "defined": "SomeRetStruct" + } + }, + { + "name": "causeError", + "accounts": [], + "args": [] + } + ], + "accounts": [ + { + "name": "State", + "docs": [ + "An account containing various fields" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "boolField", + "docs": [ + "A boolean field" + ], + "type": "bool" + }, + { + "name": "u8Field", + "type": "u8" + }, + { + "name": "i8Field", + "type": "i8" + }, + { + "name": "u16Field", + "type": "u16" + }, + { + "name": "i16Field", + "type": "i16" + }, + { + "name": "u32Field", + "type": "u32" + }, + { + "name": "i32Field", + "type": "i32" + }, + { + "name": "f32Field", + "type": "f32" + }, + { + "name": "u64Field", + "type": "u64" + }, + { + "name": "i64Field", + "type": "i64" + }, + { + "name": "f64Field", + "type": "f64" + }, + { + "name": "u128Field", + "type": "u128" + }, + { + "name": "i128Field", + "type": "i128" + }, + { + "name": "bytesField", + "type": "bytes" + }, + { + "name": "stringField", + "type": "string" + }, + { + "name": "pubkeyField", + "type": "publicKey" + }, + { + "name": "vecField", + "type": { + "vec": "u64" + } + }, + { + "name": "vecStructField", + "type": { + "vec": { + "defined": "FooStruct" + } + } + }, + { + "name": "optionField", + "type": { + "option": "bool" + } + }, + { + "name": "optionStructField", + "type": { + "option": { + "defined": "FooStruct" + } + } + }, + { + "name": "structField", + "type": { + "defined": "FooStruct" + } + }, + { + "name": "arrayField", + "type": { + "array": [ + "bool", + 3 + ] + } + }, + { + "name": "enumField1", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField2", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField3", + "type": { + "defined": "FooEnum" + } + }, + { + "name": "enumField4", + "type": { + "defined": "FooEnum" + } + } + ] + } + }, + { + "name": "State2", + "type": { + "kind": "struct", + "fields": [ + { + "name": "vecOfOption", + "type": { + "vec": { + "option": "u64" + } + } + }, + { + "name": "boxField", + "type": "bool" + } + ] + } + }, + { + "name": "SomeZcAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "field", + "type": { + "defined": "ZcStruct" + } + } + ] + } + } + ], + "types": [ + { + "name": "Baz", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someU8", + "type": "u8" + } + ] + } + }, + { + "name": "BarStruct", + "docs": [ + "Bar struct type" + ], + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "docs": [ + "Some field" + ], + "type": "bool" + }, + { + "name": "otherField", + "type": "u8" + } + ] + } + }, + { + "name": "FooStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "field1", + "type": "u8" + }, + { + "name": "field2", + "type": "u16" + }, + { + "name": "nested", + "type": { + "defined": "BarStruct" + } + }, + { + "name": "vecNested", + "type": { + "vec": { + "defined": "BarStruct" + } + } + }, + { + "name": "optionNested", + "type": { + "option": { + "defined": "BarStruct" + } + } + }, + { + "name": "enumField", + "type": { + "defined": "FooEnum" + } + } + ] + } + }, + { + "name": "ZcStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "type": "u16" + } + ] + } + }, + { + "name": "SomeRetStruct", + "type": { + "kind": "struct", + "fields": [ + { + "name": "someField", + "type": "u8" + } + ] + } + }, + { + "name": "FooEnum", + "docs": [ + "Enum type" + ], + "type": { + "kind": "enum", + "variants": [ + { + "name": "Unnamed", + "fields": [ + "bool", + "u8", + { + "defined": "BarStruct" + } + ] + }, + { + "name": "UnnamedSingle", + "fields": [ + { + "defined": "BarStruct" + } + ] + }, + { + "name": "Named", + "fields": [ + { + "name": "bool_field", + "docs": [ + "A bool field inside a struct tuple kind" + ], + "type": "bool" + }, + { + "name": "u8_field", + "type": "u8" + }, + { + "name": "nested", + "type": { + "defined": "BarStruct" + } + } + ] + }, + { + "name": "Struct", + "fields": [ + { + "defined": "BarStruct" + } + ] + }, + { + "name": "OptionStruct", + "fields": [ + { + "option": { + "defined": "BarStruct" + } + } + ] + }, + { + "name": "VecStruct", + "fields": [ + { + "vec": { + "defined": "BarStruct" + } + } + ] + }, + { + "name": "NoFields" + } + ] + } + } + ], + "events": [ + { + "name": "SomeEvent", + "fields": [ + { + "name": "boolField", + "type": "bool", + "index": false + }, + { + "name": "externalBaz", + "type": { + "defined": "some_external_program::Baz" + }, + "index": false + }, + { + "name": "otherModuleBaz", + "type": { + "defined": "some_other_module::Baz" + }, + "index": false + } + ] + } + ], + "errors": [ + { + "code": 6000, + "name": "SomeError", + "msg": "Example error." + }, + { + "code": 6001, + "name": "OtherError", + "msg": "Another error." + }, + { + "code": 6002, + "name": "ErrorWithoutMsg" + } + ] +} diff --git a/tests/idl-build/tests/testdata/relations_build_exp.json b/tests/idl-build/tests/testdata/relations_build_exp.json new file mode 100644 index 0000000000..25aad5462b --- /dev/null +++ b/tests/idl-build/tests/testdata/relations_build_exp.json @@ -0,0 +1,83 @@ +{ + "version": "0.1.0", + "name": "relations_derivation", + "instructions": [ + { + "name": "initBase", + "accounts": [ + { + "name": "myAccount", + "isMut": true, + "isSigner": true + }, + { + "name": "account", + "isMut": true, + "isSigner": false + }, + { + "name": "systemProgram", + "isMut": false, + "isSigner": false + } + ], + "args": [] + }, + { + "name": "testRelation", + "accounts": [ + { + "name": "myAccount", + "isMut": false, + "isSigner": false + }, + { + "name": "account", + "isMut": false, + "isSigner": false, + "relations": [ + "my_account" + ] + }, + { + "name": "nested", + "accounts": [ + { + "name": "myAccount", + "isMut": false, + "isSigner": false + }, + { + "name": "account", + "isMut": false, + "isSigner": false, + "relations": [ + "my_account" + ] + } + ] + } + ], + "args": [] + } + ], + "accounts": [ + { + "name": "MyAccount", + "path": "relations_derivation::MyAccount", + "type": { + "kind": "struct", + "fields": [ + { + "name": "myAccount", + "type": "publicKey" + }, + { + "name": "bump", + "type": "u8" + } + ] + } + } + ] +} diff --git a/tests/idl-build/tsconfig.json b/tests/idl-build/tsconfig.json new file mode 100644 index 0000000000..cd5d2e3d06 --- /dev/null +++ b/tests/idl-build/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "types": ["mocha", "chai"], + "typeRoots": ["./node_modules/@types"], + "lib": ["es2015"], + "module": "commonjs", + "target": "es6", + "esModuleInterop": true + } +} diff --git a/tests/package.json b/tests/package.json index 87139e3198..d14af0560d 100644 --- a/tests/package.json +++ b/tests/package.json @@ -18,6 +18,7 @@ "escrow", "events", "floats", + "idl-build", "ido-pool", "interface", "lockup",