From 365dd65c0bebbb18d72bb777d68975c317289a7a Mon Sep 17 00:00:00 2001 From: acheron Date: Fri, 1 Nov 2024 14:19:17 +0100 Subject: [PATCH 1/2] lang: Remove 3 lifetime definitions from `Context` --- lang/src/context.rs | 20 ++++++++-------- lang/syn/src/codegen/program/dispatch.rs | 4 ++-- lang/syn/src/codegen/program/entry.rs | 12 ++++++++-- lang/syn/src/codegen/program/handlers.rs | 30 +++++++++++++++++------- 4 files changed, 44 insertions(+), 22 deletions(-) diff --git a/lang/src/context.rs b/lang/src/context.rs index 6c2b7dfe92..c38b759076 100644 --- a/lang/src/context.rs +++ b/lang/src/context.rs @@ -21,14 +21,14 @@ use std::fmt; /// Ok(()) /// } /// ``` -pub struct Context<'a, 'b, 'c, 'info, T: Bumps> { +pub struct Context<'info, T: 'info + Bumps> { /// Currently executing program id. - pub program_id: &'a Pubkey, + pub program_id: &'info Pubkey, /// Deserialized accounts. - pub accounts: &'b mut T, + pub accounts: &'info mut T, /// Remaining accounts given but not deserialized or validated. /// Be very careful when using this directly. - pub remaining_accounts: &'c [AccountInfo<'info>], + pub remaining_accounts: &'info [AccountInfo<'info>], /// Bump seeds found during constraint validation. This is provided as a /// convenience so that handlers don't have to recalculate bump seeds or /// pass them in as arguments. @@ -36,7 +36,7 @@ pub struct Context<'a, 'b, 'c, 'info, T: Bumps> { pub bumps: T::Bumps, } -impl<'a, 'b, 'c, 'info, T> fmt::Debug for Context<'a, 'b, 'c, 'info, T> +impl<'info, T> fmt::Debug for Context<'info, T> where T: fmt::Debug + Bumps, { @@ -50,14 +50,14 @@ where } } -impl<'a, 'b, 'c, 'info, T> Context<'a, 'b, 'c, 'info, T> +impl<'info, T> Context<'info, T> where - T: Bumps + Accounts<'info, T::Bumps>, + T: 'info + Bumps + Accounts<'info, T::Bumps>, { pub fn new( - program_id: &'a Pubkey, - accounts: &'b mut T, - remaining_accounts: &'c [AccountInfo<'info>], + program_id: &'info Pubkey, + accounts: &'info mut T, + remaining_accounts: &'info [AccountInfo<'info>], bumps: T::Bumps, ) -> Self { Self { diff --git a/lang/syn/src/codegen/program/dispatch.rs b/lang/syn/src/codegen/program/dispatch.rs index b2cee2d371..c426673e2f 100644 --- a/lang/syn/src/codegen/program/dispatch.rs +++ b/lang/syn/src/codegen/program/dispatch.rs @@ -72,9 +72,9 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { /// If no match is found, the fallback function is executed if it exists, or an error is /// returned if it doesn't exist. fn dispatch<'info>( - program_id: &Pubkey, + program_id: &'info Pubkey, accounts: &'info [AccountInfo<'info>], - data: &[u8], + data: &'info [u8], ) -> anchor_lang::Result<()> { #(#global_ixs)* diff --git a/lang/syn/src/codegen/program/entry.rs b/lang/syn/src/codegen/program/entry.rs index 350e814f3b..70d358d9e0 100644 --- a/lang/syn/src/codegen/program/entry.rs +++ b/lang/syn/src/codegen/program/entry.rs @@ -36,14 +36,22 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { /// /// The `entry` function here, defines the standard entry to a Solana /// program, where execution begins. - pub fn entry<'info>(program_id: &Pubkey, accounts: &'info [AccountInfo<'info>], data: &[u8]) -> anchor_lang::solana_program::entrypoint::ProgramResult { + pub fn entry<'info>( + program_id: &'info Pubkey, + accounts: &'info [AccountInfo<'info>], + data: &'info [u8] + ) -> anchor_lang::solana_program::entrypoint::ProgramResult { try_entry(program_id, accounts, data).map_err(|e| { e.log(); e.into() }) } - fn try_entry<'info>(program_id: &Pubkey, accounts: &'info [AccountInfo<'info>], data: &[u8]) -> anchor_lang::Result<()> { + fn try_entry<'info>( + program_id: &'info Pubkey, + accounts: &'info [AccountInfo<'info>], + data: &'info [u8] + ) -> anchor_lang::Result<()> { #[cfg(feature = "anchor-debug")] { msg!("anchor-debug is active"); diff --git a/lang/syn/src/codegen/program/handlers.rs b/lang/syn/src/codegen/program/handlers.rs index e13f60a578..77ad122741 100644 --- a/lang/syn/src/codegen/program/handlers.rs +++ b/lang/syn/src/codegen/program/handlers.rs @@ -100,7 +100,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { let ix_arg_names: Vec<&syn::Ident> = ix.args.iter().map(|arg| &arg.name).collect(); let ix_name = generate_ix_variant_name(ix.raw_method.sig.ident.to_string()); let ix_method_name = &ix.raw_method.sig.ident; - let anchor = &ix.anchor_ident; + let accounts_struct_name = &ix.anchor_ident; let variant_arm = generate_ix_variant(ix.raw_method.sig.ident.to_string(), &ix.args); let ix_name_log = format!("Instruction: {ix_name}"); let ret_type = &ix.returns.ty.to_token_stream(); @@ -117,9 +117,9 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { #(#cfgs)* #[inline(never)] pub fn #ix_method_name<'info>( - __program_id: &Pubkey, - __accounts: &'info[AccountInfo<'info>], - __ix_data: &[u8], + __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); @@ -130,13 +130,13 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { let instruction::#variant_arm = ix; // Bump collector. - let mut __bumps = <#anchor as anchor_lang::Bumps>::Bumps::default(); + 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: &[AccountInfo] = __accounts; - let mut __accounts = #anchor::try_accounts( + let mut __remaining_accounts = __accounts; + let mut __accounts = #accounts_struct_name::try_accounts( __program_id, &mut __remaining_accounts, __ix_data, @@ -148,7 +148,21 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { let result = #program_name::#ix_method_name( anchor_lang::context::Context::new( __program_id, - &mut __accounts, + // SAFETY: The transmute shortens the inner `AccountInfo` lifetimes to + // match the lifetimes of other arguments. All references are going to + // be valid at least for the duration of the user-defined instruction + // handlers. + // + // This is done to avoid having to define multiple lifetimes for the + // `Context` struct, which requies Anchor users to do the same in + // various cases (e.g. remaining accounts usage), resulting in a poor + // developer experience. + unsafe { + ::core::mem::transmute::< + &mut #accounts_struct_name<'info>, + &mut #accounts_struct_name<'_> + >(&mut __accounts) + }, __remaining_accounts, __bumps, ), From 176531f8071adc3defa66fb85e59fbed97311060 Mon Sep 17 00:00:00 2001 From: acheron Date: Fri, 1 Nov 2024 14:20:02 +0100 Subject: [PATCH 2/2] tests: Fix remaining accounts --- tests/misc/programs/remaining-accounts/src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/misc/programs/remaining-accounts/src/lib.rs b/tests/misc/programs/remaining-accounts/src/lib.rs index b93b91fd43..78a33d7a43 100644 --- a/tests/misc/programs/remaining-accounts/src/lib.rs +++ b/tests/misc/programs/remaining-accounts/src/lib.rs @@ -22,9 +22,7 @@ pub mod remaining_accounts { Ok(()) } - pub fn test_remaining_accounts<'c: 'info, 'info>( - ctx: Context<'_, '_, 'c, 'info, TestRemainingAccounts>, - ) -> Result<()> { + pub fn test_remaining_accounts(ctx: Context) -> Result<()> { let remaining_accounts_iter = &mut ctx.remaining_accounts.iter(); let token_account =