lang: Add `declare_program!` macro (#2857)
This commit is contained in:
parent
4393d73d3d
commit
0f6090950a
|
@ -38,6 +38,7 @@ The minor version will be incremented upon a breaking change and the patch versi
|
|||
- cli: Add `--no-idl` flag to the `build` command ([#2847](https://github.com/coral-xyz/anchor/pull/2847)).
|
||||
- cli: Add priority fees to idl commands ([#2845](https://github.com/coral-xyz/anchor/pull/2845)).
|
||||
- ts: Add `prepend` option to MethodBuilder `preInstructions` method ([#2863](https://github.com/coral-xyz/anchor/pull/2863)).
|
||||
- lang: Add `declare_program!` macro ([#2857](https://github.com/coral-xyz/anchor/pull/2857)).
|
||||
|
||||
### Fixes
|
||||
|
||||
|
|
|
@ -171,7 +171,12 @@ name = "anchor-attribute-program"
|
|||
version = "0.29.0"
|
||||
dependencies = [
|
||||
"anchor-syn",
|
||||
"anyhow",
|
||||
"bs58 0.5.0",
|
||||
"heck 0.3.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde_json",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
|
|
|
@ -17,6 +17,11 @@ idl-build = ["anchor-syn/idl-build"]
|
|||
interface-instructions = ["anchor-syn/interface-instructions"]
|
||||
|
||||
[dependencies]
|
||||
anchor-syn = { path = "../../syn", version = "0.29.0" }
|
||||
anchor-syn = { path = "../../syn", version = "0.29.0", features = ["idl-types"] }
|
||||
anyhow = "1"
|
||||
bs58 = "0.5"
|
||||
heck = "0.3"
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
serde_json = "1"
|
||||
syn = { version = "1", features = ["full"] }
|
||||
|
|
|
@ -0,0 +1,360 @@
|
|||
use anchor_syn::idl::types::{
|
||||
Idl, IdlArrayLen, IdlDefinedFields, IdlField, IdlGenericArg, IdlRepr, IdlSerialization,
|
||||
IdlType, IdlTypeDef, IdlTypeDefGeneric, IdlTypeDefTy,
|
||||
};
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
/// This function should ideally return the absolute path to the declared program's id but because
|
||||
/// `proc_macro2::Span::call_site().source_file().path()` is behind an unstable feature flag, we
|
||||
/// are not able to reliably decide where the definition is.
|
||||
pub fn get_canonical_program_id() -> proc_macro2::TokenStream {
|
||||
quote! { super::__ID }
|
||||
}
|
||||
|
||||
pub fn gen_docs(docs: &[String]) -> proc_macro2::TokenStream {
|
||||
let docs = docs
|
||||
.iter()
|
||||
.map(|doc| format!("{}{doc}", if doc.is_empty() { "" } else { " " }))
|
||||
.map(|doc| quote! { #[doc = #doc] });
|
||||
quote! { #(#docs)* }
|
||||
}
|
||||
|
||||
pub fn gen_discriminator(disc: &[u8]) -> proc_macro2::TokenStream {
|
||||
quote! { [#(#disc), *] }
|
||||
}
|
||||
|
||||
pub fn gen_accounts_common(idl: &Idl, prefix: &str) -> proc_macro2::TokenStream {
|
||||
let re_exports = idl
|
||||
.instructions
|
||||
.iter()
|
||||
.map(|ix| format_ident!("__{}_accounts_{}", prefix, ix.name))
|
||||
.map(|ident| quote! { pub use super::internal::#ident::*; });
|
||||
|
||||
quote! {
|
||||
pub mod accounts {
|
||||
#(#re_exports)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_idl_type_to_syn_type(ty: &IdlType) -> syn::Type {
|
||||
syn::parse_str(&convert_idl_type_to_str(ty)).unwrap()
|
||||
}
|
||||
|
||||
// TODO: Impl `ToString` for `IdlType`
|
||||
pub fn convert_idl_type_to_str(ty: &IdlType) -> String {
|
||||
match ty {
|
||||
IdlType::Bool => "bool".into(),
|
||||
IdlType::U8 => "u8".into(),
|
||||
IdlType::I8 => "i8".into(),
|
||||
IdlType::U16 => "u16".into(),
|
||||
IdlType::I16 => "i16".into(),
|
||||
IdlType::U32 => "u32".into(),
|
||||
IdlType::I32 => "i32".into(),
|
||||
IdlType::F32 => "f32".into(),
|
||||
IdlType::U64 => "u64".into(),
|
||||
IdlType::I64 => "i64".into(),
|
||||
IdlType::F64 => "f64".into(),
|
||||
IdlType::U128 => "u128".into(),
|
||||
IdlType::I128 => "i128".into(),
|
||||
IdlType::U256 => "u256".into(),
|
||||
IdlType::I256 => "i256".into(),
|
||||
IdlType::Bytes => "bytes".into(),
|
||||
IdlType::String => "String".into(),
|
||||
IdlType::Pubkey => "Pubkey".into(),
|
||||
IdlType::Option(ty) => format!("Option<{}>", convert_idl_type_to_str(ty)),
|
||||
IdlType::Vec(ty) => format!("Vec<{}>", convert_idl_type_to_str(ty)),
|
||||
IdlType::Array(ty, len) => format!(
|
||||
"[{}; {}]",
|
||||
convert_idl_type_to_str(ty),
|
||||
match len {
|
||||
IdlArrayLen::Generic(len) => len.into(),
|
||||
IdlArrayLen::Value(len) => len.to_string(),
|
||||
}
|
||||
),
|
||||
IdlType::Defined { name, generics } => generics
|
||||
.iter()
|
||||
.map(|generic| match generic {
|
||||
IdlGenericArg::Type { ty } => convert_idl_type_to_str(ty),
|
||||
IdlGenericArg::Const { value } => value.into(),
|
||||
})
|
||||
.reduce(|mut acc, cur| {
|
||||
if !acc.is_empty() {
|
||||
acc.push(',');
|
||||
}
|
||||
acc.push_str(&cur);
|
||||
acc
|
||||
})
|
||||
.map(|generics| format!("{name}<{generics}>"))
|
||||
.unwrap_or(name.into()),
|
||||
IdlType::Generic(ty) => ty.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_idl_type_def_to_ts(
|
||||
ty_def: &IdlTypeDef,
|
||||
ty_defs: &[IdlTypeDef],
|
||||
) -> proc_macro2::TokenStream {
|
||||
let name = format_ident!("{}", ty_def.name);
|
||||
let docs = gen_docs(&ty_def.docs);
|
||||
|
||||
let generics = {
|
||||
let generics = ty_def
|
||||
.generics
|
||||
.iter()
|
||||
.map(|generic| match generic {
|
||||
IdlTypeDefGeneric::Type { name } => format_ident!("{name}"),
|
||||
IdlTypeDefGeneric::Const { name, ty } => format_ident!("{name}: {ty}"),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
if generics.is_empty() {
|
||||
quote!()
|
||||
} else {
|
||||
quote!(<#(#generics,)*>)
|
||||
}
|
||||
};
|
||||
|
||||
let attrs = {
|
||||
let debug_attr = quote!(#[derive(Debug)]);
|
||||
|
||||
let default_attr = can_derive_default(ty_def, ty_defs)
|
||||
.then(|| quote!(#[derive(Default)]))
|
||||
.unwrap_or_default();
|
||||
|
||||
let ser_attr = match &ty_def.serialization {
|
||||
IdlSerialization::Borsh => quote!(#[derive(AnchorSerialize, AnchorDeserialize)]),
|
||||
IdlSerialization::Bytemuck => quote!(#[zero_copy]),
|
||||
IdlSerialization::BytemuckUnsafe => quote!(#[zero_copy(unsafe)]),
|
||||
_ => unimplemented!("{:?}", ty_def.serialization),
|
||||
};
|
||||
|
||||
let clone_attr = matches!(ty_def.serialization, IdlSerialization::Borsh)
|
||||
.then(|| quote!(#[derive(Clone)]))
|
||||
.unwrap_or_default();
|
||||
|
||||
let copy_attr = matches!(ty_def.serialization, IdlSerialization::Borsh)
|
||||
.then(|| can_derive_copy(ty_def, ty_defs).then(|| quote!(#[derive(Copy)])))
|
||||
.flatten()
|
||||
.unwrap_or_default();
|
||||
|
||||
quote! {
|
||||
#debug_attr
|
||||
#default_attr
|
||||
#ser_attr
|
||||
#clone_attr
|
||||
#copy_attr
|
||||
}
|
||||
};
|
||||
|
||||
let repr = if let Some(repr) = &ty_def.repr {
|
||||
let kind = match repr {
|
||||
IdlRepr::Rust(_) => "Rust",
|
||||
IdlRepr::C(_) => "C",
|
||||
IdlRepr::Transparent => "transparent",
|
||||
};
|
||||
let kind = format_ident!("{kind}");
|
||||
|
||||
let modifier = match repr {
|
||||
IdlRepr::Rust(modifier) | IdlRepr::C(modifier) => {
|
||||
let packed = modifier.packed.then(|| quote!(packed)).unwrap_or_default();
|
||||
let align = modifier
|
||||
.align
|
||||
.map(|align| quote!(align(#align)))
|
||||
.unwrap_or_default();
|
||||
|
||||
if packed.is_empty() {
|
||||
align
|
||||
} else if align.is_empty() {
|
||||
packed
|
||||
} else {
|
||||
quote! { #packed, #align }
|
||||
}
|
||||
}
|
||||
_ => quote!(),
|
||||
};
|
||||
let modifier = if modifier.is_empty() {
|
||||
modifier
|
||||
} else {
|
||||
quote! { , #modifier }
|
||||
};
|
||||
|
||||
quote! { #[repr(#kind #modifier)] }
|
||||
} else {
|
||||
quote!()
|
||||
};
|
||||
|
||||
let ty = match &ty_def.ty {
|
||||
IdlTypeDefTy::Struct { fields } => {
|
||||
let declare_struct = quote! { pub struct #name #generics };
|
||||
handle_defined_fields(
|
||||
fields.as_ref(),
|
||||
|| quote! { #declare_struct; },
|
||||
|fields| {
|
||||
let fields = fields.iter().map(|field| {
|
||||
let name = format_ident!("{}", field.name);
|
||||
let ty = convert_idl_type_to_syn_type(&field.ty);
|
||||
quote! { pub #name : #ty }
|
||||
});
|
||||
quote! {
|
||||
#declare_struct {
|
||||
#(#fields,)*
|
||||
}
|
||||
}
|
||||
},
|
||||
|tys| {
|
||||
let tys = tys.iter().map(convert_idl_type_to_syn_type);
|
||||
quote! {
|
||||
#declare_struct (#(#tys,)*);
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
IdlTypeDefTy::Enum { variants } => {
|
||||
let variants = variants.iter().map(|variant| {
|
||||
let variant_name = format_ident!("{}", variant.name);
|
||||
handle_defined_fields(
|
||||
variant.fields.as_ref(),
|
||||
|| quote! { #variant_name },
|
||||
|fields| {
|
||||
let fields = fields.iter().map(|field| {
|
||||
let name = format_ident!("{}", field.name);
|
||||
let ty = convert_idl_type_to_syn_type(&field.ty);
|
||||
quote! { #name : #ty }
|
||||
});
|
||||
quote! {
|
||||
#variant_name {
|
||||
#(#fields,)*
|
||||
}
|
||||
}
|
||||
},
|
||||
|tys| {
|
||||
let tys = tys.iter().map(convert_idl_type_to_syn_type);
|
||||
quote! {
|
||||
#variant_name (#(#tys,)*)
|
||||
}
|
||||
},
|
||||
)
|
||||
});
|
||||
|
||||
quote! {
|
||||
pub enum #name #generics {
|
||||
#(#variants,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
IdlTypeDefTy::Type { alias } => {
|
||||
let alias = convert_idl_type_to_syn_type(alias);
|
||||
quote! { pub type #name = #alias; }
|
||||
}
|
||||
};
|
||||
|
||||
quote! {
|
||||
#docs
|
||||
#attrs
|
||||
#repr
|
||||
#ty
|
||||
}
|
||||
}
|
||||
|
||||
fn can_derive_copy(ty_def: &IdlTypeDef, ty_defs: &[IdlTypeDef]) -> bool {
|
||||
match &ty_def.ty {
|
||||
IdlTypeDefTy::Struct { fields } => {
|
||||
can_derive_common(fields.as_ref(), ty_defs, can_derive_copy_ty)
|
||||
}
|
||||
IdlTypeDefTy::Enum { variants } => variants
|
||||
.iter()
|
||||
.all(|variant| can_derive_common(variant.fields.as_ref(), ty_defs, can_derive_copy_ty)),
|
||||
IdlTypeDefTy::Type { alias } => can_derive_copy_ty(alias, ty_defs),
|
||||
}
|
||||
}
|
||||
|
||||
fn can_derive_default(ty_def: &IdlTypeDef, ty_defs: &[IdlTypeDef]) -> bool {
|
||||
match &ty_def.ty {
|
||||
IdlTypeDefTy::Struct { fields } => {
|
||||
can_derive_common(fields.as_ref(), ty_defs, can_derive_default_ty)
|
||||
}
|
||||
// TODO: Consider storing the default enum variant in IDL
|
||||
IdlTypeDefTy::Enum { .. } => false,
|
||||
IdlTypeDefTy::Type { alias } => can_derive_default_ty(alias, ty_defs),
|
||||
}
|
||||
}
|
||||
|
||||
fn can_derive_copy_ty(ty: &IdlType, ty_defs: &[IdlTypeDef]) -> bool {
|
||||
match ty {
|
||||
IdlType::Option(inner) => can_derive_copy_ty(inner, ty_defs),
|
||||
IdlType::Array(inner, len) => {
|
||||
if !can_derive_copy_ty(inner, ty_defs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match len {
|
||||
IdlArrayLen::Value(_) => true,
|
||||
IdlArrayLen::Generic(_) => false,
|
||||
}
|
||||
}
|
||||
IdlType::Defined { name, .. } => ty_defs
|
||||
.iter()
|
||||
.find(|ty_def| &ty_def.name == name)
|
||||
.map(|ty_def| can_derive_copy(ty_def, ty_defs))
|
||||
.expect("Type def must exist"),
|
||||
IdlType::Bytes | IdlType::String | IdlType::Vec(_) | IdlType::Generic(_) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn can_derive_default_ty(ty: &IdlType, ty_defs: &[IdlTypeDef]) -> bool {
|
||||
match ty {
|
||||
IdlType::Option(inner) => can_derive_default_ty(inner, ty_defs),
|
||||
IdlType::Vec(inner) => can_derive_default_ty(inner, ty_defs),
|
||||
IdlType::Array(inner, len) => {
|
||||
if !can_derive_default_ty(inner, ty_defs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match len {
|
||||
IdlArrayLen::Value(len) => *len <= 32,
|
||||
IdlArrayLen::Generic(_) => false,
|
||||
}
|
||||
}
|
||||
IdlType::Defined { name, .. } => ty_defs
|
||||
.iter()
|
||||
.find(|ty_def| &ty_def.name == name)
|
||||
.map(|ty_def| can_derive_default(ty_def, ty_defs))
|
||||
.expect("Type def must exist"),
|
||||
IdlType::Generic(_) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn can_derive_common(
|
||||
fields: Option<&IdlDefinedFields>,
|
||||
ty_defs: &[IdlTypeDef],
|
||||
can_derive_ty: fn(&IdlType, &[IdlTypeDef]) -> bool,
|
||||
) -> bool {
|
||||
handle_defined_fields(
|
||||
fields,
|
||||
|| true,
|
||||
|fields| {
|
||||
fields
|
||||
.iter()
|
||||
.map(|field| &field.ty)
|
||||
.all(|ty| can_derive_ty(ty, ty_defs))
|
||||
},
|
||||
|tys| tys.iter().all(|ty| can_derive_ty(ty, ty_defs)),
|
||||
)
|
||||
}
|
||||
|
||||
fn handle_defined_fields<R>(
|
||||
fields: Option<&IdlDefinedFields>,
|
||||
unit_cb: impl Fn() -> R,
|
||||
named_cb: impl Fn(&[IdlField]) -> R,
|
||||
tuple_cb: impl Fn(&[IdlType]) -> R,
|
||||
) -> R {
|
||||
match fields {
|
||||
Some(fields) => match fields {
|
||||
IdlDefinedFields::Named(fields) => named_cb(fields),
|
||||
IdlDefinedFields::Tuple(tys) => tuple_cb(tys),
|
||||
},
|
||||
_ => unit_cb(),
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
mod common;
|
||||
mod mods;
|
||||
|
||||
use anchor_syn::idl::types::Idl;
|
||||
use anyhow::anyhow;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::parse::{Parse, ParseStream};
|
||||
|
||||
use common::gen_docs;
|
||||
use mods::{
|
||||
accounts::gen_accounts_mod, client::gen_client_mod, constants::gen_constants_mod,
|
||||
cpi::gen_cpi_mod, events::gen_events_mod, internal::gen_internal_mod, program::gen_program_mod,
|
||||
types::gen_types_mod,
|
||||
};
|
||||
|
||||
pub struct DeclareProgram {
|
||||
name: syn::Ident,
|
||||
idl: Idl,
|
||||
}
|
||||
|
||||
impl Parse for DeclareProgram {
|
||||
fn parse(input: ParseStream) -> syn::Result<Self> {
|
||||
let name = input.parse()?;
|
||||
let idl = get_idl(&name).map_err(|e| syn::Error::new(name.span(), e))?;
|
||||
Ok(Self { name, idl })
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for DeclareProgram {
|
||||
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
||||
let program = gen_program(&self.idl, &self.name);
|
||||
tokens.extend(program)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_idl(name: &syn::Ident) -> anyhow::Result<Idl> {
|
||||
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("Failed to get manifest dir");
|
||||
let path = std::path::Path::new(&manifest_dir)
|
||||
.ancestors()
|
||||
.find_map(|ancestor| {
|
||||
let idl_dir = ancestor.join("idls");
|
||||
std::fs::metadata(&idl_dir).map(|_| idl_dir).ok()
|
||||
})
|
||||
.ok_or_else(|| anyhow!("`idls` directory not found"))
|
||||
.map(|idl_dir| idl_dir.join(name.to_string()).with_extension("json"))?;
|
||||
|
||||
std::fs::read(path)
|
||||
.map_err(|e| anyhow!("Failed to read IDL: {e}"))
|
||||
.map(|idl| serde_json::from_slice(&idl))?
|
||||
.map_err(|e| anyhow!("Failed to parse IDL: {e}"))
|
||||
}
|
||||
|
||||
fn gen_program(idl: &Idl, name: &syn::Ident) -> proc_macro2::TokenStream {
|
||||
let docs = gen_program_docs(idl);
|
||||
let id = gen_id(idl);
|
||||
let program_mod = gen_program_mod(&idl.metadata.name);
|
||||
|
||||
// Defined
|
||||
let constants_mod = gen_constants_mod(idl);
|
||||
let accounts_mod = gen_accounts_mod(idl);
|
||||
let events_mod = gen_events_mod(idl);
|
||||
let types_mod = gen_types_mod(idl);
|
||||
|
||||
// Clients
|
||||
let cpi_mod = gen_cpi_mod(idl);
|
||||
let client_mod = gen_client_mod(idl);
|
||||
let internal_mod = gen_internal_mod(idl);
|
||||
|
||||
quote! {
|
||||
#docs
|
||||
pub mod #name {
|
||||
use anchor_lang::prelude::*;
|
||||
|
||||
#id
|
||||
#program_mod
|
||||
|
||||
#constants_mod
|
||||
#accounts_mod
|
||||
#events_mod
|
||||
#types_mod
|
||||
|
||||
#cpi_mod
|
||||
#client_mod
|
||||
#internal_mod
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_program_docs(idl: &Idl) -> proc_macro2::TokenStream {
|
||||
let docs: &[String] = &[
|
||||
format!(
|
||||
"Generated external program declaration of program `{}`.",
|
||||
idl.metadata.name
|
||||
),
|
||||
String::default(),
|
||||
];
|
||||
let docs = [docs, &idl.docs].concat();
|
||||
gen_docs(&docs)
|
||||
}
|
||||
|
||||
fn gen_id(idl: &Idl) -> proc_macro2::TokenStream {
|
||||
let address_bytes = bs58::decode(&idl.address)
|
||||
.into_vec()
|
||||
.expect("Invalid `idl.address`");
|
||||
let doc = format!("Program ID of program `{}`.", idl.metadata.name);
|
||||
|
||||
quote! {
|
||||
#[doc = #doc]
|
||||
pub static ID: Pubkey = __ID;
|
||||
|
||||
/// The name is intentionally prefixed with `__` in order to reduce to possibility of name
|
||||
/// clashes with the crate's `ID`.
|
||||
static __ID: Pubkey = Pubkey::new_from_array([#(#address_bytes,)*]);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
use anchor_syn::idl::types::{Idl, IdlSerialization};
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
use super::common::{convert_idl_type_def_to_ts, gen_discriminator, get_canonical_program_id};
|
||||
|
||||
pub fn gen_accounts_mod(idl: &Idl) -> proc_macro2::TokenStream {
|
||||
let accounts = idl.accounts.iter().map(|acc| {
|
||||
let name = format_ident!("{}", acc.name);
|
||||
let discriminator = gen_discriminator(&acc.discriminator);
|
||||
|
||||
let ty_def = idl
|
||||
.types
|
||||
.iter()
|
||||
.find(|ty| ty.name == acc.name)
|
||||
.expect("Type must exist");
|
||||
|
||||
let impls = {
|
||||
let try_deserialize = quote! {
|
||||
fn try_deserialize(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
|
||||
if buf.len() < #discriminator.len() {
|
||||
return Err(anchor_lang::error::ErrorCode::AccountDiscriminatorNotFound.into());
|
||||
}
|
||||
|
||||
let given_disc = &buf[..8];
|
||||
if &#discriminator != given_disc {
|
||||
return Err(
|
||||
anchor_lang::error!(anchor_lang::error::ErrorCode::AccountDiscriminatorMismatch)
|
||||
.with_account_name(stringify!(#name))
|
||||
);
|
||||
}
|
||||
|
||||
Self::try_deserialize_unchecked(buf)
|
||||
}
|
||||
};
|
||||
match ty_def.serialization {
|
||||
IdlSerialization::Borsh => quote! {
|
||||
impl anchor_lang::AccountSerialize for #name {
|
||||
fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> anchor_lang::Result<()> {
|
||||
if writer.write_all(&#discriminator).is_err() {
|
||||
return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into());
|
||||
}
|
||||
if AnchorSerialize::serialize(self, writer).is_err() {
|
||||
return Err(anchor_lang::error::ErrorCode::AccountDidNotSerialize.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl anchor_lang::AccountDeserialize for #name {
|
||||
#try_deserialize
|
||||
|
||||
fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
|
||||
let mut data: &[u8] = &buf[8..];
|
||||
AnchorDeserialize::deserialize(&mut data)
|
||||
.map_err(|_| anchor_lang::error::ErrorCode::AccountDidNotDeserialize.into())
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let unsafe_bytemuck_impl =
|
||||
matches!(ty_def.serialization, IdlSerialization::BytemuckUnsafe)
|
||||
.then(|| {
|
||||
quote! {
|
||||
unsafe impl anchor_lang::__private::Pod for #name {}
|
||||
unsafe impl anchor_lang::__private::Zeroable for #name {}
|
||||
}
|
||||
})
|
||||
.unwrap_or_default();
|
||||
|
||||
quote! {
|
||||
impl anchor_lang::ZeroCopy for #name {}
|
||||
|
||||
impl anchor_lang::AccountDeserialize for #name {
|
||||
#try_deserialize
|
||||
|
||||
fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result<Self> {
|
||||
let data: &[u8] = &buf[8..];
|
||||
let account = anchor_lang::__private::bytemuck::from_bytes(data);
|
||||
Ok(*account)
|
||||
}
|
||||
}
|
||||
|
||||
#unsafe_bytemuck_impl
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let type_def_ts = convert_idl_type_def_to_ts(ty_def, &idl.types);
|
||||
let program_id = get_canonical_program_id();
|
||||
|
||||
quote! {
|
||||
#type_def_ts
|
||||
|
||||
#impls
|
||||
|
||||
impl anchor_lang::Discriminator for #name {
|
||||
const DISCRIMINATOR: [u8; 8] = #discriminator;
|
||||
}
|
||||
|
||||
impl anchor_lang::Owner for #name {
|
||||
fn owner() -> Pubkey {
|
||||
#program_id
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
/// Program account type definitions.
|
||||
pub mod accounts {
|
||||
use super::{*, types::*};
|
||||
|
||||
#(#accounts)*
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
use anchor_syn::idl::types::Idl;
|
||||
use quote::quote;
|
||||
|
||||
use super::common::gen_accounts_common;
|
||||
|
||||
pub fn gen_client_mod(idl: &Idl) -> proc_macro2::TokenStream {
|
||||
let client_args_mod = gen_client_args_mod();
|
||||
let client_accounts_mod = gen_client_accounts_mod(idl);
|
||||
|
||||
quote! {
|
||||
/// Off-chain client helpers.
|
||||
pub mod client {
|
||||
use super::*;
|
||||
|
||||
#client_args_mod
|
||||
#client_accounts_mod
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_client_args_mod() -> proc_macro2::TokenStream {
|
||||
quote! {
|
||||
/// Client args.
|
||||
pub mod args {
|
||||
pub use super::internal::args::*;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_client_accounts_mod(idl: &Idl) -> proc_macro2::TokenStream {
|
||||
gen_accounts_common(idl, "client")
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
use anchor_syn::idl::types::{Idl, IdlType};
|
||||
use quote::{format_ident, quote, ToTokens};
|
||||
|
||||
use super::common::convert_idl_type_to_str;
|
||||
|
||||
pub fn gen_constants_mod(idl: &Idl) -> proc_macro2::TokenStream {
|
||||
let constants = idl.constants.iter().map(|c| {
|
||||
let name = format_ident!("{}", c.name);
|
||||
let ty = match &c.ty {
|
||||
IdlType::String => quote!(&str),
|
||||
_ => parse_expr_ts(&convert_idl_type_to_str(&c.ty)),
|
||||
};
|
||||
let val = parse_expr_ts(&c.value);
|
||||
|
||||
// TODO: Docs
|
||||
quote! { pub const #name: #ty = #val; }
|
||||
});
|
||||
|
||||
quote! {
|
||||
/// Program constants.
|
||||
pub mod constants {
|
||||
#(#constants)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_expr_ts(s: &str) -> proc_macro2::TokenStream {
|
||||
syn::parse_str::<syn::Expr>(s).unwrap().to_token_stream()
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
use anchor_syn::idl::types::Idl;
|
||||
use heck::CamelCase;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
use super::common::{convert_idl_type_to_syn_type, gen_accounts_common, gen_discriminator};
|
||||
|
||||
pub fn gen_cpi_mod(idl: &Idl) -> proc_macro2::TokenStream {
|
||||
let cpi_instructions = gen_cpi_instructions(idl);
|
||||
let cpi_return_type = gen_cpi_return_type();
|
||||
let cpi_accounts_mod = gen_cpi_accounts_mod(idl);
|
||||
|
||||
quote! {
|
||||
/// Cross program invocation (CPI) helpers.
|
||||
pub mod cpi {
|
||||
use super::*;
|
||||
|
||||
#cpi_instructions
|
||||
#cpi_return_type
|
||||
#cpi_accounts_mod
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_cpi_instructions(idl: &Idl) -> proc_macro2::TokenStream {
|
||||
let ixs = idl.instructions.iter().map(|ix| {
|
||||
let method_name = format_ident!("{}", ix.name);
|
||||
let accounts_ident = format_ident!("{}", ix.name.to_camel_case());
|
||||
|
||||
let args = ix.args.iter().map(|arg| {
|
||||
let name = format_ident!("{}", arg.name);
|
||||
let ty = convert_idl_type_to_syn_type(&arg.ty);
|
||||
quote! { #name: #ty }
|
||||
});
|
||||
|
||||
let arg_value = if ix.args.is_empty() {
|
||||
quote! { #accounts_ident }
|
||||
} else {
|
||||
let fields= ix.args.iter().map(|arg| format_ident!("{}", arg.name));
|
||||
quote! {
|
||||
#accounts_ident {
|
||||
#(#fields),*
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let discriminator = gen_discriminator(&ix.discriminator);
|
||||
|
||||
let (ret_type, ret_value) = match ix.returns.as_ref() {
|
||||
Some(ty) => {
|
||||
let ty = convert_idl_type_to_syn_type(ty);
|
||||
(
|
||||
quote! { anchor_lang::Result<Return::<#ty>> },
|
||||
quote! { Ok(Return::<#ty> { phantom:: std::marker::PhantomData }) },
|
||||
)
|
||||
},
|
||||
None => (
|
||||
quote! { anchor_lang::Result<()> },
|
||||
quote! { Ok(()) },
|
||||
)
|
||||
};
|
||||
|
||||
quote! {
|
||||
pub fn #method_name<'a, 'b, 'c, 'info>(
|
||||
ctx: anchor_lang::context::CpiContext<'a, 'b, 'c, 'info, accounts::#accounts_ident<'info>>,
|
||||
#(#args),*
|
||||
) -> #ret_type {
|
||||
let ix = {
|
||||
let mut data = Vec::with_capacity(256);
|
||||
data.extend_from_slice(&#discriminator);
|
||||
AnchorSerialize::serialize(&internal::args::#arg_value, &mut data)
|
||||
.map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotSerialize)?;
|
||||
|
||||
let accounts = ctx.to_account_metas(None);
|
||||
anchor_lang::solana_program::instruction::Instruction {
|
||||
program_id: ctx.program.key(),
|
||||
accounts,
|
||||
data,
|
||||
}
|
||||
};
|
||||
|
||||
let mut acc_infos = ctx.to_account_infos();
|
||||
anchor_lang::solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&acc_infos,
|
||||
ctx.signer_seeds,
|
||||
).map_or_else(
|
||||
|e| Err(Into::into(e)),
|
||||
|_| { #ret_value }
|
||||
)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
#(#ixs)*
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_cpi_return_type() -> proc_macro2::TokenStream {
|
||||
quote! {
|
||||
pub struct Return<T> {
|
||||
phantom: std::marker::PhantomData<T>
|
||||
}
|
||||
|
||||
impl<T: AnchorDeserialize> Return<T> {
|
||||
pub fn get(&self) -> T {
|
||||
let (_key, data) = anchor_lang::solana_program::program::get_return_data().unwrap();
|
||||
T::try_from_slice(&data).unwrap()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_cpi_accounts_mod(idl: &Idl) -> proc_macro2::TokenStream {
|
||||
gen_accounts_common(idl, "cpi_client")
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
use anchor_syn::idl::types::Idl;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
use super::common::{convert_idl_type_def_to_ts, gen_discriminator};
|
||||
|
||||
pub fn gen_events_mod(idl: &Idl) -> proc_macro2::TokenStream {
|
||||
let events = idl.events.iter().map(|ev| {
|
||||
let name = format_ident!("{}", ev.name);
|
||||
let discriminator = gen_discriminator(&ev.discriminator);
|
||||
|
||||
let ty_def = idl
|
||||
.types
|
||||
.iter()
|
||||
.find(|ty| ty.name == ev.name)
|
||||
.map(|ty| convert_idl_type_def_to_ts(ty, &idl.types))
|
||||
.expect("Type must exist");
|
||||
|
||||
quote! {
|
||||
#[derive(anchor_lang::__private::EventIndex)]
|
||||
#ty_def
|
||||
|
||||
impl anchor_lang::Event for #name {
|
||||
fn data(&self) -> Vec<u8> {
|
||||
let mut data = Vec::with_capacity(256);
|
||||
data.extend_from_slice(&#discriminator);
|
||||
self.serialize(&mut data).unwrap();
|
||||
data
|
||||
}
|
||||
}
|
||||
|
||||
impl anchor_lang::Discriminator for #name {
|
||||
const DISCRIMINATOR: [u8; 8] = #discriminator;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
/// Program event type definitions.
|
||||
pub mod events {
|
||||
use super::{*, types::*};
|
||||
|
||||
#(#events)*
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,166 @@
|
|||
use anchor_syn::{
|
||||
codegen::accounts::{__client_accounts, __cpi_client_accounts},
|
||||
idl::types::{Idl, IdlInstructionAccountItem},
|
||||
parser::accounts,
|
||||
AccountsStruct,
|
||||
};
|
||||
use heck::CamelCase;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
use super::common::{convert_idl_type_to_syn_type, gen_discriminator, get_canonical_program_id};
|
||||
|
||||
pub fn gen_internal_mod(idl: &Idl) -> proc_macro2::TokenStream {
|
||||
let internal_args_mod = gen_internal_args_mod(idl);
|
||||
let internal_accounts_mod = gen_internal_accounts(idl);
|
||||
|
||||
quote! {
|
||||
#[doc(hidden)]
|
||||
mod internal {
|
||||
use super::*;
|
||||
|
||||
#internal_args_mod
|
||||
#internal_accounts_mod
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_internal_args_mod(idl: &Idl) -> proc_macro2::TokenStream {
|
||||
let ixs = idl.instructions.iter().map(|ix| {
|
||||
let ix_struct_name = format_ident!("{}", ix.name.to_camel_case());
|
||||
|
||||
let fields = ix.args.iter().map(|arg| {
|
||||
let name = format_ident!("{}", arg.name);
|
||||
let ty = convert_idl_type_to_syn_type(&arg.ty);
|
||||
quote! { pub #name: #ty }
|
||||
});
|
||||
|
||||
let ix_struct = if ix.args.is_empty() {
|
||||
quote! {
|
||||
pub struct #ix_struct_name;
|
||||
}
|
||||
} else {
|
||||
quote! {
|
||||
pub struct #ix_struct_name {
|
||||
#(#fields),*
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let impl_discriminator = if ix.discriminator.len() == 8 {
|
||||
let discriminator = gen_discriminator(&ix.discriminator);
|
||||
quote! {
|
||||
impl anchor_lang::Discriminator for #ix_struct_name {
|
||||
const DISCRIMINATOR: [u8; 8] = #discriminator;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
let impl_ix_data = quote! {
|
||||
impl anchor_lang::InstructionData for #ix_struct_name {}
|
||||
};
|
||||
|
||||
let program_id = get_canonical_program_id();
|
||||
let impl_owner = quote! {
|
||||
impl anchor_lang::Owner for #ix_struct_name {
|
||||
fn owner() -> Pubkey {
|
||||
#program_id
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
quote! {
|
||||
/// Instruction argument
|
||||
#[derive(AnchorSerialize, AnchorDeserialize)]
|
||||
#ix_struct
|
||||
|
||||
#impl_discriminator
|
||||
#impl_ix_data
|
||||
#impl_owner
|
||||
}
|
||||
});
|
||||
|
||||
quote! {
|
||||
/// An Anchor generated module containing the program's set of instructions, where each
|
||||
/// method handler in the `#[program]` mod is associated with a struct defining the input
|
||||
/// arguments to the method. These should be used directly, when one wants to serialize
|
||||
/// Anchor instruction data, for example, when specifying instructions instructions on a
|
||||
/// client.
|
||||
pub mod args {
|
||||
use super::*;
|
||||
|
||||
#(#ixs)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_internal_accounts(idl: &Idl) -> proc_macro2::TokenStream {
|
||||
let cpi_accounts = gen_internal_accounts_common(idl, __cpi_client_accounts::generate);
|
||||
let client_accounts = gen_internal_accounts_common(idl, __client_accounts::generate);
|
||||
|
||||
quote! {
|
||||
#cpi_accounts
|
||||
#client_accounts
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_internal_accounts_common(
|
||||
idl: &Idl,
|
||||
gen_accounts: impl Fn(&AccountsStruct) -> proc_macro2::TokenStream,
|
||||
) -> proc_macro2::TokenStream {
|
||||
let accounts = idl
|
||||
.instructions
|
||||
.iter()
|
||||
.map(|ix| {
|
||||
let ident = format_ident!("{}", ix.name.to_camel_case());
|
||||
let generics = if ix.accounts.is_empty() {
|
||||
quote!()
|
||||
} else {
|
||||
quote!(<'info>)
|
||||
};
|
||||
let accounts = ix.accounts.iter().map(|acc| match acc {
|
||||
IdlInstructionAccountItem::Single(acc) => {
|
||||
let name = format_ident!("{}", acc.name);
|
||||
|
||||
let attrs = {
|
||||
let signer = acc.signer.then(|| quote!(signer)).unwrap_or_default();
|
||||
let mt = acc.writable.then(|| quote!(mut)).unwrap_or_default();
|
||||
if signer.is_empty() {
|
||||
mt
|
||||
} else if mt.is_empty() {
|
||||
signer
|
||||
} else {
|
||||
quote! { #signer, #mt }
|
||||
}
|
||||
};
|
||||
|
||||
let acc_expr = acc
|
||||
.optional
|
||||
.then(|| quote! { Option<AccountInfo #generics> })
|
||||
.unwrap_or_else(|| quote! { AccountInfo #generics });
|
||||
|
||||
quote! {
|
||||
#[account(#attrs)]
|
||||
pub #name: #acc_expr
|
||||
}
|
||||
}
|
||||
IdlInstructionAccountItem::Composite(_accs) => todo!("Composite"),
|
||||
});
|
||||
|
||||
quote! {
|
||||
#[derive(Accounts)]
|
||||
pub struct #ident #generics {
|
||||
#(#accounts,)*
|
||||
}
|
||||
}
|
||||
})
|
||||
.map(|accs_struct| {
|
||||
let accs_struct = syn::parse2(accs_struct).expect("Failed to parse as syn::ItemStruct");
|
||||
let accs_struct =
|
||||
accounts::parse(&accs_struct).expect("Failed to parse accounts struct");
|
||||
gen_accounts(&accs_struct)
|
||||
});
|
||||
|
||||
quote! { #(#accounts)* }
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
pub mod accounts;
|
||||
pub mod client;
|
||||
pub mod constants;
|
||||
pub mod cpi;
|
||||
pub mod events;
|
||||
pub mod internal;
|
||||
pub mod program;
|
||||
pub mod types;
|
||||
|
||||
use super::common;
|
|
@ -0,0 +1,25 @@
|
|||
use heck::CamelCase;
|
||||
use quote::{format_ident, quote};
|
||||
|
||||
use super::common::get_canonical_program_id;
|
||||
|
||||
pub fn gen_program_mod(program_name: &str) -> proc_macro2::TokenStream {
|
||||
let name = format_ident!("{}", program_name.to_camel_case());
|
||||
let id = get_canonical_program_id();
|
||||
quote! {
|
||||
/// Program definition.
|
||||
pub mod program {
|
||||
use super::*;
|
||||
|
||||
/// Program type
|
||||
#[derive(Clone)]
|
||||
pub struct #name;
|
||||
|
||||
impl anchor_lang::Id for #name {
|
||||
fn id() -> Pubkey {
|
||||
#id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
use anchor_syn::idl::types::Idl;
|
||||
use quote::quote;
|
||||
|
||||
use super::common::convert_idl_type_def_to_ts;
|
||||
|
||||
pub fn gen_types_mod(idl: &Idl) -> proc_macro2::TokenStream {
|
||||
let types = idl
|
||||
.types
|
||||
.iter()
|
||||
.filter(|ty| {
|
||||
// Skip accounts and events
|
||||
!(idl.accounts.iter().any(|acc| acc.name == ty.name)
|
||||
|| idl.events.iter().any(|ev| ev.name == ty.name))
|
||||
})
|
||||
.map(|ty| convert_idl_type_def_to_ts(ty, &idl.types));
|
||||
|
||||
quote! {
|
||||
/// Program type definitions.
|
||||
///
|
||||
/// Note that account and event type definitions are not included in this module, as they
|
||||
/// have their own dedicated modules.
|
||||
pub mod types {
|
||||
use super::*;
|
||||
|
||||
#(#types)*
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
extern crate proc_macro;
|
||||
|
||||
mod declare_program;
|
||||
|
||||
use declare_program::DeclareProgram;
|
||||
use quote::ToTokens;
|
||||
use syn::parse_macro_input;
|
||||
|
||||
|
@ -15,6 +18,40 @@ pub fn program(
|
|||
.into()
|
||||
}
|
||||
|
||||
/// Declare an external program based on its IDL.
|
||||
///
|
||||
/// The IDL of the program must exist in a directory named `idls`. This directory can be at any
|
||||
/// depth, e.g. both inside the program's directory (`<PROGRAM_DIR>/idls`) and inside Anchor
|
||||
/// workspace root directory (`<PROGRAM_DIR>/../../idls`) are valid.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// ```rs
|
||||
/// declare_program!(program_name);
|
||||
/// ```
|
||||
///
|
||||
/// This generates a module named `external_program` that can be used to interact with the program
|
||||
/// without having to add the program's crate as a dependency.
|
||||
///
|
||||
/// Both on-chain and off-chain usage is supported.
|
||||
///
|
||||
/// Use `cargo doc --open` to see the generated modules and their documentation.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Re-defining the same program to use the same definitions should be avoided since this results
|
||||
/// in larger binary size.
|
||||
///
|
||||
/// A program should only be defined once. If you have multiple programs that depend on the same
|
||||
/// definition, you should consider creating a separate crate for the external program definition
|
||||
/// and reuse it in your programs.
|
||||
#[proc_macro]
|
||||
pub fn declare_program(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
parse_macro_input!(input as DeclareProgram)
|
||||
.to_token_stream()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// The `#[interface]` attribute is used to mark an instruction as belonging
|
||||
/// to an interface implementation, thus transforming its discriminator to the
|
||||
/// proper bytes for that interface instruction.
|
||||
|
|
|
@ -51,7 +51,7 @@ pub use anchor_attribute_account::{account, declare_id, zero_copy};
|
|||
pub use anchor_attribute_constant::constant;
|
||||
pub use anchor_attribute_error::*;
|
||||
pub use anchor_attribute_event::{emit, event};
|
||||
pub use anchor_attribute_program::program;
|
||||
pub use anchor_attribute_program::{declare_program, program};
|
||||
pub use anchor_derive_accounts::Accounts;
|
||||
pub use anchor_derive_serde::{AnchorDeserialize, AnchorSerialize};
|
||||
pub use anchor_derive_space::InitSpace;
|
||||
|
@ -392,9 +392,10 @@ pub mod prelude {
|
|||
accounts::interface_account::InterfaceAccount, accounts::program::Program,
|
||||
accounts::signer::Signer, accounts::system_account::SystemAccount,
|
||||
accounts::sysvar::Sysvar, accounts::unchecked_account::UncheckedAccount, constant,
|
||||
context::Context, context::CpiContext, declare_id, emit, err, error, event, program,
|
||||
require, require_eq, require_gt, require_gte, require_keys_eq, require_keys_neq,
|
||||
require_neq, solana_program::bpf_loader_upgradeable::UpgradeableLoaderState, source,
|
||||
context::Context, context::CpiContext, declare_id, declare_program, emit, err, error,
|
||||
event, program, require, require_eq, require_gt, require_gte, require_keys_eq,
|
||||
require_keys_neq, require_neq,
|
||||
solana_program::bpf_loader_upgradeable::UpgradeableLoaderState, source,
|
||||
system_program::System, zero_copy, AccountDeserialize, AccountSerialize, Accounts,
|
||||
AccountsClose, AccountsExit, AnchorDeserialize, AnchorSerialize, Id, InitSpace, Key,
|
||||
Lamports, Owner, ProgramData, Result, Space, ToAccountInfo, ToAccountInfos, ToAccountMetas,
|
||||
|
|
|
@ -5,8 +5,8 @@ use syn::punctuated::Punctuated;
|
|||
use syn::{ConstParam, LifetimeDef, Token, TypeParam};
|
||||
use syn::{GenericParam, PredicateLifetime, WhereClause, WherePredicate};
|
||||
|
||||
mod __client_accounts;
|
||||
mod __cpi_client_accounts;
|
||||
pub mod __client_accounts;
|
||||
pub mod __cpi_client_accounts;
|
||||
mod bumps;
|
||||
mod constraints;
|
||||
mod exit;
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
[programs.localnet]
|
||||
declare_program = "Dec1areProgram11111111111111111111111111111"
|
||||
external = "Externa111111111111111111111111111111111111"
|
||||
|
||||
[provider]
|
||||
cluster = "localnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[scripts]
|
||||
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
|
|
@ -0,0 +1,14 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"programs/*"
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[profile.release]
|
||||
overflow-checks = true
|
||||
lto = "fat"
|
||||
codegen-units = 1
|
||||
[profile.release.build-override]
|
||||
opt-level = 3
|
||||
incremental = false
|
||||
codegen-units = 1
|
|
@ -0,0 +1,114 @@
|
|||
{
|
||||
"address": "Externa111111111111111111111111111111111111",
|
||||
"metadata": {
|
||||
"name": "external",
|
||||
"version": "0.1.0",
|
||||
"spec": "0.1.0",
|
||||
"description": "Created with Anchor"
|
||||
},
|
||||
"instructions": [
|
||||
{
|
||||
"name": "init",
|
||||
"discriminator": [
|
||||
220,
|
||||
59,
|
||||
207,
|
||||
236,
|
||||
108,
|
||||
250,
|
||||
47,
|
||||
100
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "authority",
|
||||
"writable": true,
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "my_account",
|
||||
"writable": true,
|
||||
"pda": {
|
||||
"seeds": [
|
||||
{
|
||||
"kind": "account",
|
||||
"path": "authority"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "system_program",
|
||||
"address": "11111111111111111111111111111111"
|
||||
}
|
||||
],
|
||||
"args": []
|
||||
},
|
||||
{
|
||||
"name": "update",
|
||||
"discriminator": [
|
||||
219,
|
||||
200,
|
||||
88,
|
||||
176,
|
||||
158,
|
||||
63,
|
||||
253,
|
||||
127
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "authority",
|
||||
"signer": true
|
||||
},
|
||||
{
|
||||
"name": "my_account",
|
||||
"writable": true,
|
||||
"pda": {
|
||||
"seeds": [
|
||||
{
|
||||
"kind": "account",
|
||||
"path": "authority"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"args": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "u32"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"accounts": [
|
||||
{
|
||||
"name": "MyAccount",
|
||||
"discriminator": [
|
||||
246,
|
||||
28,
|
||||
6,
|
||||
87,
|
||||
251,
|
||||
45,
|
||||
50,
|
||||
42
|
||||
]
|
||||
}
|
||||
],
|
||||
"types": [
|
||||
{
|
||||
"name": "MyAccount",
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"fields": [
|
||||
{
|
||||
"name": "field",
|
||||
"type": "u32"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "declare-program",
|
||||
"version": "0.29.0",
|
||||
"license": "(MIT OR Apache-2.0)",
|
||||
"homepage": "https://github.com/coral-xyz/anchor#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/coral-xyz/anchor/issues"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/coral-xyz/anchor.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=17"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
[package]
|
||||
name = "declare-program"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
rust-version = "1.60"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
name = "declare_program"
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,39 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
declare_id!("Dec1areProgram11111111111111111111111111111");
|
||||
|
||||
declare_program!(external);
|
||||
use external::program::External;
|
||||
|
||||
#[program]
|
||||
pub mod declare_program {
|
||||
use super::*;
|
||||
|
||||
pub fn cpi(ctx: Context<Cpi>, value: u32) -> Result<()> {
|
||||
let cpi_my_account = &mut ctx.accounts.cpi_my_account;
|
||||
require_keys_eq!(external::accounts::MyAccount::owner(), external::ID);
|
||||
require_eq!(cpi_my_account.field, 0);
|
||||
|
||||
let cpi_ctx = CpiContext::new(
|
||||
ctx.accounts.external_program.to_account_info(),
|
||||
external::cpi::accounts::Update {
|
||||
authority: ctx.accounts.authority.to_account_info(),
|
||||
my_account: cpi_my_account.to_account_info(),
|
||||
},
|
||||
);
|
||||
external::cpi::update(cpi_ctx, value)?;
|
||||
|
||||
cpi_my_account.reload()?;
|
||||
require_eq!(cpi_my_account.field, value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Cpi<'info> {
|
||||
pub authority: Signer<'info>,
|
||||
#[account(mut)]
|
||||
pub cpi_my_account: Account<'info, external::accounts::MyAccount>,
|
||||
pub external_program: Program<'info, External>,
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
[package]
|
||||
name = "external"
|
||||
version = "0.1.0"
|
||||
description = "Created with Anchor"
|
||||
rust-version = "1.60"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = { path = "../../../../lang" }
|
|
@ -0,0 +1,2 @@
|
|||
[target.bpfel-unknown-unknown.dependencies.std]
|
||||
features = []
|
|
@ -0,0 +1,44 @@
|
|||
use anchor_lang::prelude::*;
|
||||
|
||||
declare_id!("Externa111111111111111111111111111111111111");
|
||||
|
||||
#[program]
|
||||
pub mod external {
|
||||
use super::*;
|
||||
|
||||
pub fn init(_ctx: Context<Init>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update(ctx: Context<Update>, value: u32) -> Result<()> {
|
||||
ctx.accounts.my_account.field = value;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Init<'info> {
|
||||
#[account(mut)]
|
||||
pub authority: Signer<'info>,
|
||||
#[account(
|
||||
init,
|
||||
payer = authority,
|
||||
space = 8 + 4,
|
||||
seeds = [authority.key.as_ref()],
|
||||
bump
|
||||
)]
|
||||
pub my_account: Account<'info, MyAccount>,
|
||||
pub system_program: Program<'info, System>,
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct Update<'info> {
|
||||
pub authority: Signer<'info>,
|
||||
#[account(mut, seeds = [authority.key.as_ref()], bump)]
|
||||
pub my_account: Account<'info, MyAccount>,
|
||||
}
|
||||
|
||||
#[account]
|
||||
pub struct MyAccount {
|
||||
pub field: u32,
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import assert from "assert";
|
||||
|
||||
import type { DeclareProgram } from "../target/types/declare_program";
|
||||
import type { External } from "../target/types/external";
|
||||
|
||||
describe("declare-program", () => {
|
||||
anchor.setProvider(anchor.AnchorProvider.env());
|
||||
const program: anchor.Program<DeclareProgram> =
|
||||
anchor.workspace.declareProgram;
|
||||
const externalProgram: anchor.Program<External> = anchor.workspace.external;
|
||||
|
||||
it("Can CPI", async () => {
|
||||
const { pubkeys } = await externalProgram.methods.init().rpcAndKeys();
|
||||
|
||||
const value = 5;
|
||||
await program.methods
|
||||
.cpi(value)
|
||||
.accounts({ cpiMyAccount: pubkeys.myAccount })
|
||||
.rpc();
|
||||
|
||||
const myAccount = await externalProgram.account.myAccount.fetch(
|
||||
pubkeys.myAccount
|
||||
);
|
||||
assert.strictEqual(myAccount.field, value);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"types": ["mocha", "chai"],
|
||||
"lib": ["es2015"],
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"esModuleInterop": true,
|
||||
"strict": true
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@
|
|||
"chat",
|
||||
"composite",
|
||||
"custom-coder",
|
||||
"declare-id",
|
||||
"declare-program",
|
||||
"errors",
|
||||
"escrow",
|
||||
"events",
|
||||
|
@ -42,7 +44,6 @@
|
|||
"typescript",
|
||||
"validator-clone",
|
||||
"zero-copy",
|
||||
"declare-id",
|
||||
"cpi-returns",
|
||||
"multiple-suites",
|
||||
"multiple-suites-run-single",
|
||||
|
|
Loading…
Reference in New Issue