IDL generation through compilation (#2011)

Co-authored-by: acheron <acheroncrypto@gmail.com>
This commit is contained in:
Krešimir Klas 2023-07-08 19:59:30 +02:00 committed by GitHub
parent 0225b7c0fd
commit 6ef6b79a6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 4384 additions and 363 deletions

View File

@ -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/

View File

@ -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<C> where <C: Clone + Deref<Target = impl Signer>>` with Solana clap CLI utils that loads `Signer` as `Box<dyn Signer>` ([#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

14
Cargo.lock generated
View File

@ -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",

View File

@ -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"

View File

@ -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;

View File

@ -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<String>),
EventLines(Vec<String>),
ErrorsLines(Vec<String>),
ProgramLines(Vec<String>),
}
#[derive(Serialize, Deserialize)]
struct IdlGenEventPrint {
event: IdlEvent,
defined_types: Vec<IdlTypeDefinition>,
}
let mut state = State::Pass;
let mut events: Vec<IdlEvent> = vec![];
let mut error_codes: Option<Vec<IdlErrorCode>> = None;
let mut constants: Vec<IdlConst> = vec![];
let mut defined_types: BTreeMap<String, IdlTypeDefinition> = BTreeMap::new();
let mut curr_idl: Option<Idl> = None;
let mut idls: Vec<Idl> = 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::<Vec<_>>();
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<IdlErrorCode> = 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<String>) -> 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")
}
})
}

View File

@ -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::{

View File

@ -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;

View File

@ -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"

View File

@ -12,6 +12,7 @@ edition = "2021"
proc-macro = true
[features]
idl-build = ["anchor-syn/idl-build"]
anchor-debug = ["anchor-syn/anchor-debug"]
[dependencies]

View File

@ -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

View File

@ -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"] }

View File

@ -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
}

View File

@ -12,6 +12,7 @@ edition = "2021"
proc-macro = true
[features]
idl-build = ["anchor-syn/idl-build"]
anchor-debug = ["anchor-syn/anchor-debug"]
[dependencies]

View File

@ -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"]

View File

@ -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

View File

@ -12,6 +12,7 @@ edition = "2021"
proc-macro = true
[features]
idl-build = ["anchor-syn/idl-build"]
anchor-debug = ["anchor-syn/anchor-debug"]
[dependencies]

View File

@ -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]

View File

@ -0,0 +1,24 @@
[package]
name = "anchor-derive-serde"
version = "0.28.0"
authors = ["Anchor Maintainers <accounts@200ms.io>"]
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"

View File

@ -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))
}

View File

@ -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<T> = std::result::Result<T, error::Error>;
/// A data structure of validated accounts that can be deserialized from the

View File

@ -11,7 +11,9 @@ edition = "2021"
[features]
allow-missing-optionals = []
init-if-needed = []
idl = []
idl-build = []
idl-parse = []
idl-types = []
hash = []
default = []
anchor-debug = []

View File

@ -62,12 +62,12 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
if f.is_optional {
quote! {
#docs
pub #name: Option<anchor_lang::solana_program::pubkey::Pubkey>
pub #name: Option<Pubkey>
}
} else {
quote! {
#docs
pub #name: anchor_lang::solana_program::pubkey::Pubkey
pub #name: Pubkey
}
}
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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::*;
#[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
}
#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
}

915
lang/syn/src/idl/build.rs Normal file
View File

@ -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<Ident>,
) -> Result<(TokenStream, Vec<syn::TypePath>), ()> {
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, second::path> -> 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<syn::Ident>,
) -> Result<(TokenStream, Vec<syn::TypePath>), ()> {
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<syn::TypePath>), ()> {
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<syn::TypePath>), ()> {
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::<Vec<_>>();
let (fields, defined): (Vec<TokenStream>, Vec<Vec<syn::TypePath>>) = 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::<Result<Vec<_>, _>>()?
.into_iter()
.unzip::<_, _, Vec<_>, Vec<_>>(),
_ => return Err(()),
};
let defined = defined
.into_iter()
.flatten()
.collect::<Vec<syn::TypePath>>();
let generics = if !type_params.is_empty() {
let g: Vec<String> = 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<syn::TypePath>), ()> {
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::<Vec<_>>();
let (variants, defined): (Vec<TokenStream>, Vec<Vec<syn::TypePath>>) = enum_item.variants.iter().map(|variant: &syn::Variant| {
let name = variant.ident.to_string();
let (fields, defined): (TokenStream, Vec<syn::TypePath>) = 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::<Result<Vec<_>, _>>()?
.into_iter()
.unzip::<TokenStream, Vec<syn::TypePath>, Vec<TokenStream>, Vec<Vec<syn::TypePath>>>();
let defined = defined
.into_iter()
.flatten()
.collect::<Vec<_>>();
(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::<Result<Vec<_>, _>>()?
.into_iter()
.unzip::<TokenStream, Vec<syn::TypePath>, Vec<TokenStream>, Vec<Vec<syn::TypePath>>>();
let defined = defined
.into_iter()
.flatten()
.collect::<Vec<_>>();
(quote!{ Some(#idl::EnumFields::Named(vec![#(#fields),*]))}, defined)
}
};
Ok((quote!{ #idl::IdlEnumVariant{ name: #name.into(), fields: #fields }}, defined))
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.unzip::<TokenStream, Vec<syn::TypePath>, Vec<TokenStream>, Vec<Vec<syn::TypePath>>>();
let defined = defined
.into_iter()
.flatten()
.collect::<Vec<syn::TypePath>>();
let generics = if !type_params.is_empty() {
let g: Vec<String> = 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<String, #idl::IdlTypeDefinition>
) {
#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<TokenStream>, Vec<syn::TypePath>), ()> {
let (fields, defined) = fields
.named
.iter()
.map(idl_event_field_ts_from_syn_field)
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.unzip::<_, _, Vec<_>, Vec<_>>();
let defined = defined
.into_iter()
.flatten()
.collect::<Vec<syn::TypePath>>();
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<String, #idl::IdlTypeDefinition>,
) -> 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<TokenStream>, Vec<Option<&syn::TypePath>>) = 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::<TokenStream, Option<&syn::TypePath>, Vec<TokenStream>, Vec<Option<&syn::TypePath>>>();
let acc_types = acc_types
.into_iter()
.flatten()
.collect::<Vec<&syn::TypePath>>();
quote! {
impl #impl_generics #ident #ty_generics #where_clause {
pub fn __anchor_private_gen_idl_accounts(
accounts: &mut std::collections::HashMap<String, #idl::IdlTypeDefinition>,
defined_types: &mut std::collections::HashMap<String, #idl::IdlTypeDefinition>,
) -> 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::<Result<Vec<_>, ()>>()?
.into_iter()
.unzip::<TokenStream, Vec<syn::TypePath>, Vec<TokenStream>, Vec<Vec<syn::TypePath>>>();
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::<TokenStream, Vec<Vec<syn::TypePath>>, Vec<TokenStream>, Vec<Vec<Vec<syn::TypePath>>>>();
let defined = defined
.into_iter()
.flatten()
.flatten()
.collect::<Vec<syn::TypePath>>();
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<String, #idl::IdlTypeDefinition> =
std::collections::HashMap::new();
let mut defined_types: std::collections::HashMap<String, #idl::IdlTypeDefinition> =
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<String, #idl::IdlTypeDefinition> = 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::<Vec<#idl::IdlTypeDefinition>>()
});
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::<Vec<TokenStream>>();
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 ----");
}
}
}

View File

@ -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<Vec<String>>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub constants: Vec<IdlConst>,
pub instructions: Vec<IdlInstruction>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub accounts: Vec<IdlTypeDefinition>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub types: Vec<IdlTypeDefinition>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub events: Option<Vec<IdlEvent>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub errors: Option<Vec<IdlErrorCode>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub metadata: Option<JsonValue>,
}
#[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<IdlInstruction>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct IdlInstruction {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub docs: Option<Vec<String>>,
pub accounts: Vec<IdlAccountItem>,
pub args: Vec<IdlField>,
#[serde(skip_serializing_if = "Option::is_none")]
pub returns: Option<IdlType>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IdlAccounts {
pub name: String,
pub accounts: Vec<IdlAccountItem>,
}
#[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<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub docs: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub pda: Option<IdlPda>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub relations: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IdlPda {
pub seeds: Vec<IdlSeed>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub program_id: Option<IdlSeed>,
}
#[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<T>` type is used.
#[serde(skip_serializing_if = "Option::is_none")]
pub account: Option<String>,
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<Vec<String>>,
#[serde(rename = "type")]
pub ty: IdlType,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct IdlEvent {
pub name: String,
pub fields: Vec<IdlEventField>,
}
#[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<Vec<String>>,
#[serde(rename = "type")]
pub ty: IdlTypeDefinitionTy,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase", tag = "kind")]
pub enum IdlTypeDefinitionTy {
Struct { fields: Vec<IdlField> },
Enum { variants: Vec<IdlEnumVariant> },
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct IdlEnumVariant {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub fields: Option<EnumFields>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum EnumFields {
Named(Vec<IdlField>),
Tuple(Vec<IdlType>),
}
#[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<IdlType>),
Vec(Box<IdlType>),
Array(Box<IdlType>, usize),
}
impl std::str::FromStr for IdlType {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
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::<usize>().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<u8>" => 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<String>,
}
#[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<bool>").unwrap(),
IdlType::Option(Box::new(IdlType::Bool))
)
}
#[test]
fn vector() {
assert_eq!(
IdlType::from_str("Vec<bool>").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;

View File

@ -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<Vec<IdlTypeDefinit
Some(fields.map(|fields| IdlTypeDefinition {
name,
path: None,
generics: None,
docs: doc,
ty: IdlTypeDefinitionTy::Struct { fields },
}))
@ -404,6 +408,8 @@ fn parse_ty_defs(ctx: &CrateContext, no_docs: bool) -> Result<Vec<IdlTypeDefinit
.collect::<Vec<IdlEnumVariant>>();
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),
}),
})

View File

@ -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<Self, Self::Err> {
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::<usize>().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<u8>" => 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<bool>").unwrap(),
IdlType::Option(Box::new(IdlType::Bool))
)
}
#[test]
fn vector() {
assert_eq!(
IdlType::from_str("Vec<bool>").unwrap(),
IdlType::Vec(Box::new(IdlType::Bool))
)
}
}

View File

@ -1,4 +1,4 @@
use crate::idl::*;
use crate::idl::types::*;
use crate::parser;
use crate::parser::context::CrateContext;
use crate::ConstraintSeedsGroup;

229
lang/syn/src/idl/types.rs Normal file
View File

@ -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<Vec<String>>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub constants: Vec<IdlConst>,
pub instructions: Vec<IdlInstruction>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub accounts: Vec<IdlTypeDefinition>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub types: Vec<IdlTypeDefinition>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub events: Option<Vec<IdlEvent>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub errors: Option<Vec<IdlErrorCode>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub metadata: Option<JsonValue>,
}
#[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<IdlInstruction>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct IdlInstruction {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub docs: Option<Vec<String>>,
pub accounts: Vec<IdlAccountItem>,
pub args: Vec<IdlField>,
#[serde(skip_serializing_if = "Option::is_none")]
pub returns: Option<IdlType>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IdlAccounts {
pub name: String,
pub accounts: Vec<IdlAccountItem>,
}
#[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<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub docs: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub pda: Option<IdlPda>,
#[serde(skip_serializing_if = "Vec::is_empty", default)]
pub relations: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct IdlPda {
pub seeds: Vec<IdlSeed>,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub program_id: Option<IdlSeed>,
}
#[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<T>` type is used.
#[serde(skip_serializing_if = "Option::is_none")]
pub account: Option<String>,
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<Vec<String>>,
#[serde(rename = "type")]
pub ty: IdlType,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct IdlEvent {
pub name: String,
pub fields: Vec<IdlEventField>,
}
#[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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub generics: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub docs: Option<Vec<String>>,
#[serde(rename = "type")]
pub ty: IdlTypeDefinitionTy,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "lowercase", tag = "kind")]
pub enum IdlTypeDefinitionTy {
Struct { fields: Vec<IdlField> },
Enum { variants: Vec<IdlEnumVariant> },
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct IdlEnumVariant {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none", default)]
pub fields: Option<EnumFields>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum EnumFields {
Named(Vec<IdlField>),
Tuple(Vec<IdlType>),
}
#[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<IdlType>),
Vec(Box<IdlType>),
Array(Box<IdlType>, usize),
GenericLenArray(Box<IdlType>, String),
Generic(String),
DefinedWithTypeArgs {
path: String,
args: Vec<IdlDefinedTypeArg>,
},
}
#[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<String>,
}

View File

@ -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;

View File

@ -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"

View File

@ -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

11
tests/idl-build/gen_testdata.sh Executable file
View File

@ -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

View File

@ -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"
}
}

View File

@ -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"] }

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -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<GenericCtx>,
generic_field: GenericType::<u32, u64, 10>
) -> 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<u32, u64, 10>
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct GenericType<T, U, const N: usize>
where
T: AnchorSerialize + AnchorDeserialize,
U: AnchorSerialize + AnchorDeserialize,
{
pub gen1: T,
pub gen2: U,
pub gen3: GenericNested<u32, U>,
pub gen4: GenericNested<T, some_external_program::Baz>,
pub gen5: GenericNested<T, U>,
pub gen6: GenericNested<u32, u64>,
pub gen7: GenericNested<T, GenericNested<T, U>>,
pub arr: [u8; N],
pub warr: WrappedU8Array<N>,
pub warrval: WrappedU8Array<10>,
pub enm1: GenericEnum<T, U, N>,
pub enm2: GenericEnum<GenericNested<T, u64>, u32, 30>,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Default)]
pub struct GenericNested<V, Z>
where
V: AnchorSerialize + AnchorDeserialize,
Z: AnchorSerialize + AnchorDeserialize,
{
pub gen1: V,
pub gen2: Z,
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct WrappedU8Array<const N: usize>(u8);
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub enum GenericEnum<T, U, const N: usize>
where
T: AnchorSerialize + AnchorDeserialize,
U: AnchorSerialize + AnchorDeserialize,
{
Unnamed(T, U),
Named {
gen1: T,
gen2: U,
},
Struct(GenericNested<T, U>),
Arr([T; N]),
}

View File

@ -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"] }

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -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<Initialize>) -> Result<()> {
ctx.accounts.state.set_inner(State::default());
Ok(())
}
/// Initializes an account with specified values
pub fn initialize_with_values(
ctx: Context<Initialize>,
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<u8>,
string_field: String,
pubkey_field: Pubkey,
vec_field: Vec<u64>,
vec_struct_field: Vec<FooStruct>,
option_field: Option<bool>,
option_struct_field: Option<FooStruct>,
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<Initialize2>,
vec_of_option: Vec<Option<u64>>,
box_field: Box<bool>,
) -> Result<SomeRetStruct> {
ctx.accounts.state.set_inner(State2 { vec_of_option, box_field });
Ok(SomeRetStruct { some_field: 3})
}
pub fn cause_error(_ctx: Context<CauseError>) -> 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<BarStruct>),
VecStruct(Vec<BarStruct>),
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<BarStruct>,
option_nested: Option<BarStruct>,
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<u8>,
string_field: String,
pubkey_field: Pubkey,
vec_field: Vec<u64>,
vec_struct_field: Vec<FooStruct>,
option_field: Option<bool>,
option_struct_field: Option<FooStruct>,
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<Option<u64>>,
box_field: Box<bool>,
}
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,
}

View File

@ -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" }

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -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<InitBase>) -> 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<TestRelation>) -> 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
}

View File

@ -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" }

View File

@ -0,0 +1,2 @@
[target.bpfel-unknown-unknown.dependencies.std]
features = []

View File

@ -0,0 +1,20 @@
use anchor_lang::prelude::*;
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");
#[program]
pub mod idl_2 {
use super::*;
pub fn initialize(_ctx: Context<Initialize>, _baz: Baz) -> Result<()> {
Ok(())
}
}
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
pub struct Baz {
some_field: u8,
}
#[derive(Accounts)]
pub struct Initialize {}

53
tests/idl-build/test.sh Executable file
View File

@ -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))

View File

@ -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"
}
]
}
}
]
}

View File

@ -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"
}
]
}

View File

@ -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"
}
]
}

View File

@ -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"
}
]
}
}
]
}

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"types": ["mocha", "chai"],
"typeRoots": ["./node_modules/@types"],
"lib": ["es2015"],
"module": "commonjs",
"target": "es6",
"esModuleInterop": true
}
}

View File

@ -18,6 +18,7 @@
"escrow",
"events",
"floats",
"idl-build",
"ido-pool",
"interface",
"lockup",