1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
use account::derive_account;
use instruction::derive_instruction;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Error as ParseError};

mod account;
mod instruction;

// -----------------
// #[derive(ShankAccount)]
// -----------------

/// Annotates a _struct_ that shank will consider an account containing de/serializable data.
///
/// # Example
///
/// ```
/// use shank::ShankAccount;
/// use borsh::{BorshDeserialize, BorshSerialize};
///
/// #[derive(Clone, BorshSerialize, BorshDeserialize, ShankAccount)]
/// pub struct Metadata {
///     pub update_authority: Pubkey,
///     pub mint: Pubkey,
///     pub primary_sale_happened: bool,
/// }
/// ```
///
/// # Seeds
///
/// You can include a `#[seeds]` annotation which allows shank to generate the following `impl`
/// methods for the particular account.
///
/// A seed takes one of the following patterns:
///
/// - `"literal"` this will be hardcoded into the seed/pda methods and does not need to be passed
/// via an argument
/// - `program_id` (known pubkey) this is the program id of the program which is passed to methods
/// - `label("description"[, type])` a seed of name _label_ with the provided description and an
/// optional type (if no type is provided `Pubkey` is assumed); this will be passed as an argument
///
/// Below is an example of each:
///
/// ```
/// #[derive(ShankAccount)]
/// #[seeds(
///     "lit:prefix",                        // a string literal which will be hard coded
///     program_id                           // the public key of the program which needs to be provided
///     pub_key_implicit("desc of the key"), // a public key which needs to be provided
///     pub_key("desc of the key", Pubkey),  // same as the above, explicitly declaring as pubkey
///     id("desc of byte", u8),              // a byte
///     name("desc of name", String)         // a string
/// )]
/// struct AccountStructWithSeeds {
///     count: u8,
/// }
/// ```
/// When seeds are specified for an account it will derive the following _static_ methods for that
/// account:
///
/// ```
/// AccountName::shank_seeds<'a>(..) -> [&'a [u8]; Nusize]
/// AccountName::shank_seeds_with_bump<'a>(.., bump: &'a [u8; 1]) -> [&'a [u8]; Nusize]
///
/// AccountName::shank_pda(program_id: Pubkey, ..) -> (Pubkey, u8)
/// AccountName::shank_pda_with_bump(program_id: Pubkey, bump: u8, ..) -> (Pubkey, u8)
/// ```
///
///# Note
///
/// The fields of a _ShankAccount_ struct can reference other types as long as they are annotated
/// with `BorshSerialize` or `BorshDeserialize`.
#[proc_macro_derive(ShankAccount, attributes(padding, seeds))]
pub fn shank_account(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    derive_account(input)
        .unwrap_or_else(to_compile_error)
        .into()
}

// -----------------
// #[derive(ShankInstructions)]
// -----------------

/// Annotates the program _Instruction_ `Enum` in order to include `#[account]` attributes.
///
/// The `#[account]` attributes indicate for each instruction _variant_ which accounts it expects
/// and how they should be configured.
///
/// # `#[account]` attribute
///
/// This attribute allows you to configure each account that is provided to the particular
/// instruction. These annotations need to follow the order in which the accounts are provided.
/// They take the following general form:
///
/// ```
/// #[account(index?, (writable|signer)?, optional?, name="<account_name>", desc?="optional description")]
/// ```
///
/// - `index`: optionally provides the account index in the provided accounts array which needs to
///   match its position of `#[account]` attributes
/// - `signer` | `sign` | `sig`: indicates that the account is _signer_
/// - `writable` | `write` | `writ` | `mut`: indicates that the account is _writable_ which means it may be
///   mutated as part of processing the particular instruction
/// - `optional | option | opt`: indicates that this account is optional
/// - `name`: (required) provides the name for the account
/// - `desc` | `description`: allows to provide a description of the account
///
/// # Known Accounts
///
/// If an account `name` matches either of the a _known_ accounts indicated below then
/// [solita](https://github.com/metaplex-foundation/solita) generated SDK code won't require providing
/// it as the program id is known.
///
/// - `token_program` uses `TOKEN_PROGRAM_ID`
/// - `ata_program` uses `ASSOCIATED_TOKEN_PROGRAM_ID`
/// - `system_program` uses `SystemProgram.programId`
/// - `rent` uses `SYSVAR_RENT_PUBKEY`
///
/// # Strategies
///
/// ## Defaulting Optional Accounts
///
/// When the `#[default_optional_accounts]` attribute is added to an Instruction enum, shank will mark it
/// such that optional accounts should default to the `progam_id` if they are not provided by the client.
/// Thus their position is static and optional accounts that are set can follow ones that are not.
///
/// The default strategy (without `#[default_optional_accounts]`) is to just omit unset optional
/// accounts from the accounts array.
///
/// **NOTE**: shank doesn't do anything different here aside from setting a flag for the
/// particular instruction. Thus adding that strategy to an instruction enum is merely advisory and
/// will is expected to be properly respected by code generator tools like
/// [solita](https://github.com/metaplex-foundation/solita).
///
/// # Examples
///
/// ```
/// use borsh::{BorshDeserialize, BorshSerialize};
/// use shank::ShankInstruction;
/// #[derive(Debug, Clone, ShankInstruction, BorshSerialize, BorshDeserialize)]
/// #[rustfmt::skip]
/// pub enum VaultInstruction {
///     /// Initialize a token vault, starts inactivate. Add tokens in subsequent instructions, then activate.
///     #[account(0, writable, name="fraction_mint",
///               desc="Initialized fractional share mint with 0 tokens in supply, authority on mint must be pda of program with seed [prefix, programid]")]
///     #[account(1, writable, name="redeem_treasury",
///             desc = "Initialized redeem treasury token account with 0 tokens in supply, owner of account must be pda of program like above")]
///     #[account(2, writable, name="fraction_treasury",
///             desc = "Initialized fraction treasury token account with 0 tokens in supply, owner of account must be pda of program like above")]
///     #[account(3, writable, name="vault",
///             desc = "Uninitialized vault account")]
///     #[account(4, name="authority",
///             desc = "Authority on the vault")]
///     #[account(5, name="pricing_lookup_address",
///             desc = "Pricing Lookup Address")]
///     #[account(6, name="token_program",
///             desc = "Token program")]
///     #[account(7, name="rent",
///             desc = "Rent sysvar")]
///     InitVault(InitVaultArgs),
///
///     /// Activates the vault, distributing initial shares into the fraction treasury.
///     /// Tokens can no longer be removed in this state until Combination.
///     #[account(0, writable, name="vault", desc = "Initialized inactivated fractionalized token vault")]
///     #[account(1, writable, name="fraction_mint", desc = "Fraction mint")]
///     #[account(2, writable, name="fraction_treasury", desc = "Fraction treasury")]
///     #[account(3, name="fraction_mint_authority", desc = "Fraction mint authority for the program - seed of [PREFIX, program_id]")]
///     #[account(4, signer, name="vault_authority", desc = "Authority on the vault")]
///     #[account(5, name="token_program", desc = "Token program")]
///     ActivateVault(NumberOfShareArgs)
/// }
/// ```
#[proc_macro_derive(
    ShankInstruction,
    attributes(account, default_optional_accounts)
)]
pub fn shank_instruction(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    derive_instruction(input)
        .unwrap_or_else(to_compile_error)
        .into()
}

fn to_compile_error(error: ParseError) -> proc_macro2::TokenStream {
    let compile_error = ParseError::to_compile_error(&error);
    quote!(#compile_error)
}