Skip to content

Commit 81b554e

Browse files
authored
Support zero copy and repr(packed) (#7)
* Fix default array impls * get whirlpool working * fix tests
1 parent fea1a77 commit 81b554e

File tree

15 files changed

+2081
-91
lines changed

15 files changed

+2081
-91
lines changed

crates/anchor-gen/Cargo.toml

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,7 @@ readme = "../../README.md"
1212
[lib]
1313
name = "anchor_gen"
1414
path = "src/lib.rs"
15-
proc-macro = true
1615

1716
[dependencies]
18-
anchor-idl = { version = "0.2.0", path = "../anchor-idl" }
19-
serde_json = "1.0.81"
20-
anchor-syn = { version = "0.24.2", features = ["idl"] }
21-
syn = { version = "1", features = ["full"] }
22-
23-
[dev-dependencies]
24-
anchor-lang = "0.24.2"
17+
anchor-generate-cpi-crate = { version = "0.2.0", path = "../anchor-generate-cpi-crate" }
18+
anchor-generate-cpi-interface = { version = "0.2.0", path = "../anchor-generate-cpi-interface" }

crates/anchor-gen/src/lib.rs

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,37 +14,5 @@
1414
//!
1515
//! More examples can be found in the [examples/](https://github.com/saber-hq/anchor-gen/tree/master/examples) directory.
1616
17-
use std::{env, fs, path::PathBuf};
18-
use syn::{parse_macro_input, LitStr};
19-
20-
/// Generates an Anchor CPI crate from a JSON file.
21-
///
22-
/// # Arguments
23-
///
24-
/// * `input` - Path to a JSON IDL relative to the crate's the Cargo.toml.
25-
///
26-
/// # Examples
27-
///
28-
/// ```
29-
/// anchor_gen::generate_cpi_crate!("../../examples/govern-cpi/idl.json");
30-
/// declare_id!("GjphYQcbP1m3FuDyCTUJf2mUMxKPE3j6feWU1rxvC7Ps");
31-
/// # fn main() -> Result<()> {
32-
/// let _my_governor = GovernanceParameters {
33-
/// quorum_votes: 0,
34-
/// timelock_delay_seconds: 0,
35-
/// voting_period: 0,
36-
/// voting_delay: 0,
37-
/// };
38-
/// # Ok(())
39-
/// # }
40-
/// ```
41-
#[proc_macro]
42-
pub fn generate_cpi_crate(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
43-
let id_literal = parse_macro_input!(input as LitStr);
44-
let cargo_manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
45-
let path = PathBuf::from(cargo_manifest_dir).join(id_literal.value());
46-
let idl_contents = fs::read_to_string(&path).unwrap();
47-
let idl: anchor_syn::idl::Idl = serde_json::from_str(&idl_contents).unwrap();
48-
let output = anchor_idl::generate_cpi_helpers(&idl);
49-
output.into()
50-
}
17+
pub use anchor_generate_cpi_crate::generate_cpi_crate;
18+
pub use anchor_generate_cpi_interface::generate_cpi_interface;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[package]
2+
name = "anchor-generate-cpi-crate"
3+
version = "0.2.0"
4+
edition = "2021"
5+
description = "Generates an Anchor CPI crate from a JSON IDL."
6+
authors = ["Ian Macalinao <[email protected]>"]
7+
repository = "https://github.com/saber-hq/anchor-gen"
8+
license = "Apache-2.0"
9+
keywords = ["solana", "anchor"]
10+
readme = "../../README.md"
11+
12+
[lib]
13+
name = "anchor_generate_cpi_crate"
14+
path = "src/lib.rs"
15+
proc-macro = true
16+
17+
[dependencies]
18+
anchor-idl = { version = "0.2.0", path = "../anchor-idl" }
19+
syn = { version = "1", features = ["full"] }
20+
21+
[dev-dependencies]
22+
anchor-lang = "0.24.2"
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//! Generates a crate for cross-program invocations to an Anchor program from a JSON IDL.
2+
//!
3+
//! # Usage
4+
//!
5+
//! In a new crate, write:
6+
//!
7+
//! ```skip
8+
//! anchor_gen::generate_cpi_crate!("../../examples/govern-cpi/idl.json");
9+
//!
10+
//! declare_id!("GjphYQcbP1m3FuDyCTUJf2mUMxKPE3j6feWU1rxvC7Ps");
11+
//! ```
12+
//!
13+
//! This will generate a fully functional Rust CPI client for your IDL.
14+
//!
15+
//! More examples can be found in the [examples/](https://github.com/saber-hq/anchor-gen/tree/master/examples) directory.
16+
17+
use anchor_idl::GeneratorOptions;
18+
use syn::{parse_macro_input, LitStr};
19+
20+
/// Generates an Anchor CPI crate from a JSON file.
21+
///
22+
/// # Arguments
23+
///
24+
/// * `input` - Path to a JSON IDL relative to the crate's the Cargo.toml.
25+
///
26+
/// # Examples
27+
///
28+
/// ```
29+
/// anchor_generate_cpi_crate::generate_cpi_crate!("../../examples/govern-cpi/idl.json");
30+
/// declare_id!("GjphYQcbP1m3FuDyCTUJf2mUMxKPE3j6feWU1rxvC7Ps");
31+
/// # fn main() -> Result<()> {
32+
/// let _my_governor = GovernanceParameters {
33+
/// quorum_votes: 0,
34+
/// timelock_delay_seconds: 0,
35+
/// voting_period: 0,
36+
/// voting_delay: 0,
37+
/// };
38+
/// # Ok(())
39+
/// # }
40+
/// ```
41+
#[proc_macro]
42+
pub fn generate_cpi_crate(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
43+
let id_literal = parse_macro_input!(input as LitStr);
44+
let opts = GeneratorOptions {
45+
idl_path: id_literal.value(),
46+
..Default::default()
47+
};
48+
opts.to_generator().generate_cpi_interface().into()
49+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "anchor-generate-cpi-interface"
3+
version = "0.2.0"
4+
edition = "2021"
5+
description = "Generates an Anchor CPI crate from a JSON IDL."
6+
authors = ["Ian Macalinao <[email protected]>"]
7+
repository = "https://github.com/saber-hq/anchor-gen"
8+
license = "Apache-2.0"
9+
keywords = ["solana", "anchor"]
10+
readme = "../../README.md"
11+
12+
[lib]
13+
name = "anchor_generate_cpi_interface"
14+
path = "src/lib.rs"
15+
proc-macro = true
16+
17+
[dependencies]
18+
anchor-idl = { version = "0.2.0", path = "../anchor-idl" }
19+
darling = "0.14"
20+
syn = { version = "1", features = ["full"] }
21+
22+
[dev-dependencies]
23+
anchor-lang = "0.24.2"
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//! Generates a crate for cross-program invocations to an Anchor program from a JSON IDL.
2+
//!
3+
//! # Usage
4+
//!
5+
//! In a new crate, write:
6+
//!
7+
//! ```skip
8+
//! anchor_gen::generate_cpi_crate!("../../examples/govern-cpi/idl.json");
9+
//!
10+
//! declare_id!("GjphYQcbP1m3FuDyCTUJf2mUMxKPE3j6feWU1rxvC7Ps");
11+
//! ```
12+
//!
13+
//! This will generate a fully functional Rust CPI client for your IDL.
14+
//!
15+
//! More examples can be found in the [examples/](https://github.com/saber-hq/anchor-gen/tree/master/examples) directory.
16+
17+
use anchor_idl::GeneratorOptions;
18+
use darling::FromMeta;
19+
use proc_macro::TokenStream;
20+
use syn::parse_macro_input;
21+
22+
/// Generates an Anchor CPI crate from a JSON file.
23+
///
24+
/// # Arguments
25+
///
26+
/// * `idl_path` - Path to a JSON IDL relative to the crate's the Cargo.toml.
27+
///
28+
/// # Examples
29+
///
30+
/// ```
31+
/// anchor_generate_cpi_interface::generate_cpi_interface!(idl_path = "../../examples/govern-cpi/idl.json");
32+
/// declare_id!("GjphYQcbP1m3FuDyCTUJf2mUMxKPE3j6feWU1rxvC7Ps");
33+
/// # fn main() -> Result<()> {
34+
/// let _my_governor = GovernanceParameters {
35+
/// quorum_votes: 0,
36+
/// timelock_delay_seconds: 0,
37+
/// voting_period: 0,
38+
/// voting_delay: 0,
39+
/// };
40+
/// # Ok(())
41+
/// # }
42+
/// ```
43+
#[proc_macro]
44+
pub fn generate_cpi_interface(input: proc_macro::TokenStream) -> TokenStream {
45+
let attr_args = parse_macro_input!(input as syn::AttributeArgs);
46+
let parsed = match GeneratorOptions::from_list(&attr_args) {
47+
Ok(v) => v,
48+
Err(e) => {
49+
return TokenStream::from(e.write_errors());
50+
}
51+
};
52+
parsed.to_generator().generate_cpi_interface().into()
53+
}

crates/anchor-idl/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ license = "Apache-2.0"
99
keywords = ["solana", "anchor"]
1010

1111
[dependencies]
12-
heck = "0.4.0"
1312
anchor-syn = { version = "0.24.2", features = ["idl"] }
13+
darling = "0.14"
14+
heck = "0.4.0"
1415
proc-macro2 = "1"
1516
quote = "1"
17+
serde_json = "1.0.81"
1618
syn = { version = "1", features = ["full"] }
1719

1820
[dev-dependencies]

crates/anchor-idl/src/program.rs

Lines changed: 102 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,122 @@
1-
use crate::*;
1+
use std::{
2+
collections::{BTreeMap, HashSet},
3+
env, fs,
4+
path::PathBuf,
5+
};
6+
7+
use darling::{util::PathList, FromMeta};
28
use proc_macro2::{Ident, TokenStream};
39
use quote::{format_ident, quote};
410

5-
/// Generates all CPI helpers.
6-
pub fn generate_cpi_helpers(idl: &anchor_syn::idl::Idl) -> TokenStream {
7-
let program_name: Ident = format_ident!("{}", idl.name);
11+
use crate::{
12+
generate_accounts, generate_ix_handlers, generate_ix_structs, generate_typedefs, GEN_VERSION,
13+
};
14+
15+
#[derive(Default, FromMeta)]
16+
pub struct GeneratorOptions {
17+
/// Path to the IDL.
18+
pub idl_path: String,
19+
/// List of zero copy structs.
20+
pub zero_copy: Option<PathList>,
21+
/// List of `repr(packed)` structs.
22+
pub packed: Option<PathList>,
23+
}
24+
25+
fn path_list_to_string(list: Option<&PathList>) -> HashSet<String> {
26+
list.map(|el| {
27+
el.iter()
28+
.map(|el| el.get_ident().unwrap().to_string())
29+
.collect()
30+
})
31+
.unwrap_or_default()
32+
}
33+
34+
impl GeneratorOptions {
35+
pub fn to_generator(&self) -> Generator {
36+
let cargo_manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
37+
let path = PathBuf::from(cargo_manifest_dir).join(&self.idl_path);
38+
let idl_contents = fs::read_to_string(&path).unwrap();
39+
let idl: anchor_syn::idl::Idl = serde_json::from_str(&idl_contents).unwrap();
40+
41+
let zero_copy = path_list_to_string(self.zero_copy.as_ref());
42+
let packed = path_list_to_string(self.packed.as_ref());
43+
44+
let mut struct_opts: BTreeMap<String, StructOpts> = BTreeMap::new();
45+
let all_structs: HashSet<&String> = zero_copy.union(&packed).collect::<HashSet<_>>();
46+
all_structs.into_iter().for_each(|name| {
47+
struct_opts.insert(
48+
name.to_string(),
49+
StructOpts {
50+
zero_copy: zero_copy.contains(name),
51+
packed: packed.contains(name),
52+
},
53+
);
54+
});
855

9-
let accounts = generate_accounts(&idl.types, &idl.accounts);
10-
let typedefs = generate_typedefs(&idl.types);
11-
let ix_handlers = generate_ix_handlers(&idl.instructions);
12-
let ix_structs = generate_ix_structs(&idl.instructions);
56+
Generator { idl, struct_opts }
57+
}
58+
}
1359

14-
let docs = format!(
60+
#[derive(Clone, Copy, Default)]
61+
pub struct StructOpts {
62+
pub packed: bool,
63+
pub zero_copy: bool,
64+
}
65+
66+
pub struct Generator {
67+
pub idl: anchor_syn::idl::Idl,
68+
pub struct_opts: BTreeMap<String, StructOpts>,
69+
}
70+
71+
impl Generator {
72+
pub fn generate_cpi_interface(&self) -> TokenStream {
73+
let idl = &self.idl;
74+
let program_name: Ident = format_ident!("{}", idl.name);
75+
76+
let accounts = generate_accounts(&idl.types, &idl.accounts, &self.struct_opts);
77+
let typedefs = generate_typedefs(&idl.types, &self.struct_opts);
78+
let ix_handlers = generate_ix_handlers(&idl.instructions);
79+
let ix_structs = generate_ix_structs(&idl.instructions);
80+
81+
let docs = format!(
1582
" Anchor CPI crate generated from {} v{} using [anchor-gen](https://crates.io/crates/anchor-gen) v{}.",
1683
&idl.name,
1784
&idl.version,
1885
&GEN_VERSION.unwrap_or("unknown")
1986
);
2087

21-
quote! {
22-
use anchor_lang::prelude::*;
88+
quote! {
89+
use anchor_lang::prelude::*;
2390

24-
pub mod typedefs {
25-
//! User-defined types.
26-
use super::*;
27-
#typedefs
28-
}
91+
pub mod typedefs {
92+
//! User-defined types.
93+
use super::*;
94+
#typedefs
95+
}
2996

30-
pub mod state {
31-
//! Structs of accounts which hold state.
32-
use super::*;
33-
#accounts
34-
}
97+
pub mod state {
98+
//! Structs of accounts which hold state.
99+
use super::*;
100+
#accounts
101+
}
35102

36-
pub mod ix_accounts {
37-
//! Accounts used in instructions.
38-
use super::*;
39-
#ix_structs
40-
}
103+
pub mod ix_accounts {
104+
//! Accounts used in instructions.
105+
use super::*;
106+
#ix_structs
107+
}
41108

42-
use ix_accounts::*;
43-
pub use state::*;
44-
pub use typedefs::*;
109+
use ix_accounts::*;
110+
pub use state::*;
111+
pub use typedefs::*;
45112

46-
#[program]
47-
pub mod #program_name {
48-
#![doc = #docs]
113+
#[program]
114+
pub mod #program_name {
115+
#![doc = #docs]
49116

50-
use super::*;
51-
#ix_handlers
117+
use super::*;
118+
#ix_handlers
119+
}
52120
}
53121
}
54122
}

0 commit comments

Comments
 (0)