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
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@ All notable changes to this repository are documented here.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [2026-09-23] - Anchor v1 copies match their v2 counterparts where the version allows

A scan of every `anchor/` and `anchor-v1/` pair, comparing function names,
error variants, account fields, constants and seeds, and listing every v2
program commit since July that left its v1 copy untouched, found these
differences that the Anchor version does not force:

- The transfer-hook `counter` and `account-data-as-seed` v1 copies still had
the bug v2 fixed in August: the hook computed the new transfer count and
dropped it, because `counter_account` was not `mut`, so the count read one
after every transfer. Both now write the count back and report an overflow
with the new `CounterOverflow` error instead of `AmountTooBig`, and each
test reads the counter back after the hooked transfer.
- Interest-bearing (v1): `check_mint_data` becomes `check_rate_authority`
with v2's signature, taking the parsed `InterestBearingConfig`. The
handlers read the extension with anchor-spl's `get_mint_extension_data`.
- Account fields that v2 declares `pub` are `pub` in the v1 copies of
account-data, checking-accounts, program-derived-addresses, rent,
transfer-sol and nft-operations.
- Order book (v1): `OrderTreeRoot` derives `Default` like the v2 copy,
replacing a hand-written impl.

What the scan still reports is forced by the Anchor version: v2's
transfer-hook `entrypoint` fallback, the local session-token reader that
replaces the v1-only `session-keys` crate, the `last_restart_slot` syscall
wrapper (the v1 copies make the same restart check through the sysvar), the
wincode span in rent, zero-copy padding, the Pyth account traits, and the
betting market's borrow-release helper.

## [2026-09-23] - Order book tests cover the deepest critbit path

A critbit tree does not rebalance, so asks at doubling prices stretch the path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ use anchor_lang::prelude::*;
#[derive(Accounts)]
pub struct CreateAddressInfoAccountConstraints<'info> {
#[account(mut)]
payer: Signer<'info>,
pub payer: Signer<'info>,

#[account(
init,
payer = payer,
space = AddressInfo::DISCRIMINATOR.len() + AddressInfo::INIT_SPACE,
)]
address_info: Account<'info, AddressInfo>,
system_program: Program<'info, System>,
pub address_info: Account<'info, AddressInfo>,
pub system_program: Program<'info, System>,
}

pub fn handle_create_address_info(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ pub mod checking_account_program {
// This is a simple example and does not include all possible constraints and types
#[derive(Accounts)]
pub struct CheckingAccountsAccountConstraints<'info> {
payer: Signer<'info>, // checks account is signer
pub payer: Signer<'info>, // checks account is signer

/// CHECK: No checks performed, example of an unchecked account
#[account(mut)]
account_to_create: UncheckedAccount<'info>,
pub account_to_create: UncheckedAccount<'info>,
/// CHECK: Perform owner check using constraint
#[account(
mut,
owner = id()
)]
account_to_change: UncheckedAccount<'info>,
system_program: Program<'info, System>, // checks account is executable, and is the system program
pub account_to_change: UncheckedAccount<'info>,
pub system_program: Program<'info, System>, // checks account is executable, and is the system program
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use anchor_lang::prelude::*;
#[derive(Accounts)]
pub struct CreatePageVisitsAccountConstraints<'info> {
#[account(mut)]
payer: Signer<'info>,
pub payer: Signer<'info>,

#[account(
init,
Expand All @@ -16,8 +16,8 @@ pub struct CreatePageVisitsAccountConstraints<'info> {
],
bump,
)]
page_visits: Account<'info, PageVisits>,
system_program: Program<'info, System>,
pub page_visits: Account<'info, PageVisits>,
pub system_program: Program<'info, System>,
}

pub fn handle_create_page_visits(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use anchor_lang::prelude::*;

#[derive(Accounts)]
pub struct IncrementPageVisitsAccountConstraints<'info> {
user: SystemAccount<'info>,
pub user: SystemAccount<'info>,
#[account(
mut,
seeds = [
Expand All @@ -12,7 +12,7 @@ pub struct IncrementPageVisitsAccountConstraints<'info> {
],
bump = page_visits.bump,
)]
page_visits: Account<'info, PageVisits>,
pub page_visits: Account<'info, PageVisits>,
}

pub fn handle_increment_page_visits(
Expand Down
4 changes: 2 additions & 2 deletions basics/rent/anchor-v1/programs/rent-example/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ pub struct CreateSystemAccountAccountConstraints<'info> {

#[derive(AnchorSerialize, AnchorDeserialize, Debug)]
pub struct AddressData {
name: String,
address: String,
pub name: String,
pub address: String,
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ use anchor_lang::system_program;
#[derive(Accounts)]
pub struct TransferSolWithCpiAccountConstraints<'info> {
#[account(mut)]
payer: Signer<'info>,
pub payer: Signer<'info>,
#[account(mut)]
recipient: SystemAccount<'info>,
system_program: Program<'info, System>,
pub recipient: SystemAccount<'info>,
pub system_program: Program<'info, System>,
}

pub fn handler(context: Context<TransferSolWithCpiAccountConstraints>, amount: u64) -> Result<()> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ pub struct TransferSolWithProgramAccountConstraints<'info> {
mut,
owner = crate::ID // value of declare_id!()
)]
payer: UncheckedAccount<'info>,
pub payer: UncheckedAccount<'info>,

#[account(mut)]
recipient: SystemAccount<'info>,
pub recipient: SystemAccount<'info>,
}

// Directly modifying lamports is only possible if the program is the owner of the account
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,12 +247,3 @@ impl OrderBook {
}
}
}

impl Default for OrderTreeRoot {
fn default() -> Self {
Self {
maybe_node: 0,
leaf_count: 0,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub const MAX_TREE_NODES: usize = 1024;
///
/// `maybe_node` is only meaningful when `leaf_count > 0` - a freshly-zeroed
/// root represents an empty tree.
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
#[derive(Copy, Clone, Default, bytemuck::Pod, bytemuck::Zeroable)]
#[repr(C)]
pub struct OrderTreeRoot {
pub maybe_node: NodeHandle,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use super::validate_metadata_strings;
#[derive(Accounts)]
pub struct CreateCollectionAccountConstraints<'info> {
#[account(mut)]
user: Signer<'info>,
pub user: Signer<'info>,

#[account(
init,
Expand All @@ -28,7 +28,7 @@ pub struct CreateCollectionAccountConstraints<'info> {
mint::authority = mint_authority,
mint::freeze_authority = mint_authority,
)]
mint: Account<'info, Mint>,
pub mint: Account<'info, Mint>,

#[account(
seeds = [b"authority"],
Expand All @@ -39,24 +39,24 @@ pub struct CreateCollectionAccountConstraints<'info> {

#[account(mut)]
/// CHECK: This account will be initialized by the metaplex program
metadata: UncheckedAccount<'info>,
pub metadata: UncheckedAccount<'info>,

#[account(mut)]
/// CHECK: This account will be initialized by the metaplex program
master_edition: UncheckedAccount<'info>,
pub master_edition: UncheckedAccount<'info>,

#[account(
init,
payer = user,
associated_token::mint = mint,
associated_token::authority = user
)]
destination: Account<'info, TokenAccount>,
pub destination: Account<'info, TokenAccount>,

system_program: Program<'info, System>,
token_program: Program<'info, Token>,
associated_token_program: Program<'info, AssociatedToken>,
token_metadata_program: Program<'info, Metadata>,
pub system_program: Program<'info, System>,
pub token_program: Program<'info, Token>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub token_metadata_program: Program<'info, Metadata>,
}

/// Creates a collection NFT with caller-supplied metadata.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ use anchor_spl::{
spl_token_2022::{extension::ExtensionType, pod::PodMint},
InitializeMint2,
},
token_interface::{interest_bearing_mint_initialize, InterestBearingMintInitialize, Token2022},
token_2022::spl_token_2022::extension::interest_bearing_mint::InterestBearingConfig,
token_interface::{
get_mint_extension_data, interest_bearing_mint_initialize, InterestBearingMintInitialize,
Token2022,
},
};

use crate::check_mint_data;
use crate::check_rate_authority;

#[derive(Accounts)]
pub struct InitializeAccountConstraints<'info> {
Expand Down Expand Up @@ -72,9 +76,9 @@ pub fn handler(context: Context<InitializeAccountConstraints>, rate: i16) -> Res
Some(&context.accounts.payer.key()), // freeze authority
)?;

check_mint_data(
let config = get_mint_extension_data::<InterestBearingConfig>(
&context.accounts.mint_account.to_account_info(),
&context.accounts.payer.key(),
)?;
check_rate_authority(&config, &context.accounts.payer.key())?;
Ok(())
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use anchor_lang::prelude::*;
use anchor_spl::token_2022::spl_token_2022::extension::interest_bearing_mint::InterestBearingConfig;
use anchor_spl::token_interface::{
interest_bearing_mint_update_rate, InterestBearingMintUpdateRate, Mint, Token2022,
get_mint_extension_data, interest_bearing_mint_update_rate, InterestBearingMintUpdateRate, Mint,
Token2022,
};

use crate::check_mint_data;
use crate::check_rate_authority;

#[derive(Accounts)]
pub struct UpdateRateAccountConstraints<'info> {
Expand All @@ -29,9 +31,9 @@ pub fn handler(context: Context<UpdateRateAccountConstraints>, rate: i16) -> Res
rate,
)?;

check_mint_data(
let config = get_mint_extension_data::<InterestBearingConfig>(
&context.accounts.mint_account.to_account_info(),
&context.accounts.authority.key(),
)?;
check_rate_authority(&config, &context.accounts.authority.key())?;
Ok(())
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
use anchor_lang::prelude::*;
use anchor_spl::token_2022::spl_token_2022::{
extension::{
interest_bearing_mint::InterestBearingConfig, BaseStateWithExtensions,
StateWithExtensions,
},
state::Mint as MintState,
};
use anchor_spl::token_2022::spl_token_2022::extension::interest_bearing_mint::InterestBearingConfig;
use anchor_spl::token_interface::spl_pod::optional_keys::OptionalNonZeroPubkey;

mod instructions;
Expand All @@ -27,16 +21,15 @@ pub mod interest_bearing {
}
}

pub fn check_mint_data(mint_account_info: &AccountInfo, authority_key: &Pubkey) -> Result<()> {
let mint_data = mint_account_info.data.borrow();
let mint_with_extension = StateWithExtensions::<MintState>::unpack(&mint_data)?;
let extension_data = mint_with_extension.get_extension::<InterestBearingConfig>()?;

/// Assert the extension names `authority_key` as the account allowed to change
/// the rate. Both callers read the extension with anchor-spl's
/// `get_mint_extension_data`, which parses the mint's TLV data.
pub fn check_rate_authority(config: &InterestBearingConfig, authority_key: &Pubkey) -> Result<()> {
assert_eq!(
extension_data.rate_authority,
config.rate_authority,
OptionalNonZeroPubkey::try_from(Some(*authority_key))?
);

msg!("{:?}", extension_data);
msg!("{:?}", config);
Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,32 @@ pub struct TransferHookAccountConstraints<'info> {
/// CHECK: ExtraAccountMetaList Account,
#[account(seeds = [b"extra-account-metas", mint.key().as_ref()], bump)]
pub extra_account_meta_list: UncheckedAccount<'info>,
#[account(seeds = [b"counter", owner.key().as_ref()], bump)]
#[account(mut, seeds = [b"counter", owner.key().as_ref()], bump)]
pub counter_account: Account<'info, CounterAccount>,
}

pub fn handler(context: Context<TransferHookAccountConstraints>, amount: u64) -> Result<()> {
// Fail this instruction if it is not called from within a transfer hook
check_is_transferring(&context)?;

// Check if the amount is too big
// A hook can reject a transfer by returning an error. This one only logs,
// so the example stays runnable: `amount` arrives in minor units, so any
// transfer of a token with decimals clears 50 immediately. Return
// `err!(TransferError::AmountTooBig)` here to make the limit binding, and
// pick a threshold in the mint's own minor units.
if amount > 50 {
msg!("The amount is too big: {}", amount);
//return err!(TransferError::AmountTooBig);
}

// Increment the transfer count safely
// Increment the transfer count safely and write it back, so the count
// survives the transfer that produced it.
let count = context
.accounts
.counter_account
.counter
.checked_add(1)
.ok_or(TransferError::AmountTooBig)?;
.ok_or(TransferError::CounterOverflow)?;
context.accounts.counter_account.counter = count;

msg!("This token has been transferred {} times", count);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub enum TransferError {
AmountTooBig,
#[msg("The token is not currently transferring")]
IsNotCurrentlyTransferring,
#[msg("The transfer counter would overflow")]
CounterOverflow,
}

#[program]
Expand Down
Loading
Loading