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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
use {
crate::{codegen::program::common::*, Program},
quote::{quote, ToTokens},
};
// Generate non-inlined wrappers for each instruction handler, since Solana's
// BPF max stack size can't handle reasonable sized dispatch trees without doing
// so.
pub fn generate(program: &Program) -> proc_macro2::TokenStream {
let program_name = &program.name;
let event_cpi_mod = generate_event_cpi_mod();
let non_inlined_handlers: Vec<proc_macro2::TokenStream> = program
.ixs
.iter()
.map(|ix| {
let ix_arg_names: Vec<&syn::Ident> = ix.args.iter().map(|arg| &arg.name).collect();
let ix_method_name = &ix.raw_method.sig.ident;
let ix_method_name_str = ix_method_name.to_string();
let ix_name = match generate_ix_variant_name(&ix_method_name_str) {
Ok(name) => quote! { #name },
Err(e) => {
let err = e.to_string();
return quote! { compile_error!(concat!("error generating ix variant name: `", #err, "`")) };
}
};
let variant_arm = match generate_ix_variant(&ix_method_name_str, &ix.args) {
Ok(v) => v,
Err(e) => {
let err = e.to_string();
return quote! { compile_error!(concat!("error generating ix variant arm: `", #err, "`")) };
}
};
let ix_name_log = format!("Instruction: {ix_name}");
let accounts_struct_name = &ix.anchor_ident;
let ret_type = &ix.returns.ty.to_token_stream();
let cfgs = &ix.cfgs;
let maybe_set_return_data = match ret_type.to_string().as_str() {
"()" => quote! {},
_ => quote! {
let mut return_data = Vec::with_capacity(256);
result.serialize(&mut return_data).unwrap();
anchor_lang::solana_program::program::set_return_data(&return_data);
},
};
// Build clear error messages
let actual_param_count = ix.args.len();
let count_error_msg = format!(
"#[instruction(...)] on Account `{}<'_>` expects MORE args, the ix `{}(...)` has only {} args.",
accounts_struct_name,
ix_method_name_str,
actual_param_count,
);
// Generate type validation calls for each argument
let type_validations: Vec<proc_macro2::TokenStream> = ix.args
.iter()
.enumerate()
.map(|(idx, arg)| {
let arg_ty = &arg.raw_arg.ty;
let method_name = syn::Ident::new(
&format!("__anchor_validate_ix_arg_type_{}", idx),
proc_macro2::Span::call_site(),
);
quote! {
// Type validation for argument #idx
if #accounts_struct_name::__ANCHOR_IX_PARAM_COUNT > #idx {
#[allow(unreachable_code)]
if false {
// This code is never executed but is type-checked at compile time
let __type_check_arg: #arg_ty = panic!();
#accounts_struct_name::#method_name(&__type_check_arg);
}
}
}
})
.collect();
let param_validation = quote! {
const _: () = {
const EXPECTED_COUNT: usize = #accounts_struct_name::__ANCHOR_IX_PARAM_COUNT;
const HANDLER_PARAM_COUNT: usize = #actual_param_count;
// Count validation
if EXPECTED_COUNT > HANDLER_PARAM_COUNT {
panic!(#count_error_msg);
}
};
// Type validations
#(#type_validations)*
};
quote! {
#(#cfgs)*
#[inline(never)]
pub fn #ix_method_name<'info>(
__program_id: &'info Pubkey,
__accounts: &'info [AccountInfo<'info>],
__ix_data: &'info [u8],
) -> anchor_lang::Result<()> {
#[cfg(not(feature = "no-log-ix-name"))]
anchor_lang::prelude::msg!(#ix_name_log);
#param_validation
// Deserialize data.
let ix = instruction::#ix_name::deserialize(&mut &__ix_data[..])
.map_err(|_| anchor_lang::error::ErrorCode::InstructionDidNotDeserialize)?;
let instruction::#variant_arm = ix;
// Bump collector.
let mut __bumps = <#accounts_struct_name as anchor_lang::Bumps>::Bumps::default();
let mut __reallocs = std::collections::BTreeSet::new();
// Deserialize accounts.
let mut __remaining_accounts = __accounts;
let mut __accounts = #accounts_struct_name::try_accounts(
__program_id,
&mut __remaining_accounts,
__ix_data,
&mut __bumps,
&mut __reallocs,
)?;
unsafe fn __shrink_lifetime<'from, 'to, T>(value: &'from mut T) -> &'to mut T {
unsafe { ::core::mem::transmute(value) }
}
// Invoke user defined handler.
let result = #program_name::#ix_method_name(
anchor_lang::context::Context::new(
__program_id,
// SAFETY: `__shrink_lifetime` is used to *shrink* the lifetime of
// the inner `AccountInfo` from `'info` to the local function lifetime.
// No lifetime is extended by this operation.
// The lifetime is not shrunk automatically as `RefCell` causes `AccountInfo`
// to be invariant.
// This is sound provided the following invariants hold:
// (1) The `'info` lifetime strictly outlives the local function
// lifetime; therefore, the transmuted references cannot outlive
// their backing data.
// (2) `AccountInfo` does not implement custom `Drop` logic and does not
// rely on its lifetime parameter during destruction.
// (3) The `Context` value is dropped before the `__accounts` reference
// is dropped or otherwise accessed, preventing any use-after-scope.
//
// This lifetime narrowing is required to conform to the `Context`
// struct’s single-lifetime parameterization, which uses a single
// lifetime to keep the API simple and ergonomic.
unsafe {
__shrink_lifetime(&mut __accounts)
},
__remaining_accounts,
__bumps,
),
#(#ix_arg_names),*
)?;
// Maybe set Solana return data.
#maybe_set_return_data
// Exit routine.
__accounts.exit(__program_id)
}
}
})
.collect();
quote! {
/// Create a private module to not clutter the program's namespace.
/// Defines an entrypoint for each individual instruction handler
/// wrapper.
mod __private {
use super::*;
/// __global mod defines wrapped handlers for global instructions.
pub mod __global {
use super::*;
#(#non_inlined_handlers)*
}
#event_cpi_mod
}
}
}
/// Generate the event module based on whether the `event-cpi` feature is enabled.
fn generate_event_cpi_mod() -> proc_macro2::TokenStream {
#[cfg(feature = "event-cpi")]
{
let authority = crate::parser::accounts::event_cpi::EventAuthority::get();
let authority_name = authority.name;
quote! {
/// __events mod defines handler for self-cpi based event logging
pub mod __events {
use super::*;
#[inline(never)]
pub fn __event_dispatch(
program_id: &Pubkey,
accounts: &[AccountInfo],
event_data: &[u8],
) -> anchor_lang::Result<()> {
let given_event_authority = next_account_info(&mut accounts.iter())?;
if !given_event_authority.is_signer {
return Err(anchor_lang::error::Error::from(
anchor_lang::error::ErrorCode::ConstraintSigner,
)
.with_account_name(#authority_name));
}
if given_event_authority.key() != crate::EVENT_AUTHORITY_AND_BUMP.0 {
return Err(anchor_lang::error::Error::from(
anchor_lang::error::ErrorCode::ConstraintSeeds,
)
.with_account_name(#authority_name)
.with_pubkeys((given_event_authority.key(), crate::EVENT_AUTHORITY_AND_BUMP.0)));
}
Ok(())
}
}
}
}
#[cfg(not(feature = "event-cpi"))]
quote! {}
}