2021-01-09 22:03:14 -08:00
|
|
|
extern crate proc_macro;
|
|
|
|
|
|
|
|
use quote::quote;
|
2021-07-07 21:02:39 -07:00
|
|
|
use syn::{parse_macro_input, parse_quote};
|
2021-01-09 22:03:14 -08:00
|
|
|
|
2021-02-04 06:01:48 -08:00
|
|
|
/// A data structure representing a Solana account, implementing various traits:
|
|
|
|
///
|
|
|
|
/// - [`AccountSerialize`](./trait.AccountSerialize.html)
|
|
|
|
/// - [`AccountDeserialize`](./trait.AccountDeserialize.html)
|
|
|
|
/// - [`AnchorSerialize`](./trait.AnchorSerialize.html)
|
|
|
|
/// - [`AnchorDeserialize`](./trait.AnchorDeserialize.html)
|
|
|
|
///
|
|
|
|
/// When implementing account serialization traits the first 8 bytes are
|
|
|
|
/// reserved for a unique account discriminator, self described by the first 8
|
|
|
|
/// bytes of the SHA256 of the account's Rust ident.
|
|
|
|
///
|
|
|
|
/// As a result, any calls to `AccountDeserialize`'s `try_deserialize` will
|
|
|
|
/// check this discriminator. If it doesn't match, an invalid account was given,
|
|
|
|
/// and the account deserialization will exit with an error.
|
2021-04-17 12:07:48 -07:00
|
|
|
///
|
|
|
|
/// # Zero Copy Deserialization
|
|
|
|
///
|
2021-04-18 17:42:01 -07:00
|
|
|
/// **WARNING**: Zero copy deserialization is an experimental feature. It's
|
|
|
|
/// recommended to use it only when necessary, i.e., when you have extremely
|
|
|
|
/// large accounts that cannot be Borsh deserialized without hitting stack or
|
|
|
|
/// heap limits.
|
|
|
|
///
|
|
|
|
/// ## Usage
|
|
|
|
///
|
2021-04-17 12:07:48 -07:00
|
|
|
/// To enable zero-copy-deserialization, one can pass in the `zero_copy`
|
|
|
|
/// argument to the macro as follows:
|
|
|
|
///
|
|
|
|
/// ```ignore
|
|
|
|
/// #[account(zero_copy)]
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// This can be used to conveniently implement
|
|
|
|
/// [`ZeroCopy`](./trait.ZeroCopy.html) so that the account can be used
|
2021-04-18 17:42:01 -07:00
|
|
|
/// with [`Loader`](./struct.Loader.html).
|
2021-04-17 12:07:48 -07:00
|
|
|
///
|
|
|
|
/// Other than being more efficient, the most salient benefit this provides is
|
|
|
|
/// the ability to define account types larger than the max stack or heap size.
|
2021-04-18 17:42:01 -07:00
|
|
|
/// When using borsh, the account has to be copied and deserialized into a new
|
|
|
|
/// data structure and thus is constrained by stack and heap limits imposed by
|
|
|
|
/// the BPF VM. With zero copy deserialization, all bytes from the account's
|
|
|
|
/// backing `RefCell<&mut [u8]>` are simply re-interpreted as a reference to
|
|
|
|
/// the data structure. No allocations or copies necessary. Hence the ability
|
|
|
|
/// to get around stack and heap limitations.
|
|
|
|
///
|
|
|
|
/// To facilitate this, all fields in an account must be constrained to be
|
|
|
|
/// "plain old data", i.e., they must implement
|
|
|
|
/// [`Pod`](../bytemuck/trait.Pod.html). Please review the
|
|
|
|
/// [`safety`](file:///home/armaniferrante/Documents/code/src/github.com/project-serum/anchor/target/doc/bytemuck/trait.Pod.html#safety)
|
|
|
|
/// section before using.
|
2021-01-09 22:03:14 -08:00
|
|
|
#[proc_macro_attribute]
|
|
|
|
pub fn account(
|
2021-03-26 15:21:31 -07:00
|
|
|
args: proc_macro::TokenStream,
|
2021-01-09 22:03:14 -08:00
|
|
|
input: proc_macro::TokenStream,
|
|
|
|
) -> proc_macro::TokenStream {
|
2021-05-25 14:50:12 -07:00
|
|
|
let mut namespace = "".to_string();
|
|
|
|
let mut is_zero_copy = false;
|
2021-06-27 17:09:46 -07:00
|
|
|
if args.to_string().split(',').count() > 2 {
|
2021-05-25 14:50:12 -07:00
|
|
|
panic!("Only two args are allowed to the account attribute.")
|
|
|
|
}
|
2021-06-27 17:09:46 -07:00
|
|
|
for arg in args.to_string().split(',') {
|
2021-05-25 14:50:12 -07:00
|
|
|
let ns = arg
|
|
|
|
.to_string()
|
|
|
|
.replace("\"", "")
|
|
|
|
.chars()
|
|
|
|
.filter(|c| !c.is_whitespace())
|
|
|
|
.collect();
|
|
|
|
if ns == "zero_copy" {
|
|
|
|
is_zero_copy = true;
|
|
|
|
} else {
|
|
|
|
namespace = ns;
|
|
|
|
}
|
|
|
|
}
|
2021-03-26 15:21:31 -07:00
|
|
|
|
2021-01-09 22:03:14 -08:00
|
|
|
let account_strct = parse_macro_input!(input as syn::ItemStruct);
|
|
|
|
let account_name = &account_strct.ident;
|
2021-07-08 13:14:39 -07:00
|
|
|
let (impl_gen, type_gen, where_clause) = account_strct.generics.split_for_impl();
|
2021-01-22 03:35:57 -08:00
|
|
|
|
2021-02-05 03:17:40 -08:00
|
|
|
let discriminator: proc_macro2::TokenStream = {
|
|
|
|
// Namespace the discriminator to prevent collisions.
|
2021-03-26 15:21:31 -07:00
|
|
|
let discriminator_preimage = {
|
2021-04-17 12:07:48 -07:00
|
|
|
// For now, zero copy accounts can't be namespaced.
|
2021-05-25 14:50:12 -07:00
|
|
|
if namespace.is_empty() {
|
2021-03-26 15:21:31 -07:00
|
|
|
format!("account:{}", account_name.to_string())
|
|
|
|
} else {
|
|
|
|
format!("{}:{}", namespace, account_name.to_string())
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-02-05 03:17:40 -08:00
|
|
|
let mut discriminator = [0u8; 8];
|
|
|
|
discriminator.copy_from_slice(
|
|
|
|
&anchor_syn::hash::hash(discriminator_preimage.as_bytes()).to_bytes()[..8],
|
|
|
|
);
|
|
|
|
format!("{:?}", discriminator).parse().unwrap()
|
2021-01-22 03:35:57 -08:00
|
|
|
};
|
2021-01-09 22:03:14 -08:00
|
|
|
|
2021-04-17 12:07:48 -07:00
|
|
|
proc_macro::TokenStream::from({
|
|
|
|
if is_zero_copy {
|
|
|
|
quote! {
|
|
|
|
#[zero_copy]
|
|
|
|
#account_strct
|
|
|
|
|
2021-07-08 13:14:39 -07:00
|
|
|
#[automatically_derived]
|
|
|
|
unsafe impl #impl_gen anchor_lang::__private::bytemuck::Pod for #account_name #type_gen #where_clause {}
|
|
|
|
#[automatically_derived]
|
|
|
|
unsafe impl #impl_gen anchor_lang::__private::bytemuck::Zeroable for #account_name #type_gen #where_clause {}
|
2021-04-17 12:07:48 -07:00
|
|
|
|
2021-07-08 13:14:39 -07:00
|
|
|
#[automatically_derived]
|
|
|
|
impl #impl_gen anchor_lang::ZeroCopy for #account_name #type_gen #where_clause {}
|
2021-01-09 22:03:14 -08:00
|
|
|
|
2021-07-08 13:14:39 -07:00
|
|
|
#[automatically_derived]
|
|
|
|
impl #impl_gen anchor_lang::Discriminator for #account_name #type_gen #where_clause {
|
2021-04-17 12:07:48 -07:00
|
|
|
fn discriminator() -> [u8; 8] {
|
|
|
|
#discriminator
|
|
|
|
}
|
2021-01-09 22:03:14 -08:00
|
|
|
}
|
2021-04-17 12:07:48 -07:00
|
|
|
|
|
|
|
// This trait is useful for clients deserializing accounts.
|
|
|
|
// It's expected on-chain programs deserialize via zero-copy.
|
2021-07-08 13:14:39 -07:00
|
|
|
#[automatically_derived]
|
|
|
|
impl #impl_gen anchor_lang::AccountDeserialize for #account_name #type_gen #where_clause {
|
2021-04-17 12:07:48 -07:00
|
|
|
fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
|
|
|
|
if buf.len() < #discriminator.len() {
|
2021-06-09 13:02:50 -07:00
|
|
|
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorNotFound.into());
|
2021-04-17 12:07:48 -07:00
|
|
|
}
|
|
|
|
let given_disc = &buf[..8];
|
|
|
|
if &#discriminator != given_disc {
|
2021-06-09 13:02:50 -07:00
|
|
|
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorMismatch.into());
|
2021-04-17 12:07:48 -07:00
|
|
|
}
|
|
|
|
Self::try_deserialize_unchecked(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn try_deserialize_unchecked(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
|
|
|
|
let data: &[u8] = &buf[8..];
|
|
|
|
// Re-interpret raw bytes into the POD data structure.
|
|
|
|
let account = anchor_lang::__private::bytemuck::from_bytes(data);
|
|
|
|
// Copy out the bytes into a new, owned data structure.
|
|
|
|
Ok(*account)
|
|
|
|
}
|
2021-01-09 22:03:14 -08:00
|
|
|
}
|
|
|
|
}
|
2021-04-17 12:07:48 -07:00
|
|
|
} else {
|
|
|
|
quote! {
|
|
|
|
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
|
|
|
|
#account_strct
|
2021-01-09 22:03:14 -08:00
|
|
|
|
2021-07-08 13:14:39 -07:00
|
|
|
#[automatically_derived]
|
|
|
|
impl #impl_gen anchor_lang::AccountSerialize for #account_name #type_gen #where_clause {
|
2021-04-17 12:07:48 -07:00
|
|
|
fn try_serialize<W: std::io::Write>(&self, writer: &mut W) -> std::result::Result<(), ProgramError> {
|
2021-06-09 13:02:50 -07:00
|
|
|
writer.write_all(&#discriminator).map_err(|_| anchor_lang::__private::ErrorCode::AccountDidNotSerialize)?;
|
2021-04-17 12:07:48 -07:00
|
|
|
AnchorSerialize::serialize(
|
|
|
|
self,
|
|
|
|
writer
|
|
|
|
)
|
2021-06-09 13:02:50 -07:00
|
|
|
.map_err(|_| anchor_lang::__private::ErrorCode::AccountDidNotSerialize)?;
|
2021-04-17 12:07:48 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2021-03-24 20:19:29 -07:00
|
|
|
|
2021-07-08 13:14:39 -07:00
|
|
|
#[automatically_derived]
|
|
|
|
impl #impl_gen anchor_lang::AccountDeserialize for #account_name #type_gen #where_clause {
|
2021-04-17 12:07:48 -07:00
|
|
|
fn try_deserialize(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
|
|
|
|
if buf.len() < #discriminator.len() {
|
2021-06-09 13:02:50 -07:00
|
|
|
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorNotFound.into());
|
2021-04-17 12:07:48 -07:00
|
|
|
}
|
|
|
|
let given_disc = &buf[..8];
|
|
|
|
if &#discriminator != given_disc {
|
2021-06-09 13:02:50 -07:00
|
|
|
return Err(anchor_lang::__private::ErrorCode::AccountDiscriminatorMismatch.into());
|
2021-04-17 12:07:48 -07:00
|
|
|
}
|
|
|
|
Self::try_deserialize_unchecked(buf)
|
|
|
|
}
|
2021-01-09 22:03:14 -08:00
|
|
|
|
2021-04-17 12:07:48 -07:00
|
|
|
fn try_deserialize_unchecked(buf: &mut &[u8]) -> std::result::Result<Self, ProgramError> {
|
|
|
|
let mut data: &[u8] = &buf[8..];
|
|
|
|
AnchorDeserialize::deserialize(&mut data)
|
2021-06-09 13:02:50 -07:00
|
|
|
.map_err(|_| anchor_lang::__private::ErrorCode::AccountDidNotDeserialize.into())
|
2021-04-17 12:07:48 -07:00
|
|
|
}
|
|
|
|
}
|
2021-01-09 22:03:14 -08:00
|
|
|
|
2021-07-08 13:14:39 -07:00
|
|
|
#[automatically_derived]
|
|
|
|
impl #impl_gen anchor_lang::Discriminator for #account_name #type_gen #where_clause {
|
2021-04-17 12:07:48 -07:00
|
|
|
fn discriminator() -> [u8; 8] {
|
|
|
|
#discriminator
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-01-09 22:03:14 -08:00
|
|
|
})
|
|
|
|
}
|
2021-04-13 11:47:54 -07:00
|
|
|
|
2021-04-17 12:07:48 -07:00
|
|
|
/// Extends the `#[account]` attribute to allow one to create associated
|
2021-04-13 11:47:54 -07:00
|
|
|
/// accounts. This includes a `Default` implementation, which means all fields
|
|
|
|
/// in an `#[associated]` struct must implement `Default` and an
|
|
|
|
/// `anchor_lang::Bump` trait implementation, which allows the account to be
|
|
|
|
/// used as a program derived address.
|
2021-04-17 12:07:48 -07:00
|
|
|
///
|
|
|
|
/// # Zero Copy Deserialization
|
|
|
|
///
|
|
|
|
/// Similar to the `#[account]` attribute one can enable zero copy
|
|
|
|
/// deserialization by using the `zero_copy` argument:
|
|
|
|
///
|
|
|
|
/// ```ignore
|
|
|
|
/// #[associated(zero_copy)]
|
|
|
|
/// ```
|
|
|
|
///
|
|
|
|
/// For more, see the [`account`](./attr.account.html) attribute.
|
2021-04-13 11:47:54 -07:00
|
|
|
#[proc_macro_attribute]
|
|
|
|
pub fn associated(
|
2021-04-17 12:07:48 -07:00
|
|
|
args: proc_macro::TokenStream,
|
2021-04-13 11:47:54 -07:00
|
|
|
input: proc_macro::TokenStream,
|
|
|
|
) -> proc_macro::TokenStream {
|
|
|
|
let mut account_strct = parse_macro_input!(input as syn::ItemStruct);
|
2021-04-14 17:41:18 -07:00
|
|
|
let account_name = &account_strct.ident;
|
2021-07-08 13:14:39 -07:00
|
|
|
let (impl_gen, ty_gen, where_clause) = account_strct.generics.split_for_impl();
|
2021-04-13 11:47:54 -07:00
|
|
|
|
|
|
|
// Add a `__nonce: u8` field to the struct to hold the bump seed for
|
|
|
|
// the program dervied address.
|
|
|
|
match &mut account_strct.fields {
|
|
|
|
syn::Fields::Named(fields) => {
|
|
|
|
let mut segments = syn::punctuated::Punctuated::new();
|
|
|
|
segments.push(syn::PathSegment {
|
|
|
|
ident: syn::Ident::new("u8", proc_macro2::Span::call_site()),
|
|
|
|
arguments: syn::PathArguments::None,
|
|
|
|
});
|
|
|
|
fields.named.push(syn::Field {
|
|
|
|
attrs: Vec::new(),
|
2021-07-07 21:02:39 -07:00
|
|
|
vis: syn::Visibility::Restricted(syn::VisRestricted {
|
|
|
|
pub_token: syn::token::Pub::default(),
|
|
|
|
paren_token: syn::token::Paren::default(),
|
|
|
|
in_token: None,
|
2021-07-07 23:41:31 -07:00
|
|
|
path: Box::new(parse_quote!(crate)),
|
2021-07-07 21:02:39 -07:00
|
|
|
}),
|
2021-04-13 11:47:54 -07:00
|
|
|
ident: Some(syn::Ident::new("__nonce", proc_macro2::Span::call_site())),
|
|
|
|
colon_token: Some(syn::token::Colon {
|
|
|
|
spans: [proc_macro2::Span::call_site()],
|
|
|
|
}),
|
|
|
|
ty: syn::Type::Path(syn::TypePath {
|
|
|
|
qself: None,
|
|
|
|
path: syn::Path {
|
|
|
|
leading_colon: None,
|
|
|
|
segments,
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
_ => panic!("Fields must be named"),
|
|
|
|
}
|
|
|
|
|
2021-04-17 12:07:48 -07:00
|
|
|
let args: proc_macro2::TokenStream = args.into();
|
2021-04-13 11:47:54 -07:00
|
|
|
proc_macro::TokenStream::from(quote! {
|
2021-04-17 12:07:48 -07:00
|
|
|
#[anchor_lang::account(#args)]
|
2021-04-13 11:47:54 -07:00
|
|
|
#account_strct
|
2021-04-14 17:41:18 -07:00
|
|
|
|
2021-07-08 13:14:39 -07:00
|
|
|
#[automatically_derived]
|
|
|
|
impl #impl_gen anchor_lang::Bump for #account_name #ty_gen #where_clause {
|
2021-04-14 17:41:18 -07:00
|
|
|
fn seed(&self) -> u8 {
|
|
|
|
self.__nonce
|
|
|
|
}
|
|
|
|
}
|
2021-04-13 11:47:54 -07:00
|
|
|
})
|
|
|
|
}
|
2021-04-17 12:07:48 -07:00
|
|
|
|
|
|
|
#[proc_macro_derive(ZeroCopyAccessor, attributes(accessor))]
|
|
|
|
pub fn derive_zero_copy_accessor(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
|
|
|
let account_strct = parse_macro_input!(item as syn::ItemStruct);
|
|
|
|
let account_name = &account_strct.ident;
|
2021-07-08 13:14:39 -07:00
|
|
|
let (impl_gen, ty_gen, where_clause) = account_strct.generics.split_for_impl();
|
2021-04-17 12:07:48 -07:00
|
|
|
|
|
|
|
let fields = match &account_strct.fields {
|
|
|
|
syn::Fields::Named(n) => n,
|
|
|
|
_ => panic!("Fields must be named"),
|
|
|
|
};
|
|
|
|
let methods: Vec<proc_macro2::TokenStream> = fields
|
|
|
|
.named
|
|
|
|
.iter()
|
|
|
|
.filter_map(|field: &syn::Field| {
|
|
|
|
field
|
|
|
|
.attrs
|
|
|
|
.iter()
|
2021-06-27 17:09:46 -07:00
|
|
|
.find(|attr| anchor_syn::parser::tts_to_string(&attr.path) == "accessor")
|
2021-04-17 12:07:48 -07:00
|
|
|
.map(|attr| {
|
|
|
|
let mut tts = attr.tokens.clone().into_iter();
|
|
|
|
let g_stream = match tts.next().expect("Must have a token group") {
|
|
|
|
proc_macro2::TokenTree::Group(g) => g.stream(),
|
|
|
|
_ => panic!("Invalid syntax"),
|
|
|
|
};
|
|
|
|
let accessor_ty = match g_stream.into_iter().next() {
|
|
|
|
Some(token) => token,
|
|
|
|
_ => panic!("Missing accessor type"),
|
|
|
|
};
|
|
|
|
|
|
|
|
let field_name = field.ident.as_ref().unwrap();
|
|
|
|
|
|
|
|
let get_field: proc_macro2::TokenStream =
|
|
|
|
format!("get_{}", field_name.to_string()).parse().unwrap();
|
|
|
|
let set_field: proc_macro2::TokenStream =
|
|
|
|
format!("set_{}", field_name.to_string()).parse().unwrap();
|
|
|
|
|
|
|
|
quote! {
|
|
|
|
pub fn #get_field(&self) -> #accessor_ty {
|
|
|
|
anchor_lang::__private::ZeroCopyAccessor::get(&self.#field_name)
|
|
|
|
}
|
|
|
|
pub fn #set_field(&mut self, input: &#accessor_ty) {
|
|
|
|
self.#field_name = anchor_lang::__private::ZeroCopyAccessor::set(input);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.collect();
|
|
|
|
proc_macro::TokenStream::from(quote! {
|
2021-07-08 13:14:39 -07:00
|
|
|
#[automatically_derived]
|
|
|
|
impl #impl_gen #account_name #ty_gen #where_clause {
|
2021-04-17 12:07:48 -07:00
|
|
|
#(#methods)*
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A data structure that can be used as an internal field for a zero copy
|
|
|
|
/// deserialized account, i.e., a struct marked with `#[account(zero_copy)]`.
|
|
|
|
///
|
|
|
|
/// This is just a convenient alias for
|
|
|
|
///
|
|
|
|
/// ```ignore
|
|
|
|
/// #[derive(Copy, Clone)]
|
|
|
|
/// #[repr(packed)]
|
|
|
|
/// struct MyStruct {...}
|
|
|
|
/// ```
|
|
|
|
#[proc_macro_attribute]
|
|
|
|
pub fn zero_copy(
|
|
|
|
_args: proc_macro::TokenStream,
|
|
|
|
item: proc_macro::TokenStream,
|
|
|
|
) -> proc_macro::TokenStream {
|
|
|
|
let account_strct = parse_macro_input!(item as syn::ItemStruct);
|
|
|
|
|
|
|
|
proc_macro::TokenStream::from(quote! {
|
2021-06-09 13:02:50 -07:00
|
|
|
#[derive(anchor_lang::__private::ZeroCopyAccessor, Copy, Clone)]
|
|
|
|
#[repr(packed)]
|
|
|
|
#account_strct
|
2021-04-17 12:07:48 -07:00
|
|
|
})
|
|
|
|
}
|