diff --git a/lang/src/context.rs b/lang/src/context.rs index 0e24b21efa..2fedb6deb5 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: 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 fmt::Debug for Context<'_, '_, '_, '_, T> +impl fmt::Debug for Context<'_, 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>, { 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 23ca78821c..a43cac9911 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 06e73a072e..c1cde38e28 100644 --- a/lang/syn/src/codegen/program/handlers.rs +++ b/lang/syn/src/codegen/program/handlers.rs @@ -33,7 +33,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { }; let ix_name_log = format!("Instruction: {ix_name}"); - let anchor = &ix.anchor_ident; + 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() { @@ -45,15 +45,13 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { }, }; - let actual_param_count = ix.args.len(); - let ix_name_str = ix_method_name.to_string(); - let accounts_type_str = anchor.to_string(); // 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_type_str, - ix_name_str, + accounts_struct_name, + ix_method_name_str, actual_param_count, ); @@ -69,12 +67,12 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { ); quote! { // Type validation for argument #idx - if #anchor::__ANCHOR_IX_PARAM_COUNT > #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!(); - #anchor::#method_name(&__type_check_arg); + #accounts_struct_name::#method_name(&__type_check_arg); } } } @@ -83,7 +81,7 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { let param_validation = quote! { const _: () = { - const EXPECTED_COUNT: usize = #anchor::__ANCHOR_IX_PARAM_COUNT; + const EXPECTED_COUNT: usize = #accounts_struct_name::__ANCHOR_IX_PARAM_COUNT; const HANDLER_PARAM_COUNT: usize = #actual_param_count; // Count validation @@ -100,9 +98,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); @@ -114,13 +112,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, @@ -128,11 +126,34 @@ pub fn generate(program: &Program) -> proc_macro2::TokenStream { &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, - &mut __accounts, + // 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, ), diff --git a/tests/auction-house/programs/auction-house/src/lib.rs b/tests/auction-house/programs/auction-house/src/lib.rs index 13b9bcfd7d..b4efaaec26 100644 --- a/tests/auction-house/programs/auction-house/src/lib.rs +++ b/tests/auction-house/programs/auction-house/src/lib.rs @@ -34,8 +34,8 @@ const ZERO: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; pub mod auction_house { use super::*; - pub fn create_auction_house<'info>( - ctx: Context<'_, '_, '_, 'info, CreateAuctionHouse<'info>>, + pub fn create_auction_house( + ctx: Context, seller_fee_basis_points: u16, requires_sign_off: bool, can_change_sale_price: bool, @@ -126,10 +126,7 @@ pub mod auction_house { Ok(()) } - pub fn deposit<'info>( - ctx: Context<'_, '_, '_, 'info, Deposit<'info>>, - amount: u64, - ) -> Result<()> { + pub fn deposit(ctx: Context, amount: u64) -> Result<()> { let wallet = &ctx.accounts.wallet; let payment_account = &ctx.accounts.payment_account; let transfer_authority = &ctx.accounts.transfer_authority; @@ -221,10 +218,7 @@ pub mod auction_house { Ok(()) } - pub fn withdraw<'info>( - ctx: Context<'_, '_, '_, 'info, Withdraw<'info>>, - amount: u64, - ) -> Result<()> { + pub fn withdraw(ctx: Context, amount: u64) -> Result<()> { let wallet = &ctx.accounts.wallet; let receipt_account = &ctx.accounts.receipt_account; let escrow_payment_account = &ctx.accounts.escrow_payment_account; @@ -340,11 +334,7 @@ pub mod auction_house { Ok(()) } - pub fn sell<'info>( - ctx: Context<'_, '_, '_, 'info, Sell<'info>>, - buyer_price: u64, - token_size: u64, - ) -> Result<()> { + pub fn sell(ctx: Context, buyer_price: u64, token_size: u64) -> Result<()> { let wallet = &ctx.accounts.wallet; let token_account = &ctx.accounts.token_account; let metadata = &ctx.accounts.metadata; @@ -458,11 +448,7 @@ pub mod auction_house { Ok(()) } - pub fn cancel<'info>( - ctx: Context<'_, '_, '_, 'info, Cancel<'info>>, - _buyer_price: u64, - _token_size: u64, - ) -> Result<()> { + pub fn cancel(ctx: Context, _buyer_price: u64, _token_size: u64) -> Result<()> { let wallet = &ctx.accounts.wallet; let token_account = &ctx.accounts.token_account; let authority = &ctx.accounts.authority; @@ -520,11 +506,7 @@ pub mod auction_house { Ok(()) } - pub fn buy<'info>( - ctx: Context<'_, '_, '_, 'info, Buy<'info>>, - buyer_price: u64, - token_size: u64, - ) -> Result<()> { + pub fn buy(ctx: Context, buyer_price: u64, token_size: u64) -> Result<()> { let wallet = &ctx.accounts.wallet; let payment_account = &ctx.accounts.payment_account; let transfer_authority = &ctx.accounts.transfer_authority; @@ -666,7 +648,7 @@ pub mod auction_house { } pub fn execute_sale<'info>( - ctx: Context<'_, '_, '_, 'info, ExecuteSale<'info>>, + ctx: Context<'info, ExecuteSale<'info>>, buyer_price: u64, token_size: u64, ) -> Result<()> { @@ -947,10 +929,7 @@ pub mod auction_house { Ok(()) } - pub fn withdraw_from_fee<'info>( - ctx: Context<'_, '_, '_, 'info, WithdrawFromFee<'info>>, - amount: u64, - ) -> Result<()> { + pub fn withdraw_from_fee(ctx: Context, amount: u64) -> Result<()> { let auction_house_fee_account = &ctx.accounts.auction_house_fee_account; let fee_withdrawal_destination = &ctx.accounts.fee_withdrawal_destination; let auction_house = &ctx.accounts.auction_house; @@ -981,10 +960,7 @@ pub mod auction_house { Ok(()) } - pub fn withdraw_from_treasury<'info>( - ctx: Context<'_, '_, '_, 'info, WithdrawFromTreasury<'info>>, - amount: u64, - ) -> Result<()> { + pub fn withdraw_from_treasury(ctx: Context, amount: u64) -> Result<()> { let treasury_mint = &ctx.accounts.treasury_mint; let treasury_withdrawal_destination = &ctx.accounts.treasury_withdrawal_destination; let auction_house_treasury = &ctx.accounts.auction_house_treasury; @@ -1044,8 +1020,8 @@ pub mod auction_house { Ok(()) } - pub fn update_auction_house<'info>( - ctx: Context<'_, '_, '_, 'info, UpdateAuctionHouse<'info>>, + pub fn update_auction_house( + ctx: Context, seller_fee_basis_points: Option, requires_sign_off: Option, can_change_sale_price: Option, diff --git a/tests/cfo/programs/cfo/src/lib.rs b/tests/cfo/programs/cfo/src/lib.rs index b0c7f6ab3d..9ce5b951bd 100644 --- a/tests/cfo/programs/cfo/src/lib.rs +++ b/tests/cfo/programs/cfo/src/lib.rs @@ -87,7 +87,7 @@ pub mod cfo { } /// Transfers fees from the dex to the CFO. - pub fn sweep_fees<'info>(ctx: Context<'_, '_, '_, 'info, SweepFees<'info>>) -> Result<()> { + pub fn sweep_fees(ctx: Context) -> Result<()> { let cpi_ctx = CpiContext::from(&*ctx.accounts); let seeds = [ ctx.accounts.dex.dex_program.key.as_ref(), @@ -99,10 +99,7 @@ pub mod cfo { /// Convert the CFO's entire non-SRM token balance into USDC. /// Assumes USDC is the quote currency. #[access_control(is_not_trading(&ctx.accounts.instructions))] - pub fn swap_to_usdc<'info>( - ctx: Context<'_, '_, '_, 'info, SwapToUsdc<'info>>, - min_exchange_rate: ExchangeRate, - ) -> Result<()> { + pub fn swap_to_usdc(ctx: Context, min_exchange_rate: ExchangeRate) -> Result<()> { let seeds = [ ctx.accounts.dex_program.key.as_ref(), &[ctx.accounts.officer.bumps.bump], @@ -120,10 +117,7 @@ pub mod cfo { /// Convert the CFO's entire token balance into SRM. /// Assumes SRM is the base currency. #[access_control(is_not_trading(&ctx.accounts.instructions))] - pub fn swap_to_srm<'info>( - ctx: Context<'_, '_, '_, 'info, SwapToSrm<'info>>, - min_exchange_rate: ExchangeRate, - ) -> Result<()> { + pub fn swap_to_srm(ctx: Context, min_exchange_rate: ExchangeRate) -> Result<()> { let seeds = [ ctx.accounts.dex_program.key.as_ref(), &[ctx.accounts.officer.bumps.bump], @@ -141,7 +135,7 @@ pub mod cfo { /// Distributes srm tokens to the various categories. Before calling this, /// one must convert the fees into SRM via the swap APIs. #[access_control(is_distribution_ready(&ctx.accounts))] - pub fn distribute<'info>(ctx: Context<'_, '_, '_, 'info, Distribute<'info>>) -> Result<()> { + pub fn distribute(ctx: Context) -> Result<()> { let total_fees = ctx.accounts.srm_vault.amount; let seeds = [ ctx.accounts.dex_program.key.as_ref(), @@ -188,9 +182,7 @@ pub mod cfo { } #[access_control(is_stake_reward_ready(&ctx.accounts))] - pub fn drop_stake_reward<'info>( - ctx: Context<'_, '_, '_, 'info, DropStakeReward<'info>>, - ) -> Result<()> { + pub fn drop_stake_reward(ctx: Context) -> Result<()> { // Common reward parameters. let expiry_ts = 1853942400; // 9/30/2028. let expiry_receiver = *ctx.accounts.officer.to_account_info().key; diff --git a/tests/lockup/programs/lockup/src/lib.rs b/tests/lockup/programs/lockup/src/lib.rs index 2c9aac54e3..62ed301b74 100644 --- a/tests/lockup/programs/lockup/src/lib.rs +++ b/tests/lockup/programs/lockup/src/lib.rs @@ -134,8 +134,8 @@ pub mod lockup { } // Sends funds from the lockup program to a whitelisted program. - pub fn whitelist_withdraw<'a, 'b, 'c, 'info>( - ctx: Context<'a, 'b, 'c, 'info, WhitelistWithdraw<'info>>, + pub fn whitelist_withdraw( + ctx: Context, instruction_data: Vec, amount: u64, ) -> Result<()> { @@ -161,8 +161,8 @@ pub mod lockup { } // Sends funds from a whitelisted program back to the lockup program. - pub fn whitelist_deposit<'a, 'b, 'c, 'info>( - ctx: Context<'a, 'b, 'c, 'info, WhitelistDeposit<'info>>, + pub fn whitelist_deposit( + ctx: Context, instruction_data: Vec, ) -> Result<()> { let before_amount = ctx.accounts.transfer.vault.amount; diff --git a/tests/lockup/programs/registry/src/lib.rs b/tests/lockup/programs/registry/src/lib.rs index c7fe9e35f9..caafc1c3f5 100644 --- a/tests/lockup/programs/registry/src/lib.rs +++ b/tests/lockup/programs/registry/src/lib.rs @@ -464,10 +464,7 @@ mod registry { } #[access_control(reward_eligible(&ctx.accounts.cmn))] - pub fn claim_reward_locked<'a, 'b, 'c, 'info>( - ctx: Context<'a, 'b, 'c, 'info, ClaimRewardLocked<'info>>, - nonce: u8, - ) -> Result<()> { + pub fn claim_reward_locked(ctx: Context, nonce: u8) -> Result<()> { let (start_ts, end_ts, period_count) = match ctx.accounts.cmn.vendor.kind { RewardVendorKind::Unlocked => return err!(ErrorCode::ExpectedLockedVendor), RewardVendorKind::Locked { 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 = diff --git a/tests/swap/programs/swap/src/lib.rs b/tests/swap/programs/swap/src/lib.rs index c04102f1ba..152e2a1639 100644 --- a/tests/swap/programs/swap/src/lib.rs +++ b/tests/swap/programs/swap/src/lib.rs @@ -35,8 +35,8 @@ pub mod swap { /// client expects to receive from the swap. The instruction fails if /// execution would result in less. #[access_control(is_valid_swap(&ctx))] - pub fn swap<'info>( - ctx: Context<'_, '_, '_, 'info, Swap<'info>>, + pub fn swap( + ctx: Context, side: Side, amount: u64, min_expected_swap_amount: u64, @@ -106,8 +106,8 @@ pub mod swap { /// client expects to receive from the swap. The instruction fails if /// execution would result in less. #[access_control(is_valid_swap_transitive(&ctx))] - pub fn swap_transitive<'info>( - ctx: Context<'_, '_, '_, 'info, SwapTransitive<'info>>, + pub fn swap_transitive( + ctx: Context, amount: u64, min_expected_swap_amount: u64, ) -> Result<()> {