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
1,251 changes: 1,158 additions & 93 deletions Cargo.lock

Large diffs are not rendered by default.

59 changes: 58 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
[workspace]
# Every anchor program is listed twice: here, and in its own project's
# `anchor/Cargo.toml` workspace. `anchor build` uses the project workspace; this
# one is what the repository-wide `cargo fmt` and `cargo clippy` jobs see, and
# they only ever look at members. A crate missing here is a crate CI never lints.
members = [
#basics
"basics/account-data/native/program",
Expand Down Expand Up @@ -42,6 +46,8 @@ members = [
"basics/rent/anchor/programs/*",
"basics/favorites/native/program",
"basics/favorites/pinocchio/program",
"basics/favorites/anchor/programs/favorites",
"basics/pyth/anchor/programs/pythexample",
"basics/repository-layout/native/program",
"basics/repository-layout/pinocchio/program",
"basics/repository-layout/anchor/programs/*",
Expand All @@ -50,7 +56,24 @@ members = [
"basics/transfer-sol/anchor/programs/*",
"basics/transfer-sol/asm",

# compression
"compression/cnft-burn/anchor/programs/cnft-burn",
"compression/cnft-vault/anchor/programs/cnft-vault",
"compression/cutils/anchor/programs/cutils",

# finance
#
# The two `mock-switchboard` crates are excluded below: they share a package
# name, and one workspace cannot hold two packages called the same thing.
# They stay path dependencies of the programs that test against them.
"finance/betting-market/anchor/programs/betting-market",
"finance/escrow/anchor/programs/escrow",
"finance/lending/anchor/programs/lending",
"finance/order-book/anchor/programs/order-book",
"finance/perpetual-futures/anchor/programs/perpetual-futures",
"finance/prop-amm/anchor/programs/prop-amm",
"finance/token-fundraiser/anchor/programs/fundraiser",
"finance/token-swap/anchor/programs/token-swap",
"finance/vault-strategy/anchor/programs/vault-strategy",
"finance/vault-strategy/anchor/programs/mock-swap-router",

Expand All @@ -65,6 +88,40 @@ members = [
"tokens/transfer-tokens/native/program",
"tokens/nft-minter/native/program",
"tokens/pda-mint-authority/native/program",
"tokens/create-token/anchor/programs/create-token",
"tokens/external-delegate-token-master/anchor/programs/external-delegate-token-master",
"tokens/nft-minter/anchor/programs/nft-minter",
"tokens/nft-operations/anchor/programs/mint-nft",
"tokens/pda-mint-authority/anchor/programs/token-minter",
"tokens/token-minter/anchor/programs/token-minter",
"tokens/transfer-tokens/anchor/programs/transfer-tokens",
"tokens/token-extensions/basics/anchor/programs/basics",
"tokens/token-extensions/cpi-guard/anchor/programs/cpi-guard",
"tokens/token-extensions/default-account-state/anchor/programs/default-account-state",
"tokens/token-extensions/group/anchor/programs/group",
"tokens/token-extensions/immutable-owner/anchor/programs/immutable-owner",
"tokens/token-extensions/interest-bearing/anchor/programs/interest-bearing",
"tokens/token-extensions/memo-transfer/anchor/programs/memo-transfer",
"tokens/token-extensions/metadata/anchor/programs/metadata",
"tokens/token-extensions/mint-close-authority/anchor/programs/mint-close-authority",
"tokens/token-extensions/nft-meta-data-pointer/anchor-example/anchor/programs/extension_nft",
"tokens/token-extensions/non-transferable/anchor/programs/non-transferable",
"tokens/token-extensions/permanent-delegate/anchor/programs/permanent-delegate",
"tokens/token-extensions/transfer-fee/anchor/programs/transfer-fee",
"tokens/token-extensions/transfer-hook/account-data-as-seed/anchor/programs/transfer-hook",
"tokens/token-extensions/transfer-hook/allow-block-list-token/anchor/programs/abl-token",
"tokens/token-extensions/transfer-hook/counter/anchor/programs/transfer-hook",
"tokens/token-extensions/transfer-hook/hello-world/anchor/programs/transfer-hook",
"tokens/token-extensions/transfer-hook/transfer-cost/anchor/programs/transfer-hook",
"tokens/token-extensions/transfer-hook/transfer-switch/anchor/programs/transfer-switch",
"tokens/token-extensions/transfer-hook/whitelist/anchor/programs/transfer-hook",
]
# A path dependency inside the workspace directory joins the workspace unless it
# is excluded. Both of these are named `mock_switchboard`, so leaving them in
# fails with "two packages named `mock_switchboard` in this workspace".
exclude = [
"finance/perpetual-futures/anchor/programs/mock-switchboard",
"finance/prop-amm/anchor/programs/mock-switchboard",
]
resolver = "2"

Expand All @@ -76,7 +133,7 @@ overflow-checks = true
# misc
borsh = "1.6.1"
borsh-derive = "1.5.7"
mpl-token-metadata = { version = "5.1.1", features = [ "no-entrypoint" ] }
mpl-token-metadata = { version = "=5.1.2-alpha.2", features = [ "no-entrypoint" ] }


# spl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use anchor_lang::solana_program::{
use borsh::BorshSerialize;

#[derive(Accounts)]
#[instruction(params: MintParams)]
#[instruction(_params: MintParams)]
pub struct MintAccountConstraints {
pub payer: Signer,

Expand Down Expand Up @@ -192,7 +192,10 @@ pub fn handle_mint(
context.accounts.payer.cpi_handle(),
context.accounts.tree_delegate.cpi_handle(),
context.accounts.collection_authority.cpi_handle(),
context.accounts.collection_authority_record_pda.cpi_handle(),
context
.accounts
.collection_authority_record_pda
.cpi_handle(),
context.accounts.collection_mint.cpi_handle(),
context.accounts.collection_metadata.cpi_handle_mut().into(),
context.accounts.edition_account.cpi_handle(),
Expand All @@ -203,7 +206,6 @@ pub fn handle_mint(
context.accounts.system_program.cpi_handle(),
];


invoke(&instruction, &account_infos)?;

Ok(())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::*;
use anchor_lang::solana_program::instruction::{AccountMeta, Instruction};

#[derive(Accounts)]
#[instruction(params: VerifyParams)]
#[instruction(_params: VerifyParams)]
pub struct VerifyAccountConstraints {
pub leaf_owner: Signer,

Expand Down Expand Up @@ -40,11 +40,11 @@ pub fn handle_verify(
// and the proof accounts stay alive for the CPI below.
let proof_accounts = context.remaining_accounts()?;

let asset_id = get_asset_id(&context.accounts.merkle_tree.address(), params.nonce);
let asset_id = get_asset_id(context.accounts.merkle_tree.address(), params.nonce);
let leaf_hash = leaf_schema_v1_hash(
&asset_id,
&context.accounts.leaf_owner.address(),
&context.accounts.leaf_delegate.address(),
context.accounts.leaf_owner.address(),
context.accounts.leaf_delegate.address(),
params.nonce,
&params.data_hash,
&params.creator_hash,
Expand Down
9 changes: 9 additions & 0 deletions docs/anchor-v2-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,15 @@ Three ways this goes wrong:
That includes the derive's own use of the account after the handler returns:
`associated_token::authority = event`, `address = event.x` and friends all
deref it. So the reacquire has to happen before the handler ends.

It also means the *seed material* has to be read before the release, since
reading `event.event_id` is a deref. Where a helper needs it, copy the fields
into a small struct while the borrow is live and pass that, so the ordering is
enforced by having to construct the struct first. `betting-market`'s
`EventSigner::new` is this; three handlers there sign a transfer for the event
PDA and all three would panic if they read `event_id` a line later. Clippy
pushes the other way here, because passing the fields individually is what
trips `too_many_arguments` on the helper: bundling them satisfies both.
2. **Release and reacquire must be on the same branch.** Releasing inside
`if fee > 0` and reacquiring unconditionally re-borrows an account you still
hold.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};

use crate::{error::BettingError, Bet, EventStatus, User};

use super::transfer_tokens_from_vault;
use super::{transfer_tokens_from_vault, EventSigner};

#[derive(Accounts)]
pub struct ClaimRefundAccountConstraints {
Expand Down Expand Up @@ -75,24 +75,21 @@ pub fn handle_claim_refund(context: &mut Context<ClaimRefundAccountConstraints>)
// transfer (effects before interactions); the Bet account itself closes
// when the instruction finishes.
let bet_key = context.accounts.bet.address();
context.accounts.user.remove_bet(&bet_key)?;
context.accounts.user.remove_bet(bet_key)?;

let event_id = context.accounts.event.event_id;
let event_bump = context.accounts.event.bump;
// Gather the signing material before the borrow goes away.
let event_signer = EventSigner::new(&context.accounts.event);
// `event` signs the transfer below. Release its borrow across
// the CPI: the runtime rejects a CPI that borrows an account we hold.
context.accounts.event.release_borrow()?;
let event_view = *context.accounts.event.account();

transfer_tokens_from_vault(
&mut context.accounts.vault,
&mut context.accounts.bettor_token_account,
stake,
&context.accounts.token_mint,
event_view,
&event_signer,
&context.accounts.token_program,
event_id,
event_bump,
)?;

// Take the borrow back before the derive's exit path touches `event` again.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use anchor_spl::token_interface::{Mint, TokenAccount, TokenInterface};

use crate::{error::BettingError, Bet, EventStatus, User};

use super::transfer_tokens_from_vault;
use super::{transfer_tokens_from_vault, EventSigner};

#[derive(Accounts)]
pub struct ClaimWinningsAccountConstraints {
Expand Down Expand Up @@ -100,24 +100,21 @@ pub fn handle_claim_winnings(context: &mut Context<ClaimWinningsAccountConstrain
// transfer (effects before interactions); the Bet account itself closes
// when the instruction finishes.
let bet_key = context.accounts.bet.address();
context.accounts.user.remove_bet(&bet_key)?;
context.accounts.user.remove_bet(bet_key)?;

let event_id = context.accounts.event.event_id;
let event_bump = context.accounts.event.bump;
// Gather the signing material before the borrow goes away.
let event_signer = EventSigner::new(&context.accounts.event);
// `event` signs the transfer below. Release its borrow across
// the CPI: the runtime rejects a CPI that borrows an account we hold.
context.accounts.event.release_borrow()?;
let event_view = *context.accounts.event.account();

transfer_tokens_from_vault(
&mut context.accounts.vault,
&mut context.accounts.bettor_token_account,
payout,
&context.accounts.token_mint,
event_view,
&event_signer,
&context.accounts.token_program,
event_id,
event_bump,
)?;

// Take the borrow back before the derive's exit path touches `event` again.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,6 @@ pub fn handle_close_losing_bet(
);

let bet_key = context.accounts.bet.address();
context.accounts.user.remove_bet(&bet_key)?;
context.accounts.user.remove_bet(bet_key)?;
Ok(())
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ use crate::{error::BettingError, Config, EventStatus};
pub const MAX_DESCRIPTION_LEN: usize = 200;

#[derive(Accounts)]
#[instruction(event_id: u64)]
// The leading underscore is for rustc: `#[derive(Accounts)]` expands
// `_event_id` into a path that never reads it, so the plain name warns as
// unused. The `seeds` expression below is the real use.
#[instruction(_event_id: u64)]
pub struct InitializeEventAccountConstraints {
#[account(mut, address = config.admin @ BettingError::Unauthorized)]
pub admin: Signer,
Expand All @@ -29,7 +32,7 @@ pub struct InitializeEventAccountConstraints {
init,
payer = admin,
space = Event::DISCRIMINATOR.len() + Event::INIT_SPACE,
seeds = [b"event", event_id.to_le_bytes()],
seeds = [b"event", _event_id.to_le_bytes()],
bump
)]
pub event: BorshAccount<Event>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ use anchor_spl::{

use crate::{error::BettingError, Config, EventStatus, Outcome};

use super::transfer_tokens_from_vault;
use super::{transfer_tokens_from_vault, EventSigner};

const BPS_DENOMINATOR: u128 = 10_000;

#[derive(Accounts)]
#[instruction(winning_outcome_index: u8)]
// The leading underscore is for rustc: `#[derive(Accounts)]` expands
// `_winning_outcome_index` into a path that never reads it, so the plain name warns as
// unused. The `seeds` expression below is the real use.
#[instruction(_winning_outcome_index: u8)]
pub struct SettleEventAccountConstraints {
#[account(mut, address = config.admin @ BettingError::Unauthorized)]
pub admin: Signer,
Expand All @@ -35,7 +38,7 @@ pub struct SettleEventAccountConstraints {
pub event: BorshAccount<Event>,

#[account(
seeds = [b"outcome", event.address().as_ref(), &[winning_outcome_index]],
seeds = [b"outcome", event.address().as_ref(), &[_winning_outcome_index]],
bump = winning_outcome.bump,
)]
pub winning_outcome: BorshAccount<Outcome>,
Expand Down Expand Up @@ -90,22 +93,19 @@ pub fn handle_settle_event(
let distributable_losing_pool = losing_pool - fee;

if fee > 0 {
let event_id = context.accounts.event.event_id;
let event_bump = context.accounts.event.bump;
// Gather the signing material before the borrow goes away.
let event_signer = EventSigner::new(&context.accounts.event);
// `event` signs the transfer below. Release its borrow across the CPI:
// the runtime rejects a CPI that borrows an account we still hold.
context.accounts.event.release_borrow()?;
let event_view = *context.accounts.event.account();

transfer_tokens_from_vault(
&mut context.accounts.vault,
&mut context.accounts.fee_recipient_token_account,
fee,
&context.accounts.token_mint,
event_view,
&event_signer,
&context.accounts.token_program,
event_id,
event_bump,
)?;

// Take the borrow back before writing the settled state through it.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,53 @@ pub fn transfer_tokens_to_vault(
transfer_checked(cpi_context, amount, decimals)
}

/// Everything needed to sign for the Event PDA, copied out of the account
/// while its borrow is still live.
///
/// `transfer_tokens_from_vault` runs a CPI the event signs for, and the runtime
/// rejects a CPI that borrows an account the program is holding, so the caller
/// has to `release_borrow()` first. `BorshAccount` derefs into the loaded copy,
/// and that deref panics once the borrow is released, so the two fields have to
/// be read before that happens. Gathering them here makes the ordering a matter
/// of calling `new` before `release_borrow`.
pub struct EventSigner {
view: AccountView,
id: u64,
bump: u8,
}

impl EventSigner {
/// Read the event's signing material. Call this **before**
/// `event.release_borrow()`.
pub fn new(event: &BorshAccount<Event>) -> Self {
Self {
view: *event.account(),
id: event.event_id,
bump: event.bump,
}
}
}

// Move tokens out of the vault, signed by the Event PDA. The event vault's
// authority is the Event account, so the program signs with the event's seeds.
pub fn transfer_tokens_from_vault(
vault: &mut InterfaceAccount<TokenAccount>,
to: &mut InterfaceAccount<TokenAccount>,
amount: u64,
mint: &InterfaceAccount<Mint>,
event: AccountView,
event: &EventSigner,
token_program: &Interface<'static, TokenInterface>,
event_id: u64,
event_bump: u8,
) -> Result<()> {
let event_id_bytes = event_id.to_le_bytes();
let seeds = &[b"event".as_ref(), event_id_bytes.as_ref(), &[event_bump]];
let event_id_bytes = event.id.to_le_bytes();
let seeds = &[b"event".as_ref(), event_id_bytes.as_ref(), &[event.bump]];
let signer_seeds = [&seeds[..]];

let decimals = mint.decimals();
let transfer_accounts = TransferChecked {
from: vault.cpi_handle_mut(),
mint: mint.cpi_handle(),
to: to.cpi_handle_mut(),
authority: CpiHandle::readonly(&event),
authority: CpiHandle::readonly(&event.view),
};
let cpi_context =
CpiContext::new_with_signer(token_program.address(), transfer_accounts, &signer_seeds);
Expand Down
Loading