Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions lang/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,22 @@ 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.
/// Type is the bumps struct generated by #[derive(Accounts)]
pub bumps: T::Bumps,
}

impl<T> fmt::Debug for Context<'_, '_, '_, '_, T>
impl<T> fmt::Debug for Context<'_, T>
where
T: fmt::Debug + Bumps,
{
Expand All @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions lang/syn/src/codegen/program/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)*

Expand Down
12 changes: 10 additions & 2 deletions lang/syn/src/codegen/program/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>(
Comment thread
acheroncrypto marked this conversation as resolved.
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");
Expand Down
53 changes: 37 additions & 16 deletions lang/syn/src/codegen/program/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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,
);

Expand All @@ -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);
}
}
}
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -114,25 +112,48 @@ 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,
&mut __bumps,
&mut __reallocs,
)?;

unsafe fn __shrink_lifetime<'from, 'to, T>(value: &'from mut T) -> &'to mut T {
unsafe { ::core::mem::transmute(value) }
}
Comment on lines +129 to +131

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the lifetime-specific helper as an extra-paranoid guard, and added a small note on variance to the safety comment to make it clear why this shrinking isn't done automatically

@jamie-osec I think the helper function clearly extends the mutable borrow lifetime instead of shortening the inner lifetime. The impl and its documentation is misleading currently.

What we want instead is something like this:

unsafe fn __shorten_invariant_lifetime<'a, 'info: 'a>(
    value: &'a mut #accounts_struct_name<'info>,
) -> &'a mut #accounts_struct_name<'a> {
    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 {
Comment thread
acheroncrypto marked this conversation as resolved.
__shrink_lifetime(&mut __accounts)
},
__remaining_accounts,
__bumps,
),
Expand Down
48 changes: 12 additions & 36 deletions tests/auction-house/programs/auction-house/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<CreateAuctionHouse>,
seller_fee_basis_points: u16,
requires_sign_off: bool,
can_change_sale_price: bool,
Expand Down Expand Up @@ -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<Deposit>, amount: u64) -> Result<()> {
let wallet = &ctx.accounts.wallet;
let payment_account = &ctx.accounts.payment_account;
let transfer_authority = &ctx.accounts.transfer_authority;
Expand Down Expand Up @@ -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<Withdraw>, amount: u64) -> Result<()> {
let wallet = &ctx.accounts.wallet;
let receipt_account = &ctx.accounts.receipt_account;
let escrow_payment_account = &ctx.accounts.escrow_payment_account;
Expand Down Expand Up @@ -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<Sell>, buyer_price: u64, token_size: u64) -> Result<()> {
let wallet = &ctx.accounts.wallet;
let token_account = &ctx.accounts.token_account;
let metadata = &ctx.accounts.metadata;
Expand Down Expand Up @@ -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<Cancel>, _buyer_price: u64, _token_size: u64) -> Result<()> {
let wallet = &ctx.accounts.wallet;
let token_account = &ctx.accounts.token_account;
let authority = &ctx.accounts.authority;
Expand Down Expand Up @@ -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<Buy>, 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;
Expand Down Expand Up @@ -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<()> {
Expand Down Expand Up @@ -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<WithdrawFromFee>, 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;
Expand Down Expand Up @@ -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<WithdrawFromTreasury>, 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;
Expand Down Expand Up @@ -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<UpdateAuctionHouse>,
seller_fee_basis_points: Option<u16>,
requires_sign_off: Option<bool>,
can_change_sale_price: Option<bool>,
Expand Down
18 changes: 5 additions & 13 deletions tests/cfo/programs/cfo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<SweepFees>) -> Result<()> {
let cpi_ctx = CpiContext::from(&*ctx.accounts);
let seeds = [
ctx.accounts.dex.dex_program.key.as_ref(),
Expand All @@ -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<SwapToUsdc>, min_exchange_rate: ExchangeRate) -> Result<()> {
let seeds = [
ctx.accounts.dex_program.key.as_ref(),
&[ctx.accounts.officer.bumps.bump],
Expand All @@ -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<SwapToSrm>, min_exchange_rate: ExchangeRate) -> Result<()> {
let seeds = [
ctx.accounts.dex_program.key.as_ref(),
&[ctx.accounts.officer.bumps.bump],
Expand All @@ -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<Distribute>) -> Result<()> {
let total_fees = ctx.accounts.srm_vault.amount;
let seeds = [
ctx.accounts.dex_program.key.as_ref(),
Expand Down Expand Up @@ -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<DropStakeReward>) -> Result<()> {
// Common reward parameters.
let expiry_ts = 1853942400; // 9/30/2028.
let expiry_receiver = *ctx.accounts.officer.to_account_info().key;
Expand Down
8 changes: 4 additions & 4 deletions tests/lockup/programs/lockup/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<WhitelistWithdraw>,
instruction_data: Vec<u8>,
amount: u64,
) -> Result<()> {
Expand All @@ -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<WhitelistDeposit>,
instruction_data: Vec<u8>,
) -> Result<()> {
let before_amount = ctx.accounts.transfer.vault.amount;
Expand Down
Loading
Loading