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)
}