Fix IDL (#2824)
* Rewrite IDL type spec * Rewrite IDL generation * Partially rewrite the TS package with the new IDL, improved account resolution and types
This commit is contained in:
parent
23028334f3
commit
d9a9f19394
|
@ -3,9 +3,18 @@ description: "Setup"
|
|||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- run: sudo apt-get update && sudo apt-get install -y pkg-config build-essential libudev-dev
|
||||
shell: bash
|
||||
- run: echo "ANCHOR_VERSION=$(cat ./VERSION)" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
- run: git submodule update --init --recursive --depth 1
|
||||
shell: bash
|
||||
- run: sudo apt-get update && sudo apt-get install -y pkg-config build-essential libudev-dev
|
||||
shell: bash
|
||||
- run: echo "ANCHOR_VERSION=$(cat ./VERSION)" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
- run: git submodule update --init --recursive --depth 1
|
||||
shell: bash
|
||||
# `nightly` toolchain is currently required for building the IDL.
|
||||
#
|
||||
# Pinning the toolchain to an older date in order to fix
|
||||
# `error[E0635]: unknown feature stdsimd` error from `ahash`.
|
||||
# See: https://github.com/tkaitchuck/aHash/issues/200
|
||||
#
|
||||
# TODO: Unpin `nightly` release after upgrading Solana to `1.18`.
|
||||
- run: rustup toolchain install nightly-2024-01-30
|
||||
shell: bash
|
||||
|
|
|
@ -452,8 +452,9 @@ jobs:
|
|||
path: tests/bench
|
||||
- cmd: cd tests/idl && ./test.sh
|
||||
path: tests/idl
|
||||
- cmd: cd tests/solang && anchor test
|
||||
path: tests/solang
|
||||
# TODO: Enable when `solang` becomes compatible with the new IDL spec
|
||||
# - cmd: cd tests/solang && anchor test
|
||||
# path: tests/solang
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/setup/
|
||||
|
|
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -27,6 +27,13 @@ The minor version will be incremented upon a breaking change and the patch versi
|
|||
- cli: Check `@coral-xyz/anchor` package and CLI version compatibility ([#2813](https://github.com/coral-xyz/anchor/pull/2813)).
|
||||
- cli: Accept package name as program name ([#2816](https://github.com/coral-xyz/anchor/pull/2816)).
|
||||
- cli: Add ability to build and test only a specified program ([#2823](https://github.com/coral-xyz/anchor/pull/2823)).
|
||||
- idl: Add new IDL spec ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
- idl: Add support for `repr`s ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
- idl: Add support for expression evaluation ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
- idl: Add support for using external types when generating the IDL ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
- idl, ts: Add unit and tuple struct support ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
- idl, ts: Add generics support ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
- ts: Add `accountsPartial` method to keep the old `accounts` method behavior ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
|
||||
### Fixes
|
||||
|
||||
|
@ -46,6 +53,9 @@ The minor version will be incremented upon a breaking change and the patch versi
|
|||
- cli: Fix `migrate` command not working without global `ts-node` installation ([#2767](https://github.com/coral-xyz/anchor/pull/2767)).
|
||||
- client, lang, spl, syn: Enable all features for docs.rs build ([#2774](https://github.com/coral-xyz/anchor/pull/2774)).
|
||||
- ts: Fix construction of field layouts for type aliased instruction arguments ([#2821](https://github.com/coral-xyz/anchor/pull/2821))
|
||||
- idl: Fix IDL ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
- idl, ts: Make casing consistent ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
- ts: Fix not being able to use numbers in instruction, account, or event names in some cases due to case conversion ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
|
||||
### Breaking
|
||||
|
||||
|
@ -58,6 +68,14 @@ The minor version will be incremented upon a breaking change and the patch versi
|
|||
- ts: Remove `associated`, `account.associated` and `account.associatedAddress` methods ([#2749](https://github.com/coral-xyz/anchor/pull/2749)).
|
||||
- cli: `idl upgrade` command closes the IDL buffer account ([#2760](https://github.com/coral-xyz/anchor/pull/2760)).
|
||||
- cli: Remove `--jest` option from the `init` command ([#2805](https://github.com/coral-xyz/anchor/pull/2805)).
|
||||
- cli: Require `idl-build` feature in program `Cargo.toml` ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
- cli: Rename `seeds` feature to `resolution` and make it enabled by default ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
- cli: Remove `idl parse` command ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
- idl: Change IDL spec ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
- syn: Remove `idl-parse` and `seeds` features ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
- ts: Change `accounts` method to no longer accept resolvable accounts ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
- ts: `Program` instances use camelCase for everything ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
- ts: Remove discriminator functions ([#2824](https://github.com/coral-xyz/anchor/pull/2824)).
|
||||
|
||||
## [0.29.0] - 2023-10-16
|
||||
|
||||
|
|
|
@ -180,8 +180,8 @@ name = "anchor-cli"
|
|||
version = "0.29.0"
|
||||
dependencies = [
|
||||
"anchor-client",
|
||||
"anchor-idl",
|
||||
"anchor-lang",
|
||||
"anchor-syn",
|
||||
"anyhow",
|
||||
"base64 0.21.4",
|
||||
"bincode",
|
||||
|
@ -257,6 +257,17 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-idl"
|
||||
version = "0.29.0"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"anyhow",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anchor-lang"
|
||||
version = "0.29.0"
|
||||
|
@ -304,6 +315,7 @@ version = "0.29.0"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58 0.5.0",
|
||||
"cargo_toml",
|
||||
"heck 0.3.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
@ -9,6 +9,7 @@ members = [
|
|||
"avm",
|
||||
"cli",
|
||||
"client",
|
||||
"idl",
|
||||
"lang",
|
||||
"lang/attribute/*",
|
||||
"lang/derive/*",
|
||||
|
|
|
@ -17,8 +17,8 @@ dev = []
|
|||
|
||||
[dependencies]
|
||||
anchor-client = { path = "../client", version = "0.29.0" }
|
||||
anchor-idl = { path = "../idl", features = ["build"], version = "0.29.0" }
|
||||
anchor-lang = { path = "../lang", version = "0.29.0" }
|
||||
anchor-syn = { path = "../lang/syn", features = ["event-cpi", "idl-parse", "init-if-needed"], version = "0.29.0" }
|
||||
anyhow = "1.0.32"
|
||||
base64 = "0.21"
|
||||
bincode = "1.3.3"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::is_hidden;
|
||||
use anchor_client::Cluster;
|
||||
use anchor_syn::idl::types::Idl;
|
||||
use anchor_idl::types::Idl;
|
||||
use anyhow::{anyhow, bail, Context, Error, Result};
|
||||
use clap::{Parser, ValueEnum};
|
||||
use dirs::home_dir;
|
||||
|
@ -375,14 +375,33 @@ pub struct ToolchainConfig {
|
|||
pub solana_version: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct FeaturesConfig {
|
||||
#[serde(default)]
|
||||
pub seeds: bool,
|
||||
/// Enable account resolution.
|
||||
///
|
||||
/// Not able to specify default bool value: https://github.com/serde-rs/serde/issues/368
|
||||
#[serde(default = "FeaturesConfig::get_default_resolution")]
|
||||
pub resolution: bool,
|
||||
/// Disable safety comment checks
|
||||
#[serde(default, rename = "skip-lint")]
|
||||
pub skip_lint: bool,
|
||||
}
|
||||
|
||||
impl FeaturesConfig {
|
||||
fn get_default_resolution() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FeaturesConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
resolution: Self::get_default_resolution(),
|
||||
skip_lint: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct RegistryConfig {
|
||||
pub url: String,
|
||||
|
@ -619,8 +638,8 @@ impl FromStr for Config {
|
|||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let cfg: _Config = toml::from_str(s)
|
||||
.map_err(|e| anyhow::format_err!("Unable to deserialize config: {}", e.to_string()))?;
|
||||
let cfg: _Config =
|
||||
toml::from_str(s).map_err(|e| anyhow!("Unable to deserialize config: {e}"))?;
|
||||
Ok(Config {
|
||||
toolchain: cfg.toolchain.unwrap_or_default(),
|
||||
features: cfg.features.unwrap_or_default(),
|
||||
|
|
486
cli/src/lib.rs
486
cli/src/lib.rs
|
@ -1,15 +1,14 @@
|
|||
#![cfg_attr(nightly, feature(proc_macro_span))]
|
||||
|
||||
use crate::config::{
|
||||
AnchorPackage, BootstrapMode, BuildConfig, Config, ConfigOverride, Manifest, ProgramArch,
|
||||
ProgramDeployment, ProgramWorkspace, ScriptsConfig, TestValidator, WithPath,
|
||||
DEFAULT_LEDGER_PATH, SHUTDOWN_WAIT, STARTUP_WAIT,
|
||||
};
|
||||
use anchor_client::Cluster;
|
||||
use anchor_idl::types::{Idl, IdlArrayLen, IdlDefinedFields, IdlType, IdlTypeDefTy};
|
||||
use anchor_lang::idl::{IdlAccount, IdlInstruction, ERASED_AUTHORITY};
|
||||
use anchor_lang::{AccountDeserialize, AnchorDeserialize, AnchorSerialize};
|
||||
use anchor_syn::idl::types::{
|
||||
EnumFields, Idl, IdlConst, IdlErrorCode, IdlEvent, IdlType, IdlTypeDefinition,
|
||||
IdlTypeDefinitionTy,
|
||||
};
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use checks::{check_anchor_version, check_overflow};
|
||||
use clap::Parser;
|
||||
|
@ -24,7 +23,7 @@ use reqwest::blocking::multipart::{Form, Part};
|
|||
use reqwest::blocking::Client;
|
||||
use rust_template::{ProgramTemplate, TestTemplate};
|
||||
use semver::{Version, VersionReq};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::Deserialize;
|
||||
use serde_json::{json, Map, Value as JsonValue};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_program::instruction::{AccountMeta, Instruction};
|
||||
|
@ -440,23 +439,11 @@ pub enum IdlCommand {
|
|||
/// The program to view.
|
||||
program_id: Pubkey,
|
||||
},
|
||||
/// Parses an IDL from source.
|
||||
Parse {
|
||||
/// Path to the program's interface definition.
|
||||
#[clap(short, long)]
|
||||
file: String,
|
||||
/// Output file for the IDL (stdout if not specified).
|
||||
#[clap(short, long)]
|
||||
out: Option<String>,
|
||||
/// Output file for the TypeScript IDL.
|
||||
#[clap(short = 't', long)]
|
||||
out_ts: Option<String>,
|
||||
/// Suppress doc strings in output
|
||||
#[clap(long)]
|
||||
no_docs: bool,
|
||||
},
|
||||
/// Generates the IDL for the program using the compilation method.
|
||||
Build {
|
||||
// Program name to build the IDL of(current dir's program if not specified)
|
||||
#[clap(short, long)]
|
||||
program_name: Option<String>,
|
||||
/// Output file for the IDL (stdout if not specified)
|
||||
#[clap(short, long)]
|
||||
out: Option<String>,
|
||||
|
@ -466,6 +453,9 @@ pub enum IdlCommand {
|
|||
/// Suppress doc strings in output
|
||||
#[clap(long)]
|
||||
no_docs: bool,
|
||||
/// Do not check for safety comments
|
||||
#[clap(long)]
|
||||
skip_lint: bool,
|
||||
},
|
||||
/// Fetches an IDL for the given address from a cluster.
|
||||
/// The address can be a program, IDL account, or IDL buffer.
|
||||
|
@ -1464,12 +1454,12 @@ fn build_cwd_verifiable(
|
|||
let idl = generate_idl(cfg, skip_lint, no_docs)?;
|
||||
// Write out the JSON file.
|
||||
println!("Writing the IDL file");
|
||||
let out_file = workspace_dir.join(format!("target/idl/{}.json", idl.name));
|
||||
let out_file = workspace_dir.join(format!("target/idl/{}.json", idl.metadata.name));
|
||||
write_idl(&idl, OutFile::File(out_file))?;
|
||||
|
||||
// Write out the TypeScript type.
|
||||
println!("Writing the .ts file");
|
||||
let ts_file = workspace_dir.join(format!("target/types/{}.ts", idl.name));
|
||||
let ts_file = workspace_dir.join(format!("target/types/{}.ts", idl.metadata.name));
|
||||
fs::write(&ts_file, rust_template::idl_ts(&idl)?)?;
|
||||
|
||||
// Copy out the TypeScript type.
|
||||
|
@ -1478,7 +1468,7 @@ fn build_cwd_verifiable(
|
|||
ts_file,
|
||||
workspace_dir
|
||||
.join(&cfg.workspace.types)
|
||||
.join(idl.name)
|
||||
.join(idl.metadata.name)
|
||||
.with_extension("ts"),
|
||||
)?;
|
||||
}
|
||||
|
@ -1758,13 +1748,17 @@ fn _build_rust_cwd(
|
|||
let idl = generate_idl(cfg, skip_lint, no_docs)?;
|
||||
// JSON out path.
|
||||
let out = match idl_out {
|
||||
None => PathBuf::from(".").join(&idl.name).with_extension("json"),
|
||||
Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("json")),
|
||||
None => PathBuf::from(".")
|
||||
.join(&idl.metadata.name)
|
||||
.with_extension("json"),
|
||||
Some(o) => PathBuf::from(&o.join(&idl.metadata.name).with_extension("json")),
|
||||
};
|
||||
// TS out path.
|
||||
let ts_out = match idl_ts_out {
|
||||
None => PathBuf::from(".").join(&idl.name).with_extension("ts"),
|
||||
Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("ts")),
|
||||
None => PathBuf::from(".")
|
||||
.join(&idl.metadata.name)
|
||||
.with_extension("ts"),
|
||||
Some(o) => PathBuf::from(&o.join(&idl.metadata.name).with_extension("ts")),
|
||||
};
|
||||
|
||||
// Write out the JSON file.
|
||||
|
@ -1778,7 +1772,7 @@ fn _build_rust_cwd(
|
|||
&ts_out,
|
||||
cfg_parent
|
||||
.join(&cfg.workspace.types)
|
||||
.join(&idl.name)
|
||||
.join(&idl.metadata.name)
|
||||
.with_extension("ts"),
|
||||
)?;
|
||||
}
|
||||
|
@ -1839,8 +1833,10 @@ fn _build_solidity_cwd(
|
|||
|
||||
// TS out path.
|
||||
let ts_out = match idl_ts_out {
|
||||
None => PathBuf::from(".").join(&idl.name).with_extension("ts"),
|
||||
Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("ts")),
|
||||
None => PathBuf::from(".")
|
||||
.join(&idl.metadata.name)
|
||||
.with_extension("ts"),
|
||||
Some(o) => PathBuf::from(&o.join(&idl.metadata.name).with_extension("ts")),
|
||||
};
|
||||
|
||||
// Write out the TypeScript type.
|
||||
|
@ -1852,7 +1848,7 @@ fn _build_solidity_cwd(
|
|||
&ts_out,
|
||||
cfg_parent
|
||||
.join(&cfg.workspace.types)
|
||||
.join(&idl.name)
|
||||
.join(&idl.metadata.name)
|
||||
.with_extension("ts"),
|
||||
)?;
|
||||
}
|
||||
|
@ -2101,17 +2097,13 @@ fn idl(cfg_override: &ConfigOverride, subcmd: IdlCommand) -> Result<()> {
|
|||
} => idl_set_authority(cfg_override, program_id, address, new_authority, print_only),
|
||||
IdlCommand::EraseAuthority { program_id } => idl_erase_authority(cfg_override, program_id),
|
||||
IdlCommand::Authority { program_id } => idl_authority(cfg_override, program_id),
|
||||
IdlCommand::Parse {
|
||||
file,
|
||||
out,
|
||||
out_ts,
|
||||
no_docs,
|
||||
} => idl_parse(cfg_override, file, out, out_ts, no_docs),
|
||||
IdlCommand::Build {
|
||||
program_name,
|
||||
out,
|
||||
out_ts,
|
||||
no_docs,
|
||||
} => idl_build(out, out_ts, no_docs),
|
||||
skip_lint,
|
||||
} => idl_build(cfg_override, program_name, out, out_ts, no_docs, skip_lint),
|
||||
IdlCommand::Fetch { address, out } => idl_fetch(cfg_override, address, out),
|
||||
}
|
||||
}
|
||||
|
@ -2419,10 +2411,6 @@ fn idl_close_account(
|
|||
// and sending multiple transactions in the event the IDL doesn't fit into
|
||||
// a single transaction.
|
||||
fn idl_write(cfg: &Config, program_id: &Pubkey, idl: &Idl, idl_address: Pubkey) -> Result<()> {
|
||||
// Remove the metadata before deploy.
|
||||
let mut idl = idl.clone();
|
||||
idl.metadata = None;
|
||||
|
||||
// Misc.
|
||||
let keypair = solana_sdk::signature::read_keypair_file(&cfg.provider.wallet.to_string())
|
||||
.map_err(|_| anyhow!("Unable to read keypair file"))?;
|
||||
|
@ -2431,7 +2419,7 @@ fn idl_write(cfg: &Config, program_id: &Pubkey, idl: &Idl, idl_address: Pubkey)
|
|||
|
||||
// Serialize and compress the idl.
|
||||
let idl_data = {
|
||||
let json_bytes = serde_json::to_vec(&idl)?;
|
||||
let json_bytes = serde_json::to_vec(idl)?;
|
||||
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
|
||||
e.write_all(&json_bytes)?;
|
||||
e.finish()?
|
||||
|
@ -2473,287 +2461,78 @@ fn idl_write(cfg: &Config, program_id: &Pubkey, idl: &Idl, idl_address: Pubkey)
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn idl_parse(
|
||||
fn idl_build(
|
||||
cfg_override: &ConfigOverride,
|
||||
file: String,
|
||||
program_name: Option<String>,
|
||||
out: Option<String>,
|
||||
out_ts: Option<String>,
|
||||
no_docs: bool,
|
||||
skip_lint: bool,
|
||||
) -> Result<()> {
|
||||
let cfg = Config::discover(cfg_override)?.expect("Not in workspace.");
|
||||
let file = shellexpand::tilde(&file);
|
||||
let manifest_path = std::env::current_dir()?.join(PathBuf::from(&*file).parent().unwrap());
|
||||
let manifest = Manifest::discover_from_path(manifest_path)?
|
||||
.ok_or_else(|| anyhow!("Cargo.toml not found"))?;
|
||||
let idl = generate_idl_parse(
|
||||
&*file,
|
||||
manifest.version(),
|
||||
cfg.features.seeds,
|
||||
let cfg = Config::discover(cfg_override)?.expect("Not in workspace");
|
||||
let program_path = match program_name {
|
||||
Some(name) => cfg.get_program(&name)?.path,
|
||||
None => {
|
||||
let current_dir = std::env::current_dir()?;
|
||||
cfg.read_all_programs()?
|
||||
.into_iter()
|
||||
.find(|program| program.path == current_dir)
|
||||
.ok_or_else(|| anyhow!("Not in a program directory"))?
|
||||
.path
|
||||
}
|
||||
};
|
||||
let idl = anchor_idl::build::build_idl(
|
||||
program_path,
|
||||
cfg.features.resolution,
|
||||
cfg.features.skip_lint || skip_lint,
|
||||
no_docs,
|
||||
!cfg.features.skip_lint,
|
||||
)?;
|
||||
|
||||
let out = match out {
|
||||
Some(path) => OutFile::File(PathBuf::from(path)),
|
||||
None => OutFile::Stdout,
|
||||
Some(out) => OutFile::File(PathBuf::from(out)),
|
||||
};
|
||||
write_idl(&idl, out)?;
|
||||
|
||||
// Write out the TypeScript IDL.
|
||||
if let Some(out) = out_ts {
|
||||
fs::write(out, rust_template::idl_ts(&idl)?)?;
|
||||
if let Some(path) = out_ts {
|
||||
fs::write(path, rust_template::idl_ts(&idl)?)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn idl_build(out: Option<String>, out_ts: Option<String>, no_docs: bool) -> Result<()> {
|
||||
let idls = generate_idl_build(no_docs)?;
|
||||
if idls.len() == 1 {
|
||||
let idl = &idls[0];
|
||||
let out = match out {
|
||||
None => OutFile::Stdout,
|
||||
Some(path) => OutFile::File(PathBuf::from(path)),
|
||||
};
|
||||
write_idl(idl, out)?;
|
||||
|
||||
if let Some(path) = out_ts {
|
||||
fs::write(path, rust_template::idl_ts(idl)?)?;
|
||||
}
|
||||
} else {
|
||||
println!("{}", serde_json::to_string_pretty(&idls)?);
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate IDL with method decided by whether manifest file has `idl-build` feature or not.
|
||||
fn generate_idl(cfg: &WithPath<Config>, skip_lint: bool, no_docs: bool) -> Result<Idl> {
|
||||
let manifest = Manifest::discover()?.ok_or_else(|| anyhow!("Cargo.toml not found"))?;
|
||||
|
||||
// Check whether the manifest has `idl-build` feature
|
||||
let manifest = Manifest::discover()?.ok_or_else(|| anyhow!("Cargo.toml not found"))?;
|
||||
let is_idl_build = manifest
|
||||
.features
|
||||
.iter()
|
||||
.any(|(feature, _)| feature == "idl-build");
|
||||
if is_idl_build {
|
||||
generate_idl_build(no_docs)?
|
||||
.into_iter()
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("Could not build IDL"))
|
||||
} else {
|
||||
generate_idl_parse(
|
||||
"src/lib.rs",
|
||||
manifest.version(),
|
||||
cfg.features.seeds,
|
||||
no_docs,
|
||||
!(cfg.features.skip_lint || skip_lint),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate IDL with the parsing method(default).
|
||||
fn generate_idl_parse(
|
||||
path: impl AsRef<Path>,
|
||||
version: String,
|
||||
seeds_feature: bool,
|
||||
no_docs: bool,
|
||||
safety_checks: bool,
|
||||
) -> Result<Idl> {
|
||||
anchor_syn::idl::parse::file::parse(path, version, seeds_feature, no_docs, safety_checks)
|
||||
}
|
||||
|
||||
/// Generate IDL with the build method.
|
||||
fn generate_idl_build(no_docs: bool) -> Result<Vec<Idl>> {
|
||||
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_BUILD_NO_DOCS", no_docs)
|
||||
.env("ANCHOR_IDL_BUILD_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 IdlBuildEventPrint {
|
||||
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 output = String::from_utf8_lossy(&exit.stdout);
|
||||
for line in output.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.name.clone(), 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: IdlBuildEventPrint = serde_json::from_str(&lines.join("\n"))?;
|
||||
events.push(event.event);
|
||||
defined_types.extend(
|
||||
event
|
||||
.defined_types
|
||||
.into_iter()
|
||||
.map(|ty| (ty.name.clone(), 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert path to name if there are no conflicts
|
||||
let path_regex = Regex::new(r#""((\w+::)+)(\w+)""#).unwrap();
|
||||
let idls = idls
|
||||
.into_iter()
|
||||
.filter_map(|idl| {
|
||||
let mut modified_idl = serde_json::to_string(&idl).unwrap();
|
||||
|
||||
// TODO: Remove. False positive https://github.com/rust-lang/rust-clippy/issues/10577
|
||||
#[allow(clippy::redundant_clone)]
|
||||
for captures in path_regex.captures_iter(&modified_idl.clone()) {
|
||||
let path = captures.get(0).unwrap().as_str();
|
||||
let name = captures.get(3).unwrap().as_str();
|
||||
|
||||
// Replace path with name
|
||||
let replaced_idl = modified_idl.replace(path, &format!(r#""{name}""#));
|
||||
|
||||
// Check whether there is a conflict
|
||||
let has_conflict = replaced_idl.contains(&format!(r#"::{name}""#));
|
||||
if !has_conflict {
|
||||
modified_idl = replaced_idl;
|
||||
}
|
||||
}
|
||||
|
||||
serde_json::from_str::<Idl>(&modified_idl).ok()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Verify IDLs are valid
|
||||
for idl in &idls {
|
||||
let full_path_account = idl
|
||||
.accounts
|
||||
if !is_idl_build {
|
||||
let path = manifest.path().display();
|
||||
let anchor_spl_idl_build = manifest
|
||||
.dependencies
|
||||
.iter()
|
||||
.find(|account| account.name.contains("::"));
|
||||
.any(|dep| dep.0 == "anchor-spl")
|
||||
.then_some(r#", "anchor-spl/idl-build""#)
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(account) = full_path_account {
|
||||
return Err(anyhow!(
|
||||
"Conflicting accounts names are not allowed.\nProgram: {}\nAccount: {}",
|
||||
idl.name,
|
||||
account.name
|
||||
));
|
||||
}
|
||||
return Err(anyhow!(
|
||||
r#"`idl-build` feature is missing. To solve, add
|
||||
|
||||
[features]
|
||||
idl-build = ["anchor-lang/idl-build"{anchor_spl_idl_build}]
|
||||
|
||||
in `{path}`."#
|
||||
));
|
||||
}
|
||||
|
||||
Ok(idls)
|
||||
anchor_idl::build::build_idl(
|
||||
std::env::current_dir()?,
|
||||
cfg.features.resolution,
|
||||
cfg.features.skip_lint || skip_lint,
|
||||
no_docs,
|
||||
)
|
||||
}
|
||||
|
||||
fn idl_fetch(cfg_override: &ConfigOverride, address: Pubkey, out: Option<String>) -> Result<()> {
|
||||
|
@ -2841,7 +2620,7 @@ fn account(
|
|||
let bytes = fs::read(idl_path).expect("Unable to read IDL.");
|
||||
let idl: Idl = serde_json::from_reader(&*bytes).expect("Invalid IDL format.");
|
||||
|
||||
if idl.name != program_name {
|
||||
if idl.metadata.name != program_name {
|
||||
panic!("IDL does not match program {program_name}.");
|
||||
}
|
||||
|
||||
|
@ -2882,25 +2661,40 @@ fn deserialize_idl_defined_type_to_json(
|
|||
data: &mut &[u8],
|
||||
) -> Result<JsonValue, anyhow::Error> {
|
||||
let defined_type = &idl
|
||||
.types
|
||||
.accounts
|
||||
.iter()
|
||||
.chain(idl.accounts.iter())
|
||||
.find(|defined_type| defined_type.name == defined_type_name)
|
||||
.find(|acc| acc.name == defined_type_name)
|
||||
.and_then(|acc| idl.types.iter().find(|ty| ty.name == acc.name))
|
||||
.or_else(|| idl.types.iter().find(|ty| ty.name == defined_type_name))
|
||||
.ok_or_else(|| anyhow!("Type `{}` not found in IDL.", defined_type_name))?
|
||||
.ty;
|
||||
|
||||
let mut deserialized_fields = Map::new();
|
||||
|
||||
match defined_type {
|
||||
IdlTypeDefinitionTy::Struct { fields } => {
|
||||
for field in fields {
|
||||
deserialized_fields.insert(
|
||||
field.name.clone(),
|
||||
deserialize_idl_type_to_json(&field.ty, data, idl)?,
|
||||
);
|
||||
IdlTypeDefTy::Struct { fields } => {
|
||||
if let Some(fields) = fields {
|
||||
match fields {
|
||||
IdlDefinedFields::Named(fields) => {
|
||||
for field in fields {
|
||||
deserialized_fields.insert(
|
||||
field.name.clone(),
|
||||
deserialize_idl_type_to_json(&field.ty, data, idl)?,
|
||||
);
|
||||
}
|
||||
}
|
||||
IdlDefinedFields::Tuple(fields) => {
|
||||
let mut values = Vec::new();
|
||||
for field in fields {
|
||||
values.push(deserialize_idl_type_to_json(field, data, idl)?);
|
||||
}
|
||||
deserialized_fields
|
||||
.insert(defined_type_name.to_owned(), JsonValue::Array(values));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
IdlTypeDefinitionTy::Enum { variants } => {
|
||||
IdlTypeDefTy::Enum { variants } => {
|
||||
let repr = <u8 as AnchorDeserialize>::deserialize(data)?;
|
||||
|
||||
let variant = variants
|
||||
|
@ -2911,25 +2705,21 @@ fn deserialize_idl_defined_type_to_json(
|
|||
|
||||
if let Some(enum_field) = &variant.fields {
|
||||
match enum_field {
|
||||
EnumFields::Named(fields) => {
|
||||
IdlDefinedFields::Named(fields) => {
|
||||
let mut values = Map::new();
|
||||
|
||||
for field in fields {
|
||||
values.insert(
|
||||
field.name.clone(),
|
||||
deserialize_idl_type_to_json(&field.ty, data, idl)?,
|
||||
);
|
||||
}
|
||||
|
||||
value = JsonValue::Object(values);
|
||||
}
|
||||
EnumFields::Tuple(fields) => {
|
||||
IdlDefinedFields::Tuple(fields) => {
|
||||
let mut values = Vec::new();
|
||||
|
||||
for field in fields {
|
||||
values.push(deserialize_idl_type_to_json(field, data, idl)?);
|
||||
}
|
||||
|
||||
value = JsonValue::Array(values);
|
||||
}
|
||||
}
|
||||
|
@ -2937,8 +2727,8 @@ fn deserialize_idl_defined_type_to_json(
|
|||
|
||||
deserialized_fields.insert(variant.name.clone(), value);
|
||||
}
|
||||
IdlTypeDefinitionTy::Alias { value } => {
|
||||
return deserialize_idl_type_to_json(value, data, idl);
|
||||
IdlTypeDefTy::Type { alias } => {
|
||||
return deserialize_idl_type_to_json(alias, data, idl);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3000,12 +2790,22 @@ fn deserialize_idl_type_to_json(
|
|||
.collect(),
|
||||
),
|
||||
IdlType::String => json!(<String as AnchorDeserialize>::deserialize(data)?),
|
||||
IdlType::PublicKey => {
|
||||
IdlType::Pubkey => {
|
||||
json!(<Pubkey as AnchorDeserialize>::deserialize(data)?.to_string())
|
||||
}
|
||||
IdlType::Defined(type_name) => {
|
||||
deserialize_idl_defined_type_to_json(parent_idl, type_name, data)?
|
||||
}
|
||||
IdlType::Array(ty, size) => match size {
|
||||
IdlArrayLen::Value(size) => {
|
||||
let mut array_data: Vec<JsonValue> = Vec::with_capacity(*size);
|
||||
|
||||
for _ in 0..*size {
|
||||
array_data.push(deserialize_idl_type_to_json(ty, data, parent_idl)?);
|
||||
}
|
||||
|
||||
JsonValue::Array(array_data)
|
||||
}
|
||||
// TODO:
|
||||
IdlArrayLen::Generic(_) => unimplemented!("Generic array length is not yet supported"),
|
||||
},
|
||||
IdlType::Option(ty) => {
|
||||
let is_present = <u8 as AnchorDeserialize>::deserialize(data)?;
|
||||
|
||||
|
@ -3028,20 +2828,14 @@ fn deserialize_idl_type_to_json(
|
|||
|
||||
JsonValue::Array(vec_data)
|
||||
}
|
||||
IdlType::Array(ty, size) => {
|
||||
let mut array_data: Vec<JsonValue> = Vec::with_capacity(*size);
|
||||
|
||||
for _ in 0..*size {
|
||||
array_data.push(deserialize_idl_type_to_json(ty, data, parent_idl)?);
|
||||
}
|
||||
|
||||
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 { name: _, args: _ } => {
|
||||
todo!("Defined types with type args are not yet supported")
|
||||
IdlType::Defined {
|
||||
name,
|
||||
generics: _generics,
|
||||
} => {
|
||||
// TODO: Generics
|
||||
deserialize_idl_defined_type_to_json(parent_idl, name, data)?
|
||||
}
|
||||
IdlType::Generic(generic) => json!(generic),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -3314,11 +3108,11 @@ fn validator_flags(
|
|||
|
||||
if let Some(idl) = program.idl.as_mut() {
|
||||
// Add program address to the IDL.
|
||||
idl.metadata = Some(serde_json::to_value(IdlTestMetadata { address })?);
|
||||
idl.address = address;
|
||||
|
||||
// Persist it.
|
||||
let idl_out = PathBuf::from("target/idl")
|
||||
.join(&idl.name)
|
||||
.join(&idl.metadata.name)
|
||||
.with_extension("json");
|
||||
write_idl(idl, OutFile::File(idl_out))?;
|
||||
}
|
||||
|
@ -3443,22 +3237,15 @@ fn stream_logs(config: &WithPath<Config>, rpc_url: &str) -> Result<Vec<std::proc
|
|||
let mut contents = vec![];
|
||||
file.read_to_end(&mut contents)?;
|
||||
let idl: Idl = serde_json::from_slice(&contents)?;
|
||||
let metadata = idl.metadata.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"Metadata property not found in IDL of program: {}",
|
||||
program.lib_name
|
||||
)
|
||||
})?;
|
||||
let metadata: IdlTestMetadata = serde_json::from_value(metadata)?;
|
||||
|
||||
let log_file = File::create(format!(
|
||||
"{}/{}.{}.log",
|
||||
program_logs_dir, metadata.address, program.lib_name,
|
||||
program_logs_dir, idl.address, program.lib_name,
|
||||
))?;
|
||||
let stdio = std::process::Stdio::from(log_file);
|
||||
let child = std::process::Command::new("solana")
|
||||
.arg("logs")
|
||||
.arg(metadata.address)
|
||||
.arg(idl.address)
|
||||
.arg("--url")
|
||||
.arg(rpc_url)
|
||||
.stdout(stdio)
|
||||
|
@ -3485,11 +3272,6 @@ fn stream_logs(config: &WithPath<Config>, rpc_url: &str) -> Result<Vec<std::proc
|
|||
Ok(handles)
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct IdlTestMetadata {
|
||||
address: String,
|
||||
}
|
||||
|
||||
fn start_test_validator(
|
||||
cfg: &Config,
|
||||
test_validator: &Option<TestValidator>,
|
||||
|
@ -3720,13 +3502,11 @@ fn deploy(
|
|||
|
||||
if let Some(idl) = program.idl.as_mut() {
|
||||
// Add program address to the IDL.
|
||||
idl.metadata = Some(serde_json::to_value(IdlTestMetadata {
|
||||
address: program_id.to_string(),
|
||||
})?);
|
||||
idl.address = program_id.to_string();
|
||||
|
||||
// Persist it.
|
||||
let idl_out = PathBuf::from("target/idl")
|
||||
.join(&idl.name)
|
||||
.join(&idl.metadata.name)
|
||||
.with_extension("json");
|
||||
write_idl(idl, OutFile::File(idl_out))?;
|
||||
}
|
||||
|
@ -4046,7 +3826,7 @@ fn shell(cfg_override: &ConfigOverride) -> Result<()> {
|
|||
.filter(|program| program.idl.is_some())
|
||||
.map(|program| {
|
||||
(
|
||||
program.idl.as_ref().unwrap().name.clone(),
|
||||
program.idl.as_ref().unwrap().metadata.name.clone(),
|
||||
program.idl.clone().unwrap(),
|
||||
)
|
||||
})
|
||||
|
|
|
@ -2,10 +2,11 @@ use crate::{
|
|||
config::ProgramWorkspace, create_files, override_or_create_files, solidity_template, Files,
|
||||
VERSION,
|
||||
};
|
||||
use anchor_syn::idl::types::Idl;
|
||||
use anchor_idl::types::Idl;
|
||||
use anyhow::Result;
|
||||
use clap::{Parser, ValueEnum};
|
||||
use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase};
|
||||
use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase};
|
||||
use regex::Regex;
|
||||
use solana_sdk::{
|
||||
pubkey::Pubkey,
|
||||
signature::{read_keypair_file, write_keypair_file, Keypair},
|
||||
|
@ -17,6 +18,7 @@ use std::{
|
|||
io::Write as _,
|
||||
path::Path,
|
||||
process::Stdio,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
/// Program initialization template
|
||||
|
@ -183,11 +185,12 @@ crate-type = ["cdylib", "lib"]
|
|||
name = "{1}"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
cpi = ["no-entrypoint"]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = "{2}"
|
||||
|
@ -228,20 +231,37 @@ token = "{token}"
|
|||
}
|
||||
|
||||
pub fn idl_ts(idl: &Idl) -> Result<String> {
|
||||
let mut idl = idl.clone();
|
||||
for acc in idl.accounts.iter_mut() {
|
||||
acc.name = acc.name.to_lower_camel_case();
|
||||
}
|
||||
let idl_json = serde_json::to_string_pretty(&idl)?;
|
||||
Ok(format!(
|
||||
r#"export type {} = {};
|
||||
let idl_name = &idl.metadata.name;
|
||||
let type_name = idl_name.to_pascal_case();
|
||||
let idl = serde_json::to_string(idl)?;
|
||||
|
||||
export const IDL: {} = {};
|
||||
"#,
|
||||
idl.name.to_upper_camel_case(),
|
||||
idl_json,
|
||||
idl.name.to_upper_camel_case(),
|
||||
idl_json
|
||||
// Convert every field of the IDL to camelCase
|
||||
let camel_idl = Regex::new(r#""\w+":"([\w\d]+)""#)?
|
||||
.captures_iter(&idl)
|
||||
.fold(idl.clone(), |acc, cur| {
|
||||
let name = cur.get(1).unwrap().as_str();
|
||||
|
||||
// Do not modify pubkeys
|
||||
if Pubkey::from_str(name).is_ok() {
|
||||
return acc;
|
||||
}
|
||||
|
||||
let camel_name = name.to_lower_camel_case();
|
||||
acc.replace(&format!(r#""{name}""#), &format!(r#""{camel_name}""#))
|
||||
});
|
||||
|
||||
// Pretty format
|
||||
let camel_idl = serde_json::to_string_pretty(&serde_json::from_str::<Idl>(&camel_idl)?)?;
|
||||
|
||||
Ok(format!(
|
||||
r#"/**
|
||||
* Program IDL in camelCase format in order to be used in JS/TS.
|
||||
*
|
||||
* Note that this is only a type helper and is not the actual IDL. The original
|
||||
* IDL can be found at `target/idl/{idl_name}.json`.
|
||||
*/
|
||||
export type {type_name} = {camel_idl};
|
||||
"#
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -347,7 +367,7 @@ describe("{}", () => {{
|
|||
}});
|
||||
"#,
|
||||
name,
|
||||
name.to_upper_camel_case(),
|
||||
name.to_pascal_case(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -368,7 +388,7 @@ describe("{}", () => {{
|
|||
}});
|
||||
"#,
|
||||
name,
|
||||
name.to_upper_camel_case(),
|
||||
name.to_pascal_case(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -478,11 +498,11 @@ describe("{}", () => {{
|
|||
}});
|
||||
}});
|
||||
"#,
|
||||
name.to_upper_camel_case(),
|
||||
name.to_pascal_case(),
|
||||
name.to_snake_case(),
|
||||
name,
|
||||
name.to_upper_camel_case(),
|
||||
name.to_upper_camel_case(),
|
||||
name.to_pascal_case(),
|
||||
name.to_pascal_case(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -505,11 +525,11 @@ describe("{}", () => {{
|
|||
}});
|
||||
}});
|
||||
"#,
|
||||
name.to_upper_camel_case(),
|
||||
name.to_pascal_case(),
|
||||
name.to_snake_case(),
|
||||
name,
|
||||
name.to_upper_camel_case(),
|
||||
name.to_upper_camel_case(),
|
||||
name.to_pascal_case(),
|
||||
name.to_pascal_case(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -606,7 +626,7 @@ anchor.setProvider(provider);
|
|||
r#"
|
||||
anchor.workspace.{} = new anchor.Program({}, new PublicKey("{}"), provider);
|
||||
"#,
|
||||
program.name.to_upper_camel_case(),
|
||||
program.name.to_pascal_case(),
|
||||
serde_json::to_string(&program.idl)?,
|
||||
program.program_id
|
||||
)?;
|
||||
|
|
|
@ -12,6 +12,7 @@ name = "basic_0"
|
|||
[features]
|
||||
no-entrypoint = []
|
||||
cpi = ["no-entrypoint"]
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../../lang" }
|
||||
|
|
|
@ -12,6 +12,7 @@ name = "basic_1"
|
|||
[features]
|
||||
no-entrypoint = []
|
||||
cpi = ["no-entrypoint"]
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../../lang" }
|
||||
|
|
|
@ -12,6 +12,7 @@ name = "basic_2"
|
|||
[features]
|
||||
no-entrypoint = []
|
||||
cpi = ["no-entrypoint"]
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../../lang" }
|
||||
|
|
|
@ -12,6 +12,7 @@ name = "puppet_master"
|
|||
[features]
|
||||
no-entrypoint = []
|
||||
cpi = ["no-entrypoint"]
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../../lang" }
|
||||
|
|
|
@ -12,6 +12,7 @@ name = "puppet"
|
|||
[features]
|
||||
no-entrypoint = []
|
||||
cpi = ["no-entrypoint"]
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../../lang" }
|
||||
|
|
|
@ -12,6 +12,7 @@ name = "basic_4"
|
|||
[features]
|
||||
no-entrypoint = []
|
||||
cpi = ["no-entrypoint"]
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../../lang" }
|
||||
|
|
|
@ -12,6 +12,7 @@ name = "basic_5"
|
|||
[features]
|
||||
no-entrypoint = []
|
||||
cpi = ["no-entrypoint"]
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../../lang" }
|
|
@ -0,0 +1,30 @@
|
|||
[package]
|
||||
name = "anchor-idl"
|
||||
version = "0.29.0"
|
||||
authors = ["Anchor Maintainers <accounts@200ms.io>"]
|
||||
repository = "https://github.com/coral-xyz/anchor"
|
||||
rust-version = "1.60"
|
||||
edition = "2021"
|
||||
license = "Apache-2.0"
|
||||
description = "Anchor framework IDL"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
all-features = true
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
||||
[features]
|
||||
build = [
|
||||
"anyhow",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anchor-syn = { path = "../lang/syn", version = "0.29.0", features = ["idl-types"] }
|
||||
|
||||
# `build` feature only
|
||||
anyhow = { version = "1", optional = true }
|
||||
regex = { version = "1", optional = true }
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
serde_json = { version = "1", optional = true }
|
|
@ -0,0 +1,252 @@
|
|||
use std::{
|
||||
collections::BTreeMap,
|
||||
env, mem,
|
||||
path::Path,
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use anchor_syn::parser::context::CrateContext;
|
||||
use anyhow::{anyhow, Result};
|
||||
use regex::Regex;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::types::{Idl, IdlEvent, IdlTypeDef};
|
||||
|
||||
/// Generate IDL via compilation.
|
||||
pub fn build_idl(
|
||||
program_path: impl AsRef<Path>,
|
||||
resolution: bool,
|
||||
skip_lint: bool,
|
||||
no_docs: bool,
|
||||
) -> Result<Idl> {
|
||||
// Check safety comments
|
||||
let program_path = program_path.as_ref();
|
||||
let lib_path = program_path.join("src").join("lib.rs");
|
||||
let ctx = CrateContext::parse(lib_path)?;
|
||||
if !skip_lint {
|
||||
ctx.safety_checks()?;
|
||||
}
|
||||
|
||||
let idl = build(program_path, resolution, no_docs)?;
|
||||
let idl = convert_module_paths(idl);
|
||||
let idl = sort(idl);
|
||||
verify(&idl)?;
|
||||
|
||||
Ok(idl)
|
||||
}
|
||||
|
||||
// Build IDL.
|
||||
fn build(program_path: &Path, resolution: bool, no_docs: bool) -> Result<Idl> {
|
||||
// `nightly` toolchain is currently required for building the IDL.
|
||||
//
|
||||
// Pinning the toolchain to an older date in order to fix
|
||||
// `error[E0635]: unknown feature stdsimd` error from `ahash`.
|
||||
// See: https://github.com/tkaitchuck/aHash/issues/200
|
||||
//
|
||||
// There is also another error when using a date after 2024-01-30
|
||||
// `error[E0412]: cannot find type `T` in this scope``
|
||||
//
|
||||
// TODO: Unpin `nightly` release after upgrading Solana to `1.18`.
|
||||
const TOOLCHAIN: &str = "+nightly-2024-01-30";
|
||||
install_toolchain_if_needed(TOOLCHAIN)?;
|
||||
|
||||
let output = Command::new("cargo")
|
||||
.args([
|
||||
TOOLCHAIN,
|
||||
"test",
|
||||
"__anchor_private_print_idl",
|
||||
"--features",
|
||||
"idl-build",
|
||||
"--",
|
||||
"--show-output",
|
||||
"--quiet",
|
||||
])
|
||||
.env(
|
||||
"ANCHOR_IDL_BUILD_NO_DOCS",
|
||||
if no_docs { "TRUE" } else { "FALSE" },
|
||||
)
|
||||
.env(
|
||||
"ANCHOR_IDL_BUILD_RESOLUTION",
|
||||
if resolution { "TRUE" } else { "FALSE" },
|
||||
)
|
||||
.env("RUSTFLAGS", "--cfg procmacro2_semver_exempt")
|
||||
.current_dir(program_path)
|
||||
.stderr(Stdio::inherit())
|
||||
.output()?;
|
||||
if !output.status.success() {
|
||||
return Err(anyhow!("Building IDL failed"));
|
||||
}
|
||||
|
||||
enum State {
|
||||
Pass,
|
||||
Address,
|
||||
Constants(Vec<String>),
|
||||
Events(Vec<String>),
|
||||
Errors(Vec<String>),
|
||||
Program(Vec<String>),
|
||||
}
|
||||
|
||||
let mut address = String::new();
|
||||
let mut events = vec![];
|
||||
let mut error_codes = vec![];
|
||||
let mut constants = vec![];
|
||||
let mut types = BTreeMap::new();
|
||||
let mut idl: Option<Idl> = None;
|
||||
|
||||
let output = String::from_utf8_lossy(&output.stdout);
|
||||
if env::var("ANCHOR_LOG").is_ok() {
|
||||
println!("{}", output);
|
||||
}
|
||||
|
||||
let mut state = State::Pass;
|
||||
for line in output.lines() {
|
||||
match &mut state {
|
||||
State::Pass => match line {
|
||||
"--- IDL begin address ---" => state = State::Address,
|
||||
"--- IDL begin const ---" => state = State::Constants(vec![]),
|
||||
"--- IDL begin event ---" => state = State::Events(vec![]),
|
||||
"--- IDL begin errors ---" => state = State::Errors(vec![]),
|
||||
"--- IDL begin program ---" => state = State::Program(vec![]),
|
||||
_ => {
|
||||
if line.starts_with("test result: ok") {
|
||||
if let Some(idl) = idl.as_mut() {
|
||||
idl.address = mem::take(&mut address);
|
||||
idl.constants = mem::take(&mut constants);
|
||||
idl.events = mem::take(&mut events);
|
||||
idl.errors = mem::take(&mut error_codes);
|
||||
idl.types = {
|
||||
let prog_ty = mem::take(&mut idl.types);
|
||||
let mut types = mem::take(&mut types);
|
||||
types.extend(prog_ty.into_iter().map(|ty| (ty.name.clone(), ty)));
|
||||
types.into_values().collect()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
State::Address => {
|
||||
address = line.replace(|c: char| !c.is_alphanumeric(), "");
|
||||
state = State::Pass;
|
||||
continue;
|
||||
}
|
||||
State::Constants(lines) => {
|
||||
if line == "--- IDL end const ---" {
|
||||
let constant = serde_json::from_str(&lines.join("\n"))?;
|
||||
constants.push(constant);
|
||||
state = State::Pass;
|
||||
continue;
|
||||
}
|
||||
|
||||
lines.push(line.to_owned());
|
||||
}
|
||||
State::Events(lines) => {
|
||||
if line == "--- IDL end event ---" {
|
||||
#[derive(Deserialize)]
|
||||
struct IdlBuildEventPrint {
|
||||
event: IdlEvent,
|
||||
types: Vec<IdlTypeDef>,
|
||||
}
|
||||
|
||||
let event = serde_json::from_str::<IdlBuildEventPrint>(&lines.join("\n"))?;
|
||||
events.push(event.event);
|
||||
types.extend(event.types.into_iter().map(|ty| (ty.name.clone(), ty)));
|
||||
state = State::Pass;
|
||||
continue;
|
||||
}
|
||||
|
||||
lines.push(line.to_owned());
|
||||
}
|
||||
State::Errors(lines) => {
|
||||
if line == "--- IDL end errors ---" {
|
||||
error_codes = serde_json::from_str(&lines.join("\n"))?;
|
||||
state = State::Pass;
|
||||
continue;
|
||||
}
|
||||
|
||||
lines.push(line.to_owned());
|
||||
}
|
||||
State::Program(lines) => {
|
||||
if line == "--- IDL end program ---" {
|
||||
idl = Some(serde_json::from_str(&lines.join("\n"))?);
|
||||
state = State::Pass;
|
||||
continue;
|
||||
}
|
||||
|
||||
lines.push(line.to_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
idl.ok_or_else(|| anyhow!("IDL doesn't exist"))
|
||||
}
|
||||
|
||||
/// Install the given toolchain if it's not already installed.
|
||||
fn install_toolchain_if_needed(toolchain: &str) -> Result<()> {
|
||||
let is_installed = Command::new("cargo")
|
||||
.arg(toolchain)
|
||||
.output()?
|
||||
.status
|
||||
.success();
|
||||
if !is_installed {
|
||||
Command::new("rustup")
|
||||
.args(["toolchain", "install", toolchain.trim_start_matches('+')])
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Convert paths to name if there are no conflicts.
|
||||
fn convert_module_paths(idl: Idl) -> Idl {
|
||||
let idl = serde_json::to_string(&idl).unwrap();
|
||||
let idl = Regex::new(r#""((\w+::)+)(\w+)""#)
|
||||
.unwrap()
|
||||
.captures_iter(&idl.clone())
|
||||
.fold(idl, |acc, cur| {
|
||||
let path = cur.get(0).unwrap().as_str();
|
||||
let name = cur.get(3).unwrap().as_str();
|
||||
|
||||
// Replace path with name
|
||||
let replaced_idl = acc.replace(path, &format!(r#""{name}""#));
|
||||
|
||||
// Check whether there is a conflict
|
||||
let has_conflict = replaced_idl.contains(&format!(r#"::{name}""#));
|
||||
if has_conflict {
|
||||
acc
|
||||
} else {
|
||||
replaced_idl
|
||||
}
|
||||
});
|
||||
|
||||
serde_json::from_str(&idl).expect("Invalid IDL")
|
||||
}
|
||||
|
||||
/// Alphabetically sort fields for consistency.
|
||||
fn sort(mut idl: Idl) -> Idl {
|
||||
idl.accounts.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
idl.constants.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
idl.events.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
idl.instructions.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
idl.types.sort_by(|a, b| a.name.cmp(&b.name));
|
||||
|
||||
idl
|
||||
}
|
||||
|
||||
/// Verify IDL is valid.
|
||||
fn verify(idl: &Idl) -> Result<()> {
|
||||
// Check full path accounts
|
||||
if let Some(account) = idl
|
||||
.accounts
|
||||
.iter()
|
||||
.find(|account| account.name.contains("::"))
|
||||
{
|
||||
return Err(anyhow!(
|
||||
"Conflicting accounts names are not allowed.\nProgram: `{}`\nAccount: `{}`",
|
||||
idl.metadata.name,
|
||||
account.name
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
//! Anchor IDL.
|
||||
|
||||
pub mod types;
|
||||
|
||||
#[cfg(feature = "build")]
|
||||
pub mod build;
|
|
@ -0,0 +1,3 @@
|
|||
//! IDL types are re-exported from [`anchor_syn`].
|
||||
|
||||
pub use anchor_syn::idl::types::*;
|
|
@ -1,7 +1,5 @@
|
|||
extern crate proc_macro;
|
||||
|
||||
#[cfg(feature = "idl-build")]
|
||||
use anchor_syn::idl::build::*;
|
||||
use quote::quote;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
|
@ -404,8 +402,18 @@ pub fn zero_copy(
|
|||
|
||||
#[cfg(feature = "idl-build")]
|
||||
{
|
||||
let no_docs = get_no_docs();
|
||||
let idl_build_impl = gen_idl_build_impl_for_struct(&account_strct, no_docs);
|
||||
let derive_unsafe = if is_unsafe {
|
||||
// Not a real proc-macro but exists in order to pass the serialization info
|
||||
quote! { #[derive(bytemuck::Unsafe)] }
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
let zc_struct = syn::parse2(quote! {
|
||||
#derive_unsafe
|
||||
#ret
|
||||
})
|
||||
.unwrap();
|
||||
let idl_build_impl = anchor_syn::idl::build::impl_idl_build_struct(&zc_struct);
|
||||
return proc_macro::TokenStream::from(quote! {
|
||||
#ret
|
||||
#idl_build_impl
|
||||
|
@ -420,6 +428,21 @@ pub fn zero_copy(
|
|||
/// based programs.
|
||||
#[proc_macro]
|
||||
pub fn declare_id(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
#[cfg(feature = "idl-build")]
|
||||
let address = input.clone().to_string();
|
||||
|
||||
let id = parse_macro_input!(input as id::Id);
|
||||
proc_macro::TokenStream::from(quote! {#id})
|
||||
let ret = quote! { #id };
|
||||
|
||||
#[cfg(feature = "idl-build")]
|
||||
{
|
||||
let idl_print = anchor_syn::idl::build::gen_idl_print_fn_address(address);
|
||||
return proc_macro::TokenStream::from(quote! {
|
||||
#ret
|
||||
#idl_print
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
proc_macro::TokenStream::from(ret)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
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]
|
||||
|
@ -12,9 +9,11 @@ pub fn constant(
|
|||
) -> proc_macro::TokenStream {
|
||||
#[cfg(feature = "idl-build")]
|
||||
{
|
||||
use quote::quote;
|
||||
|
||||
let ts = match syn::parse(input).unwrap() {
|
||||
syn::Item::Const(item) => {
|
||||
let idl_print = gen_idl_print_function_for_constant(&item);
|
||||
let idl_print = anchor_syn::idl::build::gen_idl_print_fn_constant(&item);
|
||||
quote! {
|
||||
#item
|
||||
#idl_print
|
||||
|
|
|
@ -46,7 +46,7 @@ pub fn event(
|
|||
|
||||
#[cfg(feature = "idl-build")]
|
||||
{
|
||||
let idl_build = anchor_syn::idl::build::gen_idl_print_function_for_event(&event_strct);
|
||||
let idl_build = anchor_syn::idl::build::gen_idl_print_fn_event(&event_strct);
|
||||
return proc_macro::TokenStream::from(quote! {
|
||||
#ret
|
||||
#idl_build
|
||||
|
|
|
@ -628,7 +628,7 @@ use syn::parse_macro_input;
|
|||
/// <tbody>
|
||||
/// </table>
|
||||
#[proc_macro_derive(Accounts, attributes(account, instruction))]
|
||||
pub fn derive_anchor_deserialize(item: TokenStream) -> TokenStream {
|
||||
pub fn derive_accounts(item: TokenStream) -> TokenStream {
|
||||
parse_macro_input!(item as anchor_syn::AccountsStruct)
|
||||
.to_token_stream()
|
||||
.into()
|
||||
|
|
|
@ -5,9 +5,6 @@ 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());
|
||||
|
||||
|
@ -35,15 +32,13 @@ pub fn anchor_serialize(input: TokenStream) -> TokenStream {
|
|||
|
||||
#[cfg(feature = "idl-build")]
|
||||
{
|
||||
let no_docs = get_no_docs();
|
||||
use anchor_syn::idl::build::*;
|
||||
use quote::quote;
|
||||
|
||||
let idl_build_impl = match syn::parse(input).unwrap() {
|
||||
Item::Struct(item) => gen_idl_build_impl_for_struct(&item, no_docs),
|
||||
Item::Enum(item) => gen_idl_build_impl_for_enum(item, no_docs),
|
||||
Item::Union(item) => {
|
||||
// unions are not included in the IDL - TODO print a warning
|
||||
idl_build_impl_skeleton(quote! {None}, quote! {}, &item.ident, &item.generics)
|
||||
}
|
||||
Item::Struct(item) => impl_idl_build_struct(&item),
|
||||
Item::Enum(item) => impl_idl_build_enum(&item),
|
||||
Item::Union(item) => impl_idl_build_union(&item),
|
||||
// Derive macros can only be defined on structs, enums, and unions.
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
|
|
@ -72,3 +72,13 @@ impl AccountDeserialize for UpgradeableLoaderState {
|
|||
bincode::deserialize(buf).map_err(|_| ProgramError::InvalidAccountData.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "idl-build")]
|
||||
mod idl_build {
|
||||
use super::*;
|
||||
|
||||
impl crate::IdlBuild for ProgramData {}
|
||||
impl crate::Discriminator for ProgramData {
|
||||
const DISCRIMINATOR: [u8; 8] = [u8::MAX; 8];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,12 +17,10 @@ allow-missing-optionals = []
|
|||
anchor-debug = []
|
||||
event-cpi = []
|
||||
hash = []
|
||||
idl-build = ["idl-parse", "idl-types"]
|
||||
idl-parse = ["idl-types"]
|
||||
idl-build = ["idl-types", "cargo_toml"]
|
||||
idl-types = []
|
||||
init-if-needed = []
|
||||
interface-instructions = []
|
||||
seeds = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
|
@ -35,3 +33,6 @@ serde_json = "1"
|
|||
sha2 = "0.10"
|
||||
syn = { version = "1", features = ["full", "extra-traits", "parsing"] }
|
||||
thiserror = "1"
|
||||
|
||||
# `idl-build` feature only
|
||||
cargo_toml = { version = "0.15", optional = true }
|
||||
|
|
|
@ -1098,7 +1098,7 @@ impl<'a> OptionalCheckScope<'a> {
|
|||
check_scope
|
||||
}
|
||||
pub fn generate_check(&mut self, field: impl ToTokens) -> TokenStream {
|
||||
let field_name = tts_to_string(&field);
|
||||
let field_name = parser::tts_to_string(&field);
|
||||
if self.seen.contains(&field_name) {
|
||||
quote! {}
|
||||
} else {
|
||||
|
|
|
@ -37,10 +37,7 @@ pub fn generate(accs: &AccountsStruct) -> proc_macro2::TokenStream {
|
|||
|
||||
#[cfg(feature = "idl-build")]
|
||||
{
|
||||
#![allow(warnings)]
|
||||
let no_docs = crate::idl::build::get_no_docs();
|
||||
let idl_build_impl =
|
||||
crate::idl::build::gen_idl_build_impl_for_accounts_struct(&accs, no_docs);
|
||||
let idl_build_impl = crate::idl::build::gen_idl_build_impl_accounts_struct(accs);
|
||||
return quote! {
|
||||
#ret
|
||||
#idl_build_impl
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
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;
|
||||
|
@ -103,10 +100,10 @@ pub fn generate(error: Error) -> proc_macro2::TokenStream {
|
|||
|
||||
#[cfg(feature = "idl-build")]
|
||||
{
|
||||
let idl_build = gen_idl_print_function_for_error(&error);
|
||||
let idl_print = crate::idl::build::gen_idl_print_fn_error(&error);
|
||||
return quote! {
|
||||
#ret
|
||||
#idl_build
|
||||
#idl_print
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -39,12 +39,10 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream {
|
|||
|
||||
#[cfg(feature = "idl-build")]
|
||||
{
|
||||
let no_docs = crate::idl::build::get_no_docs();
|
||||
let idl_build = crate::idl::build::gen_idl_print_function_for_program(program, no_docs);
|
||||
|
||||
let idl_build_impl = crate::idl::build::gen_idl_print_fn_program(program);
|
||||
return quote! {
|
||||
#ret
|
||||
#idl_build
|
||||
#idl_build_impl
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -1,948 +0,0 @@
|
|||
pub use serde_json;
|
||||
|
||||
use crate::{parser::docs, AccountField, AccountsStruct, Error, Program};
|
||||
use heck::MixedCase;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{Ident, ItemEnum, ItemStruct};
|
||||
|
||||
/// A trait that types must implement in order to generate the IDL via compilation.
|
||||
///
|
||||
/// This trait is automatically implemented for Anchor all types that use the `AnchorSerialize`
|
||||
/// proc macro. Note that manually implementing the `AnchorSerialize` trait will **NOT** have the
|
||||
/// same effect.
|
||||
///
|
||||
/// Types that don't implement this trait will cause a compile error during the IDL generation.
|
||||
///
|
||||
/// The methods have default implementation that allows the program to compile but the type will
|
||||
/// **NOT** be included in the IDL.
|
||||
pub trait IdlBuild {
|
||||
/// Returns the full module path of the type.
|
||||
fn __anchor_private_full_path() -> String {
|
||||
String::default()
|
||||
}
|
||||
|
||||
/// Returns the IDL type definition of the type or `None` if it doesn't exist.
|
||||
fn __anchor_private_gen_idl_type() -> Option<super::types::IdlTypeDefinition> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Insert the type definition to the defined types hashmap.
|
||||
fn __anchor_private_insert_idl_defined(
|
||||
_defined_types: &mut std::collections::HashMap<String, super::types::IdlTypeDefinition>,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
#[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_BUILD_NO_DOCS")
|
||||
.map(|val| val == "TRUE")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_seeds_feature() -> bool {
|
||||
std::option_env!("ANCHOR_IDL_BUILD_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 {
|
||||
name: <#path>::__anchor_private_full_path(),
|
||||
args: #params
|
||||
} },
|
||||
defined,
|
||||
))
|
||||
} else {
|
||||
Ok((
|
||||
quote! { #idl::IdlType::Defined(<#path>::__anchor_private_full_path()) },
|
||||
vec![path.clone()],
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
syn::Type::Reference(reference) => match reference.elem.as_ref() {
|
||||
syn::Type::Slice(slice) if matches!(&*slice.elem, syn::Type::Path(path) if the_only_segment_is(path, "u8")) =>
|
||||
{
|
||||
return Ok((quote! {#idl::IdlType::Bytes}, vec![]));
|
||||
}
|
||||
_ => panic!("Reference types other than byte slice(`&[u8]`) are not allowed"),
|
||||
},
|
||||
_ => 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 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: 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 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: Self::__anchor_private_full_path(),
|
||||
generics: #generics,
|
||||
docs: #docs,
|
||||
ty: #idl::IdlTypeDefinitionTy::Enum{
|
||||
variants: vec![
|
||||
#(#variants),*
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
defined,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn idl_build_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();
|
||||
let idl_build_trait = quote! {anchor_lang::anchor_syn::idl::build::IdlBuild};
|
||||
|
||||
quote! {
|
||||
impl #impl_generics #idl_build_trait for #ident #ty_generics #where_clause {
|
||||
fn __anchor_private_full_path() -> String {
|
||||
format!("{}::{}", std::module_path!(), #name)
|
||||
}
|
||||
|
||||
fn __anchor_private_gen_idl_type() -> Option<#idl::IdlTypeDefinition> {
|
||||
#idl_type_definition_ts
|
||||
}
|
||||
|
||||
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_build_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_build_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_build_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_build_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_build_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_build_impl_for_accounts_struct(
|
||||
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_build_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 ----");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,392 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
|
||||
use super::common::{get_idl_module_path, get_no_docs};
|
||||
use crate::{AccountField, AccountsStruct, Field, Ty};
|
||||
|
||||
/// Generate the IDL build impl for the Accounts struct.
|
||||
pub fn gen_idl_build_impl_accounts_struct(accounts: &AccountsStruct) -> TokenStream {
|
||||
let resolution = option_env!("ANCHOR_IDL_BUILD_RESOLUTION")
|
||||
.map(|val| val == "TRUE")
|
||||
.unwrap_or_default();
|
||||
let no_docs = get_no_docs();
|
||||
let idl = get_idl_module_path();
|
||||
|
||||
let ident = &accounts.ident;
|
||||
let (impl_generics, ty_generics, where_clause) = accounts.generics.split_for_impl();
|
||||
|
||||
let (accounts, defined) = accounts
|
||||
.fields
|
||||
.iter()
|
||||
.map(|acc| match acc {
|
||||
AccountField::Field(acc) => {
|
||||
let name = acc.ident.to_string();
|
||||
let writable = acc.constraints.is_mutable();
|
||||
let signer = match acc.ty {
|
||||
Ty::Signer => true,
|
||||
_ => acc.constraints.is_signer(),
|
||||
};
|
||||
let optional = acc.is_optional;
|
||||
let docs = match &acc.docs {
|
||||
Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
|
||||
_ => quote! { vec![] },
|
||||
};
|
||||
|
||||
let (address, pda, relations) = if resolution {
|
||||
(
|
||||
get_address(acc),
|
||||
get_pda(acc, accounts),
|
||||
get_relations(acc, accounts),
|
||||
)
|
||||
} else {
|
||||
(quote! { None }, quote! { None }, quote! { vec![] })
|
||||
};
|
||||
|
||||
let acc_type_path = match &acc.ty {
|
||||
Ty::Account(ty)
|
||||
// Skip `UpgradeableLoaderState` type for now until `bincode` serialization
|
||||
// is supported.
|
||||
//
|
||||
// TODO: Remove this once either `bincode` serialization is supported or
|
||||
// we wrap the type in order to implement `IdlBuild` in `anchor-lang`.
|
||||
if !ty
|
||||
.account_type_path
|
||||
.path
|
||||
.to_token_stream()
|
||||
.to_string()
|
||||
.contains("UpgradeableLoaderState") =>
|
||||
{
|
||||
Some(&ty.account_type_path)
|
||||
}
|
||||
Ty::AccountLoader(ty) => Some(&ty.account_type_path),
|
||||
Ty::InterfaceAccount(ty) => Some(&ty.account_type_path),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
(
|
||||
quote! {
|
||||
#idl::IdlInstructionAccountItem::Single(#idl::IdlInstructionAccount {
|
||||
name: #name.into(),
|
||||
docs: #docs,
|
||||
writable: #writable,
|
||||
signer: #signer,
|
||||
optional: #optional,
|
||||
address: #address,
|
||||
pda: #pda,
|
||||
relations: #relations,
|
||||
})
|
||||
},
|
||||
acc_type_path,
|
||||
)
|
||||
}
|
||||
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!(
|
||||
"Compose field type must be a path but received: {:?}",
|
||||
comp_f.raw_field.ty
|
||||
)
|
||||
};
|
||||
let name = comp_f.ident.to_string();
|
||||
|
||||
(
|
||||
quote! {
|
||||
#idl::IdlInstructionAccountItem::Composite(#idl::IdlInstructionAccounts {
|
||||
name: #name.into(),
|
||||
accounts: <#ty>::__anchor_private_gen_idl_accounts(accounts, types),
|
||||
})
|
||||
},
|
||||
None,
|
||||
)
|
||||
}
|
||||
})
|
||||
.unzip::<_, _, Vec<_>, Vec<_>>();
|
||||
let defined = defined.into_iter().flatten().collect::<Vec<_>>();
|
||||
|
||||
quote! {
|
||||
impl #impl_generics #ident #ty_generics #where_clause {
|
||||
pub fn __anchor_private_gen_idl_accounts(
|
||||
accounts: &mut std::collections::BTreeMap<String, #idl::IdlAccount>,
|
||||
types: &mut std::collections::BTreeMap<String, #idl::IdlTypeDef>,
|
||||
) -> Vec<#idl::IdlInstructionAccountItem> {
|
||||
#(
|
||||
if let Some(ty) = <#defined>::create_type() {
|
||||
let account = #idl::IdlAccount {
|
||||
name: ty.name.clone(),
|
||||
discriminator: <#defined as anchor_lang::Discriminator>::DISCRIMINATOR.into(),
|
||||
};
|
||||
accounts.insert(account.name.clone(), account);
|
||||
types.insert(ty.name.clone(), ty);
|
||||
<#defined>::insert_types(types);
|
||||
}
|
||||
);*
|
||||
|
||||
vec![#(#accounts),*]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_address(acc: &Field) -> TokenStream {
|
||||
match &acc.ty {
|
||||
Ty::Program(ty) => ty
|
||||
.account_type_path
|
||||
.path
|
||||
.segments
|
||||
.last()
|
||||
.map(|seg| &seg.ident)
|
||||
.map(|ident| quote! { Some(#ident::id().to_string()) })
|
||||
.unwrap_or_else(|| quote! { None }),
|
||||
Ty::Sysvar(_) => {
|
||||
let ty = acc.account_ty();
|
||||
let sysvar_id_trait = quote!(anchor_lang::solana_program::sysvar::SysvarId);
|
||||
quote! { Some(<#ty as #sysvar_id_trait>::id().to_string()) }
|
||||
}
|
||||
_ => acc
|
||||
.constraints
|
||||
.address
|
||||
.as_ref()
|
||||
.map(|constraint| &constraint.address)
|
||||
.map(|address| quote! { Some(#address.to_string()) })
|
||||
.unwrap_or_else(|| quote! { None }),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_pda(acc: &Field, accounts: &AccountsStruct) -> TokenStream {
|
||||
let idl = get_idl_module_path();
|
||||
let seed_constraints = acc.constraints.seeds.as_ref();
|
||||
let seeds = seed_constraints
|
||||
.map(|seed| seed.seeds.iter().map(|seed| parse_seed(seed, accounts)))
|
||||
.and_then(|seeds| seeds.collect::<Result<Vec<_>>>().ok());
|
||||
let program = seed_constraints
|
||||
.and_then(|seed| seed.program_seed.as_ref())
|
||||
.and_then(|program| parse_seed(program, accounts).ok())
|
||||
.map(|program| quote! { Some(#program) })
|
||||
.unwrap_or_else(|| quote! { None });
|
||||
match seeds {
|
||||
Some(seeds) => quote! {
|
||||
Some(
|
||||
#idl::IdlPda {
|
||||
seeds: vec![#(#seeds),*],
|
||||
program: #program,
|
||||
}
|
||||
)
|
||||
},
|
||||
_ => quote! { None },
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a seeds constraint, extracting the `IdlSeed` types.
|
||||
///
|
||||
/// Note: This implementation makes assumptions about the types that can be used (e.g., no
|
||||
/// program-defined function calls in seeds).
|
||||
///
|
||||
/// This probably doesn't cover all cases. If you see a warning log, you can add a new case here.
|
||||
/// In the worst case, we miss a seed and the parser will treat the given seeds as empty and so
|
||||
/// clients will simply fail to automatically populate the PDA accounts.
|
||||
///
|
||||
/// # Seed assumptions
|
||||
///
|
||||
/// Seeds must be of one of the following forms:
|
||||
///
|
||||
/// - Constant
|
||||
/// - Instruction argument
|
||||
/// - Account key or field
|
||||
fn parse_seed(seed: &syn::Expr, accounts: &AccountsStruct) -> Result<TokenStream> {
|
||||
let idl = get_idl_module_path();
|
||||
let args = accounts.instruction_args().unwrap_or_default();
|
||||
match seed {
|
||||
syn::Expr::MethodCall(_) => {
|
||||
let seed_path = SeedPath::new(seed)?;
|
||||
|
||||
if args.contains_key(&seed_path.name) {
|
||||
let path = seed_path.path();
|
||||
|
||||
Ok(quote! {
|
||||
#idl::IdlSeed::Arg(
|
||||
#idl::IdlSeedArg {
|
||||
path: #path.into(),
|
||||
}
|
||||
)
|
||||
})
|
||||
} else if let Some(account_field) = accounts
|
||||
.fields
|
||||
.iter()
|
||||
.find(|field| *field.ident() == seed_path.name)
|
||||
{
|
||||
let path = seed_path.path();
|
||||
let account = match account_field.ty_name() {
|
||||
Some(name) if !seed_path.subfields.is_empty() => {
|
||||
quote! { Some(#name.into()) }
|
||||
}
|
||||
_ => quote! { None },
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#idl::IdlSeed::Account(
|
||||
#idl::IdlSeedAccount {
|
||||
path: #path.into(),
|
||||
account: #account,
|
||||
}
|
||||
)
|
||||
})
|
||||
} else if seed_path.name.contains('"') {
|
||||
let seed = seed_path.name.trim_start_matches("b\"").trim_matches('"');
|
||||
Ok(quote! {
|
||||
#idl::IdlSeed::Const(
|
||||
#idl::IdlSeedConst {
|
||||
value: #seed.into(),
|
||||
}
|
||||
)
|
||||
})
|
||||
} else {
|
||||
Ok(quote! {
|
||||
#idl::IdlSeed::Const(
|
||||
#idl::IdlSeedConst {
|
||||
value: #seed.into(),
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
syn::Expr::Path(path) => {
|
||||
let seed = path
|
||||
.path
|
||||
.get_ident()
|
||||
.map(|ident| ident.to_string())
|
||||
.filter(|ident| args.contains_key(ident))
|
||||
.map(|path| {
|
||||
quote! {
|
||||
#idl::IdlSeed::Arg(
|
||||
#idl::IdlSeedArg {
|
||||
path: #path.into(),
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
// Not all types can be converted to `Vec<u8>` with `.into` call e.g. `Pubkey`.
|
||||
// This is problematic for `seeds::program` but a hacky way to handle this
|
||||
// scenerio is to check whether the last segment of the path ends with `ID`.
|
||||
let seed = path
|
||||
.path
|
||||
.segments
|
||||
.last()
|
||||
.filter(|seg| seg.ident.to_string().ends_with("ID"))
|
||||
.map(|_| quote! { #seed.as_ref() })
|
||||
.unwrap_or_else(|| quote! { #seed });
|
||||
quote! {
|
||||
#idl::IdlSeed::Const(
|
||||
#idl::IdlSeedConst {
|
||||
value: #seed.into(),
|
||||
}
|
||||
)
|
||||
}
|
||||
});
|
||||
Ok(seed)
|
||||
}
|
||||
syn::Expr::Lit(_) => Ok(quote! {
|
||||
#idl::IdlSeed::Const(
|
||||
#idl::IdlSeedConst {
|
||||
value: #seed.into(),
|
||||
}
|
||||
)
|
||||
}),
|
||||
syn::Expr::Reference(rf) => parse_seed(&rf.expr, accounts),
|
||||
_ => Err(anyhow!("Unexpected seed: {seed:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
/// SeedPath represents the deconstructed syntax of a single pda seed,
|
||||
/// consisting of a variable name and a vec of all the sub fields accessed
|
||||
/// on that variable name. For example, if a seed is `my_field.my_data.as_ref()`,
|
||||
/// then the field name is `my_field` and the vec of sub fields is `[my_data]`.
|
||||
struct SeedPath {
|
||||
/// Seed name
|
||||
name: String,
|
||||
/// All path components for the subfields accessed on this seed
|
||||
subfields: Vec<String>,
|
||||
}
|
||||
|
||||
impl SeedPath {
|
||||
/// Extract the seed path from a single seed expression.
|
||||
fn new(seed: &syn::Expr) -> Result<Self> {
|
||||
// Convert the seed into the raw string representation.
|
||||
let seed_str = seed.to_token_stream().to_string();
|
||||
|
||||
// Break up the seed into each subfield component.
|
||||
let mut components = seed_str.split('.').collect::<Vec<_>>();
|
||||
if components.len() <= 1 {
|
||||
return Err(anyhow!("Seed is in unexpected format: {seed:#?}"));
|
||||
}
|
||||
|
||||
// The name of the variable (or field).
|
||||
let name = components.remove(0).to_owned();
|
||||
|
||||
// The path to the seed (only if the `name` type is a struct).
|
||||
let mut path = Vec::new();
|
||||
while !components.is_empty() {
|
||||
let subfield = components.remove(0);
|
||||
if subfield.contains("()") {
|
||||
break;
|
||||
}
|
||||
path.push(subfield.into());
|
||||
}
|
||||
if path.len() == 1 && (path[0] == "key" || path[0] == "key()") {
|
||||
path = Vec::new();
|
||||
}
|
||||
|
||||
Ok(SeedPath {
|
||||
name,
|
||||
subfields: path,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the full path to the data this seed represents.
|
||||
fn path(&self) -> String {
|
||||
match self.subfields.len() {
|
||||
0 => self.name.to_owned(),
|
||||
_ => format!("{}.{}", self.name, self.subfields.join(".")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_relations(acc: &Field, accounts: &AccountsStruct) -> TokenStream {
|
||||
let relations = accounts
|
||||
.fields
|
||||
.iter()
|
||||
.filter_map(|af| match af {
|
||||
AccountField::Field(f) => f
|
||||
.constraints
|
||||
.has_one
|
||||
.iter()
|
||||
.filter_map(|c| match &c.join_target {
|
||||
syn::Expr::Path(path) => path
|
||||
.path
|
||||
.segments
|
||||
.first()
|
||||
.filter(|seg| seg.ident == acc.ident)
|
||||
.map(|_| Some(f.ident.to_string())),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Option<Vec<_>>>(),
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
quote! { vec![#(#relations.into()),*] }
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
use super::common::gen_print_section;
|
||||
|
||||
pub fn gen_idl_print_fn_address(address: String) -> TokenStream {
|
||||
let fn_body = gen_print_section("address", quote! { #address });
|
||||
|
||||
quote! {
|
||||
#[test]
|
||||
pub fn __anchor_private_print_idl_address() {
|
||||
#fn_body
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
|
||||
pub fn find_path(name: &str, path: impl AsRef<Path>) -> Result<PathBuf> {
|
||||
let parent_path = path.as_ref().parent().unwrap();
|
||||
for entry in fs::read_dir(parent_path)? {
|
||||
let entry = entry?;
|
||||
if entry.file_name().to_string_lossy() == name {
|
||||
return entry.path().canonicalize().map_err(Into::into);
|
||||
}
|
||||
}
|
||||
|
||||
find_path(name, parent_path)
|
||||
}
|
||||
|
||||
pub fn get_no_docs() -> bool {
|
||||
option_env!("ANCHOR_IDL_BUILD_NO_DOCS")
|
||||
.map(|val| val == "TRUE")
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn get_idl_module_path() -> TokenStream {
|
||||
quote!(anchor_lang::anchor_syn::idl::types)
|
||||
}
|
||||
|
||||
pub fn get_serde_json_module_path() -> TokenStream {
|
||||
quote!(anchor_lang::anchor_syn::idl::build::serde_json)
|
||||
}
|
||||
|
||||
pub fn gen_print_section(name: &str, value: impl ToTokens) -> TokenStream {
|
||||
let serde_json = get_serde_json_module_path();
|
||||
quote! {
|
||||
println!("--- IDL begin {} ---", #name);
|
||||
println!("{}", #serde_json::to_string_pretty(&{ #value }).unwrap());
|
||||
println!("--- IDL end {} ---", #name);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
use heck::SnakeCase;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
use super::{
|
||||
common::{gen_print_section, get_idl_module_path},
|
||||
defined::gen_idl_type,
|
||||
};
|
||||
|
||||
pub fn gen_idl_print_fn_constant(item: &syn::ItemConst) -> TokenStream {
|
||||
let idl = get_idl_module_path();
|
||||
|
||||
let name = item.ident.to_string();
|
||||
let expr = &item.expr;
|
||||
let fn_name = format_ident!("__anchor_private_print_idl_const_{}", name.to_snake_case());
|
||||
|
||||
let fn_body = match gen_idl_type(&item.ty, &[]) {
|
||||
Ok((ty, _)) => gen_print_section(
|
||||
"const",
|
||||
quote! {
|
||||
#idl::IdlConst {
|
||||
name: #name.into(),
|
||||
ty: #ty,
|
||||
value: format!("{:?}", #expr),
|
||||
}
|
||||
},
|
||||
),
|
||||
_ => quote! {},
|
||||
};
|
||||
|
||||
quote! {
|
||||
#[test]
|
||||
pub fn #fn_name() {
|
||||
#fn_body
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,654 @@
|
|||
use std::collections::BTreeMap;
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
use super::common::{get_idl_module_path, get_no_docs};
|
||||
use crate::{idl::types::IdlTypeDef, parser::docs};
|
||||
|
||||
/// A trait that types must implement in order to include the type in the IDL definition.
|
||||
///
|
||||
/// This trait is automatically implemented for Anchor all types that use the `AnchorSerialize`
|
||||
/// proc macro. Note that manually implementing the `AnchorSerialize` trait does **NOT** have the
|
||||
/// same effect.
|
||||
///
|
||||
/// Types that don't implement this trait will cause a compile error during the IDL generation.
|
||||
///
|
||||
/// The default implementation of the trait allows the program to compile but the type does **NOT**
|
||||
/// get included in the IDL.
|
||||
pub trait IdlBuild {
|
||||
/// Create an IDL type definition for the type.
|
||||
///
|
||||
/// The type is only included in the IDL if this method returns `Some`.
|
||||
fn create_type() -> Option<IdlTypeDef> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Insert all types that are included in the current type definition to the given map.
|
||||
fn insert_types(_types: &mut BTreeMap<String, IdlTypeDef>) {}
|
||||
|
||||
/// Get the full module path of the type.
|
||||
///
|
||||
/// The full path will be used in the case of a conflicting type definition, e.g. when there
|
||||
/// are multiple structs with the same name.
|
||||
///
|
||||
/// The default implementation covers most cases.
|
||||
fn get_full_path() -> String {
|
||||
std::any::type_name::<Self>().into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate [`IdlBuild`] impl for a struct.
|
||||
pub fn impl_idl_build_struct(item: &syn::ItemStruct) -> TokenStream {
|
||||
impl_idl_build(&item.ident, &item.generics, gen_idl_type_def_struct(item))
|
||||
}
|
||||
|
||||
/// Generate [`IdlBuild`] impl for an enum.
|
||||
pub fn impl_idl_build_enum(item: &syn::ItemEnum) -> TokenStream {
|
||||
impl_idl_build(&item.ident, &item.generics, gen_idl_type_def_enum(item))
|
||||
}
|
||||
|
||||
/// Generate [`IdlBuild`] impl for a union.
|
||||
///
|
||||
/// Unions are not currently supported in the IDL.
|
||||
pub fn impl_idl_build_union(item: &syn::ItemUnion) -> TokenStream {
|
||||
impl_idl_build(
|
||||
&item.ident,
|
||||
&item.generics,
|
||||
Err(anyhow!("Unions are not supported")),
|
||||
)
|
||||
}
|
||||
|
||||
/// Generate [`IdlBuild`] implementation.
|
||||
fn impl_idl_build(
|
||||
ident: &syn::Ident,
|
||||
generics: &syn::Generics,
|
||||
type_def: Result<(TokenStream, Vec<syn::TypePath>)>,
|
||||
) -> TokenStream {
|
||||
let idl = get_idl_module_path();
|
||||
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
||||
let idl_build_trait = quote!(anchor_lang::anchor_syn::idl::build::IdlBuild);
|
||||
|
||||
let (idl_type_def, insert_defined) = match type_def {
|
||||
Ok((ts, defined)) => (
|
||||
quote! { Some(#ts) },
|
||||
quote! {
|
||||
#(
|
||||
if let Some(ty) = <#defined>::create_type() {
|
||||
types.insert(<#defined>::get_full_path(), ty);
|
||||
<#defined>::insert_types(types);
|
||||
}
|
||||
);*
|
||||
},
|
||||
),
|
||||
_ => (quote! { None }, quote! {}),
|
||||
};
|
||||
|
||||
quote! {
|
||||
impl #impl_generics #idl_build_trait for #ident #ty_generics #where_clause {
|
||||
fn create_type() -> Option<#idl::IdlTypeDef> {
|
||||
#idl_type_def
|
||||
}
|
||||
|
||||
fn insert_types(
|
||||
types: &mut std::collections::BTreeMap<String, #idl::IdlTypeDef>
|
||||
) {
|
||||
#insert_defined
|
||||
}
|
||||
|
||||
fn get_full_path() -> String {
|
||||
format!("{}::{}", module_path!(), stringify!(#ident))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_idl_type_def_struct(
|
||||
strct: &syn::ItemStruct,
|
||||
) -> Result<(TokenStream, Vec<syn::TypePath>)> {
|
||||
gen_idl_type_def(&strct.attrs, &strct.generics, |generic_params| {
|
||||
let no_docs = get_no_docs();
|
||||
let idl = get_idl_module_path();
|
||||
|
||||
let (fields, defined) = match &strct.fields {
|
||||
syn::Fields::Unit => (quote! { None }, vec![]),
|
||||
syn::Fields::Named(fields) => {
|
||||
let (fields, defined) = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|f| gen_idl_field(f, generic_params, no_docs))
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.into_iter()
|
||||
.unzip::<_, _, Vec<_>, Vec<_>>();
|
||||
|
||||
(
|
||||
quote! { Some(#idl::IdlDefinedFields::Named(vec![#(#fields),*])) },
|
||||
defined,
|
||||
)
|
||||
}
|
||||
syn::Fields::Unnamed(fields) => {
|
||||
let (types, defined) = fields
|
||||
.unnamed
|
||||
.iter()
|
||||
.map(|f| gen_idl_type(&f.ty, generic_params))
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.into_iter()
|
||||
.unzip::<_, Vec<_>, Vec<_>, Vec<_>>();
|
||||
|
||||
(
|
||||
quote! { Some(#idl::IdlDefinedFields::Tuple(vec![#(#types),*])) },
|
||||
defined,
|
||||
)
|
||||
}
|
||||
};
|
||||
let defined = defined.into_iter().flatten().collect::<Vec<_>>();
|
||||
|
||||
Ok((
|
||||
quote! {
|
||||
#idl::IdlTypeDefTy::Struct {
|
||||
fields: #fields,
|
||||
}
|
||||
},
|
||||
defined,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn gen_idl_type_def_enum(enm: &syn::ItemEnum) -> Result<(TokenStream, Vec<syn::TypePath>)> {
|
||||
gen_idl_type_def(&enm.attrs, &enm.generics, |generic_params| {
|
||||
let no_docs = get_no_docs();
|
||||
let idl = get_idl_module_path();
|
||||
|
||||
let (variants, defined) = enm
|
||||
.variants
|
||||
.iter()
|
||||
.map(|variant| {
|
||||
let name = variant.ident.to_string();
|
||||
let (fields, defined) = match &variant.fields {
|
||||
syn::Fields::Unit => (quote! { None }, vec![]),
|
||||
syn::Fields::Named(fields) => {
|
||||
let (fields, defined) = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|f| gen_idl_field(f, generic_params, no_docs))
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.into_iter()
|
||||
.unzip::<_, Vec<_>, Vec<_>, Vec<_>>();
|
||||
let defined = defined.into_iter().flatten().collect::<Vec<_>>();
|
||||
|
||||
(
|
||||
quote! { Some(#idl::IdlDefinedFields::Named(vec![#(#fields),*])) },
|
||||
defined,
|
||||
)
|
||||
}
|
||||
syn::Fields::Unnamed(fields) => {
|
||||
let (types, defined) = fields
|
||||
.unnamed
|
||||
.iter()
|
||||
.map(|f| gen_idl_type(&f.ty, generic_params))
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.into_iter()
|
||||
.unzip::<_, Vec<_>, Vec<_>, Vec<_>>();
|
||||
let defined = defined.into_iter().flatten().collect::<Vec<_>>();
|
||||
|
||||
(
|
||||
quote! { Some(#idl::IdlDefinedFields::Tuple(vec![#(#types),*])) },
|
||||
defined,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
Ok((
|
||||
quote! { #idl::IdlEnumVariant { name: #name.into(), fields: #fields } },
|
||||
defined,
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.into_iter()
|
||||
.unzip::<_, _, Vec<_>, Vec<_>>();
|
||||
let defined = defined.into_iter().flatten().collect::<Vec<_>>();
|
||||
|
||||
Ok((
|
||||
quote! {
|
||||
#idl::IdlTypeDefTy::Enum {
|
||||
variants: vec![#(#variants),*],
|
||||
}
|
||||
},
|
||||
defined,
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
fn gen_idl_type_def<F>(
|
||||
attrs: &[syn::Attribute],
|
||||
generics: &syn::Generics,
|
||||
create_fields: F,
|
||||
) -> Result<(TokenStream, Vec<syn::TypePath>)>
|
||||
where
|
||||
F: Fn(&[syn::Ident]) -> Result<(TokenStream, Vec<syn::TypePath>)>,
|
||||
{
|
||||
let no_docs = get_no_docs();
|
||||
let idl = get_idl_module_path();
|
||||
|
||||
let docs = match docs::parse(attrs) {
|
||||
Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
|
||||
_ => quote! { vec![] },
|
||||
};
|
||||
|
||||
let serialization = get_attr_str("derive", attrs)
|
||||
.and_then(|derive| {
|
||||
if derive.contains("bytemuck") {
|
||||
if derive.to_lowercase().contains("unsafe") {
|
||||
Some(quote! { #idl::IdlSerialization::BytemuckUnsafe })
|
||||
} else {
|
||||
Some(quote! { #idl::IdlSerialization::Bytemuck })
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(|| quote! { #idl::IdlSerialization::default() });
|
||||
|
||||
let repr = get_attr_str("repr", attrs)
|
||||
.map(|repr| {
|
||||
let packed = repr.contains("packed");
|
||||
let align = repr
|
||||
.find("align")
|
||||
.and_then(|i| repr.get(i..))
|
||||
.and_then(|align| {
|
||||
align
|
||||
.find('(')
|
||||
.and_then(|start| align.find(')').and_then(|end| align.get(start + 1..end)))
|
||||
})
|
||||
.and_then(|size| size.parse::<usize>().ok())
|
||||
.map(|size| quote! { Some(#size) })
|
||||
.unwrap_or_else(|| quote! { None });
|
||||
let modifier = quote! {
|
||||
#idl::IdlReprModifier {
|
||||
packed: #packed,
|
||||
align: #align,
|
||||
}
|
||||
};
|
||||
|
||||
if repr.contains("transparent") {
|
||||
quote! { #idl::IdlRepr::Transparent }
|
||||
} else if repr.contains('C') {
|
||||
quote! { #idl::IdlRepr::C(#modifier) }
|
||||
} else {
|
||||
quote! { #idl::IdlRepr::Rust(#modifier) }
|
||||
}
|
||||
})
|
||||
.map(|repr| quote! { Some(#repr) })
|
||||
.unwrap_or_else(|| quote! { None });
|
||||
|
||||
let generic_params = 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 (ty, defined) = create_fields(&generic_params)?;
|
||||
|
||||
let generics = generics
|
||||
.params
|
||||
.iter()
|
||||
.filter_map(|p| match p {
|
||||
syn::GenericParam::Type(ty) => {
|
||||
let name = ty.ident.to_string();
|
||||
Some(quote! {
|
||||
#idl::IdlTypeDefGeneric::Type {
|
||||
name: #name.into(),
|
||||
}
|
||||
})
|
||||
}
|
||||
syn::GenericParam::Const(c) => {
|
||||
let name = c.ident.to_string();
|
||||
let ty = match &c.ty {
|
||||
syn::Type::Path(path) => get_first_segment(path).ident.to_string(),
|
||||
_ => unreachable!("Const generic type can only be path"),
|
||||
};
|
||||
Some(quote! {
|
||||
#idl::IdlTypeDefGeneric::Const {
|
||||
name: #name.into(),
|
||||
ty: #ty.into(),
|
||||
}
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok((
|
||||
quote! {
|
||||
#idl::IdlTypeDef {
|
||||
name: Self::get_full_path(),
|
||||
docs: #docs,
|
||||
serialization: #serialization,
|
||||
repr: #repr,
|
||||
generics: vec![#(#generics.into()),*],
|
||||
ty: #ty,
|
||||
}
|
||||
},
|
||||
defined,
|
||||
))
|
||||
}
|
||||
|
||||
fn get_attr_str(name: impl AsRef<str>, attrs: &[syn::Attribute]) -> Option<String> {
|
||||
attrs
|
||||
.iter()
|
||||
.filter(|attr| {
|
||||
attr.path
|
||||
.segments
|
||||
.first()
|
||||
.filter(|seg| seg.ident == name)
|
||||
.is_some()
|
||||
})
|
||||
.map(|attr| attr.tokens.to_string())
|
||||
.reduce(|acc, cur| {
|
||||
format!(
|
||||
"{} , {}",
|
||||
acc.get(..acc.len() - 1).unwrap(),
|
||||
cur.get(1..).unwrap()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn gen_idl_field(
|
||||
field: &syn::Field,
|
||||
generic_params: &[syn::Ident],
|
||||
no_docs: bool,
|
||||
) -> Result<(TokenStream, Vec<syn::TypePath>)> {
|
||||
let idl = get_idl_module_path();
|
||||
|
||||
let name = field.ident.as_ref().unwrap().to_string();
|
||||
let docs = match docs::parse(&field.attrs) {
|
||||
Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
|
||||
_ => quote! { vec![] },
|
||||
};
|
||||
let (ty, defined) = gen_idl_type(&field.ty, generic_params)?;
|
||||
|
||||
Ok((
|
||||
quote! {
|
||||
#idl::IdlField {
|
||||
name: #name.into(),
|
||||
docs: #docs,
|
||||
ty: #ty,
|
||||
}
|
||||
},
|
||||
defined,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn gen_idl_type(
|
||||
ty: &syn::Type,
|
||||
generic_params: &[syn::Ident],
|
||||
) -> Result<(TokenStream, Vec<syn::TypePath>)> {
|
||||
let idl = get_idl_module_path();
|
||||
|
||||
fn the_only_segment_is(path: &syn::TypePath, cmp: &str) -> bool {
|
||||
if path.path.segments.len() != 1 {
|
||||
return false;
|
||||
};
|
||||
return get_first_segment(path).ident == cmp;
|
||||
}
|
||||
|
||||
fn get_angle_bracketed_type_args(seg: &syn::PathSegment) -> Vec<&syn::Type> {
|
||||
match &seg.arguments {
|
||||
syn::PathArguments::AngleBracketed(ab) => ab
|
||||
.args
|
||||
.iter()
|
||||
.filter_map(|arg| match arg {
|
||||
syn::GenericArgument::Type(ty) => Some(ty),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
_ => panic!("No angle bracket for {seg:#?}"),
|
||||
}
|
||||
}
|
||||
|
||||
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::Pubkey }, vec![]))
|
||||
}
|
||||
syn::Type::Path(path) if the_only_segment_is(path, "Option") => {
|
||||
let segment = get_first_segment(path);
|
||||
let arg = get_angle_bracketed_type_args(segment)
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap();
|
||||
let (inner, defined) = gen_idl_type(arg, generic_params)?;
|
||||
Ok((quote! { #idl::IdlType::Option(Box::new(#inner)) }, defined))
|
||||
}
|
||||
syn::Type::Path(path) if the_only_segment_is(path, "Vec") => {
|
||||
let segment = get_first_segment(path);
|
||||
let arg = get_angle_bracketed_type_args(segment)
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap();
|
||||
match arg {
|
||||
syn::Type::Path(path) if the_only_segment_is(path, "u8") => {
|
||||
return Ok((quote! {#idl::IdlType::Bytes}, vec![]));
|
||||
}
|
||||
_ => (),
|
||||
};
|
||||
let (inner, defined) = gen_idl_type(arg, generic_params)?;
|
||||
Ok((quote! { #idl::IdlType::Vec(Box::new(#inner)) }, defined))
|
||||
}
|
||||
syn::Type::Path(path) if the_only_segment_is(path, "Box") => {
|
||||
let segment = get_first_segment(path);
|
||||
let arg = get_angle_bracketed_type_args(segment)
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap();
|
||||
gen_idl_type(arg, generic_params)
|
||||
}
|
||||
syn::Type::Array(arr) => {
|
||||
let len = &arr.len;
|
||||
let is_generic = generic_params.iter().any(|param| match len {
|
||||
syn::Expr::Path(path) => path.path.is_ident(param),
|
||||
_ => false,
|
||||
});
|
||||
|
||||
let len = if is_generic {
|
||||
match len {
|
||||
syn::Expr::Path(len) => {
|
||||
let len = len.path.get_ident().unwrap().to_string();
|
||||
quote! { #idl::IdlArrayLen::Generic(#len.into()) }
|
||||
}
|
||||
_ => unreachable!("Array length can only be a generic parameter"),
|
||||
}
|
||||
} else {
|
||||
quote! { #idl::IdlArrayLen::Value(#len) }
|
||||
};
|
||||
|
||||
let (inner, defined) = gen_idl_type(&arr.elem, generic_params)?;
|
||||
Ok((
|
||||
quote! { #idl::IdlType::Array(Box::new(#inner), #len) },
|
||||
defined,
|
||||
))
|
||||
}
|
||||
// Defined
|
||||
syn::Type::Path(path) => {
|
||||
let is_generic_param = generic_params.iter().any(|param| path.path.is_ident(param));
|
||||
if is_generic_param {
|
||||
let generic = get_first_segment(path).ident.to_string();
|
||||
return Ok((quote! { #idl::IdlType::Generic(#generic.into()) }, vec![]));
|
||||
}
|
||||
|
||||
// Handle type aliases and external types
|
||||
#[cfg(procmacro2_semver_exempt)]
|
||||
{
|
||||
use super::{common::find_path, external::get_external_type};
|
||||
use crate::parser::context::CrateContext;
|
||||
use quote::ToTokens;
|
||||
|
||||
let source_path = proc_macro2::Span::call_site().source_file().path();
|
||||
let lib_path = find_path("lib.rs", &source_path).expect("lib.rs should exist");
|
||||
|
||||
if let Ok(ctx) = CrateContext::parse(lib_path) {
|
||||
let name = path.path.segments.last().unwrap().ident.to_string();
|
||||
let alias = ctx.type_aliases().find(|ty| ty.ident == name);
|
||||
if let Some(alias) = alias {
|
||||
if let Some(segment) = path.path.segments.last() {
|
||||
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
|
||||
let inners = args
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| match arg {
|
||||
syn::GenericArgument::Type(ty) => match ty {
|
||||
syn::Type::Path(inner_ty) => {
|
||||
inner_ty.path.to_token_stream().to_string()
|
||||
}
|
||||
_ => {
|
||||
unimplemented!("Inner type not implemented: {ty:?}")
|
||||
}
|
||||
},
|
||||
syn::GenericArgument::Const(c) => {
|
||||
c.to_token_stream().to_string()
|
||||
}
|
||||
_ => unimplemented!("Arg not implemented: {arg:?}"),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let outer = match &*alias.ty {
|
||||
syn::Type::Path(outer_ty) => outer_ty.path.to_token_stream(),
|
||||
syn::Type::Array(outer_ty) => outer_ty.to_token_stream(),
|
||||
_ => unimplemented!("Type not implemented: {:?}", alias.ty),
|
||||
}
|
||||
.to_string();
|
||||
|
||||
let resolved_alias = alias
|
||||
.generics
|
||||
.params
|
||||
.iter()
|
||||
.map(|param| match param {
|
||||
syn::GenericParam::Const(param) => param.ident.to_string(),
|
||||
syn::GenericParam::Type(param) => param.ident.to_string(),
|
||||
_ => panic!("Lifetime parameters are not allowed"),
|
||||
})
|
||||
.enumerate()
|
||||
.fold(outer, |acc, (i, cur)| {
|
||||
let inner = &inners[i];
|
||||
acc.replace(&format!(" {cur}"), &format!(" {inner} "))
|
||||
.replace(&format!(" {cur},"), &format!(" {inner},"))
|
||||
.replace(&format!("[{cur} "), &format!("[{inner} ",))
|
||||
.replace(&format!(" {cur}]"), &format!(" {inner}]"))
|
||||
});
|
||||
if let Ok(ty) = syn::parse_str(&resolved_alias) {
|
||||
return gen_idl_type(&ty, generic_params);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Non-generic type alias e.g. `type UnixTimestamp = i64`
|
||||
return gen_idl_type(&*alias.ty, generic_params);
|
||||
}
|
||||
|
||||
// Handle external types
|
||||
let is_external = ctx
|
||||
.structs()
|
||||
.map(|s| s.ident.to_string())
|
||||
.chain(ctx.enums().map(|e| e.ident.to_string()))
|
||||
.find(|defined| defined == &name)
|
||||
.is_none();
|
||||
if is_external {
|
||||
if let Ok(Some(ty)) = get_external_type(&name, source_path) {
|
||||
return gen_idl_type(&ty, generic_params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Defined in crate
|
||||
let mut generics = vec![];
|
||||
let mut defined = vec![path.clone()];
|
||||
|
||||
if let Some(segment) = path.path.segments.last() {
|
||||
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
|
||||
for arg in &args.args {
|
||||
match arg {
|
||||
syn::GenericArgument::Type(ty) => {
|
||||
let (ty, def) = gen_idl_type(ty, generic_params)?;
|
||||
generics.push(quote! { #idl::IdlGenericArg::Type { ty: #ty } });
|
||||
defined.extend(def);
|
||||
}
|
||||
syn::GenericArgument::Const(c) => generics.push(
|
||||
quote! { #idl::IdlGenericArg::Const { value: #c.to_string() } },
|
||||
),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
quote! {
|
||||
#idl::IdlType::Defined {
|
||||
name: <#path>::get_full_path(),
|
||||
generics: vec![#(#generics),*],
|
||||
}
|
||||
},
|
||||
defined,
|
||||
))
|
||||
}
|
||||
syn::Type::Reference(reference) => match reference.elem.as_ref() {
|
||||
syn::Type::Slice(slice) if matches!(&*slice.elem, syn::Type::Path(path) if the_only_segment_is(path, "u8")) => {
|
||||
Ok((quote! {#idl::IdlType::Bytes}, vec![]))
|
||||
}
|
||||
_ => gen_idl_type(&reference.elem, generic_params),
|
||||
},
|
||||
_ => Err(anyhow!("Unknown type: {ty:#?}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_first_segment(type_path: &syn::TypePath) -> &syn::PathSegment {
|
||||
type_path.path.segments.first().unwrap()
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
use heck::SnakeCase;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
use super::common::{gen_print_section, get_idl_module_path};
|
||||
use crate::Error;
|
||||
|
||||
pub fn gen_idl_print_fn_error(error: &Error) -> TokenStream {
|
||||
let idl = get_idl_module_path();
|
||||
|
||||
let fn_name = format_ident!(
|
||||
"__anchor_private_print_idl_error_{}",
|
||||
error.ident.to_string().to_snake_case()
|
||||
);
|
||||
|
||||
let error_codes = error
|
||||
.codes
|
||||
.iter()
|
||||
.map(|code| {
|
||||
let id = code.id;
|
||||
let name = code.ident.to_string();
|
||||
let msg = match &code.msg {
|
||||
Some(msg) => quote! { Some(#msg.into()) },
|
||||
None => quote! { None },
|
||||
};
|
||||
|
||||
quote! {
|
||||
#idl::IdlErrorCode {
|
||||
code: anchor_lang::error::ERROR_CODE_OFFSET + #id,
|
||||
name: #name.into(),
|
||||
msg: #msg,
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let fn_body = gen_print_section("errors", quote! { vec![#(#error_codes),*] });
|
||||
|
||||
quote! {
|
||||
#[test]
|
||||
pub fn #fn_name() {
|
||||
#fn_body
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
use heck::SnakeCase;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
use super::{
|
||||
common::{gen_print_section, get_idl_module_path, get_serde_json_module_path},
|
||||
defined::gen_idl_type_def_struct,
|
||||
};
|
||||
|
||||
pub fn gen_idl_print_fn_event(event_struct: &syn::ItemStruct) -> TokenStream {
|
||||
let idl = get_idl_module_path();
|
||||
let serde_json = get_serde_json_module_path();
|
||||
|
||||
let ident = &event_struct.ident;
|
||||
let fn_name = format_ident!(
|
||||
"__anchor_private_print_idl_event_{}",
|
||||
ident.to_string().to_snake_case()
|
||||
);
|
||||
let idl_build_impl = impl_idl_build_event(event_struct);
|
||||
|
||||
let print_ts = gen_print_section(
|
||||
"event",
|
||||
quote! {
|
||||
#serde_json::json!({
|
||||
"event": event,
|
||||
"types": types.into_values().collect::<Vec<_>>()
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
#idl_build_impl
|
||||
|
||||
#[test]
|
||||
pub fn #fn_name() {
|
||||
let mut types: std::collections::BTreeMap<String, #idl::IdlTypeDef> =
|
||||
std::collections::BTreeMap::new();
|
||||
if let Some(event) = #ident::__anchor_private_gen_idl_event(&mut types) {
|
||||
#print_ts
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate IDL build impl for an event.
|
||||
fn impl_idl_build_event(event_struct: &syn::ItemStruct) -> TokenStream {
|
||||
let idl = get_idl_module_path();
|
||||
|
||||
let ident = &event_struct.ident;
|
||||
let (impl_generics, ty_generics, where_clause) = event_struct.generics.split_for_impl();
|
||||
|
||||
let fn_body = match gen_idl_type_def_struct(event_struct) {
|
||||
Ok((ts, defined)) => quote! {
|
||||
#(
|
||||
if let Some(ty) = <#defined>::create_type() {
|
||||
types.insert(<#defined>::get_full_path(), ty);
|
||||
<#defined>::insert_types(types);
|
||||
}
|
||||
);*
|
||||
|
||||
let ty = #ts;
|
||||
let event = #idl::IdlEvent {
|
||||
name: ty.name.clone(),
|
||||
discriminator: <Self as anchor_lang::Discriminator>::DISCRIMINATOR.into(),
|
||||
};
|
||||
types.insert(ty.name.clone(), ty);
|
||||
Some(event)
|
||||
},
|
||||
_ => quote! { None },
|
||||
};
|
||||
|
||||
quote! {
|
||||
impl #impl_generics #ident #ty_generics #where_clause {
|
||||
pub fn __anchor_private_gen_idl_event(
|
||||
types: &mut std::collections::BTreeMap<String, #idl::IdlTypeDef>,
|
||||
) -> Option<#idl::IdlEvent> {
|
||||
#fn_body
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use cargo_toml::Manifest;
|
||||
use quote::ToTokens;
|
||||
|
||||
use super::common::find_path;
|
||||
use crate::parser::context::CrateContext;
|
||||
|
||||
pub fn get_external_type(name: &str, path: impl AsRef<Path>) -> Result<Option<syn::Type>> {
|
||||
let use_path = get_uses(path.as_ref())?
|
||||
.into_iter()
|
||||
.find(|u| u.split("::").last().unwrap() == name)
|
||||
.ok_or_else(|| anyhow!("`{name}` not found in use statements"))?;
|
||||
|
||||
// Get crate name and version from lock file
|
||||
let lib_path = find_path("lib.rs", path)?;
|
||||
let lock_path = find_path("Cargo.lock", lib_path)?;
|
||||
let lock_file = parse_lock_file(lock_path)?;
|
||||
let registry_path = get_registry_path()?;
|
||||
|
||||
recursively_find_type(name, &use_path, ®istry_path, &lock_file)
|
||||
}
|
||||
|
||||
fn recursively_find_type(
|
||||
defined_name: &str,
|
||||
use_path: &str,
|
||||
registry_path: &Path,
|
||||
lock_file: &[(String, String)],
|
||||
) -> Result<Option<syn::Type>> {
|
||||
let crate_name = use_path.split("::").next().unwrap();
|
||||
let (crate_name, version) = lock_file
|
||||
.iter()
|
||||
.find(|(name, _)| name == crate_name || name == &crate_name.replace('_', "-"))
|
||||
.ok_or_else(|| anyhow!("Crate should exist in the lock file"))?;
|
||||
|
||||
let crate_path = registry_path.join(format!("{crate_name}-{version}"));
|
||||
let lib_path = crate_path.join("src").join("lib.rs");
|
||||
let ctx = CrateContext::parse(&lib_path)?;
|
||||
|
||||
// TODO: Struct and enum
|
||||
|
||||
let alias = ctx.type_aliases().find(|item| item.ident == defined_name);
|
||||
match alias {
|
||||
Some(alias) => Ok(Some(*alias.ty.to_owned())),
|
||||
None => {
|
||||
// Check re-exported deps e.g. `anchor_lang::solana_program::...`
|
||||
let cargo_toml_path = find_path("Cargo.toml", &lib_path)?;
|
||||
let deps = Manifest::from_path(cargo_toml_path)?.dependencies;
|
||||
let paths = use_path.split("::").skip(1).collect::<Vec<_>>();
|
||||
let paths = paths.iter().enumerate().filter_map(|(i, path)| {
|
||||
if deps.contains_key(*path) || deps.contains_key(&path.replace('_', "-")) {
|
||||
Some(paths.iter().skip(i).copied().collect::<Vec<_>>().join("::"))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
for path in paths {
|
||||
let result = recursively_find_type(defined_name, &path, registry_path, lock_file);
|
||||
if result.is_ok() {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_registry_path() -> Result<PathBuf> {
|
||||
#[allow(deprecated)]
|
||||
let path = env::home_dir()
|
||||
.unwrap()
|
||||
.join(".cargo")
|
||||
.join("registry")
|
||||
.join("src");
|
||||
fs::read_dir(&path)?
|
||||
.filter_map(|entry| entry.ok())
|
||||
.find_map(|entry| {
|
||||
let file_name = entry.file_name();
|
||||
if file_name.to_string_lossy().starts_with("index.crates.io") {
|
||||
Some(file_name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|name| path.join(name))
|
||||
.ok_or_else(|| anyhow!("crates.io registry not found"))
|
||||
}
|
||||
|
||||
fn parse_lock_file(path: impl AsRef<Path>) -> Result<Vec<(String, String)>> {
|
||||
let parsed = fs::read_to_string(path.as_ref())?
|
||||
.split("[[package]]")
|
||||
.skip(1)
|
||||
.map(|pkg| {
|
||||
let get_value = |key: &str| -> String {
|
||||
pkg.lines()
|
||||
.find(|line| line.starts_with(key))
|
||||
.expect(&format!("`{key}` line not found"))
|
||||
.split('"')
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
.to_owned()
|
||||
};
|
||||
let name = get_value("name");
|
||||
let version = get_value("version");
|
||||
(name, version)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
fn get_uses(path: impl AsRef<Path>) -> Result<Vec<String>> {
|
||||
let content = fs::read_to_string(path.as_ref())?;
|
||||
let uses = syn::parse_file(&content)?
|
||||
.items
|
||||
.into_iter()
|
||||
.filter_map(|item| match item {
|
||||
syn::Item::Use(u) => Some(flatten_uses(&u.tree)),
|
||||
_ => None,
|
||||
})
|
||||
.flatten()
|
||||
.collect::<Vec<_>>();
|
||||
Ok(uses)
|
||||
}
|
||||
|
||||
fn flatten_uses(tree: &syn::UseTree) -> Vec<String> {
|
||||
match tree {
|
||||
syn::UseTree::Group(group) => group.items.iter().flat_map(flatten_uses).collect(),
|
||||
syn::UseTree::Path(path) => flatten_uses(&path.tree)
|
||||
.into_iter()
|
||||
.map(|item| format!("{}::{}", path.ident, item))
|
||||
.collect(),
|
||||
syn::UseTree::Glob(glob) => {
|
||||
vec![format!("{}", glob.star_token.to_token_stream().to_string())]
|
||||
}
|
||||
syn::UseTree::Name(name) => vec![name.ident.to_string()],
|
||||
syn::UseTree::Rename(rename) => vec![rename.ident.to_string()],
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
mod accounts;
|
||||
mod address;
|
||||
mod common;
|
||||
mod constant;
|
||||
mod defined;
|
||||
mod error;
|
||||
mod event;
|
||||
mod external;
|
||||
mod program;
|
||||
|
||||
pub use accounts::gen_idl_build_impl_accounts_struct;
|
||||
pub use address::gen_idl_print_fn_address;
|
||||
pub use constant::gen_idl_print_fn_constant;
|
||||
pub use defined::{impl_idl_build_enum, impl_idl_build_struct, impl_idl_build_union, IdlBuild};
|
||||
pub use error::gen_idl_print_fn_error;
|
||||
pub use event::gen_idl_print_fn_event;
|
||||
pub use program::gen_idl_print_fn_program;
|
||||
|
||||
pub use serde_json;
|
|
@ -0,0 +1,140 @@
|
|||
use anyhow::Result;
|
||||
use heck::CamelCase;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
use super::{
|
||||
common::{gen_print_section, get_idl_module_path, get_no_docs},
|
||||
defined::gen_idl_type,
|
||||
};
|
||||
use crate::{parser::docs, Program};
|
||||
|
||||
/// Generate the IDL build print function for the program module.
|
||||
pub fn gen_idl_print_fn_program(program: &Program) -> TokenStream {
|
||||
let idl = get_idl_module_path();
|
||||
let no_docs = get_no_docs();
|
||||
|
||||
let name = program.name.to_string();
|
||||
let docs = match &program.docs {
|
||||
Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
|
||||
_ => quote! { vec![] },
|
||||
};
|
||||
|
||||
let (instructions, defined) = program
|
||||
.ixs
|
||||
.iter()
|
||||
.flat_map(|ix| -> Result<_> {
|
||||
let name = ix.ident.to_string();
|
||||
let name_pascal = format_ident!("{}", name.to_camel_case());
|
||||
let ctx_ident = &ix.anchor_ident;
|
||||
let discriminator = quote! {
|
||||
<crate::instruction::#name_pascal as anchor_lang::Discriminator>::DISCRIMINATOR
|
||||
};
|
||||
|
||||
let docs = match &ix.docs {
|
||||
Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
|
||||
_ => quote! { vec![] },
|
||||
};
|
||||
|
||||
let (args, mut defined) = ix
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
let name = arg.name.to_string();
|
||||
let docs = match docs::parse(&arg.raw_arg.attrs) {
|
||||
Some(docs) if !no_docs => quote! { vec![#(#docs.into()),*] },
|
||||
_ => quote! { vec![] },
|
||||
};
|
||||
let (ty, defined) = gen_idl_type(&arg.raw_arg.ty, &[])?;
|
||||
|
||||
Ok((
|
||||
quote! {
|
||||
#idl::IdlField {
|
||||
name: #name.into(),
|
||||
docs: #docs,
|
||||
ty: #ty,
|
||||
}
|
||||
},
|
||||
defined,
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.into_iter()
|
||||
.unzip::<_, Vec<_>, Vec<_>, Vec<_>>();
|
||||
|
||||
let returns = match gen_idl_type(&ix.returns.ty, &[]) {
|
||||
Ok((ty, def)) => {
|
||||
defined.push(def);
|
||||
quote! { Some(#ty) }
|
||||
}
|
||||
_ => quote! { None },
|
||||
};
|
||||
|
||||
Ok((
|
||||
quote! {
|
||||
#idl::IdlInstruction {
|
||||
name: #name.into(),
|
||||
docs: #docs,
|
||||
discriminator: #discriminator.into(),
|
||||
accounts: #ctx_ident::__anchor_private_gen_idl_accounts(
|
||||
&mut accounts,
|
||||
&mut types,
|
||||
),
|
||||
args: vec![#(#args),*],
|
||||
returns: #returns,
|
||||
}
|
||||
},
|
||||
defined,
|
||||
))
|
||||
})
|
||||
.unzip::<_, Vec<_>, Vec<_>, Vec<_>>();
|
||||
let defined = defined.into_iter().flatten().flatten().collect::<Vec<_>>();
|
||||
|
||||
let fn_body = gen_print_section(
|
||||
"program",
|
||||
quote! {
|
||||
let mut accounts: std::collections::BTreeMap<String, #idl::IdlAccount> =
|
||||
std::collections::BTreeMap::new();
|
||||
let mut types: std::collections::BTreeMap<String, #idl::IdlTypeDef> =
|
||||
std::collections::BTreeMap::new();
|
||||
|
||||
#(
|
||||
if let Some(ty) = <#defined>::create_type() {
|
||||
types.insert(<#defined>::get_full_path(), ty);
|
||||
<#defined>::insert_types(&mut types);
|
||||
}
|
||||
);*
|
||||
|
||||
#idl::Idl {
|
||||
address: Default::default(),
|
||||
metadata: #idl::IdlMetadata {
|
||||
name: #name.into(),
|
||||
version: env!("CARGO_PKG_VERSION").into(),
|
||||
spec: #idl::IDL_SPEC.into(),
|
||||
description: option_env!("CARGO_PKG_DESCRIPTION")
|
||||
.filter(|d| !d.is_empty())
|
||||
.map(|d| d.into()),
|
||||
repository: option_env!("CARGO_PKG_REPOSITORY")
|
||||
.filter(|r| !r.is_empty())
|
||||
.map(|r| r.into()),
|
||||
dependencies: Default::default(),
|
||||
contact: Default::default(),
|
||||
},
|
||||
docs: #docs,
|
||||
instructions: vec![#(#instructions),*],
|
||||
accounts: accounts.into_values().collect(),
|
||||
events: Default::default(),
|
||||
errors: Default::default(),
|
||||
types: types.into_values().collect(),
|
||||
constants: Default::default(),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
quote! {
|
||||
#[test]
|
||||
pub fn __anchor_private_print_idl_program() {
|
||||
#fn_body
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,3 @@ pub mod types;
|
|||
|
||||
#[cfg(feature = "idl-build")]
|
||||
pub mod build;
|
||||
|
||||
#[cfg(feature = "idl-parse")]
|
||||
pub mod parse;
|
||||
|
|
|
@ -1,590 +0,0 @@
|
|||
use crate::idl::types::*;
|
||||
use crate::parser::context::CrateContext;
|
||||
use crate::parser::{self, accounts, docs, error, program};
|
||||
use crate::Ty;
|
||||
use crate::{AccountField, AccountsStruct};
|
||||
use anyhow::anyhow;
|
||||
use anyhow::Result;
|
||||
use heck::MixedCase;
|
||||
use quote::ToTokens;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use syn::{
|
||||
Expr, ExprLit, ItemConst,
|
||||
Lit::{Byte, ByteStr},
|
||||
};
|
||||
|
||||
use super::relations;
|
||||
|
||||
const DERIVE_NAME: &str = "Accounts";
|
||||
// TODO: share this with `anchor_lang` crate.
|
||||
const ERROR_CODE_OFFSET: u32 = 6000;
|
||||
|
||||
/// Parse an entire interface file.
|
||||
pub fn parse(
|
||||
path: impl AsRef<Path>,
|
||||
version: String,
|
||||
seeds_feature: bool,
|
||||
no_docs: bool,
|
||||
safety_checks: bool,
|
||||
) -> Result<Idl> {
|
||||
let ctx = CrateContext::parse(path)?;
|
||||
if safety_checks {
|
||||
ctx.safety_checks()?;
|
||||
}
|
||||
|
||||
let program_mod = parse_program_mod(&ctx)?;
|
||||
let mut program = program::parse(program_mod)?;
|
||||
|
||||
if no_docs {
|
||||
program.docs = None;
|
||||
for ix in &mut program.ixs {
|
||||
ix.docs = None;
|
||||
}
|
||||
}
|
||||
|
||||
let accs = parse_account_derives(&ctx);
|
||||
|
||||
let error = parse_error_enum(&ctx).map(|mut e| error::parse(&mut e, None));
|
||||
let error_codes = error.as_ref().map(|e| {
|
||||
e.codes
|
||||
.iter()
|
||||
.map(|code| IdlErrorCode {
|
||||
code: ERROR_CODE_OFFSET + code.id,
|
||||
name: code.ident.to_string(),
|
||||
msg: code.msg.clone(),
|
||||
})
|
||||
.collect::<Vec<IdlErrorCode>>()
|
||||
});
|
||||
|
||||
let instructions = program
|
||||
.ixs
|
||||
.iter()
|
||||
.map(|ix| {
|
||||
let args = ix
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
let doc = if !no_docs {
|
||||
docs::parse(&arg.raw_arg.attrs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
IdlField {
|
||||
name: arg.name.to_string().to_mixed_case(),
|
||||
docs: doc,
|
||||
ty: to_idl_type(&ctx, &arg.raw_arg.ty),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
// todo: don't unwrap
|
||||
let accounts_strct = accs.get(&ix.anchor_ident.to_string()).unwrap();
|
||||
let accounts = idl_accounts(&ctx, accounts_strct, &accs, seeds_feature, no_docs);
|
||||
let ret_type_str = ix.returns.ty.to_token_stream().to_string();
|
||||
let returns = match ret_type_str.as_str() {
|
||||
"()" => None,
|
||||
_ => Some(ret_type_str.parse().unwrap()),
|
||||
};
|
||||
IdlInstruction {
|
||||
name: ix.ident.to_string().to_mixed_case(),
|
||||
docs: ix.docs.clone(),
|
||||
accounts,
|
||||
args,
|
||||
returns,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let events = parse_events(&ctx)
|
||||
.iter()
|
||||
.map(|e: &&syn::ItemStruct| {
|
||||
let fields = match &e.fields {
|
||||
syn::Fields::Named(n) => n,
|
||||
_ => panic!("Event fields must be named"),
|
||||
};
|
||||
let fields = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|f: &syn::Field| {
|
||||
let index = match f.attrs.first() {
|
||||
None => false,
|
||||
Some(i) => parser::tts_to_string(&i.path) == "index",
|
||||
};
|
||||
IdlEventField {
|
||||
name: f.ident.clone().unwrap().to_string().to_mixed_case(),
|
||||
ty: to_idl_type(&ctx, &f.ty),
|
||||
index,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<IdlEventField>>();
|
||||
|
||||
IdlEvent {
|
||||
name: e.ident.to_string(),
|
||||
fields,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<IdlEvent>>();
|
||||
|
||||
// All user defined types.
|
||||
let mut accounts = vec![];
|
||||
let mut types = vec![];
|
||||
let ty_defs = parse_ty_defs(&ctx, no_docs)?;
|
||||
|
||||
let account_structs = parse_accounts(&ctx);
|
||||
let account_names: HashSet<String> = account_structs
|
||||
.iter()
|
||||
.map(|a| a.ident.to_string())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let error_name = error.map(|e| e.name).unwrap_or_default();
|
||||
|
||||
// All types that aren't in the accounts section, are in the types section.
|
||||
for ty_def in ty_defs {
|
||||
// Don't add the error type to the types or accounts sections.
|
||||
if ty_def.name != error_name {
|
||||
if account_names.contains(&ty_def.name) {
|
||||
accounts.push(ty_def);
|
||||
} else if !events.iter().any(|e| e.name == ty_def.name) {
|
||||
types.push(ty_def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let constants = parse_consts(&ctx)
|
||||
.iter()
|
||||
.map(|c: &&syn::ItemConst| to_idl_const(c))
|
||||
.collect::<Vec<IdlConst>>();
|
||||
|
||||
Ok(Idl {
|
||||
version,
|
||||
name: program.name.to_string(),
|
||||
docs: program.docs.clone(),
|
||||
instructions,
|
||||
types,
|
||||
accounts,
|
||||
events: if events.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(events)
|
||||
},
|
||||
errors: error_codes,
|
||||
metadata: None,
|
||||
constants,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse the main program mod.
|
||||
fn parse_program_mod(ctx: &CrateContext) -> Result<syn::ItemMod> {
|
||||
let root = ctx.root_module();
|
||||
let mods = root
|
||||
.items()
|
||||
.filter_map(|i| match i {
|
||||
syn::Item::Mod(item_mod) => {
|
||||
let mod_count = item_mod
|
||||
.attrs
|
||||
.iter()
|
||||
.filter(|attr| attr.path.segments.last().unwrap().ident == "program")
|
||||
.count();
|
||||
if mod_count != 1 {
|
||||
return None;
|
||||
}
|
||||
Some(item_mod)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
match mods.len() {
|
||||
0 => Err(anyhow!("Program module not found")),
|
||||
1 => Ok(mods[0].clone()),
|
||||
_ => Err(anyhow!("Multiple program modules are not allowed")),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_error_enum(ctx: &CrateContext) -> Option<syn::ItemEnum> {
|
||||
ctx.enums()
|
||||
.find(|item_enum| {
|
||||
let attrs_count = item_enum
|
||||
.attrs
|
||||
.iter()
|
||||
.filter(|attr| {
|
||||
let segment = attr.path.segments.last().unwrap();
|
||||
segment.ident == "error_code"
|
||||
})
|
||||
.count();
|
||||
match attrs_count {
|
||||
0 => false,
|
||||
1 => true,
|
||||
_ => panic!("Invalid syntax: one error attribute allowed"),
|
||||
}
|
||||
})
|
||||
.cloned()
|
||||
}
|
||||
|
||||
fn parse_events(ctx: &CrateContext) -> Vec<&syn::ItemStruct> {
|
||||
ctx.structs()
|
||||
.filter(|item_strct| {
|
||||
let attrs_count = item_strct
|
||||
.attrs
|
||||
.iter()
|
||||
.filter(|attr| {
|
||||
let segment = attr.path.segments.last().unwrap();
|
||||
segment.ident == "event"
|
||||
})
|
||||
.count();
|
||||
match attrs_count {
|
||||
0 => false,
|
||||
1 => true,
|
||||
_ => panic!("Invalid syntax: one event attribute allowed"),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_accounts(ctx: &CrateContext) -> Vec<&syn::ItemStruct> {
|
||||
ctx.structs()
|
||||
.filter(|item_strct| {
|
||||
let attrs_count = item_strct
|
||||
.attrs
|
||||
.iter()
|
||||
.filter(|attr| {
|
||||
let segment = attr.path.segments.last().unwrap();
|
||||
segment.ident == "account" || segment.ident == "associated"
|
||||
})
|
||||
.count();
|
||||
match attrs_count {
|
||||
0 => false,
|
||||
1 => true,
|
||||
_ => panic!("Invalid syntax: one account attribute allowed"),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Parse all structs implementing the `Accounts` trait.
|
||||
fn parse_account_derives(ctx: &CrateContext) -> HashMap<String, AccountsStruct> {
|
||||
// TODO: parse manual implementations. Currently we only look
|
||||
// for derives.
|
||||
ctx.structs()
|
||||
.filter_map(|i_strct| {
|
||||
for attr in &i_strct.attrs {
|
||||
if attr.path.is_ident("derive") && attr.tokens.to_string().contains(DERIVE_NAME) {
|
||||
let strct = accounts::parse(i_strct).expect("Code not parseable");
|
||||
return Some((strct.ident.to_string(), strct));
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn parse_consts(ctx: &CrateContext) -> Vec<&syn::ItemConst> {
|
||||
ctx.consts()
|
||||
.filter(|item_strct| {
|
||||
for attr in &item_strct.attrs {
|
||||
if attr.path.segments.last().unwrap().ident == "constant" {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Parse all user defined types in the file.
|
||||
fn parse_ty_defs(ctx: &CrateContext, no_docs: bool) -> Result<Vec<IdlTypeDefinition>> {
|
||||
ctx.structs()
|
||||
.filter_map(|item_strct| {
|
||||
// Only take public types
|
||||
if !matches!(&item_strct.vis, syn::Visibility::Public(_)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Only take serializable types
|
||||
let serializable = item_strct.attrs.iter().any(|attr| {
|
||||
let attr_string = attr.tokens.to_string();
|
||||
let attr_name = attr.path.segments.last().unwrap().ident.to_string();
|
||||
let attr_serializable = ["account", "associated", "event", "zero_copy"];
|
||||
|
||||
let derived_serializable = attr_name == "derive"
|
||||
&& attr_string.contains("AnchorSerialize")
|
||||
&& attr_string.contains("AnchorDeserialize");
|
||||
|
||||
attr_serializable.iter().any(|a| *a == attr_name) || derived_serializable
|
||||
});
|
||||
|
||||
if !serializable {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = item_strct.ident.to_string();
|
||||
let doc = if !no_docs {
|
||||
docs::parse(&item_strct.attrs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let fields = match &item_strct.fields {
|
||||
syn::Fields::Named(fields) => fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|f: &syn::Field| {
|
||||
let doc = if !no_docs {
|
||||
docs::parse(&f.attrs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Ok(IdlField {
|
||||
name: f.ident.as_ref().unwrap().to_string().to_mixed_case(),
|
||||
docs: doc,
|
||||
ty: to_idl_type(ctx, &f.ty),
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<IdlField>>>(),
|
||||
syn::Fields::Unnamed(_) => return None,
|
||||
_ => panic!("Empty structs are not allowed."),
|
||||
};
|
||||
|
||||
Some(fields.map(|fields| IdlTypeDefinition {
|
||||
name,
|
||||
generics: None,
|
||||
docs: doc,
|
||||
ty: IdlTypeDefinitionTy::Struct { fields },
|
||||
}))
|
||||
})
|
||||
.chain(ctx.enums().filter_map(|enm| {
|
||||
// Only take public types
|
||||
if !matches!(&enm.vis, syn::Visibility::Public(_)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = enm.ident.to_string();
|
||||
let doc = if !no_docs {
|
||||
docs::parse(&enm.attrs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let variants = enm
|
||||
.variants
|
||||
.iter()
|
||||
.map(|variant: &syn::Variant| {
|
||||
let name = variant.ident.to_string();
|
||||
let fields = match &variant.fields {
|
||||
syn::Fields::Unit => None,
|
||||
syn::Fields::Unnamed(fields) => {
|
||||
let fields: Vec<IdlType> = fields
|
||||
.unnamed
|
||||
.iter()
|
||||
.map(|f| to_idl_type(ctx, &f.ty))
|
||||
.collect();
|
||||
Some(EnumFields::Tuple(fields))
|
||||
}
|
||||
syn::Fields::Named(fields) => {
|
||||
let fields: Vec<IdlField> = fields
|
||||
.named
|
||||
.iter()
|
||||
.map(|f: &syn::Field| {
|
||||
let name =
|
||||
f.ident.as_ref().unwrap().to_string().to_mixed_case();
|
||||
let doc = if !no_docs {
|
||||
docs::parse(&f.attrs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let ty = to_idl_type(ctx, &f.ty);
|
||||
IdlField {
|
||||
name,
|
||||
docs: doc,
|
||||
ty,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
Some(EnumFields::Named(fields))
|
||||
}
|
||||
};
|
||||
IdlEnumVariant { name, fields }
|
||||
})
|
||||
.collect::<Vec<IdlEnumVariant>>();
|
||||
|
||||
Some(Ok(IdlTypeDefinition {
|
||||
name,
|
||||
generics: None,
|
||||
docs: doc,
|
||||
ty: IdlTypeDefinitionTy::Enum { variants },
|
||||
}))
|
||||
}))
|
||||
.chain(ctx.type_aliases().filter_map(|alias| {
|
||||
// Only take public types
|
||||
if !matches!(&alias.vis, syn::Visibility::Public(_)) {
|
||||
return None;
|
||||
}
|
||||
// Generic type aliases are not currently supported
|
||||
if alias.generics.lt_token.is_some() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = alias.ident.to_string();
|
||||
let doc = if !no_docs {
|
||||
docs::parse(&alias.attrs)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let result = IdlType::from_str(&alias.ty.to_token_stream().to_string()).map(|value| {
|
||||
IdlTypeDefinition {
|
||||
name,
|
||||
generics: None,
|
||||
docs: doc,
|
||||
ty: IdlTypeDefinitionTy::Alias { value },
|
||||
}
|
||||
});
|
||||
|
||||
match &result {
|
||||
Ok(_) => Some(result),
|
||||
Err(_) => None,
|
||||
}
|
||||
}))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Replace variable array lengths with values
|
||||
fn resolve_variable_array_lengths(ctx: &CrateContext, mut tts_string: String) -> String {
|
||||
for constant in ctx.consts().filter(|c| match *c.ty {
|
||||
// Filter to only those consts that are of type usize or could be cast to usize
|
||||
syn::Type::Path(ref p) => {
|
||||
let segment = p.path.segments.last().unwrap();
|
||||
matches!(
|
||||
segment.ident.to_string().as_str(),
|
||||
"usize"
|
||||
| "u8"
|
||||
| "u16"
|
||||
| "u32"
|
||||
| "u64"
|
||||
| "u128"
|
||||
| "isize"
|
||||
| "i8"
|
||||
| "i16"
|
||||
| "i32"
|
||||
| "i64"
|
||||
| "i128"
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
}) {
|
||||
let mut check_string = tts_string.clone();
|
||||
// Strip whitespace to handle accidental double whitespaces
|
||||
check_string.retain(|c| !c.is_whitespace());
|
||||
let size_string = format!("{}]", &constant.ident.to_string());
|
||||
let cast_size_string = format!("{}asusize]", &constant.ident.to_string());
|
||||
// Check for something to replace
|
||||
let mut replacement_string = None;
|
||||
if check_string.contains(cast_size_string.as_str()) {
|
||||
replacement_string = Some(cast_size_string);
|
||||
} else if check_string.contains(size_string.as_str()) {
|
||||
replacement_string = Some(size_string);
|
||||
}
|
||||
if let Some(replacement_string) = replacement_string {
|
||||
// Check for the existence of consts existing elsewhere in the
|
||||
// crate which have the same name, are usize, and have a
|
||||
// different value. We can't know which was intended for the
|
||||
// array size from ctx.
|
||||
if ctx.consts().any(|c| {
|
||||
c != constant
|
||||
&& c.ident == constant.ident
|
||||
&& c.ty == constant.ty
|
||||
&& c.expr != constant.expr
|
||||
}) {
|
||||
panic!("Crate wide unique name required for array size const.");
|
||||
}
|
||||
// Replace the match, don't break because there might be multiple replacements to be
|
||||
// made in the case of multidimensional arrays
|
||||
tts_string = check_string.replace(
|
||||
&replacement_string,
|
||||
format!("{}]", &constant.expr.to_token_stream()).as_str(),
|
||||
);
|
||||
}
|
||||
}
|
||||
tts_string
|
||||
}
|
||||
|
||||
fn to_idl_type(ctx: &CrateContext, ty: &syn::Type) -> IdlType {
|
||||
let mut tts_string = parser::tts_to_string(ty);
|
||||
if tts_string.starts_with('[') {
|
||||
tts_string = resolve_variable_array_lengths(ctx, tts_string);
|
||||
}
|
||||
// Box<FooType> -> FooType
|
||||
tts_string = tts_string
|
||||
.strip_prefix("Box < ")
|
||||
.and_then(|t| t.strip_suffix(" >"))
|
||||
.unwrap_or(&tts_string)
|
||||
.into();
|
||||
|
||||
tts_string.parse().unwrap()
|
||||
}
|
||||
|
||||
// TODO parse other issues
|
||||
fn to_idl_const(item: &ItemConst) -> IdlConst {
|
||||
let name = item.ident.to_string();
|
||||
|
||||
if let Expr::Lit(ExprLit { lit, .. }) = &*item.expr {
|
||||
match lit {
|
||||
ByteStr(lit_byte_str) => {
|
||||
return IdlConst {
|
||||
name,
|
||||
ty: IdlType::Bytes,
|
||||
value: format!("{:?}", lit_byte_str.value()),
|
||||
}
|
||||
}
|
||||
Byte(lit_byte) => {
|
||||
return IdlConst {
|
||||
name,
|
||||
ty: IdlType::U8,
|
||||
value: lit_byte.value().to_string(),
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
IdlConst {
|
||||
name,
|
||||
ty: item.ty.to_token_stream().to_string().parse().unwrap(),
|
||||
value: item.expr.to_token_stream().to_string().parse().unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
fn idl_accounts(
|
||||
ctx: &CrateContext,
|
||||
accounts: &AccountsStruct,
|
||||
global_accs: &HashMap<String, AccountsStruct>,
|
||||
seeds_feature: bool,
|
||||
no_docs: bool,
|
||||
) -> Vec<IdlAccountItem> {
|
||||
accounts
|
||||
.fields
|
||||
.iter()
|
||||
.map(|acc: &AccountField| match acc {
|
||||
AccountField::CompositeField(comp_f) => {
|
||||
let accs_strct = global_accs.get(&comp_f.symbol).unwrap_or_else(|| {
|
||||
panic!("Could not resolve Accounts symbol {}", comp_f.symbol)
|
||||
});
|
||||
let accounts = idl_accounts(ctx, accs_strct, global_accs, seeds_feature, no_docs);
|
||||
IdlAccountItem::IdlAccounts(IdlAccounts {
|
||||
name: comp_f.ident.to_string().to_mixed_case(),
|
||||
accounts,
|
||||
})
|
||||
}
|
||||
AccountField::Field(acc) => IdlAccountItem::IdlAccount(IdlAccount {
|
||||
name: acc.ident.to_string().to_mixed_case(),
|
||||
is_mut: acc.constraints.is_mutable(),
|
||||
is_signer: match acc.ty {
|
||||
Ty::Signer => true,
|
||||
_ => acc.constraints.is_signer(),
|
||||
},
|
||||
is_optional: if acc.is_optional { Some(true) } else { None },
|
||||
docs: if !no_docs { acc.docs.clone() } else { None },
|
||||
pda: super::pda::parse(ctx, accounts, acc, seeds_feature),
|
||||
relations: relations::parse(acc, seeds_feature),
|
||||
}),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
}
|
|
@ -1,120 +0,0 @@
|
|||
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))
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,389 +0,0 @@
|
|||
use crate::idl::types::*;
|
||||
use crate::parser;
|
||||
use crate::parser::context::CrateContext;
|
||||
use crate::ConstraintSeedsGroup;
|
||||
use crate::{AccountsStruct, Field};
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use syn::{Expr, ExprLit, Lit, Path};
|
||||
|
||||
// Parses a seeds constraint, extracting the IdlSeed types.
|
||||
//
|
||||
// Note: This implementation makes assumptions about the types that can be used
|
||||
// (e.g., no program-defined function calls in seeds).
|
||||
//
|
||||
// This probably doesn't cover all cases. If you see a warning log, you
|
||||
// can add a new case here. In the worst case, we miss a seed and
|
||||
// the parser will treat the given seeds as empty and so clients will
|
||||
// simply fail to automatically populate the PDA accounts.
|
||||
//
|
||||
// Seed Assumptions: Seeds must be of one of the following forms:
|
||||
//
|
||||
// - instruction argument.
|
||||
// - account context field pubkey.
|
||||
// - account data, where the account is defined in the current program.
|
||||
// We make an exception for the SPL token program, since it is so common
|
||||
// and sometimes convenient to use fields as a seed (e.g. Auction house
|
||||
// program). In the case of nested structs/account data, all nested structs
|
||||
// must be defined in the current program as well.
|
||||
// - byte string literal (e.g. b"MY_SEED").
|
||||
// - byte string literal constant (e.g. `pub const MY_SEED: [u8; 2] = *b"hi";`).
|
||||
// - array constants.
|
||||
//
|
||||
pub fn parse(
|
||||
ctx: &CrateContext,
|
||||
accounts: &AccountsStruct,
|
||||
acc: &Field,
|
||||
seeds_feature: bool,
|
||||
) -> Option<IdlPda> {
|
||||
if !seeds_feature {
|
||||
return None;
|
||||
}
|
||||
let pda_parser = PdaParser::new(ctx, accounts);
|
||||
acc.constraints
|
||||
.seeds
|
||||
.as_ref()
|
||||
.map(|s| pda_parser.parse(s))
|
||||
.unwrap_or(None)
|
||||
}
|
||||
|
||||
struct PdaParser<'a> {
|
||||
ctx: &'a CrateContext,
|
||||
// Accounts context.
|
||||
accounts: &'a AccountsStruct,
|
||||
// Maps var name to var type. These are the instruction arguments in a
|
||||
// given accounts context.
|
||||
ix_args: HashMap<String, String>,
|
||||
// Constants available in the crate.
|
||||
const_names: Vec<String>,
|
||||
// Constants declared in impl blocks available in the crate
|
||||
impl_const_names: Vec<String>,
|
||||
// All field names of the accounts in the accounts context.
|
||||
account_field_names: Vec<String>,
|
||||
}
|
||||
|
||||
impl<'a> PdaParser<'a> {
|
||||
fn new(ctx: &'a CrateContext, accounts: &'a AccountsStruct) -> Self {
|
||||
// All the available sources of seeds.
|
||||
let ix_args = accounts.instruction_args().unwrap_or_default();
|
||||
let const_names: Vec<String> = ctx.consts().map(|c| c.ident.to_string()).collect();
|
||||
|
||||
let impl_const_names: Vec<String> = ctx
|
||||
.impl_consts()
|
||||
.map(|(ident, item)| format!("{} :: {}", ident, item.ident))
|
||||
.collect();
|
||||
|
||||
let account_field_names = accounts.field_names();
|
||||
|
||||
Self {
|
||||
ctx,
|
||||
accounts,
|
||||
ix_args,
|
||||
const_names,
|
||||
impl_const_names,
|
||||
account_field_names,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse(&self, seeds_grp: &ConstraintSeedsGroup) -> Option<IdlPda> {
|
||||
// Extract the idl seed types from the constraints.
|
||||
let seeds = seeds_grp
|
||||
.seeds
|
||||
.iter()
|
||||
.map(|s| self.parse_seed(s))
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
// Parse the program id from the constraints.
|
||||
let program_id = seeds_grp
|
||||
.program_seed
|
||||
.as_ref()
|
||||
.map(|pid| self.parse_seed(pid))
|
||||
.unwrap_or_default();
|
||||
|
||||
// Done.
|
||||
Some(IdlPda { seeds, program_id })
|
||||
}
|
||||
|
||||
fn parse_seed(&self, seed: &Expr) -> Option<IdlSeed> {
|
||||
match seed {
|
||||
Expr::MethodCall(_) => {
|
||||
let seed_path = parse_seed_path(seed)?;
|
||||
|
||||
if self.is_instruction(&seed_path) {
|
||||
self.parse_instruction(&seed_path)
|
||||
} else if self.is_const(&seed_path) {
|
||||
self.parse_const(&seed_path)
|
||||
} else if self.is_impl_const(&seed_path) {
|
||||
self.parse_impl_const(&seed_path)
|
||||
} else if self.is_account(&seed_path) {
|
||||
self.parse_account(&seed_path)
|
||||
} else if self.is_str_literal(&seed_path) {
|
||||
self.parse_str_literal(&seed_path)
|
||||
} else {
|
||||
println!("WARNING: unexpected seed category for var: {seed_path:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
Expr::Reference(expr_reference) => self.parse_seed(&expr_reference.expr),
|
||||
Expr::Index(_) => {
|
||||
println!("WARNING: auto pda derivation not currently supported for slice literals");
|
||||
None
|
||||
}
|
||||
Expr::Lit(ExprLit {
|
||||
lit: Lit::ByteStr(lit_byte_str),
|
||||
..
|
||||
}) => {
|
||||
let seed_path: SeedPath = SeedPath(lit_byte_str.token().to_string(), Vec::new());
|
||||
self.parse_str_literal(&seed_path)
|
||||
}
|
||||
Expr::Path(expr_path) => self.parse_const_path(&expr_path.path),
|
||||
// Unknown type. Please file an issue.
|
||||
_ => {
|
||||
println!("WARNING: unexpected seed: {seed:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_instruction(&self, seed_path: &SeedPath) -> Option<IdlSeed> {
|
||||
let idl_ty = IdlType::from_str(self.ix_args.get(&seed_path.name()).unwrap()).ok()?;
|
||||
Some(IdlSeed::Arg(IdlSeedArg {
|
||||
ty: idl_ty,
|
||||
path: seed_path.path(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_const_path(&self, path: &Path) -> Option<IdlSeed> {
|
||||
let ident = &path.segments.first().unwrap().ident;
|
||||
|
||||
let const_item = self.ctx.consts().find(|c| c.ident == *ident).unwrap();
|
||||
let idl_ty = IdlType::from_str(&parser::tts_to_string(&const_item.ty)).ok()?;
|
||||
|
||||
let idl_ty_value = parser::tts_to_string(&const_item.expr);
|
||||
let idl_ty_value: String = str_lit_to_array(&idl_ty, &idl_ty_value);
|
||||
|
||||
let seed_path: SeedPath = SeedPath(idl_ty_value, Vec::new());
|
||||
|
||||
if self.is_str_literal(&seed_path) {
|
||||
self.parse_str_literal(&seed_path)
|
||||
} else {
|
||||
println!("WARNING: unexpected constant path value: {seed_path:?}");
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_const(&self, seed_path: &SeedPath) -> Option<IdlSeed> {
|
||||
// Pull in the constant value directly into the IDL.
|
||||
assert!(seed_path.components().is_empty());
|
||||
let const_item = self
|
||||
.ctx
|
||||
.consts()
|
||||
.find(|c| c.ident == seed_path.name())
|
||||
.unwrap();
|
||||
let idl_ty = IdlType::from_str(&parser::tts_to_string(&const_item.ty)).ok()?;
|
||||
|
||||
let idl_ty_value = parser::tts_to_string(&const_item.expr);
|
||||
let idl_ty_value = str_lit_to_array(&idl_ty, &idl_ty_value);
|
||||
|
||||
Some(IdlSeed::Const(IdlSeedConst {
|
||||
ty: idl_ty,
|
||||
value: serde_json::from_str(&idl_ty_value).unwrap(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_impl_const(&self, seed_path: &SeedPath) -> Option<IdlSeed> {
|
||||
// Pull in the constant value directly into the IDL.
|
||||
assert!(seed_path.components().is_empty());
|
||||
let static_item = self
|
||||
.ctx
|
||||
.impl_consts()
|
||||
.find(|(ident, item)| format!("{} :: {}", ident, item.ident) == seed_path.name())
|
||||
.unwrap()
|
||||
.1;
|
||||
|
||||
let idl_ty = IdlType::from_str(&parser::tts_to_string(&static_item.ty)).ok()?;
|
||||
|
||||
let idl_ty_value = parser::tts_to_string(&static_item.expr);
|
||||
let idl_ty_value = str_lit_to_array(&idl_ty, &idl_ty_value);
|
||||
|
||||
Some(IdlSeed::Const(IdlSeedConst {
|
||||
ty: idl_ty,
|
||||
value: serde_json::from_str(&idl_ty_value).unwrap(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_account(&self, seed_path: &SeedPath) -> Option<IdlSeed> {
|
||||
// Get the anchor account field from the derive accounts struct.
|
||||
let account_field = self
|
||||
.accounts
|
||||
.fields
|
||||
.iter()
|
||||
.find(|field| *field.ident() == seed_path.name())
|
||||
.unwrap();
|
||||
|
||||
// Follow the path to find the seed type.
|
||||
let ty = {
|
||||
let mut path = seed_path.components();
|
||||
match path.len() {
|
||||
0 => IdlType::PublicKey,
|
||||
1 => {
|
||||
// Name of the account struct.
|
||||
let account = account_field.ty_name()?;
|
||||
if account == "TokenAccount" {
|
||||
assert!(path.len() == 1);
|
||||
match path[0].as_str() {
|
||||
"mint" => IdlType::PublicKey,
|
||||
"amount" => IdlType::U64,
|
||||
"authority" => IdlType::PublicKey,
|
||||
"delegated_amount" => IdlType::U64,
|
||||
_ => {
|
||||
println!("WARNING: token field isn't supported: {}", &path[0]);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Get the rust representation of the field's struct.
|
||||
let strct = self.ctx.structs().find(|s| s.ident == account).unwrap();
|
||||
parse_field_path(self.ctx, strct, &mut path)
|
||||
}
|
||||
}
|
||||
_ => panic!("invariant violation"),
|
||||
}
|
||||
};
|
||||
|
||||
Some(IdlSeed::Account(IdlSeedAccount {
|
||||
ty,
|
||||
account: account_field.ty_name(),
|
||||
path: seed_path.path(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn parse_str_literal(&self, seed_path: &SeedPath) -> Option<IdlSeed> {
|
||||
let mut var_name = seed_path.name();
|
||||
// Remove the byte `b` prefix if the string is of the form `b"seed".
|
||||
if var_name.starts_with("b\"") {
|
||||
var_name.remove(0);
|
||||
}
|
||||
let value_string: String = var_name.chars().filter(|c| *c != '"').collect();
|
||||
Some(IdlSeed::Const(IdlSeedConst {
|
||||
value: serde_json::Value::String(value_string),
|
||||
ty: IdlType::String,
|
||||
}))
|
||||
}
|
||||
|
||||
fn is_instruction(&self, seed_path: &SeedPath) -> bool {
|
||||
self.ix_args.contains_key(&seed_path.name())
|
||||
}
|
||||
|
||||
fn is_const(&self, seed_path: &SeedPath) -> bool {
|
||||
self.const_names.contains(&seed_path.name())
|
||||
}
|
||||
|
||||
fn is_impl_const(&self, seed_path: &SeedPath) -> bool {
|
||||
self.impl_const_names.contains(&seed_path.name())
|
||||
}
|
||||
|
||||
fn is_account(&self, seed_path: &SeedPath) -> bool {
|
||||
self.account_field_names.contains(&seed_path.name())
|
||||
}
|
||||
|
||||
fn is_str_literal(&self, seed_path: &SeedPath) -> bool {
|
||||
seed_path.components().is_empty() && seed_path.name().contains('"')
|
||||
}
|
||||
}
|
||||
|
||||
// SeedPath represents the deconstructed syntax of a single pda seed,
|
||||
// consisting of a variable name and a vec of all the sub fields accessed
|
||||
// on that variable name. For example, if a seed is `my_field.my_data.as_ref()`,
|
||||
// then the field name is `my_field` and the vec of sub fields is `[my_data]`.
|
||||
#[derive(Debug)]
|
||||
struct SeedPath(String, Vec<String>);
|
||||
|
||||
impl SeedPath {
|
||||
fn name(&self) -> String {
|
||||
self.0.clone()
|
||||
}
|
||||
|
||||
// Full path to the data this seed represents.
|
||||
fn path(&self) -> String {
|
||||
match self.1.len() {
|
||||
0 => self.0.clone(),
|
||||
_ => format!("{}.{}", self.name(), self.components().join(".")),
|
||||
}
|
||||
}
|
||||
|
||||
// All path components for the subfields accessed on this seed.
|
||||
fn components(&self) -> &[String] {
|
||||
&self.1
|
||||
}
|
||||
}
|
||||
|
||||
// Extracts the seed path from a single seed expression.
|
||||
fn parse_seed_path(seed: &Expr) -> Option<SeedPath> {
|
||||
// Convert the seed into the raw string representation.
|
||||
let seed_str = parser::tts_to_string(seed);
|
||||
|
||||
// Break up the seed into each sub field component.
|
||||
let mut components: Vec<&str> = seed_str.split(" . ").collect();
|
||||
if components.len() <= 1 {
|
||||
println!("WARNING: seeds are in an unexpected format: {seed:?}");
|
||||
return None;
|
||||
}
|
||||
|
||||
// The name of the variable (or field).
|
||||
let name = components.remove(0).to_string();
|
||||
|
||||
// The path to the seed (only if the `name` type is a struct).
|
||||
let mut path = Vec::new();
|
||||
while !components.is_empty() {
|
||||
let c = components.remove(0);
|
||||
if c.contains("()") {
|
||||
break;
|
||||
}
|
||||
path.push(c.to_string());
|
||||
}
|
||||
if path.len() == 1 && (path[0] == "key" || path[0] == "key()") {
|
||||
path = Vec::new();
|
||||
}
|
||||
|
||||
Some(SeedPath(name, path))
|
||||
}
|
||||
|
||||
fn parse_field_path(ctx: &CrateContext, strct: &syn::ItemStruct, path: &mut &[String]) -> IdlType {
|
||||
let field_name = &path[0];
|
||||
*path = &path[1..];
|
||||
|
||||
// Get the type name for the field.
|
||||
let next_field = strct
|
||||
.fields
|
||||
.iter()
|
||||
.find(|f| &f.ident.clone().unwrap().to_string() == field_name)
|
||||
.unwrap();
|
||||
let next_field_ty_str = parser::tts_to_string(&next_field.ty);
|
||||
|
||||
// The path is empty so this must be a primitive type.
|
||||
if path.is_empty() {
|
||||
return next_field_ty_str.parse().unwrap();
|
||||
}
|
||||
|
||||
// Get the rust representation of hte field's struct.
|
||||
let strct = ctx
|
||||
.structs()
|
||||
.find(|s| s.ident == next_field_ty_str)
|
||||
.unwrap();
|
||||
|
||||
parse_field_path(ctx, strct, path)
|
||||
}
|
||||
|
||||
fn str_lit_to_array(idl_ty: &IdlType, idl_ty_value: &String) -> String {
|
||||
if let IdlType::Array(_ty, _size) = &idl_ty {
|
||||
// Convert str literal to array.
|
||||
if idl_ty_value.contains("b\"") {
|
||||
let components: Vec<&str> = idl_ty_value.split('b').collect();
|
||||
assert_eq!(components.len(), 2);
|
||||
let mut str_lit = components[1].to_string();
|
||||
str_lit.retain(|c| c != '"');
|
||||
return format!("{:?}", str_lit.as_bytes());
|
||||
}
|
||||
}
|
||||
idl_ty_value.to_string()
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
use crate::Field;
|
||||
use syn::Expr;
|
||||
|
||||
pub fn parse(acc: &Field, seeds_feature: bool) -> Vec<String> {
|
||||
if !seeds_feature {
|
||||
return vec![];
|
||||
}
|
||||
acc.constraints
|
||||
.has_one
|
||||
.iter()
|
||||
.flat_map(|s| match &s.join_target {
|
||||
Expr::Path(path) => path.path.segments.first().map(|l| l.ident.to_string()),
|
||||
_ => {
|
||||
println!("WARNING: unexpected seed: {s:?}");
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
|
@ -1,24 +1,137 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use anyhow::anyhow;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// IDL specification Semantic Version
|
||||
pub const IDL_SPEC: &str = "0.1.0";
|
||||
|
||||
#[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 address: String,
|
||||
pub metadata: IdlMetadata,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub docs: Vec<String>,
|
||||
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<serde_json::Value>,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub accounts: Vec<IdlAccount>,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub events: Vec<IdlEvent>,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub errors: Vec<IdlErrorCode>,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub types: Vec<IdlTypeDef>,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub constants: Vec<IdlConst>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IdlMetadata {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub spec: String,
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub description: Option<String>,
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub repository: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub dependencies: Vec<IdlDependency>,
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub contact: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IdlDependency {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IdlInstruction {
|
||||
pub name: String,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub docs: Vec<String>,
|
||||
pub discriminator: IdlDiscriminator,
|
||||
pub accounts: Vec<IdlInstructionAccountItem>,
|
||||
pub args: Vec<IdlField>,
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub returns: Option<IdlType>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum IdlInstructionAccountItem {
|
||||
Composite(IdlInstructionAccounts),
|
||||
Single(IdlInstructionAccount),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IdlInstructionAccount {
|
||||
pub name: String,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub docs: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub writable: bool,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub signer: bool,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub optional: bool,
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub address: Option<String>,
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub pda: Option<IdlPda>,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub relations: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IdlInstructionAccounts {
|
||||
pub name: String,
|
||||
pub accounts: Vec<IdlInstructionAccountItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IdlPda {
|
||||
pub seeds: Vec<IdlSeed>,
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub program: Option<IdlSeed>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(tag = "kind", rename_all = "lowercase")]
|
||||
pub enum IdlSeed {
|
||||
Const(IdlSeedConst),
|
||||
Arg(IdlSeedArg),
|
||||
Account(IdlSeedAccount),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IdlSeedConst {
|
||||
pub value: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IdlSeedArg {
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IdlSeedAccount {
|
||||
pub path: String,
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub account: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IdlAccount {
|
||||
pub name: String,
|
||||
pub discriminator: IdlDiscriminator,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IdlEvent {
|
||||
pub name: String,
|
||||
pub discriminator: IdlDiscriminator,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
|
@ -29,161 +142,128 @@ pub struct IdlConst {
|
|||
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 {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct IdlErrorCode {
|
||||
pub code: u32,
|
||||
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,
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub msg: Option<String>,
|
||||
}
|
||||
|
||||
#[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(default, skip_serializing_if = "is_default")]
|
||||
pub docs: 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 struct IdlTypeDef {
|
||||
pub name: String,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub docs: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub serialization: IdlSerialization,
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub repr: Option<IdlRepr>,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub generics: Vec<IdlTypeDefGeneric>,
|
||||
#[serde(rename = "type")]
|
||||
pub ty: IdlType,
|
||||
pub index: bool,
|
||||
pub ty: IdlTypeDefTy,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum IdlSerialization {
|
||||
#[default]
|
||||
Borsh,
|
||||
Bytemuck,
|
||||
BytemuckUnsafe,
|
||||
Custom(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IdlTypeDefinition {
|
||||
/// - `idl-parse`: always the name of the type
|
||||
/// - `idl-build`: full path if there is a name conflict, otherwise the name of the type
|
||||
pub name: String,
|
||||
/// Documentation comments
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub docs: Option<Vec<String>>,
|
||||
/// Generics, only supported with `idl-build`
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub generics: Option<Vec<String>>,
|
||||
/// Type definition, `struct` or `enum`
|
||||
#[serde(rename = "type")]
|
||||
pub ty: IdlTypeDefinitionTy,
|
||||
#[serde(tag = "kind", rename_all = "lowercase")]
|
||||
pub enum IdlRepr {
|
||||
Rust(IdlReprModifier),
|
||||
C(IdlReprModifier),
|
||||
Transparent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "lowercase", tag = "kind")]
|
||||
pub enum IdlTypeDefinitionTy {
|
||||
Struct { fields: Vec<IdlField> },
|
||||
Enum { variants: Vec<IdlEnumVariant> },
|
||||
Alias { value: IdlType },
|
||||
pub struct IdlReprModifier {
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
pub packed: bool,
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub align: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(tag = "kind", rename_all = "lowercase")]
|
||||
pub enum IdlTypeDefGeneric {
|
||||
Type {
|
||||
name: String,
|
||||
},
|
||||
Const {
|
||||
name: String,
|
||||
#[serde(rename = "type")]
|
||||
ty: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(tag = "kind", rename_all = "lowercase")]
|
||||
pub enum IdlTypeDefTy {
|
||||
Struct {
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
fields: Option<IdlDefinedFields>,
|
||||
},
|
||||
Enum {
|
||||
variants: Vec<IdlEnumVariant>,
|
||||
},
|
||||
Type {
|
||||
alias: IdlType,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct IdlEnumVariant {
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub fields: Option<EnumFields>,
|
||||
#[serde(skip_serializing_if = "is_default")]
|
||||
pub fields: Option<IdlDefinedFields>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(untagged)]
|
||||
pub enum EnumFields {
|
||||
pub enum IdlDefinedFields {
|
||||
Named(Vec<IdlField>),
|
||||
Tuple(Vec<IdlType>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum IdlArrayLen {
|
||||
Generic(String),
|
||||
#[serde(untagged)]
|
||||
Value(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(tag = "kind", rename_all = "lowercase")]
|
||||
pub enum IdlGenericArg {
|
||||
Type {
|
||||
#[serde(rename = "type")]
|
||||
ty: IdlType,
|
||||
},
|
||||
Const {
|
||||
value: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum IdlType {
|
||||
Bool,
|
||||
U8,
|
||||
|
@ -202,31 +282,205 @@ pub enum IdlType {
|
|||
I256,
|
||||
Bytes,
|
||||
String,
|
||||
PublicKey,
|
||||
Defined(String),
|
||||
Pubkey,
|
||||
Option(Box<IdlType>),
|
||||
Vec(Box<IdlType>),
|
||||
Array(Box<IdlType>, usize),
|
||||
GenericLenArray(Box<IdlType>, String),
|
||||
Generic(String),
|
||||
DefinedWithTypeArgs {
|
||||
Array(Box<IdlType>, IdlArrayLen),
|
||||
Defined {
|
||||
name: String,
|
||||
args: Vec<IdlDefinedTypeArg>,
|
||||
#[serde(default, skip_serializing_if = "is_default")]
|
||||
generics: Vec<IdlGenericArg>,
|
||||
},
|
||||
}
|
||||
|
||||
#[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>,
|
||||
impl FromStr for IdlType {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut s = s.to_owned();
|
||||
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::Pubkey,
|
||||
_ => {
|
||||
if let Some(inner) = s.strip_prefix("Option<") {
|
||||
let inner_ty = Self::from_str(
|
||||
inner
|
||||
.strip_suffix('>')
|
||||
.ok_or_else(|| anyhow!("Invalid Option"))?,
|
||||
)?;
|
||||
return Ok(IdlType::Option(Box::new(inner_ty)));
|
||||
}
|
||||
|
||||
if let Some(inner) = s.strip_prefix("Vec<") {
|
||||
let inner_ty = Self::from_str(
|
||||
inner
|
||||
.strip_suffix('>')
|
||||
.ok_or_else(|| anyhow!("Invalid Vec"))?,
|
||||
)?;
|
||||
return Ok(IdlType::Vec(Box::new(inner_ty)));
|
||||
}
|
||||
|
||||
if s.starts_with('[') {
|
||||
fn array_from_str(inner: &str) -> IdlType {
|
||||
match inner.strip_suffix(']') {
|
||||
Some(nested_inner) => array_from_str(&nested_inner[1..]),
|
||||
None => {
|
||||
let (raw_type, raw_length) = inner.rsplit_once(';').unwrap();
|
||||
let ty = IdlType::from_str(raw_type).unwrap();
|
||||
let len = match raw_length.replace('_', "").parse::<usize>() {
|
||||
Ok(len) => IdlArrayLen::Value(len),
|
||||
Err(_) => IdlArrayLen::Generic(raw_length.to_owned()),
|
||||
};
|
||||
IdlType::Array(Box::new(ty), len)
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(array_from_str(&s));
|
||||
}
|
||||
|
||||
// Defined
|
||||
let (name, generics) = if let Some(i) = s.find('<') {
|
||||
(
|
||||
s.get(..i).unwrap().to_owned(),
|
||||
s.get(i + 1..)
|
||||
.unwrap()
|
||||
.strip_suffix('>')
|
||||
.unwrap()
|
||||
.split(',')
|
||||
.map(|g| g.trim().to_owned())
|
||||
.map(|g| {
|
||||
if g.parse::<bool>().is_ok()
|
||||
|| g.parse::<u128>().is_ok()
|
||||
|| g.parse::<i128>().is_ok()
|
||||
|| g.parse::<char>().is_ok()
|
||||
{
|
||||
Ok(IdlGenericArg::Const { value: g })
|
||||
} else {
|
||||
Self::from_str(&g).map(|ty| IdlGenericArg::Type { ty })
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
)
|
||||
} else {
|
||||
(s.to_owned(), vec![])
|
||||
};
|
||||
|
||||
IdlType::Defined { name, generics }
|
||||
}
|
||||
};
|
||||
Ok(r)
|
||||
}
|
||||
}
|
||||
|
||||
pub type IdlDiscriminator = Vec<u8>;
|
||||
|
||||
/// Get whether the given data is the default of its type.
|
||||
fn is_default<T: Default + PartialEq>(it: &T) -> bool {
|
||||
*it == T::default()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[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))
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array() {
|
||||
assert_eq!(
|
||||
IdlType::from_str("[Pubkey; 16]").unwrap(),
|
||||
IdlType::Array(Box::new(IdlType::Pubkey), IdlArrayLen::Value(16))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn array_with_underscored_length() {
|
||||
assert_eq!(
|
||||
IdlType::from_str("[u8; 50_000]").unwrap(),
|
||||
IdlType::Array(Box::new(IdlType::U8), IdlArrayLen::Value(50000))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multidimensional_array() {
|
||||
assert_eq!(
|
||||
IdlType::from_str("[[u8; 16]; 32]").unwrap(),
|
||||
IdlType::Array(
|
||||
Box::new(IdlType::Array(
|
||||
Box::new(IdlType::U8),
|
||||
IdlArrayLen::Value(16)
|
||||
)),
|
||||
IdlArrayLen::Value(32)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_array() {
|
||||
assert_eq!(
|
||||
IdlType::from_str("[u64; T]").unwrap(),
|
||||
IdlType::Array(Box::new(IdlType::U64), IdlArrayLen::Generic("T".into()))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn defined() {
|
||||
assert_eq!(
|
||||
IdlType::from_str("MyStruct").unwrap(),
|
||||
IdlType::Defined {
|
||||
name: "MyStruct".into(),
|
||||
generics: vec![]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn defined_with_generics() {
|
||||
assert_eq!(
|
||||
IdlType::from_str("MyStruct<Pubkey, u64, 8>").unwrap(),
|
||||
IdlType::Defined {
|
||||
name: "MyStruct".into(),
|
||||
generics: vec![
|
||||
IdlGenericArg::Type {
|
||||
ty: IdlType::Pubkey
|
||||
},
|
||||
IdlGenericArg::Type { ty: IdlType::U64 },
|
||||
IdlGenericArg::Const { value: "8".into() },
|
||||
],
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,6 @@ pub mod hash;
|
|||
#[cfg(not(feature = "hash"))]
|
||||
pub(crate) mod hash;
|
||||
|
||||
use crate::parser::tts_to_string;
|
||||
use codegen::accounts as accounts_codegen;
|
||||
use codegen::program as program_codegen;
|
||||
use parser::accounts as accounts_parser;
|
||||
|
@ -175,7 +174,7 @@ impl AccountsStruct {
|
|||
let matching_field = self
|
||||
.fields
|
||||
.iter()
|
||||
.find(|f| *f.ident() == tts_to_string(field));
|
||||
.find(|f| *f.ident() == parser::tts_to_string(field));
|
||||
if let Some(matching_field) = matching_field {
|
||||
matching_field.is_optional()
|
||||
} else {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use anyhow::anyhow;
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use syn::parse::{Error as ParseError, Result as ParseResult};
|
||||
|
@ -12,6 +12,12 @@ pub struct CrateContext {
|
|||
}
|
||||
|
||||
impl CrateContext {
|
||||
pub fn parse(root: impl AsRef<Path>) -> Result<Self> {
|
||||
Ok(CrateContext {
|
||||
modules: ParsedModule::parse_recursive(root.as_ref())?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn consts(&self) -> impl Iterator<Item = &syn::ItemConst> {
|
||||
self.modules.iter().flat_map(|(_, ctx)| ctx.consts())
|
||||
}
|
||||
|
@ -42,16 +48,10 @@ impl CrateContext {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn parse(root: impl AsRef<Path>) -> Result<Self, anyhow::Error> {
|
||||
Ok(CrateContext {
|
||||
modules: ParsedModule::parse_recursive(root.as_ref())?,
|
||||
})
|
||||
}
|
||||
|
||||
// Perform Anchor safety checks on the parsed create
|
||||
pub fn safety_checks(&self) -> Result<(), anyhow::Error> {
|
||||
pub fn safety_checks(&self) -> Result<()> {
|
||||
// Check all structs for unsafe field types, i.e. AccountInfo and UncheckedAccount.
|
||||
for (_, ctx) in self.modules.iter() {
|
||||
for ctx in self.modules.values() {
|
||||
for unsafe_field in ctx.unsafe_struct_fields() {
|
||||
// Check if unsafe field type has been documented with a /// SAFETY: doc string.
|
||||
let is_documented = unsafe_field.attrs.iter().any(|attr| {
|
||||
|
@ -104,8 +104,15 @@ struct ParsedModule {
|
|||
items: Vec<syn::Item>,
|
||||
}
|
||||
|
||||
struct UnparsedModule {
|
||||
file: PathBuf,
|
||||
path: String,
|
||||
name: String,
|
||||
item: syn::ItemMod,
|
||||
}
|
||||
|
||||
impl ParsedModule {
|
||||
fn parse_recursive(root: &Path) -> Result<BTreeMap<String, ParsedModule>, anyhow::Error> {
|
||||
fn parse_recursive(root: &Path) -> Result<BTreeMap<String, ParsedModule>> {
|
||||
let mut modules = BTreeMap::new();
|
||||
|
||||
let root_content = std::fs::read_to_string(root)?;
|
||||
|
@ -117,35 +124,13 @@ impl ParsedModule {
|
|||
root_file.items,
|
||||
);
|
||||
|
||||
struct UnparsedModule {
|
||||
file: PathBuf,
|
||||
path: String,
|
||||
name: String,
|
||||
item: syn::ItemMod,
|
||||
}
|
||||
|
||||
let mut unparsed = root_mod
|
||||
.submodules()
|
||||
.map(|item| UnparsedModule {
|
||||
file: root_mod.file.clone(),
|
||||
path: root_mod.path.clone(),
|
||||
name: item.ident.to_string(),
|
||||
item: item.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut unparsed = root_mod.unparsed_submodules();
|
||||
while let Some(to_parse) = unparsed.pop() {
|
||||
let path = format!("{}::{}", to_parse.path, to_parse.name);
|
||||
let name = to_parse.name;
|
||||
let module = Self::from_item_mod(&to_parse.file, &path, to_parse.item)?;
|
||||
|
||||
unparsed.extend(module.submodules().map(|item| UnparsedModule {
|
||||
item: item.clone(),
|
||||
file: module.file.clone(),
|
||||
path: module.path.clone(),
|
||||
name: item.ident.to_string(),
|
||||
}));
|
||||
modules.insert(format!("{}{}", module.path.clone(), name.clone()), module);
|
||||
unparsed.extend(module.unparsed_submodules());
|
||||
modules.insert(format!("{}{}", module.path, to_parse.name), module);
|
||||
}
|
||||
|
||||
modules.insert(root_mod.name.clone(), root_mod);
|
||||
|
@ -209,6 +194,17 @@ impl ParsedModule {
|
|||
}
|
||||
}
|
||||
|
||||
fn unparsed_submodules(&self) -> Vec<UnparsedModule> {
|
||||
self.submodules()
|
||||
.map(|item| UnparsedModule {
|
||||
file: self.file.clone(),
|
||||
path: self.path.clone(),
|
||||
name: item.ident.to_string(),
|
||||
item: item.clone(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn submodules(&self) -> impl Iterator<Item = &syn::ItemMod> {
|
||||
self.items.iter().filter_map(|i| match i {
|
||||
syn::Item::Mod(item) => Some(item),
|
||||
|
@ -259,6 +255,13 @@ impl ParsedModule {
|
|||
})
|
||||
}
|
||||
|
||||
fn type_aliases(&self) -> impl Iterator<Item = &syn::ItemType> {
|
||||
self.items.iter().filter_map(|i| match i {
|
||||
syn::Item::Type(item) => Some(item),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn consts(&self) -> impl Iterator<Item = &syn::ItemConst> {
|
||||
self.items.iter().filter_map(|i| match i {
|
||||
syn::Item::Const(item) => Some(item),
|
||||
|
@ -297,11 +300,4 @@ impl ParsedModule {
|
|||
})
|
||||
.flatten()
|
||||
}
|
||||
|
||||
fn type_aliases(&self) -> impl Iterator<Item = &syn::ItemType> {
|
||||
self.items.iter().filter_map(|i| match i {
|
||||
syn::Item::Type(item) => Some(item),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,5 @@ pub mod program;
|
|||
pub mod spl_interface;
|
||||
|
||||
pub fn tts_to_string<T: quote::ToTokens>(item: T) -> String {
|
||||
let mut tts = proc_macro2::TokenStream::new();
|
||||
item.to_tokens(&mut tts);
|
||||
tts.to_string()
|
||||
item.to_token_stream().to_string()
|
||||
}
|
||||
|
|
|
@ -57,5 +57,10 @@ macro_rules! vote_weight_record {
|
|||
|
||||
#[cfg(feature = "idl-build")]
|
||||
impl anchor_lang::IdlBuild for VoterWeightRecord {}
|
||||
|
||||
#[cfg(feature = "idl-build")]
|
||||
impl anchor_lang::Discriminator for VoterWeightRecord {
|
||||
const DISCRIMINATOR: [u8; 8] = [0; 8];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/// Crate a default [`anchor_lang::IdlBuild`] implementation for the given type.
|
||||
///
|
||||
/// This is used in order to make wrapper accounts of `anchor-spl` work with `idl-build` feature.
|
||||
macro_rules! impl_idl_build {
|
||||
($ty: ty) => {
|
||||
impl anchor_lang::IdlBuild for $ty {}
|
||||
|
||||
// This is not used for the IDL generation since default `IdlBuild` impl doesn't include
|
||||
// the type in the IDL but it stil needs to be added in order to make compilation work.
|
||||
//
|
||||
// TODO: Find a better way to handle discriminators of wrapped external accounts.
|
||||
impl anchor_lang::Discriminator for $ty {
|
||||
const DISCRIMINATOR: [u8; 8] = [0; 8];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "metadata")]
|
||||
impl_idl_build!(crate::metadata::MetadataAccount);
|
||||
#[cfg(feature = "metadata")]
|
||||
impl_idl_build!(crate::metadata::MasterEditionAccount);
|
||||
#[cfg(feature = "metadata")]
|
||||
impl_idl_build!(crate::metadata::TokenRecordAccount);
|
||||
|
||||
#[cfg(feature = "stake")]
|
||||
impl_idl_build!(crate::stake::StakeAccount);
|
||||
|
||||
impl_idl_build!(crate::token::Mint);
|
||||
impl_idl_build!(crate::token::TokenAccount);
|
||||
|
||||
impl_idl_build!(crate::token_interface::Mint);
|
||||
impl_idl_build!(crate::token_interface::TokenAccount);
|
|
@ -31,3 +31,6 @@ pub mod metadata;
|
|||
|
||||
#[cfg(feature = "memo")]
|
||||
pub mod memo;
|
||||
|
||||
#[cfg(feature = "idl-build")]
|
||||
mod idl_build;
|
||||
|
|
|
@ -810,9 +810,6 @@ impl Deref for MetadataAccount {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "idl-build")]
|
||||
impl anchor_lang::IdlBuild for MetadataAccount {}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct MasterEditionAccount(mpl_token_metadata::accounts::MasterEdition);
|
||||
|
||||
|
@ -846,9 +843,6 @@ impl anchor_lang::Owner for MasterEditionAccount {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "idl-build")]
|
||||
impl anchor_lang::IdlBuild for MasterEditionAccount {}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct TokenRecordAccount(mpl_token_metadata::accounts::TokenRecord);
|
||||
|
||||
|
@ -885,9 +879,6 @@ impl Deref for TokenRecordAccount {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "idl-build")]
|
||||
impl anchor_lang::IdlBuild for TokenRecordAccount {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Metadata;
|
||||
|
||||
|
|
|
@ -156,9 +156,6 @@ impl Deref for StakeAccount {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "idl-build")]
|
||||
impl anchor_lang::IdlBuild for StakeAccount {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Stake;
|
||||
|
||||
|
|
|
@ -471,9 +471,6 @@ impl Deref for TokenAccount {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "idl-build")]
|
||||
impl anchor_lang::IdlBuild for TokenAccount {}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Mint(spl_token::state::Mint);
|
||||
|
||||
|
@ -505,9 +502,6 @@ impl Deref for Mint {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "idl-build")]
|
||||
impl anchor_lang::IdlBuild for Mint {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Token;
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use anchor_lang::solana_program::pubkey::Pubkey;
|
||||
use std::ops::Deref;
|
||||
|
||||
pub use crate::token_2022::*;
|
||||
|
||||
static IDS: [Pubkey; 2] = [spl_token::ID, spl_token_2022::ID];
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
|
@ -32,9 +34,6 @@ impl Deref for TokenAccount {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "idl-build")]
|
||||
impl anchor_lang::IdlBuild for TokenAccount {}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Mint(spl_token_2022::state::Mint);
|
||||
|
||||
|
@ -62,9 +61,6 @@ impl Deref for Mint {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "idl-build")]
|
||||
impl anchor_lang::IdlBuild for Mint {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TokenInterface;
|
||||
|
||||
|
@ -73,5 +69,3 @@ impl anchor_lang::Ids for TokenInterface {
|
|||
&IDS
|
||||
}
|
||||
}
|
||||
|
||||
pub use crate::token_2022::*;
|
||||
|
|
|
@ -14,6 +14,7 @@ no-idl = []
|
|||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -55,9 +55,9 @@ describe("Test CLI account commands", () => {
|
|||
await sleep(5000);
|
||||
}
|
||||
|
||||
assert(output.balance == balance, "Balance deserialized incorrectly");
|
||||
assert(output.balance === balance, "Balance deserialized incorrectly");
|
||||
assert(
|
||||
output.delegatePubkey == provider.wallet.publicKey,
|
||||
output.delegate_pubkey === provider.wallet.publicKey.toBase58(),
|
||||
"delegatePubkey deserialized incorrectly"
|
||||
);
|
||||
assert(
|
||||
|
|
|
@ -14,6 +14,7 @@ no-idl = []
|
|||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -14,6 +14,7 @@ no-idl = []
|
|||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -8,8 +8,12 @@ RANDOM_DATA=$(openssl rand -base64 $((10*1680)) | sed 's/.*/"&",/')
|
|||
|
||||
# Create the JSON object with the "docs" field containing random data
|
||||
echo '{
|
||||
"version": "0.1.0",
|
||||
"name": "idl_commands_one",
|
||||
"address": "2uA3amp95zsEHUpo8qnLMhcFAUsiKVEcKHXS1JetFjU5",
|
||||
"metadata": {
|
||||
"name": "idl_commands_one",
|
||||
"version": "0.1.0",
|
||||
"spec": "0.1.0"
|
||||
},
|
||||
"instructions": [
|
||||
{
|
||||
"name": "initialize",
|
||||
|
@ -17,6 +21,7 @@ echo '{
|
|||
'"$RANDOM_DATA"'
|
||||
"trailing comma begone"
|
||||
],
|
||||
"discriminator": [],
|
||||
"accounts": [],
|
||||
"args": []
|
||||
}
|
||||
|
|
|
@ -24,12 +24,12 @@ describe("Test CLI IDL commands", () => {
|
|||
|
||||
it("Can fetch an IDL using the TypeScript client", async () => {
|
||||
const idl = await anchor.Program.fetchIdl(programOne.programId, provider);
|
||||
assert.deepEqual(idl, programOne.idl);
|
||||
assert.deepEqual(idl, programOne.rawIdl);
|
||||
});
|
||||
|
||||
it("Can fetch an IDL via the CLI", async () => {
|
||||
const idl = execSync(`anchor idl fetch ${programOne.programId}`).toString();
|
||||
assert.deepEqual(JSON.parse(idl), programOne.idl);
|
||||
assert.deepEqual(JSON.parse(idl), programOne.rawIdl);
|
||||
});
|
||||
|
||||
it("Can write a new IDL using the upgrade command", async () => {
|
||||
|
@ -39,7 +39,7 @@ describe("Test CLI IDL commands", () => {
|
|||
{ stdio: "inherit" }
|
||||
);
|
||||
const idl = await anchor.Program.fetchIdl(programOne.programId, provider);
|
||||
assert.deepEqual(idl, programTwo.idl);
|
||||
assert.deepEqual(idl, programTwo.rawIdl);
|
||||
});
|
||||
|
||||
it("Can write a new IDL using write-buffer and set-buffer", async () => {
|
||||
|
@ -53,7 +53,7 @@ describe("Test CLI IDL commands", () => {
|
|||
{ stdio: "inherit" }
|
||||
);
|
||||
const idl = await anchor.Program.fetchIdl(programOne.programId, provider);
|
||||
assert.deepEqual(idl, programOne.idl);
|
||||
assert.deepEqual(idl, programOne.rawIdl);
|
||||
});
|
||||
|
||||
it("Can fetch an IDL authority via the CLI", async () => {
|
||||
|
|
|
@ -14,6 +14,7 @@ no-entrypoint = []
|
|||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -15,7 +15,9 @@ import {
|
|||
import { u64, Token, TOKEN_PROGRAM_ID } from "@solana/spl-token";
|
||||
import * as metaplex from "@metaplex/js";
|
||||
import * as assert from "assert";
|
||||
import { IDL, AuctionHouse } from "../target/types/auction_house";
|
||||
import { AuctionHouse } from "../target/types/auction_house";
|
||||
|
||||
const IDL = require("../target/idl/auction_house.json");
|
||||
|
||||
const MetadataDataData = metaplex.programs.metadata.MetadataDataData;
|
||||
const CreateMetadata = metaplex.programs.metadata.CreateMetadata;
|
||||
|
|
|
@ -10,6 +10,7 @@ crate-type = ["cdylib", "lib"]
|
|||
[features]
|
||||
no-entrypoint = []
|
||||
cpi = ["no-entrypoint"]
|
||||
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import * as fs from "fs/promises";
|
||||
import path from "path";
|
||||
|
||||
import { IDL } from "../target/types/bench";
|
||||
import { BenchData, BinarySize } from "../scripts/utils";
|
||||
|
||||
const IDL = require("../target/idl/bench.json");
|
||||
|
||||
describe("Binary size", () => {
|
||||
const binarySize: BinarySize = {};
|
||||
|
||||
it("Measure binary size", async () => {
|
||||
const stat = await fs.stat(path.join("target", "deploy", `${IDL.name}.so`));
|
||||
binarySize[IDL.name] = stat.size;
|
||||
const stat = await fs.stat(
|
||||
path.join("target", "deploy", `${IDL.metadata.name}.so`)
|
||||
);
|
||||
binarySize[IDL.metadata.name] = stat.size;
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import * as token from "@coral-xyz/spl-token";
|
||||
|
||||
import { Bench, IDL } from "../target/types/bench";
|
||||
import { Bench } from "../target/types/bench";
|
||||
import { BenchData, ComputeUnits } from "../scripts/utils";
|
||||
|
||||
describe("Compute units", () => {
|
||||
|
@ -31,7 +31,7 @@ describe("Compute units", () => {
|
|||
for (const accountCount of options.accountCounts) {
|
||||
// Check whether the init version of the instruction exists
|
||||
const ixNameInit = `${ixName}Init`;
|
||||
const hasInitVersion = IDL.instructions.some((ix) =>
|
||||
const hasInitVersion = program.idl.instructions.some((ix) =>
|
||||
ix.name.startsWith(ixNameInit)
|
||||
);
|
||||
|
||||
|
@ -53,7 +53,7 @@ describe("Compute units", () => {
|
|||
signers.splice(0);
|
||||
}
|
||||
|
||||
for (const ix of IDL.instructions) {
|
||||
for (const ix of program.idl.instructions) {
|
||||
if (ix.name !== method) continue;
|
||||
|
||||
for (const account of ix.accounts) {
|
||||
|
@ -80,7 +80,7 @@ describe("Compute units", () => {
|
|||
const keypair = options.generateKeypair(account.name);
|
||||
accounts[account.name] = keypair.publicKey;
|
||||
|
||||
if (account.isSigner) {
|
||||
if (account.signer) {
|
||||
signers.push(keypair);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,8 @@ import path from "path";
|
|||
import fs from "fs/promises";
|
||||
|
||||
import { BenchData, StackMemory, spawn } from "../scripts/utils";
|
||||
import { IDL } from "../target/types/bench";
|
||||
|
||||
const IDL = require("../target/idl/bench.json");
|
||||
|
||||
describe("Stack memory", () => {
|
||||
const stackMemory: StackMemory = {};
|
||||
|
@ -15,7 +16,7 @@ describe("Stack memory", () => {
|
|||
].join("\n\t\t");
|
||||
|
||||
it("Measure stack memory usage", async () => {
|
||||
const libPath = path.join("programs", IDL.name, "src", "lib.rs");
|
||||
const libPath = path.join("programs", IDL.metadata.name, "src", "lib.rs");
|
||||
const lib = await fs.readFile(libPath, "utf8");
|
||||
const indices = [...lib.matchAll(/fn\s[\w\d]+\(/g)]
|
||||
.map((match) => match.index)
|
||||
|
|
|
@ -14,6 +14,7 @@ no-entrypoint = []
|
|||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
use crate::program::BpfUpgradeableState;
|
||||
|
||||
declare_id!("Cum9tTyj5HwcEiAmhgaS7Bbj4UczCwsucrCkxRECzM4e");
|
||||
|
||||
#[program]
|
||||
|
@ -73,7 +75,7 @@ pub struct SetAdminSettingsUseProgramState<'info> {
|
|||
#[account(mut)]
|
||||
pub authority: Signer<'info>,
|
||||
#[account(constraint = program.programdata_address()? == Some(program_data.key()))]
|
||||
pub program: Program<'info, crate::program::BpfUpgradeableState>,
|
||||
pub program: Program<'info, BpfUpgradeableState>,
|
||||
#[account(constraint = program_data.upgrade_authority_address == Some(authority.key()))]
|
||||
pub program_data: Account<'info, ProgramData>,
|
||||
pub system_program: Program<'info, System>,
|
||||
|
|
|
@ -14,6 +14,7 @@ no-entrypoint = []
|
|||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -14,6 +14,7 @@ no-entrypoint = []
|
|||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -12,6 +12,7 @@ name = "composite"
|
|||
[features]
|
||||
no-entrypoint = []
|
||||
cpi = ["no-entrypoint"]
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -14,6 +14,7 @@ no-idl = []
|
|||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang", features = ["init-if-needed"] }
|
||||
|
|
|
@ -14,6 +14,7 @@ no-idl = []
|
|||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang", features = ["init-if-needed"] }
|
||||
|
|
|
@ -159,7 +159,7 @@ describe("CPI return", () => {
|
|||
(f) => f.name == "returnStruct"
|
||||
);
|
||||
assert.deepStrictEqual(returnStructInstruction.returns, {
|
||||
defined: "StructReturn",
|
||||
defined: { name: "structReturn" },
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ no-idl = []
|
|||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -14,6 +14,7 @@ no-idl = []
|
|||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[profile.release]
|
||||
overflow-checks = true
|
||||
|
|
|
@ -15,6 +15,7 @@ no-idl = []
|
|||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -11,6 +11,7 @@ name = "declare_id"
|
|||
[features]
|
||||
no-entrypoint = []
|
||||
cpi = ["no-entrypoint"]
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -11,6 +11,7 @@ name = "errors"
|
|||
[features]
|
||||
no-entrypoint = []
|
||||
cpi = ["no-entrypoint"]
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -12,6 +12,7 @@ name = "errors"
|
|||
[features]
|
||||
no-entrypoint = []
|
||||
cpi = ["no-entrypoint"]
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -258,7 +258,7 @@ describe("errors", () => {
|
|||
},
|
||||
],
|
||||
programId: program.programId,
|
||||
data: program.coder.instruction.encode("signer_error", {}),
|
||||
data: program.coder.instruction.encode("signerError", {}),
|
||||
})
|
||||
);
|
||||
await program.provider.sendAndConfirm(tx);
|
||||
|
|
|
@ -14,6 +14,7 @@ no-entrypoint = []
|
|||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -14,6 +14,7 @@ no-entrypoint = []
|
|||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang", features = ["event-cpi"] }
|
||||
|
|
|
@ -27,15 +27,15 @@ describe("Events", () => {
|
|||
|
||||
describe("Normal event", () => {
|
||||
it("Single event works", async () => {
|
||||
const event = await getEvent("MyEvent", "initialize");
|
||||
const event = await getEvent("myEvent", "initialize");
|
||||
|
||||
assert.strictEqual(event.data.toNumber(), 5);
|
||||
assert.strictEqual(event.label, "hello");
|
||||
});
|
||||
|
||||
it("Multiple events work", async () => {
|
||||
const eventOne = await getEvent("MyEvent", "initialize");
|
||||
const eventTwo = await getEvent("MyOtherEvent", "testEvent");
|
||||
const eventOne = await getEvent("myEvent", "initialize");
|
||||
const eventTwo = await getEvent("myOtherEvent", "testEvent");
|
||||
|
||||
assert.strictEqual(eventOne.data.toNumber(), 5);
|
||||
assert.strictEqual(eventOne.label, "hello");
|
||||
|
@ -61,7 +61,7 @@ describe("Events", () => {
|
|||
const eventData = anchor.utils.bytes.base64.encode(ixData.slice(8));
|
||||
const event = program.coder.events.decode(eventData);
|
||||
|
||||
assert.strictEqual(event.name, "MyOtherEvent");
|
||||
assert.strictEqual(event.name, "myOtherEvent");
|
||||
assert.strictEqual(event.data.label, "cpi");
|
||||
assert.strictEqual((event.data.data as anchor.BN).toNumber(), 7);
|
||||
});
|
||||
|
|
|
@ -14,6 +14,7 @@ no-idl = []
|
|||
no-log-ix-name = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
[features]
|
||||
seeds = true
|
||||
skip-lint = true
|
||||
|
||||
[programs.localnet]
|
||||
client_interactions = "C1ient1nteractions1111111111111111111111111"
|
||||
docs = "Docs111111111111111111111111111111111111111"
|
||||
external = "Externa1111111111111111111111111111111111111"
|
||||
generics = "Generics111111111111111111111111111111111111"
|
||||
idl = "id11111111111111111111111111111111111111111"
|
||||
idl_build_features = "id1Bui1dFeatures111111111111111111111111111"
|
||||
relations_derivation = "Re1ationsDerivation111111111111111111111111"
|
||||
non_existent = { address = "NonExistent11111111111111111111111111111111", idl = "non-existent.json" }
|
||||
numbers_123 = { address = "Numbers111111111111111111111111111111111111", idl = "idls/relations_build.json" }
|
||||
numbers_123 = { address = "Numbers111111111111111111111111111111111111", idl = "idls/relations.json" }
|
||||
new_idl = "Newid11111111111111111111111111111111111111"
|
||||
|
||||
[provider]
|
||||
cluster = "localnet"
|
||||
|
|
|
@ -8,11 +8,10 @@ else
|
|||
fi
|
||||
|
||||
cd programs/idl
|
||||
anchor idl parse --file src/lib.rs -o $dir/parse.json
|
||||
anchor idl build -o $dir/build.json
|
||||
anchor idl build -o $dir/new.json
|
||||
|
||||
cd ../generics
|
||||
anchor idl build -o $dir/generics_build.json
|
||||
anchor idl build -o $dir/generics.json
|
||||
|
||||
cd ../relations-derivation
|
||||
anchor idl build -o $dir/relations_build.json
|
||||
anchor idl build -o $dir/relations.json
|
||||
|
|
|
@ -1,40 +1,55 @@
|
|||
{
|
||||
"version": "0.1.0",
|
||||
"name": "generics",
|
||||
"address": "Generics111111111111111111111111111111111111",
|
||||
"metadata": {
|
||||
"name": "generics",
|
||||
"version": "0.1.0",
|
||||
"spec": "0.1.0",
|
||||
"description": "Created with Anchor"
|
||||
},
|
||||
"instructions": [
|
||||
{
|
||||
"name": "generic",
|
||||
"discriminator": [
|
||||
63,
|
||||
235,
|
||||
150,
|
||||
148,
|
||||
7,
|
||||
255,
|
||||
185,
|
||||
159
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "genericAcc",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
"name": "generic_acc"
|
||||
},
|
||||
{
|
||||
"name": "payer",
|
||||
"isMut": true,
|
||||
"isSigner": true
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "systemProgram",
|
||||
"isMut": false,
|
||||
"isSigner": false
|
||||
"name": "system_program",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "genericField",
|
||||
"name": "generic_field",
|
||||
"type": {
|
||||
"definedWithTypeArgs": {
|
||||
"defined": {
|
||||
"name": "GenericType",
|
||||
"args": [
|
||||
"generics": [
|
||||
{
|
||||
"kind": "type",
|
||||
"type": "u32"
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"kind": "const",
|
||||
"value": "10"
|
||||
}
|
||||
]
|
||||
|
@ -45,6 +60,21 @@
|
|||
}
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "GenericAccount",
|
||||
"discriminator": [
|
||||
10,
|
||||
71,
|
||||
68,
|
||||
49,
|
||||
51,
|
||||
72,
|
||||
147,
|
||||
245
|
||||
]
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "GenericAccount",
|
||||
"type": {
|
||||
|
@ -53,16 +83,19 @@
|
|||
{
|
||||
"name": "data",
|
||||
"type": {
|
||||
"definedWithTypeArgs": {
|
||||
"defined": {
|
||||
"name": "GenericType",
|
||||
"args": [
|
||||
"generics": [
|
||||
{
|
||||
"kind": "type",
|
||||
"type": "u32"
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"type": "u64"
|
||||
},
|
||||
{
|
||||
"kind": "const",
|
||||
"value": "10"
|
||||
}
|
||||
]
|
||||
|
@ -71,27 +104,23 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "Baz",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "someField",
|
||||
"type": "u8"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "GenericEnum",
|
||||
"generics": [
|
||||
"T",
|
||||
"U",
|
||||
"N"
|
||||
{
|
||||
"kind": "type",
|
||||
"name": "T"
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"name": "U"
|
||||
},
|
||||
{
|
||||
"kind": "const",
|
||||
"name": "N",
|
||||
"type": "usize"
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "enum",
|
||||
|
@ -128,15 +157,17 @@
|
|||
"name": "Struct",
|
||||
"fields": [
|
||||
{
|
||||
"definedWithTypeArgs": {
|
||||
"defined": {
|
||||
"name": "GenericNested",
|
||||
"args": [
|
||||
"generics": [
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"generic": "T"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"generic": "U"
|
||||
}
|
||||
|
@ -150,11 +181,13 @@
|
|||
"name": "Arr",
|
||||
"fields": [
|
||||
{
|
||||
"genericLenArray": [
|
||||
"array": [
|
||||
{
|
||||
"generic": "T"
|
||||
},
|
||||
"N"
|
||||
{
|
||||
"generic": "N"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -165,8 +198,14 @@
|
|||
{
|
||||
"name": "GenericNested",
|
||||
"generics": [
|
||||
"V",
|
||||
"Z"
|
||||
{
|
||||
"kind": "type",
|
||||
"name": "V"
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"name": "Z"
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
|
@ -189,9 +228,19 @@
|
|||
{
|
||||
"name": "GenericType",
|
||||
"generics": [
|
||||
"T",
|
||||
"U",
|
||||
"N"
|
||||
{
|
||||
"kind": "type",
|
||||
"name": "T"
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"name": "U"
|
||||
},
|
||||
{
|
||||
"kind": "const",
|
||||
"name": "N",
|
||||
"type": "usize"
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
|
@ -211,13 +260,15 @@
|
|||
{
|
||||
"name": "gen3",
|
||||
"type": {
|
||||
"definedWithTypeArgs": {
|
||||
"defined": {
|
||||
"name": "GenericNested",
|
||||
"args": [
|
||||
"generics": [
|
||||
{
|
||||
"kind": "type",
|
||||
"type": "u32"
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"generic": "U"
|
||||
}
|
||||
|
@ -229,17 +280,21 @@
|
|||
{
|
||||
"name": "gen4",
|
||||
"type": {
|
||||
"definedWithTypeArgs": {
|
||||
"defined": {
|
||||
"name": "GenericNested",
|
||||
"args": [
|
||||
"generics": [
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"generic": "T"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"defined": "Baz"
|
||||
"defined": {
|
||||
"name": "MyStruct"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -249,15 +304,17 @@
|
|||
{
|
||||
"name": "gen5",
|
||||
"type": {
|
||||
"definedWithTypeArgs": {
|
||||
"defined": {
|
||||
"name": "GenericNested",
|
||||
"args": [
|
||||
"generics": [
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"generic": "T"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"generic": "U"
|
||||
}
|
||||
|
@ -269,13 +326,15 @@
|
|||
{
|
||||
"name": "gen6",
|
||||
"type": {
|
||||
"definedWithTypeArgs": {
|
||||
"defined": {
|
||||
"name": "GenericNested",
|
||||
"args": [
|
||||
"generics": [
|
||||
{
|
||||
"kind": "type",
|
||||
"type": "u32"
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
|
@ -285,25 +344,29 @@
|
|||
{
|
||||
"name": "gen7",
|
||||
"type": {
|
||||
"definedWithTypeArgs": {
|
||||
"defined": {
|
||||
"name": "GenericNested",
|
||||
"args": [
|
||||
"generics": [
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"generic": "T"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"definedWithTypeArgs": {
|
||||
"defined": {
|
||||
"name": "GenericNested",
|
||||
"args": [
|
||||
"generics": [
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"generic": "T"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"generic": "U"
|
||||
}
|
||||
|
@ -319,19 +382,22 @@
|
|||
{
|
||||
"name": "arr",
|
||||
"type": {
|
||||
"genericLenArray": [
|
||||
"array": [
|
||||
"u8",
|
||||
"N"
|
||||
{
|
||||
"generic": "N"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "warr",
|
||||
"type": {
|
||||
"definedWithTypeArgs": {
|
||||
"defined": {
|
||||
"name": "WrappedU8Array",
|
||||
"args": [
|
||||
"generics": [
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"generic": "N"
|
||||
}
|
||||
|
@ -343,10 +409,11 @@
|
|||
{
|
||||
"name": "warrval",
|
||||
"type": {
|
||||
"definedWithTypeArgs": {
|
||||
"defined": {
|
||||
"name": "WrappedU8Array",
|
||||
"args": [
|
||||
"generics": [
|
||||
{
|
||||
"kind": "const",
|
||||
"value": "10"
|
||||
}
|
||||
]
|
||||
|
@ -356,20 +423,23 @@
|
|||
{
|
||||
"name": "enm1",
|
||||
"type": {
|
||||
"definedWithTypeArgs": {
|
||||
"defined": {
|
||||
"name": "GenericEnum",
|
||||
"args": [
|
||||
"generics": [
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"generic": "T"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"generic": "U"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"generic": "N"
|
||||
}
|
||||
|
@ -381,20 +451,23 @@
|
|||
{
|
||||
"name": "enm2",
|
||||
"type": {
|
||||
"definedWithTypeArgs": {
|
||||
"defined": {
|
||||
"name": "GenericEnum",
|
||||
"args": [
|
||||
"generics": [
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"definedWithTypeArgs": {
|
||||
"defined": {
|
||||
"name": "GenericNested",
|
||||
"args": [
|
||||
"generics": [
|
||||
{
|
||||
"kind": "type",
|
||||
"type": {
|
||||
"generic": "T"
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"type": "u64"
|
||||
}
|
||||
]
|
||||
|
@ -402,9 +475,11 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"kind": "type",
|
||||
"type": "u32"
|
||||
},
|
||||
{
|
||||
"kind": "const",
|
||||
"value": "30"
|
||||
}
|
||||
]
|
||||
|
@ -413,6 +488,34 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "MyStruct",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "some_field",
|
||||
"type": "u8"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "WrappedU8Array",
|
||||
"generics": [
|
||||
{
|
||||
"kind": "const",
|
||||
"name": "N",
|
||||
"type": "usize"
|
||||
}
|
||||
],
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
"u8"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -526,7 +526,7 @@
|
|||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "Baz",
|
||||
"name": "MyStruct",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
|
@ -720,16 +720,16 @@
|
|||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "externalBaz",
|
||||
"name": "externalMyStruct",
|
||||
"type": {
|
||||
"defined": "external::Baz"
|
||||
"defined": "external::MyStruct"
|
||||
},
|
||||
"index": false
|
||||
},
|
||||
{
|
||||
"name": "otherModuleBaz",
|
||||
"name": "otherModuleMyStruct",
|
||||
"type": {
|
||||
"defined": "some_other_module::Baz"
|
||||
"defined": "some_other_module::MyStruct"
|
||||
},
|
||||
"index": false
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
{
|
||||
"address": "Re1ationsDerivation111111111111111111111111",
|
||||
"metadata": {
|
||||
"name": "relations_derivation",
|
||||
"version": "0.1.0",
|
||||
"spec": "0.1.0",
|
||||
"description": "Created with Anchor"
|
||||
},
|
||||
"instructions": [
|
||||
{
|
||||
"name": "init_base",
|
||||
"discriminator": [
|
||||
85,
|
||||
87,
|
||||
185,
|
||||
141,
|
||||
241,
|
||||
191,
|
||||
213,
|
||||
88
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "my_account",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"writable": true,
|
||||
"pda": {
|
||||
"seeds": [
|
||||
{
|
||||
"kind": "const",
|
||||
"value": [
|
||||
115,
|
||||
101,
|
||||
101,
|
||||
100
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "system_program",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "test_relation",
|
||||
"discriminator": [
|
||||
247,
|
||||
199,
|
||||
255,
|
||||
202,
|
||||
7,
|
||||
0,
|
||||
197,
|
||||
158
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "my_account",
|
||||
"relations": [
|
||||
"account"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"pda": {
|
||||
"seeds": [
|
||||
{
|
||||
"kind": "const",
|
||||
"value": [
|
||||
115,
|
||||
101,
|
||||
101,
|
||||
100
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "nested",
|
||||
"accounts": [
|
||||
{
|
||||
"name": "my_account",
|
||||
"relations": [
|
||||
"account"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "account",
|
||||
"pda": {
|
||||
"seeds": [
|
||||
{
|
||||
"kind": "const",
|
||||
"value": [
|
||||
115,
|
||||
101,
|
||||
101,
|
||||
100
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"args": []
|
||||
}
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "MyAccount",
|
||||
"discriminator": [
|
||||
246,
|
||||
28,
|
||||
6,
|
||||
87,
|
||||
251,
|
||||
45,
|
||||
50,
|
||||
42
|
||||
]
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "MyAccount",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "my_account",
|
||||
"type": "pubkey"
|
||||
},
|
||||
{
|
||||
"name": "bump",
|
||||
"type": "u8"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
{
|
||||
"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",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "myAccount",
|
||||
"type": "publicKey"
|
||||
},
|
||||
{
|
||||
"name": "bump",
|
||||
"type": "u8"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
[package]
|
||||
name = "client-interactions"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
rust-version = "1.60"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "client_interactions"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
|
@ -1,131 +0,0 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
declare_id!("C1ient1nteractions1111111111111111111111111");
|
||||
|
||||
#[program]
|
||||
pub mod client_interactions {
|
||||
use super::*;
|
||||
|
||||
pub fn int(ctx: Context<Int>, i8: i8, i16: i16, i32: i32, i64: i64, i128: i128) -> Result<()> {
|
||||
ctx.accounts.account.i8 = i8;
|
||||
ctx.accounts.account.i16 = i16;
|
||||
ctx.accounts.account.i32 = i32;
|
||||
ctx.accounts.account.i64 = i64;
|
||||
ctx.accounts.account.i128 = i128;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn uint(
|
||||
ctx: Context<UnsignedInt>,
|
||||
u8: u8,
|
||||
u16: u16,
|
||||
u32: u32,
|
||||
u64: u64,
|
||||
u128: u128,
|
||||
) -> Result<()> {
|
||||
ctx.accounts.account.u8 = u8;
|
||||
ctx.accounts.account.u16 = u16;
|
||||
ctx.accounts.account.u32 = u32;
|
||||
ctx.accounts.account.u64 = u64;
|
||||
ctx.accounts.account.u128 = u128;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn enm(ctx: Context<Enum>, enum_arg: MyEnum) -> Result<()> {
|
||||
ctx.accounts.account.enum_field = enum_arg;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn type_alias(
|
||||
ctx: Context<TypeAlias>,
|
||||
type_alias_u8: TypeAliasU8,
|
||||
type_alias_u8_array: TypeAliasU8Array,
|
||||
type_alias_struct: TypeAliasStruct,
|
||||
) -> Result<()> {
|
||||
ctx.accounts.account.type_alias_u8 = type_alias_u8;
|
||||
ctx.accounts.account.type_alias_u8_array = type_alias_u8_array;
|
||||
ctx.accounts.account.type_alias_struct = type_alias_struct;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Int<'info> {
|
||||
#[account(zero)]
|
||||
pub account: Account<'info, IntAccount>,
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct IntAccount {
|
||||
pub i8: i8,
|
||||
pub i16: i16,
|
||||
pub i32: i32,
|
||||
pub i64: i64,
|
||||
pub i128: i128,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct UnsignedInt<'info> {
|
||||
#[account(zero)]
|
||||
pub account: Account<'info, UnsignedIntAccount>,
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct UnsignedIntAccount {
|
||||
pub u8: u8,
|
||||
pub u16: u16,
|
||||
pub u32: u32,
|
||||
pub u64: u64,
|
||||
pub u128: u128,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Enum<'info> {
|
||||
#[account(zero)]
|
||||
pub account: Account<'info, EnumAccount>,
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct EnumAccount {
|
||||
pub enum_field: MyEnum,
|
||||
}
|
||||
|
||||
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum MyEnum {
|
||||
Unit,
|
||||
Named { point_x: u64, point_y: u64 },
|
||||
Unnamed(u8, u8, u16, u16),
|
||||
UnnamedStruct(MyStruct),
|
||||
}
|
||||
|
||||
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct MyStruct {
|
||||
pub u8: u8,
|
||||
pub u16: u16,
|
||||
pub u32: u32,
|
||||
pub u64: u64,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct TypeAlias<'info> {
|
||||
#[account(zero)]
|
||||
pub account: Account<'info, TypeAliasAccount>,
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct TypeAliasAccount {
|
||||
pub type_alias_u8: TypeAliasU8,
|
||||
pub type_alias_u8_array: TypeAliasU8Array,
|
||||
pub type_alias_struct: TypeAliasStruct,
|
||||
}
|
||||
|
||||
pub type TypeAliasU8 = u8;
|
||||
pub type TypeAliasU8Array = [TypeAliasU8; 8];
|
||||
pub type TypeAliasStruct = MyStruct;
|
||||
|
||||
/// Generic type aliases should not get included in the IDL
|
||||
pub type TypeAliasNotSupported<'a, T> = NotSupported<'a, T>;
|
||||
pub struct NotSupported<'a, T> {
|
||||
_t: T,
|
||||
_s: &'a str,
|
||||
}
|
|
@ -14,6 +14,7 @@ no-entrypoint = []
|
|||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
||||
|
|
|
@ -6,15 +6,26 @@ declare_id!("Externa1111111111111111111111111111111111111");
|
|||
pub mod external {
|
||||
use super::*;
|
||||
|
||||
pub fn initialize(_ctx: Context<Initialize>, _baz: Baz) -> Result<()> {
|
||||
pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
|
||||
pub struct Baz {
|
||||
pub struct MyStruct {
|
||||
some_field: u8,
|
||||
}
|
||||
|
||||
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
|
||||
pub enum MyEnum {
|
||||
Unit,
|
||||
Named { name: String },
|
||||
Tuple(String),
|
||||
}
|
||||
|
||||
pub struct NonBorshStruct {
|
||||
pub data: i32,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Initialize {}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue