Skip to content

Commit

Permalink
Add handling for custom serialization, like spl-token
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyera Eulberg committed Jun 27, 2020
1 parent f8e5062 commit e104a09
Show file tree
Hide file tree
Showing 6 changed files with 302 additions and 41 deletions.
106 changes: 72 additions & 34 deletions sdk/program-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use syn::{
parse_macro_input, parse_quote,
punctuated::Punctuated,
token::{Brace, Comma},
ExprCall, Field, Fields, FieldsNamed, FnArg, Ident, ItemEnum, Lit, Meta, NestedMeta, Path,
Type,
Attribute, ExprCall, Field, Fields, FieldsNamed, FnArg, Ident, ItemEnum, Lit, Meta, NestedMeta,
Path, Type,
};

struct ProgramId(ExprCall);
Expand Down Expand Up @@ -154,18 +154,18 @@ impl AccountDetails {
if self.is_optional {
quote! {
if let Some(#ident) = #ident {
account_metas.push(#account_meta_path(#ident, #is_signer));
accounts.push(#account_meta_path(#ident, #is_signer));
}
}
} else if self.allows_multiple {
quote! {
for pubkey in #ident.into_iter() {
account_metas.push(#account_meta_path(pubkey, #is_signer));
accounts.push(#account_meta_path(pubkey, #is_signer));
}
}
} else {
quote! {
account_metas.push(#account_meta_path(#ident, #is_signer));
accounts.push(#account_meta_path(#ident, #is_signer));
}
}
}
Expand All @@ -190,11 +190,40 @@ struct VariantDetails {
struct ProgramDetails {
instruction_enum: ItemEnum,
variants: Vec<VariantDetails>,
serializer: Serializer,
}

enum Serializer {
Custom,
Serde,
}

impl Serializer {
fn get(attrs: &[Attribute]) -> Result<Self> {
for attr in attrs {
if let Meta::List(list) = attr.parse_meta()? {
if list.path.is_ident("derive") {
for nested_meta in list.nested {
if let NestedMeta::Meta(meta) = nested_meta {
if let Meta::Path(derived) = meta {
if derived.is_ident("Serialize") {
return Ok(Serializer::Serde);
}
}
}
}
}
}
}
Ok(Serializer::Custom)
}
}

impl Parse for ProgramDetails {
fn parse(input: ParseStream) -> Result<Self> {
let mut instruction_enum = ItemEnum::parse(input)?;
let serializer = Serializer::get(&instruction_enum.attrs)?;

let mut variants: Vec<VariantDetails> = vec![];
for variant in instruction_enum.variants.iter_mut() {
let mut account_details: Vec<AccountDetails> = vec![];
Expand Down Expand Up @@ -229,6 +258,7 @@ impl Parse for ProgramDetails {
Ok(ProgramDetails {
instruction_enum,
variants,
serializer,
})
}
}
Expand Down Expand Up @@ -278,12 +308,12 @@ fn build_helper_fns(

let mut accounts_stream = proc_macro2::TokenStream::new();
accounts_stream.extend(quote! {
let mut account_metas: Vec<::solana_sdk::instruction::AccountMeta> = vec![];
let mut accounts: Vec<::solana_sdk::instruction::AccountMeta> = vec![];
});
for account in &variant_details.account_details {
let account_meta = account.format_account_meta();
let accounts = account.format_account_meta();
accounts_stream.extend(quote! {
#account_meta
#accounts
});

args.push(account.format_arg());
Expand All @@ -299,14 +329,20 @@ fn build_helper_fns(
}
}

let data_serialization = match program_details.serializer {
Serializer::Custom => quote!(#ident::#variant_ident{ #fields }.serialize()),
Serializer::Serde => quote!(serialize(&#ident::#variant_ident{ #fields })),
};

stream.extend(
quote!( pub fn #fn_ident( #args ) -> ::solana_sdk::instruction::Instruction {
#accounts_stream
::solana_sdk::instruction::Instruction::new(
#program_id,
&#ident::#variant_ident{ #fields },
account_metas,
)
let data = #data_serialization.unwrap();
::solana_sdk::instruction::Instruction {
program_id: #program_id,
data,
accounts,
}
}
),
);
Expand Down Expand Up @@ -468,31 +504,31 @@ mod tests {
let account_meta = account_details.format_account_meta();
let account_meta: Stmt = parse_quote!(#account_meta);
let expected_account_meta: Stmt = parse_quote!(
account_metas.push(::solana_sdk::instruction::AccountMeta::new_readonly(test, false));
accounts.push(::solana_sdk::instruction::AccountMeta::new_readonly(test, false));
);
assert_eq!(account_meta, expected_account_meta);

account_details.is_signer = true;
let account_meta = account_details.format_account_meta();
let account_meta: Stmt = parse_quote!(#account_meta);
let expected_account_meta: Stmt = parse_quote!(
account_metas.push(::solana_sdk::instruction::AccountMeta::new_readonly(test, true));
accounts.push(::solana_sdk::instruction::AccountMeta::new_readonly(test, true));
);
assert_eq!(account_meta, expected_account_meta);

account_details.is_writable = true;
let account_meta = account_details.format_account_meta();
let account_meta: Stmt = parse_quote!(#account_meta);
let expected_account_meta: Stmt = parse_quote!(
account_metas.push(::solana_sdk::instruction::AccountMeta::new(test, true));
accounts.push(::solana_sdk::instruction::AccountMeta::new(test, true));
);
assert_eq!(account_meta, expected_account_meta);

account_details.is_optional = true;
let account_meta = account_details.format_account_meta();
let account_meta: Stmt = parse_quote!(#account_meta);
let expected_account_meta: Stmt = parse_quote!(if let Some(test) = test {
account_metas.push(::solana_sdk::instruction::AccountMeta::new(test, true));
accounts.push(::solana_sdk::instruction::AccountMeta::new(test, true));
});
assert_eq!(account_meta, expected_account_meta);

Expand All @@ -501,7 +537,7 @@ mod tests {
let account_meta = account_details.format_account_meta();
let account_meta: Stmt = parse_quote!(#account_meta);
let expected_account_meta: Stmt = parse_quote!(for pubkey in test.into_iter() {
account_metas.push(::solana_sdk::instruction::AccountMeta::new(pubkey, true));
accounts.push(::solana_sdk::instruction::AccountMeta::new(pubkey, true));
});
assert_eq!(account_meta, expected_account_meta);
}
Expand Down Expand Up @@ -578,6 +614,7 @@ mod tests {
account_details: account_details1,
},
],
serializer: Serializer::Serde,
}
}

Expand Down Expand Up @@ -624,37 +661,38 @@ mod tests {
another: ::solana_sdk::pubkey::Pubkey,
lamports: u64,
) -> ::solana_sdk::instruction::Instruction {
let mut account_metas: Vec<::solana_sdk::instruction::AccountMeta> = vec![];
account_metas.push(
let mut accounts: Vec<::solana_sdk::instruction::AccountMeta> = vec![];
accounts.push(
::solana_sdk::instruction::AccountMeta::new(test_account, true)
);
if let Some(another) = another {
account_metas.push(
accounts.push(
::solana_sdk::instruction::AccountMeta::new_readonly(another, false)
);
}
::solana_sdk::instruction::Instruction::new(
program::id(),
&TestInstruction::Transfer{lamports,},
account_metas,
)
let data = serialize(&TestInstruction::Transfer{lamports,}).unwrap();
::solana_sdk::instruction::Instruction {
program_id: program::id(),
data,
accounts,
}
}

pub fn multiple(
signers: Vec<::solana_sdk::pubkey::Pubkey>
) -> ::solana_sdk::instruction::Instruction {
let mut account_metas: Vec<::solana_sdk::instruction::AccountMeta> = vec![];
let mut accounts: Vec<::solana_sdk::instruction::AccountMeta> = vec![];
for pubkey in signers.into_iter() {
account_metas.push(
accounts.push(
::solana_sdk::instruction::AccountMeta::new_readonly(pubkey, true)
);
}

::solana_sdk::instruction::Instruction::new(
program::id(),
&TestInstruction::Multiple{},
account_metas,
)
let data = serialize(&TestInstruction::Multiple{}).unwrap();
::solana_sdk::instruction::Instruction {
program_id: program::id(),
data,
accounts,
}
}
};
assert_eq!(helper_fns[1], expected_fns[1]);
Expand Down
14 changes: 14 additions & 0 deletions sdk/tests/compile-fail/custom_serde.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use solana_sdk_program_macros::instructions;

mod test_program;

#[instructions(test_program::id())]
#[derive(Clone, Debug, PartialEq)]
pub enum TestInstruction {
#[accounts(
from_account(SIGNER, WRITABLE, desc = "Funding account")
)]
Transfer { lamports: u64 },
}

fn main() {}
14 changes: 14 additions & 0 deletions sdk/tests/compile-fail/custom_serde.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error[E0599]: no method named `serialize` found for enum `TestInstruction` in the current scope
--> $DIR/custom_serde.rs:5:1
|
5 | #[instructions(test_program::id())]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ method not found in `TestInstruction`
6 | #[derive(Clone, Debug, PartialEq)]
7 | pub enum TestInstruction {
| ------------------------ method `serialize` not found for this
|
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following traits define an item `serialize`, perhaps you need to implement one of them:
candidate #1: `serde::ser::Serialize`
candidate #2: `serde_bytes::ser::Serialize`
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
1 change: 1 addition & 0 deletions sdk/tests/compile_fail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ fn compile_test() {
t.compile_fail("tests/compile-fail/accounts_list_missing.rs");
t.compile_fail("tests/compile-fail/account_optional_multiple.rs");
t.compile_fail("tests/compile-fail/account_tag.rs");
t.compile_fail("tests/compile-fail/custom_serde.rs");
t.compile_fail("tests/compile-fail/unnamed_fields.rs");
}
12 changes: 5 additions & 7 deletions sdk/tests/program_macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,11 @@ fn test_helper_fns() {
multiple_accounts(pubkey0, vec![]),
Instruction {
program_id: test_program::id(),
accounts: vec![
AccountMeta {
pubkey: pubkey0,
is_signer: false,
is_writable: true,
}
],
accounts: vec![AccountMeta {
pubkey: pubkey0,
is_signer: false,
is_writable: true,
}],
data: serialize(&TestInstruction::MultipleAccounts).unwrap(),
}
);
Expand Down
Loading

0 comments on commit e104a09

Please sign in to comment.