lang/syn: Allow state structs with no ctor or impl block

This commit is contained in:
Armani Ferrante 2021-02-09 22:25:23 +08:00
parent 845df6d196
commit a780002683
No known key found for this signature in database
GPG Key ID: D597A80BCF8E12B7
6 changed files with 591 additions and 540 deletions

View File

@ -17,6 +17,7 @@ incremented for features.
* cli: Stream program logs to `.anchor/program-logs` directory when testing.
* spl: Add shared memory api.
* lang/attribute/access-control: Allow specifying multiple modifier functions.
* lang/syn: Allow state structs that don't have a ctor or impl block (just trait implementations).
## [0.2.0] - 2021-02-08

View File

@ -13,15 +13,7 @@ pub mod counter_auth {
use super::*;
#[state]
pub struct CounterAuth {}
// TODO: remove this impl block after addressing
// https://github.com/project-serum/anchor/issues/71.
impl CounterAuth {
pub fn new(_ctx: Context<Empty>) -> Result<Self, ProgramError> {
Ok(Self {})
}
}
pub struct CounterAuth;
impl<'info> Auth<'info, Empty> for CounterAuth {
fn is_authorized(_ctx: Context<Empty>, current: u64, new: u64) -> ProgramResult {

View File

@ -69,39 +69,14 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
// Dispatch the state constructor.
let ctor_state_dispatch_arm = match &program.state {
None => quote! { /* no-op */ },
Some(state) => {
let variant_arm = generate_ctor_variant(state);
let ctor_args = generate_ctor_args(state);
let ix_name: proc_macro2::TokenStream = generate_ctor_variant_name().parse().unwrap();
let sighash_arr = sighash_ctor();
let sighash_tts: proc_macro2::TokenStream =
format!("{:?}", sighash_arr).parse().unwrap();
quote! {
#sighash_tts => {
let ix = instruction::#ix_name::deserialize(&mut instruction_data)
.map_err(|_| ProgramError::Custom(1))?; // todo: error code
let instruction::#variant_arm = ix;
__private::__ctor(program_id, accounts, #(#ctor_args),*)
}
}
}
};
// Dispatch the state impl instructions.
let state_dispatch_arms: Vec<proc_macro2::TokenStream> = match &program.state {
None => vec![],
Some(s) => s
.methods
.iter()
.map(|rpc: &crate::StateRpc| {
let rpc_arg_names: Vec<&syn::Ident> =
rpc.args.iter().map(|arg| &arg.name).collect();
let name = &rpc.raw_method.sig.ident.to_string();
let rpc_name: proc_macro2::TokenStream = { format!("__{}", name).parse().unwrap() };
let variant_arm =
generate_ix_variant(rpc.raw_method.sig.ident.to_string(), &rpc.args, true);
let ix_name = generate_ix_variant_name(rpc.raw_method.sig.ident.to_string(), true);
let sighash_arr = sighash(SIGHASH_STATE_NAMESPACE, &name);
Some(state) => match state.ctor_and_anchor.is_some() {
false => quote! {},
true => {
let variant_arm = generate_ctor_variant(state);
let ctor_args = generate_ctor_args(state);
let ix_name: proc_macro2::TokenStream =
generate_ctor_variant_name().parse().unwrap();
let sighash_arr = sighash_ctor();
let sighash_tts: proc_macro2::TokenStream =
format!("{:?}", sighash_arr).parse().unwrap();
quote! {
@ -109,11 +84,50 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
let ix = instruction::#ix_name::deserialize(&mut instruction_data)
.map_err(|_| ProgramError::Custom(1))?; // todo: error code
let instruction::#variant_arm = ix;
__private::#rpc_name(program_id, accounts, #(#rpc_arg_names),*)
__private::__ctor(program_id, accounts, #(#ctor_args),*)
}
}
}
},
};
// Dispatch the state impl instructions.
let state_dispatch_arms: Vec<proc_macro2::TokenStream> = match &program.state {
None => vec![],
Some(s) => s
.impl_block_and_methods
.as_ref()
.map(|(_impl_block, methods)| {
methods
.iter()
.map(|rpc: &crate::StateRpc| {
let rpc_arg_names: Vec<&syn::Ident> =
rpc.args.iter().map(|arg| &arg.name).collect();
let name = &rpc.raw_method.sig.ident.to_string();
let rpc_name: proc_macro2::TokenStream =
{ format!("__{}", name).parse().unwrap() };
let variant_arm = generate_ix_variant(
rpc.raw_method.sig.ident.to_string(),
&rpc.args,
true,
);
let ix_name =
generate_ix_variant_name(rpc.raw_method.sig.ident.to_string(), true);
let sighash_arr = sighash(SIGHASH_STATE_NAMESPACE, &name);
let sighash_tts: proc_macro2::TokenStream =
format!("{:?}", sighash_arr).parse().unwrap();
quote! {
#sighash_tts => {
let ix = instruction::#ix_name::deserialize(&mut instruction_data)
.map_err(|_| ProgramError::Custom(1))?; // todo: error code
let instruction::#variant_arm = ix;
__private::#rpc_name(program_id, accounts, #(#rpc_arg_names),*)
}
}
})
.collect()
})
.collect(),
.unwrap_or(vec![]),
};
// Dispatch all trait interface implementations.
@ -121,54 +135,59 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
None => vec![],
Some(s) => s
.interfaces
.iter()
.flat_map(|iface: &crate::StateInterface| {
iface
.methods
.as_ref()
.map(|interfaces| {
interfaces
.iter()
.map(|m: &crate::StateRpc| {
let rpc_arg_names: Vec<&syn::Ident> =
m.args.iter().map(|arg| &arg.name).collect();
let name = &m.raw_method.sig.ident.to_string();
let rpc_name: proc_macro2::TokenStream = format!("__{}_{}", iface.trait_name, name).parse().unwrap();
let raw_args: Vec<&syn::PatType> = m
.args
.flat_map(|iface: &crate::StateInterface| {
iface
.methods
.iter()
.map(|arg: &crate::RpcArg| &arg.raw_arg)
.collect();
let sighash_arr = sighash(&iface.trait_name, &m.ident.to_string());
let sighash_tts: proc_macro2::TokenStream =
format!("{:?}", sighash_arr).parse().unwrap();
let args_struct = {
if m.args.len() == 0 {
.map(|m: &crate::StateRpc| {
let rpc_arg_names: Vec<&syn::Ident> =
m.args.iter().map(|arg| &arg.name).collect();
let name = &m.raw_method.sig.ident.to_string();
let rpc_name: proc_macro2::TokenStream = format!("__{}_{}", iface.trait_name, name).parse().unwrap();
let raw_args: Vec<&syn::PatType> = m
.args
.iter()
.map(|arg: &crate::RpcArg| &arg.raw_arg)
.collect();
let sighash_arr = sighash(&iface.trait_name, &m.ident.to_string());
let sighash_tts: proc_macro2::TokenStream =
format!("{:?}", sighash_arr).parse().unwrap();
let args_struct = {
if m.args.len() == 0 {
quote! {
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
struct Args;
}
} else {
quote! {
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
struct Args {
#(#raw_args),*
}
}
}
};
quote! {
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
struct Args;
}
} else {
quote! {
#[derive(anchor_lang::AnchorSerialize, anchor_lang::AnchorDeserialize)]
struct Args {
#(#raw_args),*
#sighash_tts => {
#args_struct
let ix = Args::deserialize(&mut instruction_data)
.map_err(|_| ProgramError::Custom(1))?; // todo: error code
let Args {
#(#rpc_arg_names),*
} = ix;
__private::#rpc_name(program_id, accounts, #(#rpc_arg_names),*)
}
}
}
};
quote! {
#sighash_tts => {
#args_struct
let ix = Args::deserialize(&mut instruction_data)
.map_err(|_| ProgramError::Custom(1))?; // todo: error code
let Args {
#(#rpc_arg_names),*
} = ix;
__private::#rpc_name(program_id, accounts, #(#rpc_arg_names),*)
}
}
})
.collect::<Vec<proc_macro2::TokenStream>>()
})
.collect::<Vec<proc_macro2::TokenStream>>()
.collect()
})
.collect(),
.unwrap_or(vec![])
};
// Dispatch all global instructions.
@ -346,234 +365,246 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
};
let non_inlined_ctor: proc_macro2::TokenStream = match &program.state {
None => quote! {},
Some(state) => {
let ctor_typed_args = generate_ctor_typed_args(state);
let ctor_untyped_args = generate_ctor_args(state);
let name = &state.strct.ident;
let mod_name = &program.name;
let anchor_ident = &state.ctor_anchor;
quote! {
// One time state account initializer. Will faill on subsequent
// invocations.
#[inline(never)]
pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], #(#ctor_typed_args),*) -> ProgramResult {
let mut remaining_accounts: &[AccountInfo] = accounts;
// Deserialize accounts.
let ctor_accounts = anchor_lang::Ctor::try_accounts(program_id, &mut remaining_accounts)?;
let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts)?;
// Invoke the ctor.
let instance = #mod_name::#name::new(
anchor_lang::Context::new(
program_id,
&mut ctor_user_def_accounts,
remaining_accounts,
),
#(#ctor_untyped_args),*
)?;
// Create the solana account for the ctor data.
let from = ctor_accounts.from.key;
let (base, nonce) = Pubkey::find_program_address(&[], ctor_accounts.program.key);
let seed = anchor_lang::ProgramState::<#name>::seed();
let owner = ctor_accounts.program.key;
let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
// Add 8 for the account discriminator.
let space = 8 + instance.try_to_vec().map_err(|_| ProgramError::Custom(1))?.len();
let lamports = ctor_accounts.rent.minimum_balance(space);
let seeds = &[&[nonce][..]];
let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
from,
&to,
&base,
seed,
lamports,
space as u64,
owner,
);
anchor_lang::solana_program::program::invoke_signed(
&ix,
&[
ctor_accounts.from.clone(),
ctor_accounts.to.clone(),
ctor_accounts.base.clone(),
ctor_accounts.system_program.clone(),
],
&[seeds],
)?;
// Serialize the state and save it to storage.
ctor_user_def_accounts.exit(program_id)?;
let mut data = ctor_accounts.to.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
let mut cursor = std::io::Cursor::new(dst);
instance.try_serialize(&mut cursor)?;
Ok(())
}
}
}
};
let non_inlined_state_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
None => vec![],
Some(state) => state
.methods
.iter()
.map(|rpc| {
let rpc_params: Vec<_> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
let rpc_arg_names: Vec<&syn::Ident> =
rpc.args.iter().map(|arg| &arg.name).collect();
let private_rpc_name: proc_macro2::TokenStream = {
let n = format!("__{}", &rpc.raw_method.sig.ident.to_string());
n.parse().unwrap()
};
let rpc_name = &rpc.raw_method.sig.ident;
let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
let anchor_ident = &rpc.anchor_ident;
Some(state) => match state.ctor_and_anchor.as_ref() {
None => quote! {},
Some((_ctor, anchor_ident)) => {
let ctor_typed_args = generate_ctor_typed_args(state);
let ctor_untyped_args = generate_ctor_args(state);
let name = &state.strct.ident;
let mod_name = &program.name;
quote! {
// One time state account initializer. Will faill on subsequent
// invocations.
#[inline(never)]
pub fn #private_rpc_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
#(#rpc_params),*
) -> ProgramResult {
pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], #(#ctor_typed_args),*) -> ProgramResult {
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.len() == 0 {
return Err(ProgramError::Custom(1)); // todo
}
// Deserialize the program state account.
let state_account = &remaining_accounts[0];
let mut state: #state_ty = {
let data = state_account.try_borrow_data()?;
let mut sliced: &[u8] = &data;
anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
};
// Deserialize accounts.
let ctor_accounts = anchor_lang::Ctor::try_accounts(program_id, &mut remaining_accounts)?;
let mut ctor_user_def_accounts = #anchor_ident::try_accounts(program_id, &mut remaining_accounts)?;
remaining_accounts = &remaining_accounts[1..];
// Deserialize the program's execution context.
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
// Invoke the ctor.
let instance = #mod_name::#name::new(
anchor_lang::Context::new(
program_id,
&mut ctor_user_def_accounts,
remaining_accounts,
),
#(#ctor_untyped_args),*
)?;
let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
// Execute user defined function.
state.#rpc_name(
ctx,
#(#rpc_arg_names),*
// Create the solana account for the ctor data.
let from = ctor_accounts.from.key;
let (base, nonce) = Pubkey::find_program_address(&[], ctor_accounts.program.key);
let seed = anchor_lang::ProgramState::<#name>::seed();
let owner = ctor_accounts.program.key;
let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
// Add 8 for the account discriminator.
let space = 8 + instance.try_to_vec().map_err(|_| ProgramError::Custom(1))?.len();
let lamports = ctor_accounts.rent.minimum_balance(space);
let seeds = &[&[nonce][..]];
let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
from,
&to,
&base,
seed,
lamports,
space as u64,
owner,
);
anchor_lang::solana_program::program::invoke_signed(
&ix,
&[
ctor_accounts.from.clone(),
ctor_accounts.to.clone(),
ctor_accounts.base.clone(),
ctor_accounts.system_program.clone(),
],
&[seeds],
)?;
// Serialize the state and save it to storage.
accounts.exit(program_id)?;
let mut data = state_account.try_borrow_mut_data()?;
ctor_user_def_accounts.exit(program_id)?;
let mut data = ctor_accounts.to.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
let mut cursor = std::io::Cursor::new(dst);
state.try_serialize(&mut cursor)?;
instance.try_serialize(&mut cursor)?;
Ok(())
}
}
})
.collect(),
}
},
};
let non_inlined_state_trait_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
None => Vec::new(),
let non_inlined_state_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
None => vec![],
Some(state) => state
.interfaces
.iter()
.flat_map(|iface: &crate::StateInterface| {
iface
.methods
.impl_block_and_methods
.as_ref()
.map(|(_impl_block, methods)| {
methods
.iter()
.map(|rpc| {
let rpc_params: Vec<_> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
let rpc_arg_names: Vec<&syn::Ident> =
rpc.args.iter().map(|arg| &arg.name).collect();
let private_rpc_name: proc_macro2::TokenStream = {
let n = format!("__{}_{}", iface.trait_name, &rpc.raw_method.sig.ident.to_string());
let n = format!("__{}", &rpc.raw_method.sig.ident.to_string());
n.parse().unwrap()
};
let rpc_name = &rpc.raw_method.sig.ident;
let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
let anchor_ident = &rpc.anchor_ident;
quote! {
#[inline(never)]
pub fn #private_rpc_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
#(#rpc_params),*
) -> ProgramResult {
if rpc.has_receiver {
quote! {
#[inline(never)]
pub fn #private_rpc_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
#(#rpc_params),*
) -> ProgramResult {
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.len() == 0 {
return Err(ProgramError::Custom(1)); // todo
}
// Deserialize the program state account.
let state_account = &remaining_accounts[0];
let mut state: #state_ty = {
let data = state_account.try_borrow_data()?;
let mut sliced: &[u8] = &data;
anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
};
remaining_accounts = &remaining_accounts[1..];
// Deserialize the program's execution context.
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
)?;
let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
// Execute user defined function.
state.#rpc_name(
ctx,
#(#rpc_arg_names),*
)?;
// Serialize the state and save it to storage.
accounts.exit(program_id)?;
let mut data = state_account.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
let mut cursor = std::io::Cursor::new(dst);
state.try_serialize(&mut cursor)?;
Ok(())
}
}
} else {
let state_name: proc_macro2::TokenStream = state.name.parse().unwrap();
quote! {
#[inline(never)]
pub fn #private_rpc_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
#(#rpc_params),*
) -> ProgramResult {
let mut remaining_accounts: &[AccountInfo] = accounts;
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
)?;
#state_name::#rpc_name(
Context::new(program_id, &mut accounts, remaining_accounts),
#(#rpc_arg_names),*
)?;
accounts.exit(program_id)
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.len() == 0 {
return Err(ProgramError::Custom(1)); // todo
}
// Deserialize the program state account.
let state_account = &remaining_accounts[0];
let mut state: #state_ty = {
let data = state_account.try_borrow_data()?;
let mut sliced: &[u8] = &data;
anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
};
remaining_accounts = &remaining_accounts[1..];
// Deserialize the program's execution context.
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
)?;
let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
// Execute user defined function.
state.#rpc_name(
ctx,
#(#rpc_arg_names),*
)?;
// Serialize the state and save it to storage.
accounts.exit(program_id)?;
let mut data = state_account.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
let mut cursor = std::io::Cursor::new(dst);
state.try_serialize(&mut cursor)?;
Ok(())
}
}
})
.collect::<Vec<proc_macro2::TokenStream>>()
.collect()
})
.collect(),
.unwrap_or(vec![]),
};
let non_inlined_state_trait_handlers: Vec<proc_macro2::TokenStream> = match &program.state {
None => Vec::new(),
Some(state) => state
.interfaces
.as_ref()
.map(|interfaces| {
interfaces
.iter()
.flat_map(|iface: &crate::StateInterface| {
iface
.methods
.iter()
.map(|rpc| {
let rpc_params: Vec<_> = rpc.args.iter().map(|arg| &arg.raw_arg).collect();
let rpc_arg_names: Vec<&syn::Ident> =
rpc.args.iter().map(|arg| &arg.name).collect();
let private_rpc_name: proc_macro2::TokenStream = {
let n = format!("__{}_{}", iface.trait_name, &rpc.raw_method.sig.ident.to_string());
n.parse().unwrap()
};
let rpc_name = &rpc.raw_method.sig.ident;
let state_ty: proc_macro2::TokenStream = state.name.parse().unwrap();
let anchor_ident = &rpc.anchor_ident;
if rpc.has_receiver {
quote! {
#[inline(never)]
pub fn #private_rpc_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
#(#rpc_params),*
) -> ProgramResult {
let mut remaining_accounts: &[AccountInfo] = accounts;
if remaining_accounts.len() == 0 {
return Err(ProgramError::Custom(1)); // todo
}
// Deserialize the program state account.
let state_account = &remaining_accounts[0];
let mut state: #state_ty = {
let data = state_account.try_borrow_data()?;
let mut sliced: &[u8] = &data;
anchor_lang::AccountDeserialize::try_deserialize(&mut sliced)?
};
remaining_accounts = &remaining_accounts[1..];
// Deserialize the program's execution context.
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
)?;
let ctx = Context::new(program_id, &mut accounts, remaining_accounts);
// Execute user defined function.
state.#rpc_name(
ctx,
#(#rpc_arg_names),*
)?;
// Serialize the state and save it to storage.
accounts.exit(program_id)?;
let mut data = state_account.try_borrow_mut_data()?;
let dst: &mut [u8] = &mut data;
let mut cursor = std::io::Cursor::new(dst);
state.try_serialize(&mut cursor)?;
Ok(())
}
}
} else {
let state_name: proc_macro2::TokenStream = state.name.parse().unwrap();
quote! {
#[inline(never)]
pub fn #private_rpc_name(
program_id: &Pubkey,
accounts: &[AccountInfo],
#(#rpc_params),*
) -> ProgramResult {
let mut remaining_accounts: &[AccountInfo] = accounts;
let mut accounts = #anchor_ident::try_accounts(
program_id,
&mut remaining_accounts,
)?;
#state_name::#rpc_name(
Context::new(program_id, &mut accounts, remaining_accounts),
#(#rpc_arg_names),*
)?;
accounts.exit(program_id)
}
}
}
})
.collect::<Vec<proc_macro2::TokenStream>>()
})
.collect()
})
.unwrap_or(Vec::new()),
};
let non_inlined_handlers: Vec<proc_macro2::TokenStream> = program
.rpcs
@ -663,42 +694,50 @@ pub fn generate_ctor_typed_variant_with_semi(program: &Program) -> proc_macro2::
fn generate_ctor_typed_args(state: &State) -> Vec<syn::PatType> {
state
.ctor
.sig
.inputs
.iter()
.filter_map(|arg: &syn::FnArg| match arg {
syn::FnArg::Typed(pat_ty) => {
let mut arg_str = parser::tts_to_string(&pat_ty.ty);
arg_str.retain(|c| !c.is_whitespace());
if arg_str.starts_with("Context<") {
return None;
}
Some(pat_ty.clone())
}
_ => panic!("Invalid syntaxe,"),
.ctor_and_anchor
.as_ref()
.map(|(ctor, _anchor_ident)| {
ctor.sig
.inputs
.iter()
.filter_map(|arg: &syn::FnArg| match arg {
syn::FnArg::Typed(pat_ty) => {
let mut arg_str = parser::tts_to_string(&pat_ty.ty);
arg_str.retain(|c| !c.is_whitespace());
if arg_str.starts_with("Context<") {
return None;
}
Some(pat_ty.clone())
}
_ => panic!("Invalid syntaxe,"),
})
.collect()
})
.collect()
.unwrap_or(Vec::new())
}
fn generate_ctor_args(state: &State) -> Vec<Box<syn::Pat>> {
state
.ctor
.sig
.inputs
.iter()
.filter_map(|arg: &syn::FnArg| match arg {
syn::FnArg::Typed(pat_ty) => {
let mut arg_str = parser::tts_to_string(&pat_ty.ty);
arg_str.retain(|c| !c.is_whitespace());
if arg_str.starts_with("Context<") {
return None;
}
Some(pat_ty.pat.clone())
}
_ => panic!(""),
.ctor_and_anchor
.as_ref()
.map(|(ctor, _anchor_ident)| {
ctor.sig
.inputs
.iter()
.filter_map(|arg: &syn::FnArg| match arg {
syn::FnArg::Typed(pat_ty) => {
let mut arg_str = parser::tts_to_string(&pat_ty.ty);
arg_str.retain(|c| !c.is_whitespace());
if arg_str.starts_with("Context<") {
return None;
}
Some(pat_ty.pat.clone())
}
_ => panic!(""),
})
.collect()
})
.collect()
.unwrap_or(Vec::new())
}
pub fn generate_ix_variant(
@ -750,62 +789,67 @@ pub fn generate_instructions(program: &Program) -> proc_macro2::TokenStream {
let state_method_variants: Vec<proc_macro2::TokenStream> = match &program.state {
None => vec![],
Some(state) => state
.methods
.iter()
.map(|method| {
let rpc_name_camel: proc_macro2::TokenStream = {
let name = format!(
"__{}",
&method.raw_method.sig.ident.to_string().to_camel_case(),
);
name.parse().unwrap()
};
let raw_args: Vec<proc_macro2::TokenStream> = method
.args
.impl_block_and_methods
.as_ref()
.map(|(_impl_block, methods)| {
methods
.iter()
.map(|arg| {
format!("pub {}", parser::tts_to_string(&arg.raw_arg))
.parse()
.unwrap()
})
.collect();
.map(|method| {
let rpc_name_camel: proc_macro2::TokenStream = {
let name = format!(
"__{}",
&method.raw_method.sig.ident.to_string().to_camel_case(),
);
name.parse().unwrap()
};
let raw_args: Vec<proc_macro2::TokenStream> = method
.args
.iter()
.map(|arg| {
format!("pub {}", parser::tts_to_string(&arg.raw_arg))
.parse()
.unwrap()
})
.collect();
let ix_data_trait = {
let name = method.raw_method.sig.ident.to_string();
let sighash_arr = sighash(SIGHASH_GLOBAL_NAMESPACE, &name);
let sighash_tts: proc_macro2::TokenStream =
format!("{:?}", sighash_arr).parse().unwrap();
quote! {
impl anchor_lang::InstructionData for #rpc_name_camel {
fn data(&self) -> Vec<u8> {
let mut d = #sighash_tts.to_vec();
d.append(&mut self.try_to_vec().expect("Should always serialize"));
d
let ix_data_trait = {
let name = method.raw_method.sig.ident.to_string();
let sighash_arr = sighash(SIGHASH_GLOBAL_NAMESPACE, &name);
let sighash_tts: proc_macro2::TokenStream =
format!("{:?}", sighash_arr).parse().unwrap();
quote! {
impl anchor_lang::InstructionData for #rpc_name_camel {
fn data(&self) -> Vec<u8> {
let mut d = #sighash_tts.to_vec();
d.append(&mut self.try_to_vec().expect("Should always serialize"));
d
}
}
}
};
// If no args, output a "unit" variant instead of a struct variant.
if method.args.len() == 0 {
quote! {
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct #rpc_name_camel;
#ix_data_trait
}
} else {
quote! {
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct #rpc_name_camel {
#(#raw_args),*
}
#ix_data_trait
}
}
}
};
// If no args, output a "unit" variant instead of a struct variant.
if method.args.len() == 0 {
quote! {
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct #rpc_name_camel;
#ix_data_trait
}
} else {
quote! {
#[derive(AnchorSerialize, AnchorDeserialize)]
pub struct #rpc_name_camel {
#(#raw_args),*
}
#ix_data_trait
}
}
})
.collect()
})
.collect(),
.unwrap_or(Vec::new()),
};
let variants: Vec<proc_macro2::TokenStream> = program
.rpcs
@ -876,16 +920,18 @@ pub fn generate_instructions(program: &Program) -> proc_macro2::TokenStream {
fn generate_accounts(program: &Program) -> proc_macro2::TokenStream {
let mut accounts = std::collections::HashSet::new();
// Got through state accounts.
// Go through state accounts.
if let Some(state) = &program.state {
for rpc in &state.methods {
let anchor_ident = &rpc.anchor_ident;
// TODO: move to fn and share with accounts.rs.
let macro_name = format!(
"__client_accounts_{}",
anchor_ident.to_string().to_snake_case()
);
accounts.insert(macro_name);
if let Some((_impl_block, methods)) = &state.impl_block_and_methods {
for rpc in methods {
let anchor_ident = &rpc.anchor_ident;
// TODO: move to fn and share with accounts.rs.
let macro_name = format!(
"__client_accounts_{}",
anchor_ident.to_string().to_snake_case()
);
accounts.insert(macro_name);
}
}
}

View File

@ -30,11 +30,9 @@ pub struct Program {
pub struct State {
pub name: String,
pub strct: syn::ItemStruct,
pub impl_block: syn::ItemImpl,
pub methods: Vec<StateRpc>,
pub interfaces: Vec<StateInterface>,
pub ctor: syn::ImplItemMethod,
pub ctor_anchor: syn::Ident, // TODO: consolidate this with ctor above.
pub ctor_and_anchor: Option<(syn::ImplItemMethod, syn::Ident)>,
pub impl_block_and_methods: Option<(syn::ItemImpl, Vec<StateRpc>)>,
pub interfaces: Option<Vec<StateInterface>>,
}
#[derive(Debug)]

View File

@ -33,102 +33,112 @@ pub fn parse(filename: impl AsRef<Path>) -> Result<Idl> {
acc_names
};
let state = p.state.map(|state| {
let mut methods = state
.methods
.iter()
.map(|method: &StateRpc| {
let name = method.ident.to_string().to_mixed_case();
let args = method
.args
.iter()
.map(|arg| {
let mut tts = proc_macro2::TokenStream::new();
arg.raw_arg.ty.to_tokens(&mut tts);
let ty = tts.to_string().parse().unwrap();
IdlField {
name: arg.name.to_string().to_mixed_case(),
ty,
}
let state = match p.state {
None => None,
Some(state) => match state.ctor_and_anchor {
None => None, // State struct defined but no implementation
Some((ctor, anchor_ident)) => {
let mut methods = state
.impl_block_and_methods
.map(|(_impl_block, methods)| {
methods
.iter()
.map(|method: &StateRpc| {
let name = method.ident.to_string().to_mixed_case();
let args = method
.args
.iter()
.map(|arg| {
let mut tts = proc_macro2::TokenStream::new();
arg.raw_arg.ty.to_tokens(&mut tts);
let ty = tts.to_string().parse().unwrap();
IdlField {
name: arg.name.to_string().to_mixed_case(),
ty,
}
})
.collect::<Vec<_>>();
let accounts_strct =
accs.get(&method.anchor_ident.to_string()).unwrap();
let accounts = accounts_strct.idl_accounts(&accs);
IdlStateMethod {
name,
args,
accounts,
}
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
let accounts_strct = accs.get(&method.anchor_ident.to_string()).unwrap();
let accounts = accounts_strct.idl_accounts(&accs);
IdlStateMethod {
name,
args,
accounts,
}
})
.collect::<Vec<_>>();
let ctor = {
let name = "new".to_string();
let args = state
.ctor
.sig
.inputs
.iter()
.filter_map(|arg: &syn::FnArg| match arg {
syn::FnArg::Typed(pat_ty) => {
// TODO: this filtering should be donein the parser.
let mut arg_str = parser::tts_to_string(&pat_ty.ty);
arg_str.retain(|c| !c.is_whitespace());
if arg_str.starts_with("Context<") {
return None;
}
Some(arg)
.unwrap_or(Vec::new());
let ctor = {
let name = "new".to_string();
let args = ctor
.sig
.inputs
.iter()
.filter_map(|arg: &syn::FnArg| match arg {
syn::FnArg::Typed(pat_ty) => {
// TODO: this filtering should be donein the parser.
let mut arg_str = parser::tts_to_string(&pat_ty.ty);
arg_str.retain(|c| !c.is_whitespace());
if arg_str.starts_with("Context<") {
return None;
}
Some(arg)
}
_ => None,
})
.map(|arg: &syn::FnArg| match arg {
syn::FnArg::Typed(arg_typed) => {
let mut tts = proc_macro2::TokenStream::new();
arg_typed.ty.to_tokens(&mut tts);
let ty = tts.to_string().parse().unwrap();
IdlField {
name: parser::tts_to_string(&arg_typed.pat).to_mixed_case(),
ty,
}
}
_ => panic!("Invalid syntax"),
})
.collect();
let accounts_strct = accs.get(&anchor_ident.to_string()).unwrap();
let accounts = accounts_strct.idl_accounts(&accs);
IdlStateMethod {
name,
args,
accounts,
}
_ => None,
})
.map(|arg: &syn::FnArg| match arg {
syn::FnArg::Typed(arg_typed) => {
let mut tts = proc_macro2::TokenStream::new();
arg_typed.ty.to_tokens(&mut tts);
let ty = tts.to_string().parse().unwrap();
IdlField {
name: parser::tts_to_string(&arg_typed.pat).to_mixed_case(),
ty,
}
};
methods.insert(0, ctor);
let strct = {
let fields = match state.strct.fields {
syn::Fields::Named(f_named) => f_named
.named
.iter()
.map(|f: &syn::Field| {
let mut tts = proc_macro2::TokenStream::new();
f.ty.to_tokens(&mut tts);
let ty = tts.to_string().parse().unwrap();
IdlField {
name: f.ident.as_ref().unwrap().to_string().to_mixed_case(),
ty,
}
})
.collect::<Vec<IdlField>>(),
_ => panic!("State must be a struct"),
};
IdlTypeDef {
name: state.name,
ty: IdlTypeDefTy::Struct { fields },
}
_ => panic!("Invalid syntax"),
})
.collect();
let accounts_strct = accs.get(&state.ctor_anchor.to_string()).unwrap();
let accounts = accounts_strct.idl_accounts(&accs);
IdlStateMethod {
name,
args,
accounts,
};
Some(IdlState { strct, methods })
}
};
methods.insert(0, ctor);
let strct = {
let fields = match state.strct.fields {
syn::Fields::Named(f_named) => f_named
.named
.iter()
.map(|f: &syn::Field| {
let mut tts = proc_macro2::TokenStream::new();
f.ty.to_tokens(&mut tts);
let ty = tts.to_string().parse().unwrap();
IdlField {
name: f.ident.as_ref().unwrap().to_string().to_mixed_case(),
ty,
}
})
.collect::<Vec<IdlField>>(),
_ => panic!("State must be a struct"),
};
IdlTypeDef {
name: state.name,
ty: IdlTypeDefTy::Struct { fields },
}
};
IdlState { strct, methods }
});
},
};
let error = parse_error_enum(&f).map(|mut e| error::parse(&mut e));
let error_codes = error.as_ref().map(|e| {
e.codes

View File

@ -27,8 +27,9 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
})
.next();
let impl_block: Option<&syn::ItemImpl> = strct.map(|strct| {
let item_impls = mod_content
let impl_block: Option<syn::ItemImpl> = match strct {
None => None,
Some(strct) => mod_content
.iter()
.filter_map(|item| match item {
syn::Item::Impl(item_impl) => {
@ -40,13 +41,12 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
if strct_name != impl_ty_str {
return None;
}
Some(item_impl)
Some(item_impl.clone())
}
_ => None,
})
.collect::<Vec<&syn::ItemImpl>>();
item_impls[0]
});
.next(),
};
// All program interface implementations.
let trait_impls: Option<Vec<StateInterface>> = strct.map(|_strct| {
@ -84,83 +84,87 @@ pub fn parse(program_mod: syn::ItemMod) -> Program {
let mut strct = strct.clone();
strct.attrs = vec![];
let impl_block = impl_block.expect("Must exist if struct exists").clone();
let (ctor, ctor_anchor) = impl_block
.items
.iter()
.filter_map(|item: &syn::ImplItem| match item {
syn::ImplItem::Method(m) => {
if m.sig.ident.to_string() == "new" {
let ctx_arg = m.sig.inputs.first().unwrap(); // todo: unwrap.
match ctx_arg {
syn::FnArg::Receiver(_) => panic!("invalid syntax"),
syn::FnArg::Typed(arg) => {
Some((m.clone(), extract_ident(&arg).clone()))
let ctor_and_anchor = match &impl_block {
None => None,
Some(impl_block) => {
impl_block
.items
.iter()
.filter_map(|item: &syn::ImplItem| match item {
syn::ImplItem::Method(m) => {
if m.sig.ident.to_string() == "new" {
let ctx_arg = m.sig.inputs.first().unwrap(); // todo: unwrap.
match ctx_arg {
syn::FnArg::Receiver(_) => panic!("invalid syntax"),
syn::FnArg::Typed(arg) => {
Some((m.clone(), extract_ident(&arg).clone()))
}
}
} else {
None
}
}
} else {
None
}
}
_ => None,
})
.next()
.expect("Must exist if struct exists")
.clone();
_ => None,
})
.next()
.clone()
}
};
let methods: Vec<StateRpc> = impl_block
.items
.iter()
.filter_map(|item: &syn::ImplItem| match item {
syn::ImplItem::Method(m) => match m.sig.inputs.first() {
None => None,
Some(arg) => match arg {
syn::FnArg::Typed(_) => None,
syn::FnArg::Receiver(_) => {
let mut args = m
.sig
.inputs
.iter()
.filter_map(|arg| match arg {
syn::FnArg::Receiver(_) => None,
syn::FnArg::Typed(arg) => Some(arg),
})
.map(|raw_arg| {
let ident = match &*raw_arg.pat {
syn::Pat::Ident(ident) => &ident.ident,
_ => panic!("invalid syntax"),
};
RpcArg {
name: ident.clone(),
raw_arg: raw_arg.clone(),
}
})
.collect::<Vec<RpcArg>>();
// Remove the Anchor accounts argument
let anchor = args.remove(0);
let anchor_ident = extract_ident(&anchor.raw_arg).clone();
let impl_block_and_methods = impl_block.map(|impl_block| {
let methods: Vec<StateRpc> = impl_block
.items
.iter()
.filter_map(|item: &syn::ImplItem| match item {
syn::ImplItem::Method(m) => match m.sig.inputs.first() {
None => None,
Some(arg) => match arg {
syn::FnArg::Typed(_) => None,
syn::FnArg::Receiver(_) => {
let mut args = m
.sig
.inputs
.iter()
.filter_map(|arg| match arg {
syn::FnArg::Receiver(_) => None,
syn::FnArg::Typed(arg) => Some(arg),
})
.map(|raw_arg| {
let ident = match &*raw_arg.pat {
syn::Pat::Ident(ident) => &ident.ident,
_ => panic!("invalid syntax"),
};
RpcArg {
name: ident.clone(),
raw_arg: raw_arg.clone(),
}
})
.collect::<Vec<RpcArg>>();
// Remove the Anchor accounts argument
let anchor = args.remove(0);
let anchor_ident = extract_ident(&anchor.raw_arg).clone();
Some(StateRpc {
raw_method: m.clone(),
ident: m.sig.ident.clone(),
args,
anchor_ident,
has_receiver: true,
})
}
Some(StateRpc {
raw_method: m.clone(),
ident: m.sig.ident.clone(),
args,
anchor_ident,
has_receiver: true,
})
}
},
},
},
_ => None,
})
.collect();
_ => None,
})
.collect();
(impl_block.clone(), methods)
});
State {
name: strct.ident.to_string(),
strct: strct.clone(),
interfaces: trait_impls.expect("Some if state exists"),
impl_block,
ctor,
ctor_anchor,
methods,
interfaces: trait_impls,
impl_block_and_methods,
ctor_and_anchor,
}
})
};